Skip to content

Unattended/Automated GPG Key Scripts for Yubikey#

gpg_add_key.sh

#!/usr/bin/env sh
# Run this on an air-gapped computer with an encrypted hard drive to set up GPG keys on your yubikey.
# Derived from https://github.com/drduh/YubiKey-Guide
# Assumes OS has already been prepared (packages, services, etc) -- see dr duh guide.
# Does not configure PINs on the yubikey. If no admin pin is provided, the default 12345678 is used.
#
# Usage: gpg_add_yubi.sh gpg-backup.tar.gz gpg_passhrase [yubikey_admin_pin]
########################################################################

# Safety and portability
set -eu
set -o posix || true #notably dash doesn't support this
set -o pipefail || true #not technically posix but widely supported

# Define key and yubi parameters
export GNUPGHOME="$(mktemp -d)"
chmod 0700 "$GNUPGHOME"
backup="$1"
passphrase="$2"
puk="${3-12345678}"
key_type="rsa2048"

cd "$GNUPGHOME"

# Restore backup
tar -xzf "${backup}"

fpr="$(gpg --list-options 'show-only-fpr-mbox' --list-secret-keys | awk '{print $1}')"

# Issue superfluous admin command on the yubi so that it won't prompt
# for the admin pin on the next command
printf  '%s\n'              \
    'admin'             \
    'lang'              \
    'en'                \
    'q'             |
gpg --pinentry-mode 'loopback'  \
    --passphrase    "$puk"      \
    --command-fd    0       \
    --edit-card

# Copy keys to the yubi
printf  '%s\n'              \
    'key 1'             \
    'keytocard'         \
    '1'             \
    'y'             \
    'key 1'             \
    'key 2'             \
    'keytocard'         \
    '2'             \
    'y'             \
    'key 2'             \
    'key 3'             \
    'keytocard'         \
    '3'             \
    'y'             \
    'save'              |
gpg --pinentry-mode 'loopback'  \
    --passphrase    "$passphrase"   \
    --command-fd    0       \
    --edit-key  "$fpr"

# Print gpg key and yubi details
gpg --card-status

gpg -K

gpg --delete-secret-key

cd

rm -rf "$GNUPGHOME"

echo 'Reboot soon and before restoring network connectivity.' 1>&2

# Just cause
unset fpr GNUPGHOME passphrase

# vim: ts=8:sw=8:sts=8:noet:ft=sh

gpg_gen_yubi.sh

#!/usr/bin/env sh
# Run this on an air-gapped computer with an encrypted hard drive to set up GPG keys on your yubikey.
# Derived from https://github.com/drduh/YubiKey-Guide
# Assumes OS has already been prepared (packages, services, etc) -- see dr duh guide.
# Does not configure PINs on the yubikey. If no admin pin is provided, the default 12345678 is used.
# Some older Yubikeys do not support rsa4096. Change key_type to rsa2048.
# The GPG key passphrase is randomly generated and printed to stderr at the end of the script.
# Copy the backup tar.gz file to an encrypted drive.
#
# Usage: gpg_gen_yubi.sh name email [yubikey_admin_pin]
########################################################################

# Safety and portability
set -eu
set -o posix || true #notably dash doesn't support this
set -o pipefail || true #not technically posix but widely supported

# Backup targets -- set these or the master key will be deleted permanently
crypt1='/mnt/crypt1' #entire GNUPGHOME directory is backed up to $crypt1 and $crypt2
crypt2='/mnt/crypt2'
pub1='/mnt/pub1' #public and revocation keys are copied to $pub1 and $pub2
pub2='/mnt/pub2'

# Define key and yubi parameters
export GNUPGHOME="$(mktemp -d)"
chmod 0700 "$GNUPGHOME"
name="$1"
email="$2"
passphrase="$(  dd if=/dev/urandom bs=1k count=1 2>/dev/null    |
        LC_ALL=C tr -dc '\41\43-\46\60-\71\74-\132' |
        cut -c 1-24                     )"
puk="${3-12345678}"
key_type="rsa4096"
subkey_expire='2y'

cd "$GNUPGHOME"

