#!/bin/bash # vim: noet ft=sh sw=4 ts=2: # # Unattended GPG key generation script. # Author: Augusto Pascutti 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 | remove_blank_chars remove_blank_chars() { sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' } # Usage: validate_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_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 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 gpg_has_key() { local email="$1" [[ -n "$(gpg_list_keys "$email")" ]] && return 0 || return 1 } # Usage: gpg_delete_keys 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 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 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 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 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 $APP_NAME [options] generate-key $APP_NAME [options] remove-keys $APP_NAME [options] remove-keys $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