Skip to content

Instantly share code, notes, and snippets.

@o0-o
Last active October 28, 2021 07:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save o0-o/61e11c9928fd7698f1aaae55473e6456 to your computer and use it in GitHub Desktop.
Save o0-o/61e11c9928fd7698f1aaae55473e6456 to your computer and use it in GitHub Desktop.
Unattended/Automated GPG Key Scripts for Yubikey
#!/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
#!/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
#!/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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment