OpenBSD Journal

Using a Yubikey as smartcard for SSH public key authentication

Contributed by Sebastian Benoit (benno@) on from the token dept.

SSH is an awesome tool. Logging into other machines securely is so pervasive to us sysadmins nowadays that few of us think about what's going on underneath. Even more so once you start using the more advanced features such as the ssh-agent, agent-forwarding and ProxyJump. When doing so, care must be taken in order to not compromise one's logins or ssh keys.

You might have heard of Yubikeys.

These are USB authentication devices that support several different modes: they can be used for OTP (One Time Password) authentication, they can store OpenPGP keys, be a 2-factor authentication token and they can act as a SmartCard.

In OpenBSD, you can use them for Login (with login_yubikey(8)) with OTP since 2012, and there are many descriptions available(1) how to set this up.

I tried this years ago, and while One Time Passwords are nice, they have a few downsides: basically, you have to be root to configure each host where you use them. And you need some other way to access the machine, in case you lock yourself out, for example by losing the OTP device, but this is not as easy as adding ssh keys to the authorized_keys file, because OTP is configured through the BSD Authentication system.

But Yubikeys offer some more features:
In its OpenPGP mode, they can store PGP RSA keys. Thus, the Yubikey will take care of the RSA en- and decryption, the private key won't leave the Yubikey and is inaccessible from the computer. Since these are RSA keys, they can also be used for SSH authentication. Again, there are descriptions online(2), but all of them tell you to use gpg-agent from GnuPG with its ssh-agent functionality. Your ssh-client will then talk through gpg-agent (instead of the OpenSSH ssh-agent) with the Yubikey.

I did not like that very much. GnuPG's user interface is a disaster, and reading its documentation is a pain. Working with OpenBSD has taught me that good documentation is a must, because without that, how can you use the software safely? The documentation also shows how much the developers care. So gpg is out, at least for SSH authentication.

However, ssh(1) has another method to talk to smartcards. It can load a PKCS#11 library that contains the functions to access the SmartCard. On OpenBSD, this library is provided by the opensc package. In turn, it needs the pcsc-lite package, that actually talks to a smartcard reader.

During the recent a2k19 hackathon I saw commits to ssh's PKCS#11 code, and decided to again attempt to use my Yubikeys, this time using its "PIV" smartcard functionality. Of course this did not work right away. I had to create the yubico-piv-tool port. I also found a bug in ssh, where it would crash on encountering a smartcard protected with a PIN, that was promptly fixed by Markus Friedl and Damien Miller.

I tried the following with a Yubikey NEO and a Yubikey 4. Newer Yubikeys have more features. The NEO only supports RSA keys, Yubikey 4 and 5 support Elliptic Curve ECDSA keys. They also have another nice feature "touch-policy=always": you have to touch the Yubikey to be able to use it (in addition to entering the PIN). That way it cannot be used without your consent, with a method independent from your computer keyboard.

To get started, you need the following packages:

  • ykpers (library and tools to program Yubikeys)
  • yubico-piv-tool (Yubico Personal Identity Verification (PIV) Tool)
  • pcsc-lite (resource manager for PC/SC)
  • opensc (set of libraries and utilities to access smart cards)

Now use the ykpersonalize tool to bring the Yubikey into "OTP+U2F+CCID" mode. Plug in the Yubikey and run

  ykpersonalize -m86

Normally, the key does not work as a smartcard, and this is needed to activate that functionality. Unplug the Yubikey again.

Now run

  doas pcscd --foreground --debug

in a separate terminal and plug the key back in. In dmesg you should see something like

  uhidev1 at uhub0 port 1 configuration 1 interface 1 "Yubico Yubikey 4 OTP+U2F+CCID" rev 2.00/4.37 addr 3
  ugen0 at uhub0 port 1 configuration 1 "Yubico Yubikey 4 OTP+U2F+CCID" rev 2.00/4.37 addr 3

and pcscd should show that it recognised the Yubikey as a cardreader.

  $ yubico-piv-tool -aversion 
  Application version 4.3.7 found.

can be used to test the communication with the Yubikey.

The next step is to configure the Yubikey. The configuration and use of the Yubikey's function is protected with a management key, a PIN and a PUK. You should change the defaults here. For example, you can generate and set a key like this

  dd if=/dev/random bs=1 count=24 2>/dev/null | hexdump -v -e '/1 "%02X"'
  yubico-piv-tool -aset-mgm-key -n<key>

The default PIN and PUK are 123456 and 12345678 and you can configure your own (up to 8 digits) with

  yubico-piv-tool -achange-pin -P123456 -N<PIN>
  yubico-piv-tool -achange-puk -P12345678 -N<PUK>

Make sure that you remember or store these somewhere safe. The PUK can be used to reset the PIN, and the whole key can be reset to its default, but all configuration including your SSH key will be lost if you need to do that.

The Yubikey has the capability to generate the key on the device itself. If you do this, the private key never leaves the Yubikey.

  yubico-piv-tool --key=<key> -s 9a -a generate -o rsa.public

where --key=<key> is the management key that was configured above. The public key is written to the file rsa.public

Alternatively (and probably the preferred method, see below) you can generate a key (not protected with a passphrase) on your computer and import it into the Yubikey. These commands are for an ECDSA key, adapt for RSA:

  ssh-keygen -m PEM -t ecdsa -b 384 -C yubikey_ecdsa384 -f yubikey_ecdsa384

This also creates a file.

Next we can load the key onto the Yubikey. The Yubikey has something called "slots" which are used to differentiate keys for different uses (3), like different subkeys in PGP for signing and encryption. Here we use slot 9a:

  mv yubikey_ecdsa384 yubikey_ecdsa384.key   # just to rename the key
  yubico-piv-tool --key=<key> --pin-policy=once --touch-policy=always -s 9a \
      -a import-key -i yubikey_ecdsa384.key

Some smartcard software expects that the public key comes with a certificate. So now we self-sign the public key to get a certificate in file cert.pem. For this step we need the public key in PEM format instead of the SSH format:

  openssl ec -inform PEM -in yubikey_ecdsa384.key -outform PEM \
      -pubout -out yubikey_ecdsa384.public

  yubico-piv-tool -a verify -a selfsign --valid-days 3650 -s 9a \
      -S "/CN=SSH key yubikey_ecdsa384/" -i yubikey_ecdsa384.public -o cert.pem

This command already uses the private key that we loaded on the Yubikey. That's why you are asked for the PIN, and (when using a Yubikey 4) have to touch the Yubikey to authorise access to the key.

Import the certificate into the Yubikey:

  yubico-piv-tool --key=<key> --pin-policy=once --touch-policy=always \
      -a import-certificate -s 9a -i cert.pem

At this point, unplug the Yubikey, and put it back into the USB slot again.

Add the contents of the file to the authorized_keys file on a host you want to login to.

Now you are ready to use it:

 $ ssh -I/usr/local/lib/pkcs11/ <host>
 Enter PIN for 'SSH key yubikey_ecdsa384': …

If this works, delete the yubikey_ecdsa384.key file from your system:

 $ rm yubikey_ecdsa384.key

Instead of the -I option, you can use PKCS11Provider /usr/local/lib/pkcs11/ in your ssh configuration file.

You can also tell your ssh-agent to use the Yubikey:

 $ ssh-add -s/usr/local/lib/pkcs11/

and your agent will then talk to the Yubikey for authentication.

I mentioned above, that one should not use the command to generate the key on the Yubikey itself. The Yubikey 4 has a Infineon chip. In 2017, the firmware used to generate the RSA keys on these chips was found to produce predictable RSA keys, the same security bug that compromised millions of Estonian ID cards. Yubico reacted to this by exchanging all Yubikey 4 devices against new ones, free of charge. While this shows that they have customer support, I still don't trust the key generation on such a device, and rather use libressl or openssh running on an OpenBSD host to generate the key. If you don't want the key file to end up on your disk drive, run above commands [i.e. ssh-keygen and so on - Ed.] in a ramdisk directory mounted with mount_mfs(8).

A few thoughts about the security of this.

Let's compare it to the usual method of using SSH. In a classical scenario, you have an ssh key stored in ~/.ssh/. To access it, you enter a passphrase. The passphrase is used to decrypt the private key. It is then used to decrypt a authentication challenge to prove your identity to a server you want to log in to.

With the Yubikey, the challenge is passed to the Yubikey for decryption. To do so, you have to enter a 4-8 digit PIN code.

The PIN code protects access to it and is much shorter than a passphrase. But the passphrase of a normal ssh key needs to be much longer so that the key cannot be decrypted through bruteforce password guessing by someone who was able to get hold of the key file. Since nobody can get at the ssh key thats in the Yubikey, a short pin will be fine - at least that's what the system promises.

The fact that you need to enter a long passphrase makes a lot of people use ssh-agent. If you log in and out of machines a lot, it's cumbersome to type the passphrase all the time. The ssh-agent stores the decrypted key for you in RAM, and ssh-clients ask the ssh-agent to do the authentication proof for them, much like the Yubikey does.

If you use ssh-agent because you don't want to enter your passphrase all the time, maybe you would consider entering a shorter PIN more often? In that case the Yubikey is a more secure alternative, especially in combination with the optional touch-policy.

Both passphrase and PIN can be attacked with keyboard sniffers. But while the passphrase can be used on a stolen ssh key, the pin is only usable while the Yubikey is physically present. The touch-policy option makes the window of opportunity for an attacker even smaller.

This is also true if you use agent-forwarding: once you unplug the Yubikey, the agent cannot access it anymore.

So the Yubikey reduces the risk of leaking private keys from your computer, however it is not a perfect protection against an attacker who has gained access to your computer. You should probably have a plan to remove compromised (or just physically lost) keys from servers and replace them.

Of course this does not consider the additional attack surface created by the components opensc and pcscd. I have not looked at this in detail yet.

(1) Using Yubikey For SSH Multi-Factor Authentication, Brandon Mercer,
(2) PGP and SSH keys on a Yubikey NEO, Eric Severance
(3) PIV certificate slots,

(Comments are closed)

  1. By thomasw (thomasw_) on

    I am keen to try this, but there is no port or package in release of yubico-piv-tool. Is there another way to get and compile this program? Thanks

    1. By Benno (benno) on

      It's in -current only, so it will be available in 6.5, out in two months or so. Right now your options are: build it yourself (easy), port the port to 6.4 or upgrade to a snapshot (which is is 6.5-beta since last week).

      1. By thomasw (thomasw_) on

        Understood! Off to find sources. Thanks so much for taking the time to reply and for writing such a clear article. I have just acquired a YubiKey 5 NFC. Sincerely, TW.

  2. By Edward Ahlsen-Girard (Ed) on

    Bravo. Some of this article makes me wonder if anyone has managed to actual DoD common access cards with OpenBSD. Which might not impel me to buy a reader, but there's really no chance I would otherwise.


Copyright © - Daniel Hartmeier. All rights reserved. Articles and comments are copyright their respective authors, submission implies license to publish on this web site. Contents of the archive prior to as well as images and HTML templates were copied from the fabulous original with Jose's and Jim's kind permission. This journal runs as CGI with httpd(8) on OpenBSD, the source code is BSD licensed. undeadly \Un*dead"ly\, a. Not subject to death; immortal. [Obs.]