Store OpenPGP keys on a YubiKey

This article is the second of a serie dealing with privacy on the Internet. I strongly recommend that you do the first part of this serie of articles (if you haven’t already done so) otherwise you will have a hard time following it. Technically it is not impossible but throughout these articles, the keys used will be the same (among others) and if you don’t have the same structure, it will be a real headache.

By following this part you will configure a YubiKey as a smart card for storing GPG encryption, signing and authentication keys. Keys stored on YubiKey are non-exportable (as opposed to file-based keys that are stored on disk) and are convenient for everyday use. Instead of having to remember and enter passphrases to unlock SSH/GPG keys, YubiKey only needs a physical touch (or not) after being unlocked with a PIN. All signing and encryption operations happen on the card, rather than in OS memory.

Information and requirements

These elements are to be taken into consideration to follow this article:

Purchase

Buy a key that fits your requirement in terms of size, interface (USB-A, USB-C, NFC…). I bought the YubiKey 5 NFC.

Check that your key is genuine

To verify a YubiKey is genuine, open a browser with U2F support and move to the YubiKey verification page. Insert your security key, click Verify Device and follow the instructions. If you see Verification Complete, your device is authentic!

This website verifies YubiKey device attestation certificates has been signed by a set of Yubico certificate authorities, and helps mitigate supply chain attacks.

Configure smart card

Plug in a YubiKey and use GPG to configure it as a smartcard.

gpg --card-edit
Reader ...........: 1050:0407:X:0
Application ID ...: D2760001240103040006120735540000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
[...]

If the card is locked

The card is locked when entering the wrong PIN and Admin PIN multiple times. So you must reset and set up again using the encrypted backup.

First option

Copy the following script into a file named reset.hex.

/hex
scd serialno
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 e6 00 00
scd apdu 00 44 00 00
/echo Card has been successfully reset.

Run gpg-connect-agent --run reset.hex to lock and terminate the card. Then re-insert YubiKey to reset.

Second option

Use ykman. It’s Python library and command-line tool for configuring a YubiKey over an USB connection.

yay -S yubikey-manager
sudo systemctl start pcscd
ykman openpgp reset
WARNING! This will delete all stored OpenPGP keys and data and restore factory settings? [y/N]: y
Resetting OpenPGP data, don't remove your YubiKey...
Success! All data has been cleared and default PINs are set.
PIN:         123456
Reset code:  NOT SET
Admin PIN:   12345678

These two procedures are available here.

PINs

The default PIN is 123456 and default Admin PIN (PUK) is 12345678. Chip Card Interface Device (CCID) PINs can be up to 127 ASCII characters. They must contain at least 6 (PIN) or 8 (PUK) ASCII characters.

The Admin PIN is required for some card operations and to unblock a PIN that has been entered incorrectly more than three times. See the GnuPG documentation on Managing PINs for details.

gpg --card-edit
gpg/card> admin
Admin commands are allowed

gpg/card> passwd
gpg: OpenPGP card no. D2760001240103040006120735540000 detected

Admin PIN (PUK)

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 3
PIN changed.

PIN

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 1
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? Q

Set information

You can add some attributes to your smart card.

gpg/card> name
Cardholder's surname: Doe
Cardholder's given name: John

gpg/card> lang
Language preferences: en

gpg/card> login
Login data (account name): john.doe@example.com

gpg/card> list

Reader ...........: 1050:0407:X:0
Application ID ...: D2760001240103040006120735540000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Name of cardholder: John Doe
Language prefs ...: en
Login data .......: john.doe@example.com
[...]

gpg/card> quit

Touch feature

If your YubiKey has the touch functionnality (the YubiKey 5 NFC has it), you can configure it to request a touch to validate a process (signature, encryption and authentication). This functionnality adds extra physical security.

List actual configuration

ykman openpgp info
OpenPGP version: 3.4
Application version: 5.2.4

PIN tries remaining: 3
Reset code tries remaining: 0
Admin PIN tries remaining: 3

Touch policies
Signature key           Off
Encryption key          Off
Authentication key      Off
Attestation key         Off

As you can see inside the Touch policies section, all touch features are disabled (Off).

Enable signature touch