# Configure gpg based on dr duh guide
printf  '%s\n'                                              \
    'personal-cipher-preferences AES256 AES192 AES'                         \
    'personal-digest-preferences SHA512 SHA384 SHA256'                      \
    'personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed'                 \
    'default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES 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'                                      \
    'require-cross-certification'                                   \
    'no-symkey-cache'                                       \
    'use-agent'                                         \
    'throw-keyids'                                          \
    >'gpg.conf'

chmod 0600 'gpg.conf'

# Unattended key generation
gpg --batch                 \
    --passphrase    "$passphrase"       \
    --quick-gen-key "${name} <${email}>"    \
            "$key_type"     \
            'cert'          \
            '0'

fpr="$(gpg --list-options 'show-only-fpr-mbox' --list-secret-keys | awk '{print $1}')"

gpg --batch             \
    --pinentry-mode 'loopback'  \
    --passphrase    "$passphrase"   \
    --quick-add-key "$fpr"      \
            "$key_type" \
            'sign'      \
            "$subkey_expire"

gpg --batch             \
    --pinentry-mode 'loopback'  \
    --passphrase    "$passphrase"   \
    --quick-add-key "$fpr"      \
            "$key_type" \
            'encrypt'   \
            "$subkey_expire"

gpg --batch             \
    --pinentry-mode 'loopback'  \
    --passphrase    "$passphrase"   \
    --quick-add-key "$fpr"      \
            "$key_type" \
            'auth'      \
            "$subkey_expire"

# Exports
printf  '%s\n'                          \
    'y'                         \
    '0'                         \
    'This revocation certificate was created pre-emptively' \
    ''                          \
    'y'                         \
    'y'                         |
gpg --output    "revoke-no-reason-$fpr.asc"     \
    --pinentry-mode 'loopback'              \
    --passphrase    "$passphrase"               \
    --command-fd    0                   \
    --gen-revoke    "$fpr"

printf  '%s\n'                          \
    'y'                         \
    '1'                         \
    'This revocation certificate was created pre-emptively' \
    ''                          \
    'y'                         \
    'y'                         |
gpg --output    "revoke-compromised-$fpr.asc"       \
    --pinentry-mode 'loopback'              \
    --passphrase    "$passphrase"               \
    --command-fd    0                   \
    --gen-revoke    "$fpr"

printf  '%s\n'                          \
    'y'                         \
    '2'                         \
    'This revocation certificate was created pre-emptively' \
    ''                          \
    'y'                         \
    'y'                         |
gpg --output    "revoke-superseded-$fpr.asc"        \
    --pinentry-mode 'loopback'              \
    --passphrase    "$passphrase"               \
    --command-fd    0                   \
    --gen-revoke    "$fpr"

printf  '%s\n'                          \
    'y'                         \
    '3'                         \
    'This revocation certificate was created pre-emptively' \
    ''                          \
    'y'                         \
    'y'                         |
gpg --output    "revoke-no-longer-used-$fpr.asc"    \
    --pinentry-mode 'loopback'              \
    --passphrase    "$passphrase"               \
    --command-fd    0                   \
    --gen-revoke    "$fpr"

gpg --pinentry-mode     'loopback'  \
    --passphrase        "$passphrase"   \
    --armor                 \
    --export-secret-keys    "$fpr"      \
    >'master.key'

gpg --pinentry-mode     'loopback'  \
    --passphrase        "$passphrase"   \
    --armor                 \
    --export-secret-subkeys "$fpr"      \
    >'sub.key'

gpg --armor             \
    --export    "$fpr"      \
    >"gpg-${fpr}-$(date +%F).asc"

# Copy public key to unencrypted store
for pub_key in "gpg-${fpr}-"*".asc"; do
    cp "$pub_key" "${pub1-/dev/null}"
    cp "$pub_key" "${pub2-/dev/null}"
done

# Copy revocation certificates to unencrypted store
for rev_cert in "revoke-"*"-${fpr}.asc"; do
    cp "$rev_cert" "${pub1-/dev/null}"
    cp "$rev_cert" "${pub2-/dev/null}"
done

# Must back up to encrypted store before copying to yubi
tar -czf "backup-$(date +%F).tar.gz" *
cp "backup-"*".tar.gz" "${crypt1-/dev/null}"
cp "backup-"*".tar.gz" "${crypt2-/dev/null}"

