Using the same key with different OpenPGP cards
I recently got a Yubikey for PGP because I didn’t want to store my subkeys’ private key on disk. When doing this, it’s generally a good idea to have a backup key in case your primary is unusable for any reason – you might break it, drop in a water, or it might just stop working all of a sudden.123 In such events, the backup will come handy.
The way GnuPG works with card-based PGP is as follows. A stub secret key that references your card’s serial number is stored locally in ~/.gnupg/private-keys-v1.d
. This is why gpg --list-secret-keys
displays your keys even if your card isn’t connected and they’re not actually stored locally.
To query this information:
$ gpg-connect-agent 'keyinfo --list' /bye
S KEYINFO KEYGRIPKEYGRIPKEYGRIPKEYGRIPKEYGRIPKEYGR T SERIALNOSERIALNOSERIALNOSERIALNO OPENPGP.3 - - - - -
S KEYINFO KEYGRIPKEYGRIPKEYGRIPKEYGRIPKEYGRIPKEYGR T SERIALNOSERIALNOSERIALNOSERIALNO OPENPGP.2 - - - - -
S KEYINFO KEYGRIPKEYGRIPKEYGRIPKEYGRIPKEYGRIPKEYGR T SERIALNOSERIALNOSERIALNOSERIALNO OPENPGP.1 - - - - -
OK
The T
in the fourth column signifies that the key is stored on a smartcard. See help keyinfo
to learn more about the format.
Each line of the output is derived from a corresponding file in ~/.gnupg/private-keys-v1.d
with the keygrip as its name. Removing that file removes all references to the secret key existing. To rebuild that reference, all one needs to do is run gpg --card-status
after inserting a card.
But, of course, we’d like to have this automated. In order to automatically choose the card that’s inserted, we’ll make use of udev
rules that get triggered when a card is inserted or removed.
Plug in your Yubikey and run lsusb | grep -i Yubikey
to get your vendor and product ID. Mine were 1050:0407
for both of my keys. Add the appropriate udev
rule to /etc/udev/rules.d/
(remember to set $USER
appropriately):
$ cat /etc/udev/rules.d/40-yubikey.rules
ACTION=="add|change", \
SUBSYSTEM=="usb", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0407", \
RUN+="/usr/local/bin/clean-card-private-keys -u $USER"
The rule calls a clean-card-private-keys
script whenever a Yubikey that that matching 1050:0407
is inserted. automatically deletes all card-based secret key stubs from ~/.gnupg/private-keys-v1.d
and re-generates the stubs by running gpg --card-status
(full script):
$ cat /usr/local/bin/clean-card-private-keys
[...]
clean_card_private_keys() {
if [[ "$run_as" == "" ]]; then
keygrips=$(
gpg-connect-agent 'keyinfo --list' /bye 2>/dev/null \
| grep -v OK \
| awk '{if ($4 == "T") { print $3 ".key" }}')
for f in $keygrips; do
rm -v ~/.gnupg/private-keys-v1.d/$f
done
gpg --card-status 2>/dev/null 1>/dev/null
else
echo ${BASH_SOURCE[0]}
su "$run_as" -c "${BASH_SOURCE[0]}"
fi
}
[...]
To test, run watch gpg-connect-agent "'keyinfo --list'" /bye
and hot-swap your card a bunch of times to watch the serial numbers get updated every time. Of course, you could also not use the udev
rules and just manually run the script every time you really need to scrub your private key stubs.
Also, the script could probably be made better to remove only those keygrips associated with the newly inserted card, but I couldn’t be bothered. Pull requests to the script welcome!
-
Remember that you need to generate your encryption, signing and authentication subkeys off-key because you need to store them in two different cards. ↩
-
I recommend getting a USB-C one as a backup to a USB-A primary (or vice versa) so that you’re not locked out by the lack of appropriate ports. You shouldn’t carry your backup with you anywhere, but just in case your primary can’t be used at the same place where you store your backup, you have an extra option now. ↩
-
A backup Yubikey is useless when you lose your primary because you should be looking for your backed up revocation certificates for compromised keys instead. ↩