If you enable this feature, when you are about to sign something, you will need to touch the YubiKey to complete the signing process.

ykman openpgp keys set-touch SIG On
Enter admin PIN:
Set touch policy of signature key to on? [y/N]: y

Enable encryption touch

If you enable this feature, when you are about to encrypt something, you will need to touch the YubiKey to complete the encryption process.

ykman openpgp keys set-touch ENC On
Enter admin PIN:
Set touch policy of encryption key to on? [y/N]: y

Enable authentication touch

If you enable this feature, when you are about to authenticate, you will need to touch the YubiKey to complete the authentication process.

ykman openpgp keys set-touch AUT On
Enter admin PIN:
Set touch policy of authentication key to on? [y/N]: y

Additional notes

Previously, we set touch policy to ON but available options are: Off, On, Fixed, Cached and Cached-Fixed. In accordance with the documentation, here is the explanation of these different policies:

Modify this option according to your needs.

Transfer keys

Transferring keys to YubiKey using keytocard is a destructive, one-way operation only. Make sure you’ve made a backup before proceeding: keytocard converts the local on-disk key into a stub, which means the on-disk copy is no longer usable to transfer to subsequent security key devices or mint additional keys.

gpg --edit-key $KEYID
Secret key is available.

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ultimate] (1). John Doe <john.doe@example.com>

Signing subkey

You will be prompted for the master key passphrase and Admin PIN. Select and transfer the signature key.

gpg> key 1

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb* ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ultimate] (1). John Doe <john.doe@example.com>

gpg> keytocard
Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb* ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ultimate] (1). John Doe <john.doe@example.com>

Encrypting subkey

Type key 1 again to de-select and key 2 to select the next key.

gpg> key 1

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ultimate] (1). John Doe <john.doe@example.com>

gpg> key 2

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb* cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ultimate] (1). John Doe <john.doe@example.com>

gpg> keytocard
Please select where to store the key:
   (2) Encryption key
Your selection? 2

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb* cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ultimate] (1). John Doe <john.doe@example.com>

Authenticating subkey

Type key 2 again to deselect and key 3 to select the last key.

gpg> key 2

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ultimate] (1). John Doe <john.doe@example.com>

gpg> key 3

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb* ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ultimate] (1). John Doe <john.doe@example.com>

gpg> keytocard
Please select where to store the key:
   (3) Authentication key
Your selection? 3

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb* ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ultimate] (1). John Doe <john.doe@example.com>

Save and quit

gpg> save

Verify smart card

Verify the subkeys have been moved to YubiKey as indicated by ssb>.

gpg -K
/tmp/tmp.EOmP2zbviM/pubring.kbx
-------------------------------
sec   ed25519/0xB52326F13324098C 2020-10-11 [C]
      Key fingerprint = 0ED8 F486 1E57 693E C1E3  4EBE B523 26F1 3324 098C
uid                   [ultimate] John Doe <john.doe@example.com>
ssb>  ed25519/0x9B76AA52AF00EFEF 2020-10-11 [S] [expires: 2021-10-11]
ssb>  cv25519/0xE3AC01686E511A38 2020-10-11 [E] [expires: 2021-10-11]
ssb>  ed25519/0x2BA042FD767F18C1 2020-10-11 [A] [expires: 2021-10-11]

Multiple YubiKeys (optional)

To provision additional security keys, restore the master key backup and repeat the Configure smart card procedure.

mv -vi $GNUPGHOME $GNUPGHOME.1
renamed '/tmp/tmp.EOmP2zbviM' -> '/tmp/tmp.EOmP2zbviM.1'
cp -aiv /mnt/encrypted-storage/tmp.EOmP2zbviM/ $GNUPGHOME
'/mnt/encrypted-storage/tmp.EOmP2zbviM' -> '/tmp/tmp.EOmP2zbviM'

Cleanup

Make sure you have:

Reboot or securely delete (via srm) $GNUPGHOME and remove the secret keys from the GPG keyring.

yay -S srm
srm -r $GNUPGHOME || rm -rf $GNUPGHOME
gpg --delete-secret-key $KEYID
set -e KEYID

