Setting up GPG with YubiKey/Smart Cards

Posted on:

Before We Start

Check Your Device

If you’re using a real Smart Card, ignore this part.

If you’re using a YubiKey or a compatible security key product, check with the manufacture to make sure it has OpenPGP Smart Card feature/Applet enabled. Some products have PIV and not OpenPGP, which could not be used to store your GPG key.

After making sure you have the right product, know the key size your key supports. YubiKey 4 or 5 supports up to 4096 bits, and YubiKey NEO only supports 2048 bits.

Backup Before You Proceed

Before reading this, you should already know that using the command line without knowing what you’re doing could be dangerous. Make sure you understand clearly which line does what and do not proceed if otherwise.

Just to make sure you don’t make any unwanted mistakes(because we all make mistakes), before you proceed, create a backup of the ~/.gnupg directory.

Use a Live USB Installation

Since we’ll be deleting the master key after moving it to your card, it’s important to wipe the device afterwards. Deleting your files from a drive doesn’t mean it’s really gone. In most cases data recovery is still possible. The best option would be to store it on an usb drive and erase it completely afterwards. Even shred it into pieces if you wish. But why go through all those hassle when you can just use an in-memory/Live USB installation? If the key only resides in-memory, after a single power cycle, it will be completely gone, without a trace.

Generate the Master Key

We’ll generate a 4096 bit RSA key with only the certify capability enabled. Don’t worry about the capability part. It could be changed later. If your smart card only supports up to 2048 bits, not a big deal, use that instead. Execute the following command:

gpg --expert --full-generate-key
gpg (GnuPG) 2.2.29; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: directory '/home/skyfalls/.gnupg' created
gpg: keybox '/home/skyfalls/.gnupg/pubring.kbx' created
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
   (9) ECC and ECC
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (13) Existing key
  (14) Existing key from card
Your selection? 8

Possible actions for a RSA key: Sign Certify Encrypt Authenticate 
Current allowed actions: Sign Certify Encrypt 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? s

Possible actions for a RSA key: Sign Certify Encrypt Authenticate 
Current allowed actions: Certify Encrypt 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? e

Possible actions for a RSA key: Sign Certify Encrypt Authenticate 
Current allowed actions: Certify 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? q
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096
Requested keysize is 4096 bits
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) 0
Key does not expire at all
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: TestUser
Email address: test@example.com
Comment: 
You selected this USER-ID:
    "TestUser <test@example.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: /home/skyfalls/.gnupg/trustdb.gpg: trustdb created
gpg: key 02FCFF106CA8320A marked as ultimately trusted
gpg: directory '/home/skyfalls/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/skyfalls/.gnupg/openpgp-revocs.d/D67443B6CEED99FAC2BC0C8702FCFF106CA8320A.rev'
public and secret key created and signed.

pub   rsa4096 2021-10-16 [C]
      D67443B6CEED99FAC2BC0C8702FCFF106CA8320A
uid                      TestUser <test@example.com>

The information here is public, so if you want to stay anonymous, use a fake name instead. As for email, always use a valid one.

Test the Key

Replace TestUser with your name or email.

gpg --list-key --keyid-format long
pub   rsa4096/02FCFF106CA8320A 2021-10-16 [C]
      D67443B6CEED99FAC2BC0C8702FCFF106CA8320A
uid                 [ultimate] TestUser <test@example.com>

[C]: certify, ability to create subkeys

02FCFF106CA8320A: short id(same as the last 16 characters of the long id)

D67443B6CEED99FAC2BC0C8702FCFF106CA8320A: long id, also called the key grip

Create a Revocation Certificate

The revocation certificate exists so that you can revoke your master key if it ever gets lost. You can always regenerate a new one when you still have the secret key available(both on disk or smart card).

The output of --full-generate-key already told you that, a revocation certificate has already been created for you along with the master key. You could use that, or you could generate a new one if you need to. Keep in mind that leaking your revocation certificate could lead to a key revocation attack, so treat it as if it’s your private key.

gpg --output revoke-master.pgp --gen-revoke TestUser
sec  rsa4096/02FCFF106CA8320A 2021-10-16 TestUser <test@example.com>

Create a revocation certificate for this key? (y/N) y
Please select the reason for the revocation:
  0 = No reason specified
  1 = Key has been compromised
  2 = Key is superseded
  3 = Key is no longer used
  Q = Cancel
(Probably you want to select 1 here)
Your decision? 0
Enter an optional description; end it with an empty line:
> 
Reason for revocation: No reason specified
(No description given)
Is this okay? (y/N) y
ASCII armored output forced.
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: This is a revocation certificate

iQI2BCABCAAgFiEE1nRDts7tmfrCvAyHAvz/EGyoMgoFAmFqZUQCHQAACgkQAvz/
...
-----END PGP PUBLIC KEY BLOCK-----
Revocation certificate created.

Please move it to a medium which you can hide away; if Mallory gets
access to this certificate he can use it to make your key unusable.
It is smart to print this certificate and store it away, just in case
your media become unreadable.  But have some caution:  The print system of
your machine might store the data and make it available to others!

Keep the revocation certificate to somewhere safe, presumably offline.

Back up the Master Key

To File

gpg --armor --output public.asc --export TestUser
gpg --armor --output private.asc --export-secret-key 

The output keys should look like this:

-----BEGIN PGP PUBLIC/PRIVATE KEY BLOCK-----

mQINBGFqTbYBEADeSPqdWGBJOWzaq+TGG18JU6fw38Q1WyWfbh45RjUh/feorMTi
...
-----END PGP PUBLIC/PRIVATE KEY BLOCK-----

You should avoid storing your master key’s secret on your HDD/SSD, instead use a throwaway thumb drive.

To QR Code With PaperKey

This requires the paperkey package. In my opinion, it’s the best way to store a key long-term wise. It’s safe, cheap, and it lasts amazingly long under proper storage conditions.

gpg --export-secret-key TestUser | paperkey --output-type raw | qrencode --8bit --output secret-qr.png

The QR code generated with default settings are already quite large, mine is 387×387 pixels, and this is with the correction level set to L , which only allows about 7% of lost data. Increasing it to Q(second highest) produces an image that’s 519×519 pixels, borders included. With an 4096 bits key, the H(highest) option would throw an Input data too large error.

gpg --export-secret-key TestUser | paperkey --output-type raw | qrencode --8bit --level H --output secret-qr.png

Paperkey will require you to have your public key ready when decoding your private key. It’s important to keep that safe as well. Using a key server is a good option. I also recommend you to print out the public key grip along with your private key qr code for easy lookups.

Uploading Public Keys to a Keyserver

Upload your public key to any keyserver you want. Replace the domain with the one you’re using.

gpg --keyserver pgp.mit.edu --send-key 02FCFF106CA8320A

Most keyservers have a web page where you can submit your public key. You could also use that instead. Note: This only backs up the public key, so you still have to keep your private key safe. Most keyservers are also publicly accessible and searchable, and other people can view your email and name.

Moving the Key to Card/Yubikey

gpg --edit-key TestUser

You shouldn’t have the warning if you’re starting from scratch or having already performed a factory reset.

gpg> keytocard
Really move the primary key? (y/N) y
Please select where to store the key:
   (1) Signature key
Your selection? 1

gpg: WARNING: such a key has already been stored on the card!

Replace existing key? (y/N) y

sec  rsa4096/02FCFF106CA8320A
     created: 2021-10-16  expires: never       usage: C   
     trust: ultimate      validity: ultimate
[ultimate] (1). TestUser <test@example.com>

gpg> q
Save changes? (y/N) y

After saving your keys in a safe place, you can safely move on to a less secure environment, such as your main working pc. Currently, your private key is still present on your local keyring. It’s important to remove it. Using a Live USB installation? Now it’s the time to perform a reboot/shutdown.

After this, you always need to have your card/Yubikey connected to be able to use gpg functionalities that requires the private key.

gpg --delete-secret-key D67443B6CEED99FAC2BC0C8702FCFF106CA8320A
gpg (GnuPG) 2.2.29; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


sec  rsa4096/02FCFF106CA8320A 2021-10-16 TestUser <test@example.com>

Delete this key from the keyring? (y/N) y
This is a secret key! - really delete? (y/N) y

Optionally, verify that the secret key is present on the card:

gpg --card-edit
...
General key info..: 
pub  rsa4096/02FCFF106CA8320A 2021-10-16 TestUser <test@example.com>
sec>  rsa4096/02FCFF106CA8320A  created: 2021-10-16  expires: never     
                                card-no: **** ********

And only on your card(gone from your local keyring):

gpg --edit-key TestUser
Secret key is available.

sec  rsa4096/02FCFF106CA8320A
     created: 2021-10-16  expires: never       usage: C   
     card-no: **** ********
     trust: ultimate      validity: ultimate
[ultimate] (1). TestUser <test@example.com>

Card no. should be present, although sometimes gpg fails to update the key info. if that’s the case, run gpg --card-status, it should have your gpg key listed in “General key info”.

Setup For Your Working PC

Check the Disaster recovery section for guide for key recovery and import.

You’ll have to retrieve the public key to your working device before proceeding.

Trusting the Key

gpg --edit-key TestUser
gpg> trust
sec  rsa4096/02FCFF106CA8320A
     created: 2021-10-16  expires: never       usage: C   
     card-no: **** ********
     trust: ultimate      validity: unknown
[ultimate] (1). TestUser <test@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

Subkeys

Subkeys are the keys that you’ll use on a daily basis. It’s possible to generate and revoke individual keys, so that if one did get leaked, your master key is still valid and safe to use.

SSH Key

Expert mode is required.

gpg --expert --edit-key TestUser
gpg> addkey
Secret parts of primary key are stored on-card.
Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (12) ECC (encrypt only)
  (13) Existing key
  (14) Existing key from card
Your selection? 8

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Sign Encrypt 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? a

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Sign Encrypt Authenticate 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? s

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Encrypt Authenticate 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? e

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Authenticate 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? q
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 2048
Requested keysize is 2048 bits
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) 1y
Key expires at Sun 16 Oct 2022 01:05:39 PM CST
Is this correct? (y/N) y
Really create? (y/N) y
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

sec  rsa4096/02FCFF106CA8320A
     created: 2021-10-16  expires: never       usage: C   
     card-no: 0000 00000001
     trust: ultimate      validity: ultimate
ssb  rsa2048/DAD85D6F66B57061
     created: 2021-10-16  expires: 2022-10-16  usage: A   
[ultimate] (1). TestUser <test@example.com>
gpg -K --with-keygrip 
/home/skyfalls/.gnupg/pubring.kbx
---------------------------------
sec>  rsa4096 2021-10-16 [C]
      D67443B6CEED99FAC2BC0C8702FCFF106CA8320A
      Keygrip = 0C33AA3FB96C0D223120D8A34BD19FFAA583AD7E
      Card serial no. = 0000 
uid           [ultimate] TestUser <test@example.com>
ssb   rsa2048 2021-10-16 [A] [expires: 2022-10-16]
      Keygrip = CBE5A020311F844B3752DCA7A65E08B41DBCD563

Each key has a key grip, in this case, CBE5A020311F844B3752DCA7A65E08B41DBCD563(40chars). You’ll need to write it to ~/.gnupg/sshcontrol, in a new line.

echo CBE5A020311F844B3752DCA7A65E08B41DBCD563 >> ~/.gnupg/sshcontrol

Then, add these lines to ~/.bashrc, ~/.zshrc, or whatever your shell is using.

...
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent
#gpg-connect-agent updatestartuptty /bye

Sometimes when performing a ssh connection, authentication will fail instantly because of “agent refused operation”. Running ssh in verbose mose(ssh -v) and you’ll see a line saying “sign_and_send_pubkey: signing failed for RSA “cardno:xxx” from agent: agent refused operation”. Running gpg-connect-agent updatestartuptty /bye fixes that. Strange issue but easy enough to fix.

Get your ssh public key:

ssh-add -L

Or install directly on the target server:

ssh-copy-id root@example.com

Git Sign-offs

Essentially the same. We’ll use a 4096bit RSA key with the sign capability.

The problem here is that each smart card can only store 3 keys with respectively sign, encrypt and authenticate capabilities. And the master key always uses the sign slot. If you want to use a dedicated subkey for signing, it wouldn’t fit on the smart card. You’ll have to change your master key’s capability for that. Or you can generate a subkey per device, and use that instead.

Sign-off Commits With Master Key

gpg> change-usage
Changing usage of the primary key.

Possible actions for a RSA key: Sign Certify Encrypt Authenticate 
Current allowed actions: Certify 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? s

Possible actions for a RSA key: Sign Certify Encrypt Authenticate 
Current allowed actions: Sign Certify 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? q

Now you can use your master key for signing. IMO this works pretty well with git, on which your commit history is always preserved and there’s no need for key rotation. For other use cases you should think about it throughly before using your master key for anything else.

Continue by following the rest of Github’s documention .

Sign-off Commits With a Subkey

Simply generate a subkey with sign enabled.

After the key is ready to go:

# Get the key id
gpg --list-secret-keys --keyid-format=long
# Upload the output to github
gpg --armor --export 1C2159142FDFA9852C5247E028D20361D004716C

Continue by following the rest of Github’s documention .

Card Configuration

Key Retrieval URL

If you have your public key stored somewhere on the internet as a file, use this.

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

gpg/card> url
URL to retrieve public key: https://keys.openpgp.org/vks/v1/by-fingerprint/1C2159142FDFA9852C5247E028D20361D004716C

gpg/card> fetch
gpg: requesting key from 'https://keys.openpgp.org/vks/v1/by-fingerprint/1C2159142FDFA9852C5247E028D20361D004716C'
gpg: key 028D20361D004716C: "TestUser <test@example.com>" not changed
gpg: Total number processed: 1
gpg:              unchanged: 1

Passwords

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

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

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

Your selection? 3
PIN changed.

Pin Retries

ykman openpgp access set-retries <PIN-RETRIES> <RESET-CODE-RETRIES> <ADMIN-PIN-RETRIES>
ykman openpgp access set-retries 6 6 6

Read more on Yubico’s man page .

Enabling Touch

Require user touch when using the hardware key. You still need to type your pin once for the first request after powering on.

ykman openpgp keys set-touch <SIG|ENC|AUT|ATT> <cached|cached-fixed|fixed|off|on>

ykman openpgp keys set-touch sig on
WARNING: The use of this command is deprecated and will be removed!
Replace with: ykman openpgp keys set-touch sig on

Enter Admin PIN: 
Set touch policy of signature key to on? [y/N]: y

If you encounter the WARNING: PC/SC not available error, run:

sudo systemctl enable pcscd.service
sudo systemctl start pcscd.service

Disaster Recovery

Retrieving the Public Key From Key Server

gpg --keyserver pgp.mit.edu --recv-key 02FCFF106CA8320A

If you do not know your key grip, use the search function of your keyserver’s website.

Recovering the Private Key From Paperkey

You’ll need your public key as a file. Dearmor the public key, then try to import with paperkey:

gpg --dearmor public.pgp
zbarimg -1 --raw -q -Sbinary secret-qr.png | paperkey --pubring public.pgp.gpg | gpg --import

Import Raw/armored Key Files

gpg --import private.asc

Using the Revocation Certificate

If your master key has been stolen by someone and you’re facing identity theft, you should revoke your key ASAP. Remember the revocation certificate we’ve generated before?

gpg --import revoke.pgp 
gpg: key 02FCFF106CA8320A: "TestUser <test@example.com>" revocation certificate imported
gpg: Total number processed: 1
gpg:    new key revocations: 1
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u

Push updates to the keyserver(don’t forget to say goodbye!):

gpg --send-keys D67443B6CEED99FAC2BC0C8702FCFF106CA8320A

Revoking your master key will render all the subkeys unusable. Only consider this as a last resort, as you can always revoke individual subkeys.