Using Yubikey with OpenSSH in PIV Mode (2023 Edition)

ยท 871 words ยท 5 minute read

Just received my updated Yubikeys and thought I’d document the process of using them with OpenSSH in PIV mode. Again, but better. ๐Ÿ˜€

For some context, with a Yubikey, you have 3 options to use them with OpenSSH:

  1. OpenPGP mode: Very complicated in terms of tooling. Maybe worth it, if you already use OpenPGP or GPG.
  2. FIDO2 mode: Needs a relatively new OpenSSH version on the server and client. The simplest option, if you don’t have older servers to manage. See this other article
  3. PIV mode: Moderately complicated to set up and only supports a limited set of approved ciphers.

So we’ll be using option 3 in this article.

Prerequisites ๐Ÿ”—

Obviously you’ll need one or two Yubikeys. Two are recommended if one breaks. Or you add some kind of backup key on the server.

Next we need 2 packages installed: The Yubico PIV Tool to manage the key’s PIV mode and Yubikey Manager, a simple GUI tool to view and adjust key settings.

To install them via Homebrew on macOS:

$ brew install yubico-piv-tool yubico-yubikey-manager

Step 1: Secure Key ๐Ÿ”—

If you just received the key, it’s worth changing the default PIN and PUK. This can be done in the GUI via Yubikey Manager or in the terminal:

$ ykman piv access change-pin
$ ykman piv access change-puk
$ ykman piv access change-management-key --generate --protect

Some notes:

  • Changing the management key is optional. If you change it, you will need to add the --key flag to all future yubico-piv-tool commands.
  • The ykman command is bundled with the Yubikey Manager tool. On macOS you can find it in /Applications/YubiKey

Step 2: Generate and Import Private Key ๐Ÿ”—

PIV mode supports only 3 kinds of keys: RSA2048, ECCP256, ECCP384. As of 2023, RSA2048 is a bit dated and slow. So we’ll go with ECCP384.

At this step you have 2 options on where to generate the private key:

Option 1: Generate Private Key ON Yubikey ๐Ÿ”—

First you can generate the key on the Yubikey. Benefit is that it can never be extracted from there and will be lost if the key is damaged. This is also the downside: You can’t have a spare key. To generate the private key on the Yubikey:

$ yubico-piv-tool -s 9a -AECCP384 -a generate --pin-policy=once --touch-policy=always -o public.pem

When running the generate or import-key action, you can also customize the security policy. I.e. if and when a touch and PIN entry will be requested.

Option 2: Generate Private Key OFF Yubikey ๐Ÿ”—

Second option is to generate the key on your own machine and then copy it to as many Yubikeys as you like. This is slightly less secure, but also safer, in case a Yubikey gets damaged. To generate a private and public key in ECC 384 with openssl:

$ openssl ecparam -genkey -name secp384r1 -noout -out ec384-key.pem
$ openssl pkey -in ec384-key.pem -pubout > ec384-pub.pem

After this you will have 2 files, which correspond to your private and public key. Import them onto as many Yubikeys as needed using the commands below. Then be sure to safely delete the private key.

$ yubico-piv-tool -s 9a --pin-policy=once --touch-policy=always -a import-key -i ec384-key.pem

Step 3: Generate PIV Certificate ๐Ÿ”—

Now we’re ready to import the PIV certificate onto the Yubikey. This certificate is only needed for the PIV specification and isn’t used by SSH. Depending on where you generated your private key, it will be slightly different:

If you generated the private key on the Yubikey before, you only need one step to create a PIV certificate:

$ yubico-piv-tool -a verify-pin -a selfsign-certificate -s 9a --subject "/CN=SSH key/" --valid-days=3650 -i public.pem -o cert.pem

If you chose to generate the private key off the Yubikey:

$ yubico-piv-tool -a verify-pin -a selfsign-certificate -s 9a --subject="/CN=SSH key/" --valid-days=3650 -i ec384-pub.pem -o cert.pem

The final step of importing it is the same:

$ yubico-piv-tool -a import-certificate -s 9a -i cert.pem

Step 4: Export OpenSSH Public Key ๐Ÿ”—

With our private key stored safely on the key and PIV mode set up, we can verify that it all worked. If you see some key details listed for slot 9a, then everything worked as expected.

$ yubico-piv-tool -a status

Now we’re ready to grab the public OpenSSH key to be used on remote machines. Note that on macOS the file /usr/local/lib/libykcs11.dylib is often a symlink, which won’t work due to security restrictions. So you will need to remove the symlink and replace it with an actual copy of libykcs11.dylib.

View the public key corresponding to the private key stored on the Yubikey like this:

$ ssh-keygen -D /usr/local/lib/libykcs11.dylib -e

You will see a long string starting with ecdsa-sha2-nistp384. This is what you add to a remote server’s authorized_keys file.

Step 5: Practical Tips ๐Ÿ”—

Finally, some helpful tricks to make it work better with OpenSSH:

To (re)add the Yubikey to ssh-agent and cache the key. A good command to alias.

$ ssh-add -e  /usr/local/lib/libykcs11.dylib; ssh-add -s /usr/local/lib/libykcs11.dylib

In ~/.ssh/config add this to always offer the Yubikey for authentication:

Host *
    PKCS11Provider /usr/local/lib/libykcs11.dylib

And to cache connections for a few minutes without asking for a touch:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/S.%r@%h:%p
    ControlPersist 10m

Resources: ๐Ÿ”—