Skip to content

Instantly share code, notes, and snippets.

@linosteenkamp
Forked from djsplice/yubikey-gpg-ssh-git.md
Last active October 4, 2023 06:32
Show Gist options
  • Save linosteenkamp/83eeaa6fd940f55f9e51c80971019100 to your computer and use it in GitHub Desktop.
Save linosteenkamp/83eeaa6fd940f55f9e51c80971019100 to your computer and use it in GitHub Desktop.
Yubikey - PGP - SSH - Git Signing

Instructions largely found from here: https://www.esev.com/blog/post/2015-01-pgp-ssh-key-on-yubikey-neo/ https://www.ahrenstein.com/blog/using-a-yubikey-4-and-gpg-for-ssh-on-a-mac/

Install the required MacOS software

brew install gnupg2 pinentry-mac
brew install --cask gpg-suite
brew install ykman

Setup master GPG key

Best to store master key and backups on an encrypted removable drive. I formatted a USB drive for this purpose.

1. Mount the encrypted USB drive and link it in to the default .gnupg directory (this will create a new keyring on your encrypted drive)
mv .gnupg .gnupg.orig
ln -s /media/USB .gnupg
2. Create your new key
gpg --expert --full-generate-key
  Select Option 8 - RSA (set your own capabilities)
    Toggle sign, encrypt, authenticate to "off"
  4096 RSA
  Expire 1y
3. Now we need to grab the Key ID of the key we just generated:
gpg --list-secret-keys --keyid-format=long

You'll see output like the following:

-------------------------------
sec   ed25519/A9C4550328B2FA3C 2023-01-06 [SC]
      048282B36C1D7502380C5E8FB9F3220428C2EA3C
uid                 [ultimate] Your Name <you@email.comn>
ssb   cv25519/6CD2394A1E1F4C10 2023-01-06 [E]

The Key ID is the value after the 40 bit key just above the line starting with uid, so in this case it is 048282B36C1D7502380C5E8FB9F3220428C2EA3C. We'll need that again later so we'll temporarily stash it into an environment variable:

export KEY_ID="your-key-id-here"
4. Create environment varible to your keys backup destination
export GPG_BACKUP_FOLDER="/Volumes/FastSSD/gpgKeys"
5. Create revocation cert
gpg --gen-revoke $KEY_ID > $GPG_BACKUP_FOLDER/$KEY_ID-revocation-certificate.asc
6. Add sub keys
gpg --edit-key $KEY_ID
a. Create Encryption subkey
  addkey
  option 8
  (S) Toggle the sign capability off
  (Q) Finished
  follow prompts
b. Create Signature subkey
  addkey
  option 8
  (E) Toggle the encrypt capability off
  (Q) Finished
  follow prompts
c. Create Authentication subkey
  addkey
  option 8
  (S) Toggle the sign capability off
  (E) Toggle the encrypt capability off
  (A) Toggle the authenticate capability on
  (Q) Finished
  follow prompts
  save
7. Backup the your keys
gpg --armor --export-secret-keys $KEY_ID > $GPG_BACKUP_FOLDER/$KEY_ID-secret.asc
gpg --armor --export-secret-subkeys $KEY_ID > $GPG_BACKUP_FOLDER/$KEY_ID-sub-keys.asc
gpg --armor --export $KEY_ID > $GPG_BACKUP_FOLDER/$KEY_ID-public.asc

Process per YubiKey

1. Reset the GPG setting of this YubiKey, execute the following:
ykman openpgp reset

Note the default pin values, you will need it in the next steps.

2. Setup yubikey
ykman openpgp access change-admin-pin
ykman openpgp access change-pin
ykman openpgp keys set-touch aut off
ykman openpgp keys set-touch sig on
ykman openpgp keys set-touch enc on
3. Refresh the GnuPG secret keyring from the backup USB drive
gpg --delete-secret-key $KEY_ID
gpg --delete-keys $KEY_ID
gpg --import < $GPG_BACKUP_FOLDER/$KEY_ID-secret.asc
gpg --import < $GPG_BACKUP_FOLDER/$KEY_ID-sub-keys.asc
4. Transfer sub key's to card
  gpg --edit-key $KEY_ID
a. Transfer Encryption key to card
  key 1
  keytocard
  (2) Encryption key
  key 1
b. Transfer Signature key to card
  key 2
  keytocard
  (1) Signature key
  key 2
c. Transfer Signature key to card
  key 3
  keytocard
  (3) Authentication key
  save
