I’ve written recently on how I use a Yubikey as a hardware security token for two factor authentication.
One item I was missing was GPG, and this was mostly because setting up GPG is a bit tricker to set up and I simply hadn’t had the time. My previous key recently expired, so this is a good time to address that.
This article explains the basics of how Yubikey + GPG works, and how to get started.
First glance at the device
yubikey-manager
is required. When installed correctly, running
ykman info
should print the following:
Device type: YubiKey 5C NFC
Serial number: 12345678
Firmware version: 5.2.7
Form factor: Keychain (USB-C)
Enabled USB interfaces: OTP, FIDO, CCID
NFC transport is enabled.
Applications USB NFC
FIDO2 Enabled Enabled
OTP Enabled Disabled
FIDO U2F Enabled Enabled
OATH Enabled Enabled
YubiHSM Auth Not available Not available
OpenPGP Enabled Enabled
PIV Enabled Enabled
There’s three “Enabled USB interfaces” here:
- OTP: This is the basic OTP functionality of the yubikey. Use with
compatible applications (e.g.:
Yubikey Authenticator
) it can generate classic OTP tokens. These are the same kind used by mainstream websites like Twitter and alike. Regrettably, there is little tooling around this functionality, it seems to be tied to this specific brand only, and it can only be used with the mentioned app, which is not very convenient, and somewhat buggy. - FIDO: This is basically
webauthn
, and it’s basically the second factor when logging into website where I’m prompted for “tap your key to log in”. See my previous article for details on this. - CCID (chip card interface device): This interface allows the Yubikey to be used as a smartcard. The protocol is pretty standard and is used by devices which provide on-device keys for things like signing or encrypting. This is what I’ll be using for GPG. Good to see that it’s on by default.
For GPG usage, the Yubikey works as a “smart card”, so I will use this term in future when referring to it. I will use the term “key” to refer to actual GPG keys (e.g.: the blob of bytes that is a key).
Preparing a GPG key
It’s possible to create the key on-device, which is the safest approach, given it cannot be extracted. However, I want to set up a backup device, which needs to have the same key material. Because I can’t extract the key it from one smart card, I can’t put it in the backup one, so I need to generate the key on a computer and the import it into both smart cards. To do this, it’s best to do it on an offline machine. I booted a separate laptop with no network to do this. For full-on paranoid mode, use a Tails live image on a computer with no storage or wireless adapters.
Usually, generating the private key only needs to be done once. On later years, it’s possible to simply extend the expiration of the existing key1.
Key generation
Plug in the smart card and then generate a key with:
gpg --expert --full-gen-key
I want an ed25519 key, so pick ECC and ECC
first, then Curve 25519
. Real
name is not really important, but I just put my own. The key is going to be
tied to my identity anyway. I’m not 100% sure how important email is here, but
also provided the right one – this’ll make the public key easier to map to my
email address.
The output will include a line that looks something like this:
gpg: key 0x7880733B9D062837 marked as ultimately trusted
The key’s id in this case is 0x7880733B9D062837
.
Subkeys
GPG keys can have one or more subkeys. These are additional key pairs that are tied to the master key and can be stored, revoked and expire separately. Each subkey can have one or more of the following capabilities:
S
, signing: Used to sign arbitrary data.C
, certification: Used to sign other keys. We usually call this “key signing”.E
, encryption: Used to encrypt data.A
, authentication: This seems to be used by SSH. It’s relatively new, and I’m not 100% familiar.
Inspecting the key with gpg --list-secret-keys
indicates that the generated
key has an SC
subkey (Signing, Certification) and an E
subkey (Encryption).
Let’s create an Authentication key too with gpg --expert --edit-key $KEYID
:
addkey
(11) ECC (set your own capabilities)
(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finised
(1) Curve 25519
The key should now look something like this:
sec ed25519/7880733B9D062837
created: 2022-07-10 expires: 2023-07-10 usage: SC
trust: ultimate validity: ultimate
ssb cv25519/69799729DDF6BDD3
created: 2022-07-10 expires: 2023-07-10 usage: E
ssb ed25519/F32635370237664C
created: 2022-07-10 expires: 2023-07-10 usage: A
[ultimate] (1). Hugo Osvaldo Barrera <hugo@whynothugo.nl>
Exporting the keys
At this point, the keys need to be exported. When the keys are later imported by GPG into the primary smart card, GPG will delete its copy of the secret key material. I’ll then re-import these for the second/backup smart card:
gpg --armor --export-secret-keys $KEYID > master.key
gpg --armor --export-secret-subkeys $KEYID > subkeys.key
gpg --armor --export $KEYID > public.key
Make sure this is an ephemeral filesystem, it’s a bad idea to keep these files around after finishing this whole process.
Additionally, public.key
MUST be copied onto any system so that GPG can
identify the private key on the smart card. Failing to save the public key
will result in the smart card’s private key being unusable.
Importing the keys into the Yubikey
GPG’s smart card support works here:
gpg --edit-card
First of all, let’s change the admin PIN (default is 12345678
):
admin
passwd
3 - change Admin PIN
Next, let’s change the user PIN (default is 123456
):
1 - change PIN
Now let’s move the keys onto the device:
key 1 # selects the first subkey
keytocard
key 1 # de-selects the first subkey
key 2 # selects the second subkey
keytocard
key 2 # de-selects the second subkey
key 0 # selects the master key
keytocard
Importing the keys into the BACKUP Yubikey
Importing the keys into the PRIMARY Yubikey has deleted them from GPG’s local
storage, so they need to be imported again. It’s best to completely delete
.gnupg
to avoid any inconsistencies left over from the previous key too.
rm -rm ~/.gnupg # You're using an ephemeral system, right!?
gpg --import master.key
gpg --import subkeys.key
gpg --import public.key
The steps from the previous section need to be repeated, but with the backup card plugged in this time.
Extra steps
Finally, configure the Yubikey to require a touch to sign or decrypt anything. This avoids any random program from using the key without manual approval.
I couldn’t do this on the air-gapped machine that did the key generation,
because it need ykman
. Doing this on the daily desktop is fine:
ykman openpgp keys set-touch aut off
ykman openpgp keys set-touch sig on
ykman openpgp keys set-touch enc on
Import the public key here too, so the smart card’s key can be used on this device:
gpg --import public.key
It’s a good idea to upload the public key to a keyserver at this point, so it cannot be lost. It is a public key after all.
Finally, mark the key as trusted with gpg --edit-key 0x7880733B9D062837
:
trust
5 = I trust ultimately
Extending the expiration date
The key will expire in a year, so its expiration needs to be extended around that time. I’ll try to publish a follow-up on how to extend the key expiration when the time comes.
Configuring git to use this key
When signing tags, git
will use a key matching the email configured, so if
the right email is configured via git config --global user.email hugo@whynothugo.nl
then git will use the correct key.
If previous keys exist, it’s best to explicitly specify the key via:
git config --global user.signingkey 0x9D062837
Disclaimer
I mention Yubikey a lot here and in other articles. This is because I own this particular brand, however, there are several alternatives which can be considered. I’ve heard that the Nitrokey 3 is a good FOSS alternative, but have not had a chance to try it out. Researching alternatives is an exercise for the reader.