Close the terminal used to generate things. This action will erase the environment variables we have created. Finally, make sure you have securely erased all generated keys and revocation certificates if an ephemeral enviroment was not used.

Using keys

Strengthen the configuration

Copy the following snippet into ~/.gnupg/gpg.conf.

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
no-greeting
keyid-format 0xlong
list-options show-uid-validity
verify-options show-uid-validity
with-fingerprint
require-cross-certification
no-symkey-cache
throw-keyids

Set the correction permission

chmod 600 ~/.gnupg/gpg.conf

Install required tool

yay -S gnupg

Mount the non-encrypted volume created earlier

sudo mount /dev/sda2 /mnt/public/

Import the public key

gpg --import /mnt/public/gpg-0xB52326F13324098C-2020-10-11.key
gpg: key 0xB52326F13324098C: public key "John Doe <john.doe@example.com>" imported
gpg: Total number processed: 1
gpg:               imported: 1

Trust the key

set KEYID 0xB52326F13324098C

Edit the master key to assign it ultimate trust by selecting trust and 5.

gpg --edit-key $KEYID
pub  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: unknown       validity: unknown
sub  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
sub  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
sub  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ unknown] (1). John Doe <john.doe@example.com>

gpg> trust
pub  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: unknown       validity: unknown
sub  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
sub  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
sub  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ unknown] (1). John Doe <john.doe@example.com>

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y

pub  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: ultimate      validity: unknown
sub  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
sub  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
sub  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ unknown] (1). John Doe <john.doe@example.com>
Please note that the shown key validity is not necessarily correct
unless you restart the program.

gpg> save

Remove and re-insert YubiKey and check the status (some information has been removed).

gpg --card-status
Reader ...........: 1050:0407:X:0
Application ID ...: D2760001240103040006120735540000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Name of cardholder: John Doe
Language prefs ...: en
Login data .......: john.doe@example.com
Key attributes ...: ed25519 cv25519 ed25519
Signature key ....: 49B1 F362 745A AEB6 5EEF  5E71 9B76 AA52 AF00 EFEF
      created ....: 2020-10-11 13:34:13
Encryption key....: 7BC2 2D5E 82CB A5B8 965A  FF86 E3AC 0168 6E51 1A38
      created ....: 2020-10-11 13:35:38
Authentication key: 7296 3EBE 99CD 050F 936C  1E4E 2BA0 42FD 767F 18C1
      created ....: 2020-10-11 13:37:34
General key info..: sub  ed25519/0x9B76AA52AF00EFEF 2020-10-11 John Doe <john.doe@example.com>
sec#  ed25519/0xB52326F13324098C  created: 2020-10-11  expires: never
ssb>  ed25519/0x9B76AA52AF00EFEF  created: 2020-10-11  expires: 2021-10-11
                                  card-no: 0006 12073554
ssb>  cv25519/0xE3AC01686E511A38  created: 2020-10-11  expires: 2021-10-11
                                  card-no: 0006 12073554
ssb>  ed25519/0x2BA042FD767F18C1  created: 2020-10-11  expires: 2021-10-11
                                  card-no: 0006 12073554

Note that sec# indicates master key is not available (as it should be stored encrypted offline). If you see General key info..: [none] in the output instead - go back and import the public key using the previous step.

Encryption

Encrypt a message to your own key

echo "Sensitive information" >> password.txt
gpg --encrypt --default-recipient-self password.txt

You can also encrypt to multiple recipients (or to multiple keys).

gpg --encrypt --recipient $KEYID_0 --recipient $KEYID_1 --recipient $KEYID_2 password.txt

Decrypt the previous message

If you have configured the key for encryption, your YubiKey will blink. Touch it to complete the process.

gpg --decrypt password.txt.pgp
gpg: anonymous recipient; trying secret key 0xE3AC01686E511A38 ...
gpg: okay, we are the anonymous recipient.
gpg: encrypted with ECDH key, ID 0x0000000000000000
Sensitive information

If your YubiKey is not connected, GPG will ask you to insert it. Otherwise you will be asked for the PIN and then, the message will be decrypted.

Signature

If you have configured the key for signing, your YubiKey will blink. Touch it to complete the process.

Make a detached signature

gpg --detach-sign password.txt

Make a clear text signature

The --clearsign flag makes a cleartext signature. The content in a cleartext signature is readable without any special software. OpenPGP software is only needed to verify the signature. Cleartext signatures may modify end-of-line whitespace for platform independence and are not intended to be reversible. The signing key is chosen by default or can be set explicitly using the --local-user and --default-key options.

gpg --clearsign password.txt

Verify the signature

gpg --verify signed.txt.sig
gpg: assuming signed data in 'password.txt'
gpg: Signature made Sun 11 Oct 2020 05:10:17 PM CEST
gpg:                using EDDSA key 49B1F362745AAEB65EEF5E719B76AA52AF00EFEF
gpg: Good signature from "John Doe <john.doe@example.com>" [ultimate]
Primary key fingerprint: 0ED8 F486 1E57 693E C1E3  4EBE B523 26F1 3324 098C
     Subkey fingerprint: 49B1 F362 745A AEB6 5EEF  5E71 9B76 AA52 AF00 EFEF

Encrypt and sign

The conventional extension for a signed and encrypted message is .gpg and the resulting file will contain bot the encrypted message as well as the signature. The following example will create a signed and encrypted .gpg file.

gpg --sign --encrypt --default-recipient-self password.txt

To verify, only --decrypt must be used.

gpg --decrypt password.txt.gpg
gpg: anonymous recipient; trying secret key 0xE3AC01686E511A38 ...
gpg: okay, we are the anonymous recipient.
gpg: encrypted with ECDH key, ID 0x0000000000000000
Sensitive information
gpg: Signature made Sun 11 Oct 2020 05:10:17 PM CEST
gpg:                using EDDSA key 49B1F362745AAEB65EEF5E719B76AA52AF00EFEF
gpg: Good signature from "John Doe <john.doe@example.com>" [ultimate]
Primary key fingerprint: 0ED8 F486 1E57 693E C1E3  4EBE B523 26F1 3324 098C
     Subkey fingerprint: 49B1 F362 745A AEB6 5EEF  5E71 9B76 AA52 AF00 EFEF

Rotating keys

PGP does not provide forward secrecy - a compromised key may be used to decrypt all past messages. Although keys stored on YubiKey are difficult to steal, it is not impossible - the key and PIN could be taken, or a vulnerability may be discovered in key hardware or random number generator used to create them, for example. Therefore, it is good practice to occassionally rotate subkeys.

When a subkey expires, it can either be renewed or replaced. Both actions require access to the offline master key. Renewing subkeys by updating their expiration date indicates you are still in possession of the offline master key and is more convenient.

Replacing keys, on the other hand, is less convenient but more secure: the new subkeys will not be able to decrypt previous messages, authenticate with SSH, etc. Contacts will need to receive the updated public key and any encrypted secrets need to be decrypted and re-encrypted to new subkeys to be usable. This process is functionally equivalent to “losing” the YubiKey and provisioning a new one. However, you will always be able to decrypt previous messages using the offline encrypted backup of the original keys.

Neither rotation method is superior and it’s up to personal philosophy on identity management and individual threat model to decide which one to use, or whether to expire subkeys at all. Ideally, subkeys would be ephemeral: used only once for each encryption, signing and authentication event, however in practice that is not really feasible or worthwhile with YubiKey. Advanced users may want to dedicate an offline device for more frequent key rotations and ease of provisioning.

Set up environment

To renew or rotate subkeys, follow the same process as generating keys: boot to a secure environment, install required software and disconnect networking.

Connect the offline secret storage device with the master keys and identify the disk label.

sudo dmesg
sd 5:0:0:0: [sda] 7866368 512-byte logical blocks: (4.03 GB/3.75 GiB)

Decrypt and mount the offline volume.

sudo cryptsetup open /dev/sda1 secret
Enter passphrase for /dev/sda1:

sudo mount /dev/mapper/secret /mnt/encrypted-storage

Import the master key and configuration to a temporary working directory.

set -x GNUPGHOME (mktemp -d)
gpg --import /mnt/encrypted-storage/tmp.EOmP2zbviM/mastersub.key
gpg: keybox '/tmp/tmp.UtNncYzruW/pubring.kbx' created
gpg: /tmp/tmp.UtNncYzruW/trustdb.gpg: trustdb created
gpg: key B52326F13324098C: public key "John Doe <john.doe@example.com>" imported
gpg: key B52326F13324098C: secret key imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

Copy the configuration file.

cp -v /mnt/encrypted-storage/tmp.EOmP2zbviM/gpg.conf $GNUPGHOME
'/mnt/encrypted-storage/tmp.EOmP2zbviM/gpg.conf' -> '/tmp/tmp.UtNncYzruW/gpg.conf'

Export the master key ID.

set KEYID 0xB52326F13324098C

Renewing subkeys

Renewing subkeys is simple: you do not need to generate new keys, move keys to the YubiKey, or update any SSH public keys linked to the GPG key. All you need to do is to change the expiry time associated with the public key (which requires access to the master key you have just loaded) and then to export that public key and import it on any computer where you wish to use the GPG (as distinct from the SSH) key.

To change the expiration date of all subkeys, start by selecting all keys.

gpg --edit-key $KEYID
Secret key is available.

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: unknown       validity: unknown
ssb  ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ unknown] (1). John Doe <john.doe@example.com>

gpg> key 1

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: unknown       validity: unknown
ssb* ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb  cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ unknown] (1). John Doe <john.doe@example.com>

gpg> key 2

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: unknown       validity: unknown
ssb* ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb* cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb  ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ unknown] (1). John Doe <john.doe@example.com>

gpg> key 3

sec  ed25519/0xB52326F13324098C
     created: 2020-10-11  expires: never       usage: C
     trust: unknown       validity: unknown
ssb* ed25519/0x9B76AA52AF00EFEF
     created: 2020-10-11  expires: 2021-10-11  usage: S
ssb* cv25519/0xE3AC01686E511A38
     created: 2020-10-11  expires: 2021-10-11  usage: E
ssb* ed25519/0x2BA042FD767F18C1
     created: 2020-10-11  expires: 2021-10-11  usage: A
[ unknown] (1). John Doe <john.doe@example.com>

Then, use the expire command to set a new expiration date. Despite the name, it will not cause currently valid keys to become expired.

expire
Are you sure you want to change the expiration time for multiple subkeys? (y/N) y
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0)

Follow these prompts to set a new expiration date, then save to save your changes.

Next, export your public key.

gpg --export $KEYID > pubkey.gpg

Transfer that public key to the computer from which you use your GPG key, and then import it with.

gpg --import pubkey.gpg

This will extend the validity of your GPG key and will allow you to use it for SSH authorization. Note that you do not need to update the SSH public key located on remote servers.

Rotating keys

Rotating keys is a bit more involved. First, follow the original steps to generate each subkey. Previous subkeys may be kept or deleted from the identity.

Finish by exporting new keys.

gpg --armor --export-secret-keys $KEYID > $GNUPGHOME/mastersub.key
gpg --armor --export-secret-subkeys $KEYID > $GNUPGHOME/sub.key

Copy the new temporary working directory to encrypted offline storage, which should still be mounted.

sudo cp -avi $GNUPGHOME /mnt/encrypted-storage
[...]
'/tmp/tmp.UtNncYzruW/pubring.kbx~' -> '/mnt/encrypted-storage/tmp.UtNncYzruW/pubring.kbx~'

There should now be at least two versions of the master and subkeys backed up.

ls /mnt/encrypted-storage/
lost+found/  tmp.EOmP2zbviM/  tmp.UtNncYzruW/

Unmount and close the encrypted volume.

sudo umount /mnt/encrypted-storage
sudo cryptsetup close /dev/mapper/secret

Export the updated public key.

sudo mkdir /mnt/public
sudo mount /dev/sda2 /mnt/public
gpg --armor --export $KEYID | sudo tee /mnt/public/$KEYID-(date +%F).txt
sudo umount /mnt/public

Disconnect the storage device and follow the original steps to transfer new keys (4, 5 and 6) to YubiKey, replacing existing ones. Reboot or securely erase the GPG temporary working directory.

The second part is done. Now, we will see how to use a YubiKey for SSH connections.