Creating FIDO2 SSH keys using ssh-keygen

$ ssh-keygen -t ecdsa-sk -f ~/.ssh/id_ecdsa_sk
  • -t ecdsa-sk specifies the key type to generate. Alternatively, you can generate Ed25519 keys using -t ed25519-sk.
  • -f ~/.ssh/id_ecdsa_sk specify the output path for the newly generated key.

You can provide a passphrase for your key if you would like to do so. Unlike normal ssh keys, the private key is not that sensitive, as it is useless without the physical security key itself.

ed25519-sk vs ecdsa-sk

Newer YubiKeys (firmware >=5.2.3) and some other FIDO2 keys, support Ed25519 keys. Ed25519 have some advantages over the common ECDSA keys in several aspects:

  • Ed25519 is based on the Curve25519 vs NIST P-256 used for ecdsa-sk. Curve25519 is generally regarded as faster and safer than NIST P-256, see SafeCurves. Furthermore, the underlying signature algorithm (Schnorr vs DSA) is slightly faster for Ed25519
  • EdDSA in general, and Ed25519 in particular, uses deterministic nonce versus random nonce used by ECDSA. This means that ECDSA is prune to catastrophic entropy failure (see the famous fail0verflow PS3 hack as an example). Assuming your key has access to high entropy randomness, that shouldn’t be a problem. However, that assumption might turn out false, like in the case of the reduced initial randomness in the Yubikey FIPS Series.

In the bottom line, if you have access to a key that supports ed25519-sk then it’s preferable to use it. If you don’t, that’s not something to worry about to much. There are probably weaker points in your threat model anyway.

If your FIDO2 key doesn’t support ed25519-sk you will get the following error when trying to generate a key:

Key enrollment failed: requested feature not supported

Moving keys to a new computer

If you want to use the keys on a new computer, you will have to copy over the private key file that you generated. That will normally be ~/.ssh/id_ecdsa_sk or ~/.ssh/id_ed25519_sk, depending on the type of key you generated.

Alternatively, you can generate resident keys which are completely stored on the YubiKey. To generate resident keys, append the some flag to your ssh-keygen command. Example:

$ ssh-keygen -t ecdsa-sk -O resident

To import the keys to a new device, use the -K option:

$ ssh-keygen -K

This will download all the keys (public and private) from the YubiKey to the current directory. There is no need to manually transfer any key files.

YubiKey Series 5 devices can hold up to 25 resident keys.

Unlock LUKS volume with a YubiKey

Update: The dracut configuration has been updated and now udev consistently recognizes the YubiKey in the initramfs.

Unlocking LUKS encrypted drives with a YubiKey has been supported since systemd 248. In Debian, systemd>=250 is required, as the feature has not been enabled in prior versions. This tutorial is geared towards Yubikeys, but it should work with slight modifications with any other FIDO2 token.

YubiKey series 5 and later should support the hmac-secret extension. You can make sure your Yubikey supports the needed hmac-secret extension by querying it with ykman:

$ ykman --diagnose 2>&1 | grep hmac-secret

Backup your LUKS header

In case you mess anything up, you would need a backup of your LUKS header. Remember to save your backup to some external storage, so you can actually access it if anything goes sideways.

# cryptsetup luksHeaderBackup /dev/nvme0n1p3 --header-backup-file /media/guyru/E474-2D80/luks_backup.bin

Set FIDO2 PIN

We would like to set a FIDO2 PIN for the Yubikey, so unlocking the encrypted drive would require both the physical Yubikey and the PIN. You can set the PIN using:

$ ykman fido access change-pin

Enroll the Yubikey

Start by verifying that systemd-cryptenroll can see and can use your YubiKey:

$ systemd-cryptenroll --fido2-device=list
PATH         MANUFACTURER PRODUCT
/dev/hidraw0 Yubico       YubiKey FIDO+CCID

Now, enroll the Yubikey, replacing /dev/nvme0n1p3 with the block device of the LUKS encrypted drive.

$ sudo systemd-cryptenroll /dev/nvme0n1p3 --fido2-device=auto  --fido2-with-client-pin=yes
🔐 Please enter current passphrase for disk /dev/nvme0n1p3: (no echo)
Initializing FIDO2 credential on security token.
👆 (Hint: This might require confirmation of user presence on security token.)
🔐 Please enter security token PIN: (no echo)
Generating secret key on FIDO2 security token.
👆 In order to allow secret key generation, please confirm presence on security token.
New FIDO2 token enrolled as key slot 0.

Modify /etc/crypttab

We need to modify /etc/crypttab in order to tell cryptsetup to unlock the device using the YubiKey. Add fido2-device=auto in the options field of the crypttab entry for your device. For example:

