Created
February 28, 2023 19:01
-
-
Save augustohp/a7b44d55b3ec70c2e4ea017548d9c837 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# vim: noet ft=sh sw=4 ts=2: | |
# | |
# Unattended GPG key generation script. | |
# Author: Augusto Pascutti <augusto.hp+oss@gmail.com> | |
set -e | |
set -o pipefail | |
APP_NAME=$(basename $0) | |
APP_VERSION="1.0.0" | |
APP_AUTHOR="augusto.hp+oss@gmail.com" | |
OPTION_AUTHORS_FILE="authors-backup.txt" | |
OPTION_TEMPLATE_FILE="unattended-gen-key.template.txt" | |
OPTION_TEMPLATE_DIR="templates" | |
OPTION_KEYS_DIR="keys" | |
OPTION_AVOID_DELETION="true" | |
# Utilities ------------------------------------------------------------------- | |
# Usage: echo "Loren Ipsum" | indent | |
function indent() | |
{ | |
sed 's/^/ /' | |
} | |
# Usage: cat <file> | remove_blank_chars | |
remove_blank_chars() | |
{ | |
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | |
} | |
# Usage: validate_password <password> | |
validate_password() | |
{ | |
local password="$1" | |
[[ -z "$password" ]] && { echo "Error: password is required! " >&2; exit 2; } | |
# Checks if password has "\" and "#" chars | |
if [[ "$password" =~ [\\#\`\'\"\&] ]] | |
then | |
echo "Error: ${email} password cannot contain: \#\`'\"& " >&2 | |
exit 2 | |
fi | |
} | |
# Usage: years_in_future <years> | |
years_in_future() | |
{ | |
local years="$1" | |
[[ -z "$years" ]] && { echo "Error: years is required! " >&2; exit 2; } | |
date -v+"$years"y +%Y-%m-%d | |
} | |
# Usage: gpg_list_keys <email> | |
gpg_list_keys() | |
{ | |
local email="$1" | |
[[ -z "$email" ]] && { echo "Error: email is required! " >&2; exit 2; } | |
gpg --quiet --list-keys | \ | |
grep -B 1 "$email" | \ | |
grep -v "$email" | \ | |
grep -v '^--$' | \ | |
remove_blank_chars | |
} | |
# Usage: gpg_has_key <email> | |
gpg_has_key() | |
{ | |
local email="$1" | |
[[ -n "$(gpg_list_keys "$email")" ]] && return 0 || return 1 | |
} | |
# Usage: gpg_delete_keys <email> | |
gpg_delete_keys() | |
{ | |
local email="$1" | |
[[ -z "$email" ]] && { echo "Error: email is required! " >&2; exit 2; } | |
echo "Listing all keys for $email ..." | |
for key_id in $(gpg_list_keys "$email") | |
do | |
echo "Deleting secret and public keys for $key_id ..." | indent | |
gpg --quiet --batch --yes --delete-secret-and-public-key "$key_id" 2>&1 | indent | |
done | |
} | |
# Usage: gpg_export_private_keys <email> <password> <output_dir> | |
gpg_export_private_keys() | |
{ | |
local email="$1" | |
local password="$2" | |
local output_dir="$3" | |
[[ -d "$output_dir" ]] || { echo "Error: $output_dir does not exist! " >&2; exit 2; } | |
for key_id in $(gpg_list_keys "$email" | head -n 1) | |
do | |
gpg --batch --yes --export-secret-keys --pinentry-mode loopback --passphrase "${password}" "$key_id" > "$output_dir/$email.$key_id.private.key" | |
done | |
} | |
# Usage: generate_key <name> <email> <password> | |
generate_key() | |
{ | |
local name="$1" | |
local email="$2" | |
local password="$3" | |
expiration="$(years_in_future 2)" | |
[[ -z "$name" ]] && { echo "Error: name is required! " >&2; exit 2; } | |
[[ -z "$email" ]] && { echo "Error: email is required! " >&2; exit 2; } | |
[[ -z "$password" ]] && { echo "Error: password is required! " >&2; exit 2; } | |
validate_password "$password" | |
current_template=$(mktemp -t "${APP_NAME}.XXXXXX") || { echo "Error: Could not create temporary file! " >&2; exit 2; } | |
cat > "$current_template" <<-EOT | |
%echo Creating private key... | |
Key-Type: EDDSA | |
Key-Curve: ed25519 | |
Subkey-Type: ECDH | |
Subkey-Curve: cv25519 | |
Name-Real: %NAME% | |
Name-Comment: Chave para acesso ao repositório de segredos. | |
Name-Email: %EMAIL% | |
Expire-Date: %EXPIRE_DATE% | |
Passphrase: %PASSWORD% | |
# Do a commit here, so that we can later print "done" :-) | |
%commit | |
%echo done | |
EOT | |
echo "🔑 ${person_name}" | |
if [ "$OPTION_AVOID_DELETION" == "false" ] | |
then | |
if gpg_has_key "$email" | |
then | |
echo "Key already exists. Removing..." | indent | |
gpg_delete_keys "$email" | indent | indent | |
fi | |
fi | |
sed -i '' "s#%NAME%#${name}#" "$current_template" | |
sed -i '' "s#%EMAIL%#${email}#" "$current_template" | |
sed -i '' "s#%PASSWORD%#${password}#" "$current_template" | |
sed -i '' "s#%EXPIRE_DATE%#${expiration}#" "$current_template" | |
gpg --quiet --batch --generate-key "${current_template}" 2>&1 | indent | |
gpg_export_private_keys "${email}" "${password}" "${OPTION_KEYS_DIR}" | indent | |
if [ "$OPTION_AVOID_DELETION" == "false" ] | |
then | |
echo "Removing key because it is not necessary anymore..." | indent | |
gpg_delete_keys "$email" | indent | indent | |
fi | |
} | |
# Usage: generate_keys <authors_file> | |
generate_keys() | |
{ | |
local authors_file="$1" | |
[[ -z "$authors_file" ]] && { echo "Error: authors file is required! " >&2; exit 2; } | |
[[ -f "$authors_file" ]] || { echo "Error: $authors_file does not exist! " >&2; exit 2; } | |
while IFS=$'\t' read -r person_name person_email person_password | |
do | |
generate_key "$person_name" "$person_email" "$person_password" | |
done < "$authors_file" | |
} | |
# Usage remove_keys <author_file> | |
remove_keys() | |
{ | |
local authors_file="$1" | |
[[ -z "$authors_file" ]] && { echo "Error: authors file is required! " >&2; exit 2; } | |
[[ -f "$authors_file" ]] || { echo "Error: $authors_file does not exist! " >&2; exit 2; } | |
while IFS=$'\t' read -r person_name person_email person_password | |
do | |
echo "🗑 ${person_name}" | |
if gpg_has_key "$person_email" | |
then | |
gpg_delete_keys "$person_email" | indent | |
fi | |
done < "$authors_file" | |
} | |
# Usage: display_help | |
display_help() | |
{ | |
cat <<-EOT | |
Usage: $APP_NAME [options] generate-keys <authors file path> | |
$APP_NAME [options] generate-key <name> <email> <password> | |
$APP_NAME [options] remove-keys <authors file path> | |
$APP_NAME [options] remove-keys <email> | |
$APP_NAME -h | --help | |
$APP_NAME -v | --version | |
This script helps creating GPG keys given a list of authors. It is usefull | |
when you need to generate multiple keys for a team, for example. | |
Options | |
--debug Displays all commands being executed (set -x). | |
-h --help Show this screen. | |
-v --version Show version. | |
-t --template-dir <template dir path> Path to the template dir. [default: $OPTION_TEMPLATE_DIR] | |
-k --keys-dir <keys dir path> Path to the keys dir. [default: $OPTION_KEYS_DIR] | |
--delete-keys Delete keys after generating them. [default: $OPTION_DELETE_KEYS] | |
Authors file format (TSV) | |
One author per line, with columns separated by TAB, with the columns: name, | |
email, password. | |
Bugs and suggestions can be sent to $APP_AUTHOR. | |
EOT | |
} | |
# Sanity checks --------------------------------------------------------------- | |
[[ -f "$OPTION_AUTHORS_FILE" ]] || { echo "Error: $OPTION_AUTHORS_FILE does not exist! " >&2; exit 2; } | |
[[ -f "$OPTION_TEMPLATE_FILE" ]] || { echo "Error: $OPTION_TEMPLATE_FILE does not exist! " >&2; exit 2; } | |
[[ -d "$OPTION_TEMPLATE_DIR" ]] || { echo "Error: $OPTION_TEMPLATE_DIR does not exist! " >&2; exit 2; } | |
[[ -d "$OPTION_KEYS_DIR" ]] || { echo "Error: $OPTION_KEYS_DIR does not exist! " >&2; exit 2; } | |
for dependency in sed grep gpg | |
do | |
which "$dependency" > /dev/null || { echo "Error: $dependency is not installed! " >&2; exit 2; } | |
done | |
# Main ------------------------------------------------------------------------ | |
if [ $# -eq 0 ] | |
then | |
echo "Error: No arguments provided! Try '$APP_NAME --help'" >&2 | |
exit 2 | |
fi | |
# Parsing command options | |
while [ $# -gt 0 ] | |
do | |
case "$1" in | |
-h|--help) | |
display_help | |
exit 0 | |
;; | |
-v|--version) | |
echo "$APP_NAME $APP_VERSION" | |
exit 0 | |
;; | |
--debug) | |
set -x | |
;; | |
-t|--template-dir) | |
OPTION_TEMPLATE_DIR="$2" | |
shift | |
;; | |
-k|--keys-dir) | |
OPTION_KEYS_DIR="$2" | |
shift | |
;; | |
generate-keys|gen-keys) | |
OPTION_AUTHORS_FILE="$2" | |
shift | |
generate_keys "${OPTION_AUTHORS_FILE}" | |
exit 0 | |
;; | |
generate-key|gen-key) | |
OPTION_NAME="$2" | |
OPTION_EMAIL="$3" | |
OPTION_PASSWORD="$4" | |
shift 3 | |
generate_key "$OPTION_NAME" "$OPTION_EMAIL" "$OPTION_PASSWORD" | |
exit 0 | |
;; | |
remove-keys) | |
OPTION_AUTHORS_FILE="$2" | |
shift | |
remove_keys "${OPTION_AUTHORS_FILE}" | |
exit 0 | |
;; | |
remove-key) | |
OPTION_EMAIL="$2" | |
shift | |
gpg_delete_keys "$OPTION_EMAIL" | |
exit 0 | |
;; | |
--) | |
shift | |
break | |
;; | |
*) | |
echo "Error: Invalid option $1! " >&2 | |
exit 2 | |
;; | |
esac | |
shift | |
done | |
# Usage: echo " [ultimate] Diego Rabatone (Chave para acesso ao repositório de segredos.) <diego.rabatone@kobold.com.br>" | filter_email | |
filter_email() | |
{ | |
sed -E 's/.*<(.*)>.*/\1/' | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment