Skip to content

Instantly share code, notes, and snippets.

@sycobuny
Last active March 29, 2024 20:31
Show Gist options
  • Save sycobuny/6553c722757db197f9cf to your computer and use it in GitHub Desktop.
Save sycobuny/6553c722757db197f9cf to your computer and use it in GitHub Desktop.
Quick script to generate new GPG keys (and export pertinent details)
#!/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