Last active
April 30, 2019 17:41
-
-
Save rhowe-gds/3f514b5701081346ca0b9b05f9653e0f to your computer and use it in GitHub Desktop.
Generate GPG keys and put them on a Yubikey
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 | |
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