5. Update the Yubikey

In this step we will add the location of your public key to the YubiKey. This location will be configured later in this Gist. [Save and Distribute the public PGP Key](# Save and Distribute the public PGP Key)

gpg --card-edit
  admin #become admin 
  forcesig #make sure the pin is entered before signing
  url 
    URL to retrieve public key: https://keys.openpgp.org/vks/v1/by-fingerprint/$KEY_ID 
  quit
5. Remove your yubikey - insert next key and repeat from step 1

Save and Distribute the public PGP Key

When the master key was created, and each time a subkey was created, a public and private RSA key was also generated. The private keys should remain on the USB drive and on the Yubikey.

The public keys should be distributed to a location where others can find it. Use a public keyserver.

Once a location has been chosen, it’s a good idea to embed the location into the PGP key. That way users know where to find the version of the key with the most up-to-date signatures, subkeys, and revocations. GnuPG can also automatically fetch the latest version of the key with –refresh-keys if the location is embedded within the key. The keyserver command embeds a URL to this key within the public PGP key.

gpg --edit-key <$KEY_ID
  keyserver hkps://keys.openpgp.org
  showpref
  save
  
# upload it to a keyserver
gpg --keyserver hkps://keys.openpgp.org --send-key $KEY_ID
  1. Remove the USB drive with the master key and re-enable your local pgp keyring
# Remove the symlink pointing to /media/USB
rm .gnupg

# Replace the original directory
mv .gnupg.orig .gnupg  
  1. Verify that the master key is no longer on the keyring (it’s on the USB drive’s keyring!)
  gpg -K

make sure there is a # after sec, should look like:

sec# rsa4096 2023-08-11 [CA]

You’re now ready to setup SSH Agent config and Github signing

gpg-agent config (~/.gnupg/gpg-agent.conf)

cat << EOF > ~/.gnupg/gpg-agent.conf
default-cache-ttl-ssh 3600
max-cache-ttl 7200
max-cache-ttl-ssh 7200
enable-ssh-support
EOF

gpg config (~/.gnupg/gpg.conf)

cat << EOF > ~/.gnupg/gpg.conf
auto-key-retrieve
no-emit-version
default-key $KEY_ID
EOF

Update ZSH Profile (~/.zshrc)

cat << EOF >> ~/.zshrc
## Enable GPG SSH capability
export GPG_TTY=$(tty)
export SSH_AUTH_SOCK=${HOME}/.gnupg/S.gpg-agent.ssh
alias gpgreset='gpg-connect-agent killagent /bye; gpg-connect-agent updatestartuptty /bye; gpg-connect-agent /bye'
function switchyubi() {
  rm -r ~/.gnupg/private-keys-v1.d
  gpgconf --kill gpg-agent
  gpg --card-status
}
EOF

source ~/.zshrc

pgp-agent should be running

 ps -ef | grep gpg
  501  6826     1   0 Sat09AM ??         0:04.59 gpg-agent --homedir /Users/jeffbarrows/.gnupg --enable-ssh-support --daemon

Configure Git to sign

git config --global commit.gpgsign true
git config --global user.signingkey $KEY_ID

Upload your public key to your github account

Test your work

  1. Check the Yubikey
$ gpg --card-status

Reader ...........: Yubico Yubikey 4 OTP U2F CCID
Application ID ...: D2760001240102010006061088330000
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 06108833
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : https://pgp.mit.edu/pks/lookup?op=get&search=0x5488D417D56272B8
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: rsa4096 rsa2048 rsa4096
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 31
Signature key ....: 658B 9632 75E1 C826 2D1C  D3FD 7C31 8499 05FB 50B0
      created ....: 2018-01-05 04:43:10
Encryption key....: A238 5DBB 4B58 0BC8 8460  F170 DDDE AF66 DABF 6872
      created ....: 2018-01-05 04:18:17
Authentication key: ECAB 7100 6253 9826 9C51  FD4B 6DF8 0B9B 3DC7 45F2
      created ....: 2018-01-05 04:46:31
General key info..: sub  rsa4096/7C31849905FB50B0 2018-01-05 Jeff Barrows <barrows.jeff@gmail.com>
sec#  rsa4096/5488D417D56272B8  created: 2018-01-05  expires: 2019-01-05
ssb>  rsa2048/DDDEAF66DABF6872  created: 2018-01-05  expires: 2019-01-05
                                card-no: 0006 06108833
ssb>  rsa4096/7C31849905FB50B0  created: 2018-01-05  expires: 2019-01-05
                                card-no: 0006 06108833
ssb>  rsa4096/6DF80B9B3DC745F2  created: 2018-01-05  expires: 2019-01-05
                                card-no: 0006 06108833

Test GPG Encryption

 echo "$(uname -a)" | gpg --encrypt --armor --recipient $KEY_ID
-----BEGIN PGP MESSAGE-----

hQEMA93er2bav2hyAQf/cZGuS56Jd87ohNIcAComLGdQ8i1+VR6UMz0ueuVYx+lO
cmw4AhiebDrwHz+Lwk4ZcL8C4TluU1kU1wcwaAEQEfZgw8YNV5uijyVOwgX41KfK
b0nrLoG3AHOGhIQUA8MagzETGu9DPikV1J10SCLErrQsBdzsWWjJsnvfiUG+51Rn
Ltns+gWwygidmEwRnqwBKZNz2xX0oAWw+TluHbykcCkuTP8Hn4FR2LfVSOrA6DM2
8LxtvKdBv8FFxw1m6oGTXXA1WjUWbTHUOYKEZdxMmcvQ2lbIQ/3f3jCxV6ltBcTm
uuSBCYerls5JD1eJtLrqG0W7LOeqrjAc7c5m//TYMtK0AaLf1VSw7I8hCUJMnMQs
MjkSbG6NIvhCJvgel4FExVACNTnfIw3r0j3tD9uPJ2U7v6R/s6fMvYdhjseTsguv
uokrRbBFcUASQzfVSogOSTmhiPIhGTayIvrz5oieYvnOEWkrVHmKZHgoivE7lD92
ENGaWuLwXqJpxN3KZ/+0Q6WpFaIpq9epYDaAOcV0UwHz5Jm6OJlOsFu44Wde0uQ5
5ncUq7mA9VhXyJM16wic50xa1SnB
=S0yp
-----END PGP MESSAGE-----

Test GPG Signing

  1. You should get prompted for your yubikey pin so it can read the key
$ echo "$(uname -a)" | gpg --armor --clearsign --default-key $KEY_ID

gpg: using "0BDD08F0F38CE51DBD78EBDD33C2B6D2715D4FA8" as default secret key for signing
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Darwin Linos-Mac-Studio.local 22.6.0 Darwin Kernel Version 22.6.0: Wed Jul  5 22:22:05 PDT 2023; root:xnu-8796.141.3~6/RELEASE_ARM64_T6000 arm64
-----BEGIN PGP SIGNATURE-----

iQEzBAEBCAAdFiEE6WF7cvUnHvex2oooJkKUg3wGL9MFAmTXlDoACgkQJkKUg3wG
L9Pxngf/QAT8uZbHknZXu16ETFt6Iiq/26vPhh39HYMhDVZl8juWkMSl3XiGjY8s
lyUyyVhQ2kUazygtR2OFVHH69oE06n9rOkjWpDqAALxTvVXP9oXR7mfWM/Ed8up2
nGVtXSGtzVnIQA21L/eYAkluBOiCI8u3i5GGLxPqUS2Z9YEso/PfLx45/Xvh4XW1
QSMe8t7iWjJl+JVlkaW8MNvnIUSpxlt34m1NAGgzbiSA+cd2pp1xOAbN2fles1ul
Lpi/GxFcuiuviJr7G9IUJii9I9pMLhfONoj+g200L2M1g4aLXz8ybfh9SaW8fUq9
JYe91c2Lr45HIfoYPqrCc69arxVf6w==
=rIMy
-----END PGP SIGNATURE-----

Test Git commit

  • When you git commit -am "Foo" you should get prompted for your Yubikey PIN
  • Looking at your commit history in github should show 'verified'
  • $ git log --show-signature
commit 9686ff87cc398689b08c3084e180d8cdb75888aa (HEAD -> master)
gpg: Signature made Sun Jan  7 10:18:06 2018 PST
gpg:                using RSA key 658B963275E1C8262D1CD3FD7C31849905FB50B0
gpg:                issuer "barrows.jeff@gmail.com"
gpg: Good signature from "Jeff Barrows <barrows.jeff@gmail.com>" [ultimate]
Author: Jeff Barrows <barrows.jeff@gmail.com>
Date:   Sun Jan 7 10:18:06 2018 -0800

    Update
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment