A Yubikey for all your needs

Well, almost all, that is. A Yubikey is a great piece of hardware that supports PKI, OTP and U2F. It comes in various different models, some supporting NFC or even biometrics. They can all be attached to your (physical) keyring so it can always be brought along and they are nearly unbreakable.

I bought a Yubikey 5 NFC a few years back, primarily for my OTP needs back then, allowing me to generate OTPs by tapping the Yubikey to the back of my phone with the Yubico Authenticator. This added a nice ternary level of authentication to my OTP generation.

However, the Yubikey can be used for much more than just that. It can also store a keypair that you can bring along everywhere. The major advantage of this approach is that the private key isn’t lingering anywhere and that you have it with you all the time. The physical device, together with its PIN protection adds a great layer of security to your public key authentication needs.

Being dependent upon this device obviously has its downsides. If you forget to bring it, you can’t access your resources, but since it so easily attaches to your keyring, this is hardly an excuse. If it is lost or stolen, you lost your private key, so it is mandatory to back it up so you can restore the keypair on a fresh Yubikey.

I will explain the steps I took to get this to work and in the end also describe how it can be used with SSH, which was my primary use case.

Prerequisites

Several packages are required to get this working:

  • gpg (GNU Privacy Guard, key tooling)
  • scdaemon (smartcard daemon, to communicate with the Yubikey)
  • cryptsetup (to create an encrypted USB drive for key backup)

I dedicated two USB sticks to the backup and stored one of them offsite, so some hardware may come in handy too.

Creating a new GPG keypair

The first thing we need is a keypair. This can easily be done using the gpg tools. There are various ways to do it, with the –full-generate-key and –expert options providing the most control:

$ gpg --expert --full-generate-key
gpg (GnuPG) 2.2.20; Copyright (C) 2020 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.

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? 

By default, it will create an RSA primary key and an RSA subkey with the Encrypt capability. For this first step, this is fine.

Next, it will ask for the desired keylength, I picked 4096:

Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (4096) 
Requested keysize is 4096 bits
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want for the subkey? (4096) 
Requested keysize is 4096 bits

Next up is the expiration of the key. This is highly dependent on your use case, paranoia and/or company policy. My primary use cases for these keys are authentication and not signing and should I ever decide to distribute my public key to a keyserver, unless I forget my passphrase (unlikely) I will always be able to generate revocation certificates because I have multiple safe backups of my keys. So, I picked the default of no expiration, but feel free to pick something else:

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

Now, it needs to add an identity to this key, which I suspect needs no further explanation:

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

Real name: John Doe
Email address: johndoe@example.com
Comment: 
You selected this USER-ID:
    "John Doe <johndoe@example.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O

After this, a popup will appear asking you to protect the key with a passphrase, which you should never forget and should be safe enough to prevent bruteforcing. Once you entered a proper passphrase, it will start generating keys and you can help the RNG with some entropy by moving your mouse or typing on your keyboard (or, by running rngd in another terminal).

Once done, it will tell you it generated a revocation certificate for you. However, unless you don’t plan on backing up your private key (bad idea), you can always do it later and it is probably better to supply a reason when you need one.

Just to be sure, verify if the key has been created using –list-keys:

$ gpg --list-keys
gpg: checking the trustdb
... omitted ...
--------------------------------
pub   rsa4096 2020-08-02 [SC]
      243621FEC2D638EA895C2A10905A2A5CABE1A6D9
uid           [ultimate] John Doe <johndoe@example.com>
sub   rsa4096 2020-08-02 [E]

Adding signing and authentication subkeys

The process above created a primary key with a subkey with Encrypt capabilities. However, we actually want two additional subkeys, one for signing and one for authentication, to store separately on the Yubikey.

This can again easily be done using gpg with –edit-key and then the addkey command:

$ gpg --expert --edit-key johndoe@example.com
gpg (GnuPG) 2.2.20; Copyright (C) 2020 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.

Secret key is available.

sec  rsa4096/905A2A5CABE1A6D9
     created: 2020-08-02  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/4CBAE5C167E7E4C4
     created: 2020-08-02  expires: never       usage: E   
[ultimate] (1). John Doe <johndoe@example.com>

gpg> addkey
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?

Let’s start with the authentication subkey by picking 8, so we can set our own capabilities. By default, it will have Sign and Encrypt capabilities and the wizard will allow us to toggle capabilities. We turn off Sign and Encrypt by using S and E and then toggle Authenticate on by using A:

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? S

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: 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 Encrypt Authenticate 
Current allowed actions: 

   (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: Authenticate 

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

Your selection? Q

The rest of the process will be identical to the primary key we did before. After that, we create the signing subkey and you need to repeat this process, but can also pick 4 instead of 8 at the beginning, which is a shortcut to a sign only RSA key.

Ultimately, when doing a –list-keys again, you should see all three subkeys:

$ gpg --list-keys
...
--------------------------------
...
pub   rsa4096 2020-08-02 [SC]
      243621FEC2D638EA895C2A10905A2A5CABE1A6D9
uid           [ultimate] John Doe <johndoe@example.com>
sub   rsa4096 2020-08-02 [E]
sub   rsa4096 2020-08-02 [S]
sub   rsa4096 2020-08-02 [A]

Backing up the keys to an encrypted USB drive

Before we go any further, we need to create backups of our keys, because writing them to the Yubikey deletes them from your system.

It is easy to create backups of your key and secrets, as follows:

gpg --armor --export-secret-keys johndoe@example.com > ~/keys.asc

gpg --armor --export-secret-subkeys johndoe@example.com > ~/subkeys.asc

However, they still need to be stored somewhere safe, like an encrypted USB drive. This is easy to do using cryptsetup (note that this process erases the contents of your USB drive, so make sure you want to dedicate it):

# Initialize LUKS partition (find the right device using fdisk -l or lsblk)
sudo cryptsetup luksFormat /dev/sdc

# Now, create a mapping to the LUKS partition
sudo cryptsetup luksOpen /dev/sdc USBDrive

# Enter passphrase

# The previous command mapped the LUKS partition to /dev/mapper/USBDrive

# Create an ext4 filesystem on it
sudo mkfs -t ext4 /dev/mapper/USBDrive

# Now you can mount it and use it like any other drive
sudo mount /dev/mapper/USBDrive /media/USBDrive

Now, you can simply copy the generated key backups to this drive:

sudo cp *.asc /media/USBDrive

Once done, unmount and close the mapping:

sudo umount /media/USBDrive

sudo cryptsetup luksClose USBDrive

Now, remove the USB stick and store it somewhere safe. Repeat this process for as many backups as you’d like to create.

If you ever need to remount it, you can use the following sequence:

sudo cryptsetup luksOpen /dev/sdc USBDrive

# Enter passphrase

sudo mount /dev/mapper/USBDrive /media/USBDrive

Configuring scdaemon for use with the Yubikey

To communicate with the Yubikey, we need to configure scdaemon through its configuration file in ~/.gnupg/scdaemon.conf:

# Avoid conflicts with pcscd (falls back on pcscd driver by default)
# If you don't need pcscd it can be removed as well
# If it is not installed, you don't need this line
disable-ccid

# For Yubikey 4 and newer
reader-port Yubico Yubi
# For older Yubikeys
reader-port Yubico Yubikey

scdaemon itself is started by gpg, but you may want to kill a running scdaemon instance for these changes to take effect next time it’s used.

To test if the communication works as expected, use the –card-status command:

$ gpg --card-status
Reader ...........: Yubico YubiKey OTP FIDO CCID 00 00
Application ID ...: D2760000000000000000000000000000
Application type .: OpenPGP
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 12345678
Name of cardholder: Jeffrey Voorhaar
Language prefs ...: en
Salutation .......: Mr.
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: rsa4096 rsa4096 rsa4096
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 1
Signature key ....: C000 0000 0000 0000 0000  0000 0000 0000 0000 0000
      created ....: 2020-08-01 19:04:53
Encryption key....: B000 0000 0000 0000 0000  0000 0000 0000 0000 0000
      created ....: 2020-08-01 19:03:44
Authentication key: 6000 0000 0000 0000 0000  0000 0000 0000 0000 0000
      created ....: 2020-08-01 19:05:26
General key info..: sub  rsa4096/0000000000000000 2020-08-01 Jeffrey Voorhaar <...>
sec   rsa4096/0000000000000000  created: 2020-08-01  expires: never     
ssb>  rsa4096/0000000000000000  created: 2020-08-01  expires: never     
                                card-no: 0006 09241344
ssb>  rsa4096/0000000000000000  created: 2020-08-01  expires: never     
                                card-no: 0006 09241344
ssb>  rsa4096/0000000000000000  created: 2020-08-01  expires: never     
                                card-no: 0006 09241344

As a sidenote, if you want to modify these settings and change the Yubikey’s default PIN and admin PIN, you can do so through the following command:

$ gpg --edit-card

Writing the keys to the Yubikey

Now, we need to write the subkeys we created to the right slots of the Yubikey. This can again be done using gpg through the –edit-key command:

$ gpg --edit-key johndoe@example.com
gpg (GnuPG) 2.2.20; Copyright (C) 2020 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.

Secret key is available.

sec  rsa4096/905A2A5CABE1A6D9
     created: 2020-08-02  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/4CBAE5C167E7E4C4
     created: 2020-08-02  expires: never       usage: E   
ssb  rsa4096/CB7A4A219495577F
     created: 2020-08-02  expires: never       usage: S   
ssb  rsa4096/452EC0ECC537A2C2
     created: 2020-08-02  expires: never       usage: A   
[ultimate] (1). John Doe <johndoe@example.com>

gpg>

As you can see, there are three ssb entries, each with a different usage flag.

We can select the first one as follows, which will add an asterisk in front of it:

gpg> key 1

sec  rsa4096/905A2A5CABE1A6D9
     created: 2020-08-02  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb* rsa4096/4CBAE5C167E7E4C4
     created: 2020-08-02  expires: never       usage: E   
ssb  rsa4096/CB7A4A219495577F
     created: 2020-08-02  expires: never       usage: S   
ssb  rsa4096/452EC0ECC537A2C2
     created: 2020-08-02  expires: never       usage: A   
[ultimate] (1). John Doe <johndoe@example.com>

The keytocard command can then write the subkey to a slot you pick:

gpg> keytocard

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

For the S and A keys the options will be different and you should select (1) Signature key and (3) Authentication key respectively there.

Note that this supports multiselect and you should type “key 1” again to deselect it before selecting “key 2”:

gpg> key 1

sec  rsa4096/905A2A5CABE1A6D9
     created: 2020-08-02  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/4CBAE5C167E7E4C4
     created: 2020-08-02  expires: never       usage: E   
ssb  rsa4096/CB7A4A219495577F
     created: 2020-08-02  expires: never       usage: S   
ssb  rsa4096/452EC0ECC537A2C2
     created: 2020-08-02  expires: never       usage: A   
[ultimate] (1). John Doe <johndoe@example.com>

gpg> key 2

sec  rsa4096/905A2A5CABE1A6D9
     created: 2020-08-02  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/4CBAE5C167E7E4C4
     created: 2020-08-02  expires: never       usage: E   
ssb* rsa4096/CB7A4A219495577F
     created: 2020-08-02  expires: never       usage: S   
ssb  rsa4096/452EC0ECC537A2C2
     created: 2020-08-02  expires: never       usage: A   
[ultimate] (1). John Doe <johndoe@example.com>

gpg> 

Once you’re done copying the keys to the right slots, persist the changes and quit the gpg tool by typing “save”:

gpg> save

Your Yubikey is now ready for use!

Setting up gpg-agent for SSH

To be able to use this for SSH, we need to do something else first, namely configure gpg-agent as our default key agent for SSH.

First, we need to add a line to ~/.gnupg/gpg-agent.conf:

enable-ssh-support

And restart the gpg-agent:

gpgconf --kill gpg-agent

SSH communicates throuh a socket with its key agent, which is typically ssh-agent. We need to tell it to use gpg-agent instead. This can be persisted upon each login by the following small snippet added to .bashrc (or sourced from a different file in there):

export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)

Now, if everything went well, you can find your public key as follows:

$ ssh-add -L

ssh-rsa AAAAB3N...P1O3M2ULBw== cardno:000000000000

This can be added to your SSH server’s authorized_keys file to allow signin using the Yubikey.

If, when trying to SSH to your server, the following error is displayed:

sign_and_send_pubkey: signing failed for RSA "cardno:000000000000" from agent: agent refused operation

Run this command and try again:

gpg-connect-agent updatestartuptty /bye

Wrapping up

While the sample with SSH is trivial and a very common use case, it can be used for many more things, including for signing in to most operating systems and protecting your boot sequence with Secure Boot. Combined with using it for OTP (or even better: U2F), makes it a powerful, secure and user friendly solution.