# Issue superfluous admin command on the yubi so that it won't prompt
# for the admin pin on the next command
printf  '%s\n'              \
    'admin'             \
    'lang'              \
    'en'                \
    'q'             |
gpg --pinentry-mode 'loopback'  \
    --passphrase    "$puk"      \
    --command-fd    0       \
    --edit-card

# Copy keys to the yubi
printf  '%s\n'              \
    'key 1'             \
    'keytocard'         \
    '1'             \
    'y'             \
    'key 1'             \
    'key 2'             \
    'keytocard'         \
    '2'             \
    'y'             \
    'key 2'             \
    'key 3'             \
    'keytocard'         \
    '3'             \
    'y'             \
    'save'              |
gpg --pinentry-mode 'loopback'  \
    --passphrase    "$passphrase"   \
    --command-fd    0       \
    --edit-key  "$fpr"

# Print gpg key and yubi details
gpg --card-status

gpg -K

gpg --delete-secret-key

cd

rm -rf "$GNUPGHOME"

printf  '%s\n'                              \
    'WRITE THIS DOWN IN A SECURE PLACE' "$passphrase"       \
    'Reboot soon and before restoring network connectivity.'    1>&2

# Just cause
unset fpr GNUPGHOME passphrase

# vim: ts=8:sw=8:sts=8:noet:ft=sh

gpg_renew_yubi.sh

#!/usr/bin/env sh
# Run this on an air-gapped computer with an encrypted hard drive to set up GPG keys on your yubikey.
# Derived from https://github.com/drduh/YubiKey-Guide
# Assumes OS has already been prepared (packages, services, etc) -- see dr duh guide.
# Does not configure PINs on the yubikey. If no admin pin is provided, the default 12345678 is used.
#
# Usage: gpg_renew_yubi.sh gpg-backup.tar.gz gpg_passhrase
########################################################################

# Safety and portability
set -eu
set -o posix || true #notably dash doesn't support this
set -o pipefail || true #not technically posix but widely supported

# Define key and yubi parameters
export GNUPGHOME="$(mktemp -d)"
chmod 0700 "$GNUPGHOME"
backup="$1"
passphrase="$2"
subkey_expire='2y'

cd "$GNUPGHOME"

# Backup targets
#crypt1='/mnt/crypt1'
#crypt2='/mnt/crypt2'
#pub1='/mnt/pub1'
#pub2='/mnt/pub2'

# Restore backup
tar -xzf "${backup}"

fpr="$(gpg --list-options 'show-only-fpr-mbox' --list-secret-keys | awk '{print $1}')"

# Renew keys
printf  '%s\n'              \
    'key 1'             \
    'expire'            \
    "$subkey_expire"        \
    'key 1'             \
    'key 2'             \
    'expire'            \
    "$subkey_expire"        \
    'key 2'             \
    'key 3'             \
    'expire'            \
    "$subkey_expire"        \
    'save'              |
gpg --pinentry-mode 'loopback'  \
    --passphrase    "$passphrase"   \
    --command-fd    0       \
    --edit-key  "$fpr"

# Export new public key
gpg --armor             \
    --export    "$fpr"      \
    >"gpg-${fpr}-$(date +%F).asc"

# Copy public key to unencrypted store
: rm "gpg-${fpr}-"*'.asc'
: cp "gpg-${fpr}-"*'.asc' "${pub1-/dev/null}"
: cp "gpg-${fpr}-"*'.asc' "${pub2-/dev/null}"
gpg --import "gpg-${fpr}-"*'.asc'

# Must back up to encrypted store before copying to yubi
: rm 'backup-'*'.tar.gz'
tar -czf "backup-$(date +%F).tar.gz" *
: cp 'backup-'*'.tar.gz' "${crypt1-/dev/null}"
: cp 'backup-'*'.tar.gz' "${crypt2-/dev/null}"

gpg --import "gpg-${fpr}-"*'.asc'
# Print gpg key and yubi details
gpg --card-status

gpg -K

gpg --delete-secret-key

cd

rm -rf "$GNUPGHOME"

echo 'Reboot soon and before restoring network connectivity.' 1>&2

# Just cause
unset fpr GNUPGHOME passphrase

# vim: ts=8:sw=8:sts=8:noet:ft=sh