Skip to content

Instantly share code, notes, and snippets.

@rhowe-gds
Last active April 30, 2019 17:41
Show Gist options
  • Save rhowe-gds/3f514b5701081346ca0b9b05f9653e0f to your computer and use it in GitHub Desktop.
Save rhowe-gds/3f514b5701081346ca0b9b05f9653e0f to your computer and use it in GitHub Desktop.
Generate GPG keys and put them on a Yubikey
#!/bin/bash
set -euo pipefail
GNUPGHOME=$(mktemp -d)
cleanup() {
echo rm -rf "$GNUPGHOME"
killall gpg-agent || :
}
trap cleanup EXIT
echo "Killing any gpg-agent processes"
killall gpg-agent || :
export GNUPGHOME
if [ -z "${GIVENNAME:-}" ]; then
echo -n "Enter your given name: "
read -r GIVENNAME
fi
if [ -z "${SURNAME:-}" ]; then
echo -n "Enter your surname: "
read -r SURNAME
fi
if [ -z "${EMAILADDRESS:-}" ]; then
echo -n "Enter your email address: "
read -r EMAILADDRESS
fi
if [ -z "${KEYSIZE:-}" ]; then
echo -n "Enter your desired key size (2048 for Yubikey NEO, 4096 otherwise): "
read -r KEYSIZE
fi
if [ -z "${USER_PIN:-}" ]; then
echo -n "Choose a user PIN (6-127 chars). You will use this a lot: "
read -r USER_PIN
fi
if [ -z "${ADMIN_PIN:-}" ]; then
echo -n "Choose an admin PIN (8-127 chars). Used (hopefully rarely) for unlocking the user PIN: "
read -r ADMIN_PIN
fi
KEYCOMMENT="Yubikey $(gdate +%Y%m%d)"
cat << EOF > "$GNUPGHOME/gpg.conf"
use-agent
personal-cipher-preferences AES256 AES192 AES CAST5
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
cert-digest-algo SHA512
s2k-digest-algo SHA512
s2k-cipher-algo AES256
charset utf-8
fixed-list-mode
no-comments
no-emit-version
keyid-format 0xlong
list-options show-uid-validity
verify-options show-uid-validity
with-fingerprint
EOF
cat << EOF > "$GNUPGHOME/gpg-agent.conf"
pinentry-program /usr/local/bin/pinentry-tty
default-cache-ttl 60
max-cache-ttl 120
EOF
echo | REALNAME="$GIVENNAME $SURNAME" EMAILADDRESS=$EMAILADDRESS KEYSIZE=$KEYSIZE KEYCOMMENT=$KEYCOMMENT expect <<"EOF"
set realname $env(REALNAME)
set email $env(EMAILADDRESS)
set comment $env(KEYCOMMENT)
set keysize $env(KEYSIZE)
set timeout 1
spawn gpg --full-generate-key
expect_after timeout { puts "GPG output not as expected. Bailing"; exit 1 }
expect "(4) RSA (sign only)"
expect "Your selection?"
send "4\r"
expect "What keysize do you want? (2048)"
send "$keysize\r"
expect "Key is valid for?"
send "0\r"
expect "Is this correct? (y/N)"
send "y\r"
expect "Real name: "
send "$realname\r"
expect "Email address: "
send "$email\r"
expect "Comment: "
send "$comment\r"
expect "Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?"
send "O\r"
expect "Please enter the passphrase to"
expect "Passphrase:"
sleep 0.5
send "\r"
expect "Repeat:"
sleep 0.5
send "\r"
expect "You have not entered a passphrase - this is in general a bad idea!"
expect "Please confirm that you do not want to have any protection on your key"
expect "es, protection is not needed"
sleep 0.5
set timeout 20
send "y"
expect "public and secret key created and signed"
expect "pub rsa$keysize"
set timeout 1
wait
EOF
echo "Keys generated!"
KEYID=$(gpg --list-keys|sed -ne '/Key fingerprint =/s/Key fingerprint =//p'|tr -d \ )
echo "Key ID is $KEYID"
echo | KEYID=$KEYID KEYSIZE=$KEYSIZE expect <<"EOF"
set keyid $env(KEYID)
set keysize $env(KEYSIZE)
set timeout 1
spawn gpg --expert --edit-key $keyid
expect_after timeout { puts "GPG output not as expected. Bailing"; exit 1 }
expect "gpg>"
send "addkey\r"
expect "(4) RSA (sign only)"
send "4\r"
expect "What keysize do you want?"
send "$keysize\r"
expect "Key is valid for?"
send "0\r"
expect "Is this correct? (y/N)"
send "y\r"
expect "Really create? (y/N)"
send "y\r"
expect "Please enter the passphrase to"
expect "Passphrase:"
sleep 0.5
send "\r"
expect "Repeat:"
sleep 0.5
send "\r"
expect "You have not entered a passphrase - this is in general a bad idea!"
expect "Please confirm that you do not want to have any protection on your key"
expect "es, protection is not needed"
sleep 0.5
set timeout 20
send "y"
expect "ssb rsa$keysize"
set timeout 1
expect "usage: S"
expect "gpg>"
send "addkey\r"
expect "(6) RSA (encrypt only)"
send "6\r"
expect "What keysize do you want?"
send "$keysize\r"
expect "Key is valid for?"
send "0\r"
expect "Is this correct? (y/N)"
send "y\r"
expect "Really create? (y/N)"
send "y\r"
expect "Please enter the passphrase to"
expect "Passphrase:"
sleep 0.5
send "\r"
expect "Repeat:"
sleep 0.5
send "\r"
expect "You have not entered a passphrase - this is in general a bad idea!"
expect "Please confirm that you do not want to have any protection on your key"
expect "es, protection is not needed"
sleep 0.5
set timeout 20
send "y"
expect "ssb rsa$keysize"
set timeout 1
expect "usage: E"
expect "gpg>"
send "addkey\r"
expect "(8) RSA (set your own capabilities)"
send "8\r"
expect "Current allowed actions: Sign Encrypt"
expect "(S) Toggle the sign capability"
expect "Your selection?"
send "S\r"
expect "Current allowed actions: Encrypt"
expect "(E) Toggle the encrypt capability"
expect "Your selection?"
send "E\r"
expect "Current allowed actions:"
expect "(A) Toggle the authenticate capability"
expect "Your selection?"
send "A\r"
expect "Current allowed actions: Authenticate"
expect "(Q) Finished"
expect "Your selection?"
send "Q\r"
expect "What keysize do you want?"
send "$keysize\r"
expect "Key is valid for?"
send "0\r"
expect "Is this correct? (y/N)"
send "y\r"
expect "Really create? (y/N)"
send "y\r"
expect "Please enter the passphrase to"
expect "Passphrase:"
sleep 0.5
send "\r"
expect "Repeat:"
sleep 0.5
send "\r"
expect "You have not entered a passphrase - this is in general a bad idea!"
expect "Please confirm that you do not want to have any protection on your key"
expect "es, protection is not needed"
sleep 0.5
set timeout 20
send "y"
expect "gpg> "
set timeout 1
send "save\r"
sleep 3
EOF
echo
echo "Running hokey against the generated keys to check they are OK"
echo "It won't like the fact that our keys don't expire, but you can ignore that"
echo "It also won't like the lack of cross-certification for the authentication key but this is a false alarm"
gpg --export "$KEYID" | hokey lint
echo -n "Does everything look OK? (y/n) "
read -r yesno
[ "$yesno" == y ] || exit 1
echo -n "Insert your Yubikey and press enter"
read -r
if ! ykman mode | grep -qF 'Current connection mode is: OTP+FIDO+CCID'; then
echo "You will need to say yes to the prompt from ykman"
ykman mode OTP+FIDO+CCID
fi
echo | USER_PIN=$USER_PIN ADMIN_PIN=$ADMIN_PIN GIVENNAME=$GIVENNAME SURNAME=$SURNAME EMAILADDRESS=$EMAILADDRESS expect <<"EOF"
set user_pin $env(USER_PIN)
set admin_pin $env(ADMIN_PIN)
set givenname $env(GIVENNAME)
set surname $env(SURNAME)
set email $env(EMAILADDRESS)
set timeout 1
spawn gpg --card-edit
expect_after timeout { puts "GPG output not as expected. Bailing"; exit 1 }
expect "gpg/card> "
send "admin\r"
expect "Admin commands are allowed"
expect "gpg/card> "
send "passwd\r"
expect "3 - change Admin PIN"
expect "Your selection? "
send "3\r"
expect "Please enter the Admin PIN"
expect "Admin PIN: "
sleep 0.5
send "12345678\r"
expect "New Admin PIN"
expect "Admin PIN: "
sleep 0.5
send "$admin_pin\r"
expect "Repeat this PIN"
expect "Admin PIN: "
sleep 0.5
send "$admin_pin\r"
expect "PIN changed."
expect "1 - change PIN"
expect "Your selection? "
send "1\r"
expect "Please enter the PIN"
expect "PIN: "
sleep 0.5
send "123456\r"
expect "New PIN"
expect "PIN: "
sleep 0.5
send "$user_pin\r"
expect "Repeat this PIN"
expect "PIN: "
sleep 0.5
send "$user_pin\r"
expect "PIN changed."
expect "Q - quit"
expect "Your selection? "
send "Q\r"
expect "gpg/card> "
send "name\r"
expect "Cardholder's surname:"
send "$surname\r"
expect "Cardholder's given name:"
send "$givenname\r"
expect "Please enter the Admin PIN"
expect "Admin PIN: "
sleep 0.5
send "$admin_pin\r"
expect "gpg/card>"
send "lang\r"
expect "Language preferences:"
send "en\r"
expect "gpg/card>"
send "login\r"
expect "Login data (account name):"
send "$email\r"
expect "gpg/card>"
send "\r"
expect "General key info"
expect "gpg/card>"
send "quit\r"
sleep 3
EOF
echo | KEYSIZE=$KEYSIZE KEYID=$KEYID ADMIN_PIN=$ADMIN_PIN expect <<"EOF"
set keysize $env(KEYSIZE)
set keyid $env(KEYID)
set admin_pin $env(ADMIN_PIN)
set timeout 1
spawn gpg --edit-key $keyid
expect_after timeout { puts "GPG output not as expected. Bailing"; exit 1 }
expect "ssb rsa$keysize"
expect "gpg>"
send "key 1\r"
expect "ssb* rsa$keysize"
expect "ssb rsa$keysize"
expect "ssb rsa$keysize"
expect "gpg>"
send "keytocard\r"
expect "Please select where to store the key:"
expect "(1) Signature key"
expect "Your selection?"
send "1\r"
expect "Please enter the Admin PIN"
expect "Admin PIN: "
sleep 0.5
send "$admin_pin\r"
expect "ssb* rsa$keysize"
expect "ssb rsa$keysize"
expect "ssb rsa$keysize"
expect "gpg>"
send "key 1\r"
expect "ssb rsa$keysize"
expect "ssb rsa$keysize"
expect "ssb rsa$keysize"
expect "gpg>"
send "key 2\r"
expect "ssb rsa$keysize"
expect "ssb* rsa$keysize"
expect "ssb rsa$keysize"
expect "gpg>"
send "keytocard\r"
expect "Please select where to store the key:"
expect "(2) Encryption key"
expect "Your selection?"
send "2\r"
expect "Please enter the Admin PIN"
expect "Admin PIN: "
sleep 0.5
send "$admin_pin\r"
expect "ssb rsa$keysize"
expect "ssb* rsa$keysize"
expect "ssb rsa$keysize"
expect "gpg>"
send "key 2\r"
expect "ssb rsa$keysize"
expect "ssb rsa$keysize"
expect "ssb rsa$keysize"
expect "gpg>"
send "key 3\r"
expect "ssb rsa$keysize"
expect "ssb rsa$keysize"
expect "ssb* rsa$keysize"
expect "gpg>"
send "keytocard\r"
expect "Please select where to store the key:"
expect "(3) Authentication key"
expect "Your selection?"
send "3\r"
expect "Please enter the Admin PIN"
expect "Admin PIN: "
sleep 0.5
send "$admin_pin\r"
expect "ssb rsa$keysize"
expect "ssb rsa$keysize"
expect "ssb* rsa$keysize"
expect "gpg>"
send "save\r"
sleep 3
EOF
if [ "$(gpg --list-secret-keys | grep -c '^ssb>')" -ne 3 ]; then
echo "Unexpected output from 'gpg --list-secret-keys':"
gpg --list-secret-keys
exit 1
fi
gpg --armor --export "$KEYID" > ~/"gpg-$KEYID.txt"
echo "Exported public key to ~/gpg-$KEYID.txt"
echo "Sending key to keyserver"
if ! gpg --send-key "$KEYID"; then
echo "WARNING: Failed to send key to keyserver - run gpg --send-key $KEYID manually" >&2
fi
GNUPGHOME='' gpg --import ~/"gpg-$KEYID.txt"
echo "Killing any gpg-agent processes"
killall gpg-agent || :
echo -n "Unplug your Yubkey, re-insert it and press Enter"
read -r
[ ! -f "$HOME/.gnupg/gpg.conf" ] || mv -v "$HOME/.gnupg/gpg.conf" "$HOME/.gnupg/gpg.conf.$(gdate +%s)"
echo "Generating new ~/.gnupg/gpg.conf"
restore_umask=$(umask -p)
umask 077
cat << EOF > ~/.gnupg/gpg.conf
auto-key-locate keyserver
keyserver hkps://hkps.pool.sks-keyservers.net
keyserver-options no-honor-keyserver-url
personal-cipher-preferences AES256 AES192 AES CAST5
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
cert-digest-algo SHA512
s2k-cipher-algo AES256
s2k-digest-algo SHA512
charset utf-8
fixed-list-mode
no-comments
no-emit-version
keyid-format 0xlong
list-options show-uid-validity
verify-options show-uid-validity
with-fingerprint
use-agent
require-cross-certification
EOF
$restore_umask
[ ! -f "$HOME/.gnupg/gpg-agent.conf" ] || mv -v "$HOME/.gnupg/gpg-agent.conf" "$HOME/.gnupg/gpg-agent.conf.$(gdate +%s)"
echo "Generating new ~/.gnupg/gpg-agent.conf"
cat << EOF > ~/.gnupg/gpg-agent.conf
enable-ssh-support
pinentry-program /usr/local/bin/pinentry-mac
default-cache-ttl 60
max-cache-ttl 120
EOF
GNUPGHOME='' gpg --card-status
GNUPGHOME='' gpg --list-secret-keys
echo | KEYID=$KEYID expect <<"EOF"
set keyid $env(KEYID)
set timeout 1
spawn gpg --edit-key $keyid
expect_after timeout { puts "GPG output not as expected. Bailing"; exit 1 }
expect "Secret key is available."
expect "gpg>"
send "trust\r"
expect "Please decide how far you trust this user"
expect "5 = I trust ultimately"
expect "Your decision? "
send "5\r"
expect "Do you really want to set this key to ultimate trust? (y/N) "
send "y\r"
expect "trust: ultimate"
expect "gpg>"
send "quit\r"
sleep 3
EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment