-
-
Save slimlime/b6076c2de58c35435984aa0b70f06de7 to your computer and use it in GitHub Desktop.
Quick script to generate new GPG keys (and export pertinent details)
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 | |
# Print a welcome banner for the user. | |
function Welcome() { | |
echo "" | |
echo "================================" | |
echo "Welcome to the GPG Key Generator" | |
echo "================================" | |
echo "Please select a STRONG passphrase. Once entered and verified, the" | |
echo "system must generate and collect enough random data to create your" | |
echo "key. This may take a couple minutes, so please be patient." | |
echo "" | |
} | |
# Fetch and verify the user's entered passphrase, then return it. | |
function FetchPassphrase() { | |
# The two editions of the passphrase | |
local __PASSPHRASE __PASSPHRASE_CONFIRM | |
# We'll loop over this until the user gets it right (hopefully on the | |
# first try, but you never know). | |
while true | |
do | |
# Ask for the passphrase, but don't echo the keystrokes back to the | |
# terminal (cause it's a SECRET). | |
echo -n "Enter a passphrase: " | |
read -se __PASSPHRASE | |
# Ask for the passphrase confirmation like the original passphrase, | |
# but just to be rude to the previous prompt, we'll overwrite it. | |
echo -en "\rConfirm your passphrase: " | |
read -se __PASSPHRASE_CONFIRM | |
# Compare the passphrase to the confirmation to make sure the user did | |
# it right. | |
if [[ "$__PASSPHRASE" == "$__PASSPHRASE_CONFIRM" ]] | |
then | |
# Hooray, they did! Now we can wipe our last input and exit this | |
# merry-go-round. | |
echo -en "\r" | |
break | |
else | |
# Oh no, they mis-typed at least one of them. Go back to the | |
# start, and ask them to do it over. | |
echo -e "\rThose passphrases do not match, please try again." | |
fi | |
done | |
# All done! Tell whoever called us what the passphrase is. | |
_RETURN="$__PASSPHRASE" | |
} | |
# Generate a new key in the key ring with a given passphrase, inform the user | |
# of the new ID, and return it by reference. | |
function GenerateKey() { | |
# The passphrase is the first argument to the function. | |
local __PASSPHRASE="$1" | |
# Search for the username and email to use for the UID first in the git | |
# ENV vars, then in the git config. | |
local __USERNAME=${GIT_AUTHOR_NAME:-$(git config user.name)} | |
local __EMAIL=${GIT_AUTHOR_EMAIL:-$(git config user.email)} | |
# I/O vars for the gpg command itself, and the eventual key we'll return | |
local __GEN_KEY_INPUT __GEN_KEY_OUTPUT __KEYID | |
# Create the batch GPG key creation script. | |
read -d '' __GEN_KEY_INPUT <<EOF | |
# Both keys use RSA algorithms, as they are stronger than the DSA ones | |
Key-Type: RSA | |
Subkey-Type: RSA | |
# Declare who this is for. We don't care to include a comment in here. | |
Name-Real: $__USERNAME | |
Name-Email: $__EMAIL | |
# Ensure the keys are the (current) maximum length for RSA keys. | |
Key-Length: 4096 | |
Subkey-Length: 4096 | |
# Set the passphrase for this key. After this, we *have* to use the passphrase | |
# to access it. | |
Passphrase: $__PASSPHRASE | |
# Expire the key after 1 year. | |
Expire-Date: 1y | |
EOF | |
# Tell the user to hold their horses while we do our work. | |
echo -n "Please wait, generating your new key..." | |
# Generate the key itself, and parse the output to find the actual key ID | |
__GEN_KEY_OUTPUT=$(\ | |
echo "$__GEN_KEY_INPUT" | gpg --no-tty --gen-key --batch 2>&1 \ | |
) | |
__KEYID=$(echo "$__GEN_KEY_OUTPUT" | grep -E '^gpg: key ' | cut -d\ -f3) | |
# Overwrite our previous informative-for-the-impatient text with | |
# informative-for-everyone text telling them what key we just generated, | |
# and return the result to whoever called us. The string has extra spaces | |
# at the end so that it'll overwrite whatever is left of the previous | |
# alert string. | |
echo -e "\rYour new key ID is $__KEYID " | |
_RETURN="$__KEYID" | |
} | |
# Export a public key to a file in the current working directory. | |
function ExportPublicKey() { | |
# The key ID is the first argument to the function. | |
local __KEYID="$1" | |
# Actually, this is all we need to do. Export the key, and then tell the | |
# user we did it. | |
gpg -a --export "$__KEYID" > "$__KEYID.asc" | |
echo "Exported the public key to $__KEYID.asc" | |
echo "" | |
} | |
# Returns true if the GPG version is greater than or equal to 2, which means | |
# we'll have to hack our way around the fact that, for whatever impossibly | |
# inane reason, it won't accept a passphrase in any manner other than an | |
# ncurses-based or GUI "pinentry" program | |
function TextHackRequired() { | |
local GPG_MAJOR_VERSION=$(\ | |
gpg --version | head -n1 | cut -d\ -f3 | cut -d. -f1 | |
) | |
[ $GPG_MAJOR_VERSION -ge 2 ] | |
return $? | |
} | |
# Returns true when tmux is available as a preferred mechanism to handle the | |
# text hack mentioned above, if required | |
function TmuxAvailable() { | |
which tmux 1>/dev/null 2>/dev/null | |
return $? | |
} | |
# Returns true when screen is available as a possible mechanism to handle the | |
# text hack mentioned above, if required | |
function ScreenAvailable() { | |
which screen 1>/dev/null 2>/dev/null | |
return $? | |
} | |
# Export a backup revocation certificate that the user can use to nix their | |
# private key in case someone nefarious gets ahold of it. | |
function ExportRevocationCertificate() { | |
# The key ID is the first argument to the function. | |
local __KEYID="$1" | |
# The passphrase is the second argument to the function. | |
local __PASSPHRASE="$2" | |
if TextHackRequired | |
then | |
if TmuxAvailable | |
then | |
ExportRevocationCertificateViaTmux $@ | |
return 0 | |
elif ScreenAvailable | |
then | |
ExportRevocationCertificateViaScreen $@ | |
return 0 | |
else | |
# This looks a little funny because we're trying to format it to | |
# the same width as the other messages earlier. It looks better | |
# for the user this way. | |
echo -n "You do not have the necessary tools to automatically " | |
echo "create a" | |
echo -n "backup revocation certificate for your new keys. You " | |
echo "will now be" | |
echo -n "prompted via a dialog window to enter the passphrase " | |
echo "you just" | |
echo "selected." | |
echo "Press enter when you are ready to continue." | |
read | |
fi | |
fi | |
# The revocation command string to run | |
GenerateRevocationCommandString "$__KEYID" | |
local __COMMAND="$_RETURN" | |
# Tell the user What we're planning to do. | |
echo "Generating a backup revocation certificate..." | |
# Run the command through what is effectively an eval. We discard all the | |
# output cause we don't do any processing on it. | |
__SELF="$0" __PASSPHRASE="$__PASSPHRASE" bash -c "$__COMMAND" \ | |
2>/dev/null 1>/dev/null | |
# Tell the user we're all done, and where to find their certificate. | |
echo "Revocation certificate exported to $__KEYID.revocation.asc" | |
} | |
# Perform certificate revocation by starting a tmux session, launching the | |
# revocation command, and passing in the passphrase text later to hack around | |
# GnuPG2 making things way harder than they need to be. | |
function ExportRevocationCertificateViaTmux() { | |
# The key ID is the first argument to the function. | |
local __KEYID="$1" | |
# The passphrase is the second argument to the function. | |
local __PASSPHRASE="$2" | |
# Session name for screen | |
GenerateHackSessionName | |
local __SESSION="$_RETURN" | |
# Command to run in our session | |
GenerateRevocationCommandString "$__KEYID" | |
local __COMMAND="$_RETURN" | |
# Output file path to display to the user for their information. | |
GenerateRevocationCertificateFilename "$__KEYID" | |
local __OUTFILE="$_RETURN" | |
# Tell the user what we're up to. | |
echo "Generating a backup revocation certificate in a temporary" | |
echo "\`tmux\` session..." | |
# Make sure to start tmux with a good session name we can use later, and | |
# ensure it starts, even if we're inside another session already (we'll | |
# never attach to it, so it shouldn't matter). | |
TMUX='' __SELF="$0" tmux new -d -s "$__SESSION" | |
# Send commands to set up the environment and then run the GPG command, | |
# which should start up the pinentry window. | |
tmux send-keys -t "$__SESSION":0.0 'export GPG_TTY=$(tty)' | |
tmux send-keys -t "$__SESSION":0.0 Enter | |
tmux send-keys -t "$__SESSION":0.0 "$__COMMAND" | |
tmux send-keys -t "$__SESSION":0.0 Enter | |
# Shove the passphrase at the tmux session, where it should hopefully get | |
# dumped into the ncurses program and cause the key to get output into our | |
# our file. We sleep for a second to give ncurses time to realize we | |
# aren't just pasting text. | |
tmux send-keys -t "$__SESSION":0.0 "$__PASSPHRASE" | |
sleep 1 | |
tmux send-keys -t "$__SESSION":0.0 Enter | |
tmux send-keys -t "$__SESSION":0.0 'exit' | |
tmux send-keys -t "$__SESSION":0.0 Enter | |
# Let the user know we're done. | |
echo "Revocation certificate exported to $__OUTFILE" | |
} | |
# Perform certificate revocation by starting a screen session, launching the | |
# revocation command, and passing in the passphrase text later to hack around | |
# GnuPG2 making things way harder than they need to be. | |
function ExportRevocationCertificateViaScreen() { | |
# The key ID is the first argument to the function. | |
local __KEYID="$1" | |
# The passphrase is the second argument to the function. | |
local __PASSPHRASE="$2" | |
# Session name for screen | |
GenerateHackSessionName | |
local __SESSION="$_RETURN" | |
# Command to run in our session | |
GenerateRevocationCommandString "$__KEYID" | |
local __COMMAND="$_RETURN" | |
# Output file path to display to the user for their information. | |
GenerateRevocationCertificateFilename "$__KEYID" | |
local __OUTFILE="$_RETURN" | |
# Tell the user what we're up to. | |
echo "Generating a backup revocation certificate in a temporary" | |
echo "\`screen\` session..." | |
# Make sure to start screen with a good session name we can use later. | |
__SELF="$0" screen -dmS "$__SESSION" | |
# Send commands to set up the environment and then run the GPG command, | |
# which should start up the pinentry window. | |
screen -S "$__SESSION" -p 0 -X stuff "export GPG_TTY=\$(tty)$(printf \\r)" | |
screen -S "$__SESSION" -p 0 -X stuff "$__COMMAND$(printf \\r)" | |
# Shove the passphrase at the screen session, where it should hopefully | |
# get dumped into the ncurses program and cause the key to get output | |
# into our file. We sleep for a second to give ncurses time to realize we | |
# aren't just pasting text. | |
screen -S "$__SESSION" -p 0 -X stuff "$__PASSPHRASE" | |
sleep 1 | |
screen -S "$__SESSION" -p 0 -X stuff "$(printf \\r)exit$(printf \\r)" | |
# Let the user know we're done. | |
echo "Revocation certificate exported to $__OUTFILE" | |
} | |
# Standard revocation input, since we can call it one of three different ways | |
# and they shouldn't all have to generate it by hand. It optionally takes the | |
# passphrase, in case the input allows for entering the passphrase to bypass | |
# manual entry (GPG v1.*). | |
function GenerateRevocationInput() { | |
# The passphrase is the (optional) first argument to the function. | |
local __PASSPHRASE="$1" | |
# The standard core revocation output | |
read -d '' _RETURN <<EOF | |
y | |
0 | |
Backup revocation certificate | |
y | |
EOF | |
# If we were provided a passphrase, we can append it to the end of the | |
# input. | |
if [ -n "$__PASSPHRASE" ] | |
then | |
_RETURN=$(echo "$_RETURN"; echo "$__PASSPHRASE") | |
fi | |
} | |
# Create a standard session name based on the current date so that we minimize | |
# our chances of colliding with an existing session while also giving us a way | |
# to easily identify our own. | |
function GenerateHackSessionName() { | |
_RETURN="gen-key-$(date +'%Y%m%d%H%M%S')" | |
} | |
# Create a command string for generating a certificate revocation command. | |
# This way we don't have to copy/paste it if we need to copy it elsewhere. | |
function GenerateRevocationCommandString() { | |
# The key ID is the first argument to the function. | |
local __KEYID="$1" | |
# The command needs a path to the key certificate to output. | |
GenerateRevocationCertificateFilename "$__KEYID" | |
local __OUTFILE="$_RETURN" | |
# The command comes in two pieces: a prefix which sets up the revocation | |
# input by loading this script again in an Inception-like manner and | |
# calling one particular function/returning the result, and a command | |
# string which actually executes the `gpg --gen-revoke` command. | |
local __PREFIX __COMMAND | |
# Build up the revocation input prefix. | |
__PREFIX="echo \"\$(. \"\$__SELF\"; GenerateRevocationInput" | |
__PREFIX="$__PREFIX \"\$__PASSPHRASE\"; echo \"\$_RETURN\")\"" | |
# Build up the `--gen-revoke` command string. | |
__COMMAND="gpg --command-fd 0 --no-tty -a -o $__OUTFILE" | |
__COMMAND="$__COMMAND --gen-revoke $__KEYID" | |
# Combine the strings into a return value. | |
_RETURN="$__PREFIX | $__COMMAND" | |
} | |
# Create a path to a given key's revocation certificate file. | |
function GenerateRevocationCertificateFilename() { | |
# The key ID is the first argument to the function. | |
local __KEYID="$1" | |
# It's a pretty simple construction. | |
_RETURN="$__KEYID.revocation.asc" | |
} | |
# Perform the whole set of tasks to generate the key. | |
function __main__() { | |
# Silence any LC_CTYPE errors by providing GPG_TTY | |
export GPG_TTY=$(tty) | |
# A place to store the passphrase and new key ID for the duration | |
local PASSPHRASE KEYID | |
Welcome | |
FetchPassphrase | |
PASSPHRASE="$_RETURN" | |
GenerateKey "$PASSPHRASE" | |
KEYID="$_RETURN" | |
ExportPublicKey "$KEYID" | |
ExportRevocationCertificate "$KEYID" "$PASSPHRASE" | |
} | |
# If we're not being sourced as a library, run the script! | |
[[ "$BASH_SOURCE" == "$0" ]] && __main__ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment