Biometric FIDO2/U2F SSH Keys

Background

Smartcards and SSH

Smartcards have traditionally supported SSH public key authentication using two methods: PIV and PGP.

In PIV mode, you end up hacking around with opensc to get ssh-agent to use the certificate as a key. This also doesn’t play well with gpg-agent, which tends to acquire a lock on the smartcard.

In PGP mode, you have to ditch ssh-agent and use gpg-agent instead, which is a significantly degraded experience.

OpenSSH

As of OpenSSH version 8.0 you can use SSH keys to sign arbitrary data.

As of OpenSSH version 8.2 you can use SSH keys derived from the FIDO2 application on smartcards, using either Ed25519 or ECDSA.

YubiKeys

Yubico manufacture a few different smartcards (YubiKeys, from here on out). The YubiKey 5 series support all of the above modes, whereas the YubiKey Security Key only supports FIDO2 (since it’s designed just for 2FA on websites and so on). The Security Key is also significantly cheaper.

There’s also the YubiKey Bio, which has the same support as the Security Key, but has a fingerprint reader and comes in at a little over 3 times the price.

I held off on buying the Bio because I store my PGP key on my YubiKey and wanted to retain this functionality. But I finally decided to try it out, for several reasons:

Adding a Fingerprint

Out of the box, when I plugged it in it was flashing orange. I don’t know why.

ykman info
Device type: YubiKey C Bio - FIDO Edition
Serial number: 17244581
Firmware version: 5.5.6
Form factor: Bio (USB-C)
Enabled USB interfaces: FIDO

Applications
FIDO2       	Enabled
OTP         	Not available
FIDO U2F    	Enabled
OATH        	Not available
YubiHSM Auth	Not available
OpenPGP     	Not available
PIV         	Not available

To add a fingerprint, we first have to set a PIN. The PIN can be alphanumeric (extended ASCII) and relatively long (63 characters).

ykman fido access change-pin
Enter your new PIN:
Repeat for confirmation:

Then add a fingerprint.

ykman fido fingerprints add "right-index"
Enter your PIN:
Place your finger against the sensor now...
4 more scans needed.
Place your finger against the sensor now...
3 more scans needed.
Place your finger against the sensor now...
2 more scans needed.
Place your finger against the sensor now...
1 more scans needed.
Place your finger against the sensor now...
Capture complete.

We can then list the fingerprints.

ykman fido fingerprints list
Enter your PIN:
ID: c83b (right-index)

I remain optimistic that revealing which finger I’ve enrolled doesn’t put me at significantly greater risk of losing it. If your threat model includes this, rest easy knowing that the list is protected by the PIN.

Generating a Resident Ed25519 SSH Key

There are two types of FIDO2 SSH keys: resident and non-resident.

I’m using a resident key because the PIN/fingerprint access control is good enough for me and I value the ability to move the YubiKey between different devices.

We generate the key with ssh-keygen -t ed25519-sk. This will fail if your YubiKey’s firmware is older than 5.2.3, before they supported Ed25519 curves. If so, you can fall back to ecdsa-sk, or get a new YubiKey (recommended).

The below command makes a key that requires not just user presence (a touch) but also verification. This means a PIN for normal non-biometric YubiKeys but, despite the ssh-keygen documentation insisting otherwise, the Bio will accept a fingerprint to unlock the key.

verify-required
Require signatures made using this key indicate that the user was first verified. This option only makes sense for the FIDO authenticator algorithms ecdsa-sk and ed25519-sk. Currently PIN authentication is the only supported verification method, but other methods may be supported in the future.

I also increase the key derivation rounds and add a comment.

ssh-keygen -t ed25519-sk -O resident -O verify-required -a 128 -C 17244581
Generating public/private ed25519-sk key pair.
You may need to touch your authenticator to authorize key generation.
Enter PIN for authenticator:
Enter file in which to save the key (/home/fionn/.ssh/id_ed25519_sk):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/fionn/.ssh/id_ed25519_sk
Your public key has been saved in /home/fionn/.ssh/id_ed25519_sk.pub
The key fingerprint is:
SHA256:2fl1x8+ACfUB2UA3eV5mXLJY5Wr04K93N7nH6iN4OoY 17244581
The key's randomart image is:
+[ED25519-SK 256]-+
|           .==*++|
|           ..=o**|
|          . . ==o|
|         o o = =.|
|        S o o * =|
|           . o =o|
|         . ..  .=|
|        E + o .+*|
|         ..+ o+=*|
+----[SHA256]-----+

Using the FIDO2 SSH Key

Now test it out. I usually use GitHub for this. Upload ~/.ssh/id_ed25519_sk.pub, add

Host github.com
    User git
    PubkeyAuthentication yes
    IdentityFile ~/.ssh/id_ed25519_sk

to ~/.ssh/config and try to connect.

ssh -T github.com
Enter passphrase for key '/home/fionn/.ssh/id_ed25519_sk':
Confirm user presence for key ED25519-SK SHA256:2fl1x8+ACfUB2UA3eV5mXLJY5Wr04K93N7nH6iN4OoY
User presence confirmed
Hi fionn! You've successfully authenticated, but GitHub does not provide shell access.

Then try to connect again and notice that ssh-agent has cached the passphrase.

To use it on another machine, plug it into the new machine and run

ssh-keygen -K
Enter PIN for authenticator:
You may need to touch your authenticator to authorize key download.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Saved ED25519-SK key to id_ed25519_sk_rk

to download the key handles to the working directory. Move them to ~/.ssh/ and connect like usual.