nvme0n1p3_crypt UUID=307a6bef-5599-4963-8ce0-d9e999026c1a none luks,discard,fido2-device=auto

Switch to dracut

Debian’s default initramfs generator, update-initramfs of the initramfs-tools is using the old cryptsetup for mounting encrypted drives. However, cryptsetup doesn’t recognize the fido2-device option. Running update-initramfs will fail with the following error:

$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-5.15.0-3-amd64
cryptsetup: WARNING: nvme0n1p3_crypt: ignoring unknown option 'fido2-device'

This is unfortunate. The simplest solution is to switch to dracut, a more modern initramfs generator, which among other things relies on systemd to activate encrypted volumes. This solves the issue of the unknown fido2-device.

Before installing dracut, I would highly recommend creating a copy of the existing initramfs in the boot partition in case something goes wrong.

$ sudo apt install dracut

Dracut includes systemd-cryptsetup by default. systemd-cryptsetup depends on libfido for unlocking devices using FIDO2 tokens. At least in Debian, systemd-cryptsetup dynamically loads libfido2.so (as opposed to being dynamically linked), which causes dracut not to have libfido2.so in the initramfs. This causes systemd-cryptsetup to issue the following error upon boot:

FIDO2 tokens not supported on this build. 

We fix it by manually adding libfido2.so to the initramfs. Of course, we also need to include libfido2’s dependencies as well. Dracut has a mechanism for automatically adding dependencies for executables, but it doesn’t work on libraries. As a workaround, instead of adding libfido2 directly, we will add an executable that depends on libfido2, which will add libfido2 and its dependencies to the initramfs. We will usefido2-token from the fido2-tools package for this trick.

$ sudo apt install fido2-tools
$ cat << EOF | sudo tee /etc/dracut.conf.d/11-fido2.conf
## Spaces in the quotes are critical.
# install_optional_items+=" /usr/lib/x86_64-linux-gnu/libfido2.so.* "

## Ugly workround because the line above doesn't fetch
## dependencies of libfido2.so
install_items+=" /usr/bin/fido2-token "

# Required detecting the fido2 key
install_items+=" /usr/lib/udev/rules.d/60-fido-id.rules /usr/lib/udev/fido_id "
EOF

Now, recreate the initramfs images:

$ sudo dracut -f

Last remarks

At this point, we are done. Reboot you’re machine and it will prompt you for your YubiKey and allow you to unlock your LUKS encrypted root patition with it. If you don’t have your YubiKey, it will give the following prompt:

Security token not present for unlocking volume root (nvme0n1p3_crypt), please plug it in.

After around 30 seconds, it would time out and display the following message:

Timed out waiting for security device, aborting security device based authentication attempt.

Afterwards, it would allow you to unlock the partition using a password (or a recovery key).

In case you run into any trouble, append rd.break=initqueue to the kernel command line, and dracut will enter a shell before attempting to mount the partitions. You can manually mount the drive using the following command:

# /usr/lib/systemd/systemd-cryptsetup attach root /dev/nvme0n1p3

Exit the emergency shell, and the system will continue its normal boot.

Reset PIN for Yubikey after 3 failed attempts

When using a Yubikey as a GPG card, entering the wrong PIN multiple times will result in a disabled state. You can unblock the Yubikey by using the admin PIN.

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

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

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

Your selection? 2

Yubikey doesn’t work on Firefox installed via Snap

Installing Firefox via Snap is an easy way to get the latest Firefox version on your favorite distro, regardless of the version the distro ships with. However, due to Snap’s security model, Yubikeys, or any other FIDO tokens do not work out of the box. To enable U2F devices, like Yubikeys, you need to give the Firefox package the necessary permissions manually:

$ snap connect firefox:u2f-devices

Disable Yubikey’s OTP

By default, when you touch a Yubikey it types an OTP code. These codes looks like:

cccjgjgkhcbbirdrfdnlnghhfgrtnnlgedjlftrbdeut

Theoretically they are used for Yubico’s proprietry authentication, but in reality they are mostly annoying. This is especially true for Yubikey Nano, which is impossible to remove without touching it and triggering the OTP.

Yubikey 5 Nano

Disabling the OTP is possible using the Yubikey Manager, and does not affect any other functionality of the Yubikey.

$ sudo apt install yubikey-manager
$ ykman config usb --disable otp
Disable OTP.
Configure USB interface? [y/N]: y

Note for Yubikey 4: the above command will not work and fail withe the following error:

Error: Configuring applications is not supported on this YubiKey. Use the `mode` command to configure USB interfaces.

Instead use the following command:

$ ykman mode FIDO+CCID
Set mode of YubiKey to FIDO+CCID? [y/N]: y
Mode set! You must remove and re-insert your YubiKey for this change to take effect.