This guide will show you how to enroll one or more Yubikeys as PKCS#11 tokens for a LUKS-encrypted drive and have it decrypt at boot.
FIDO2 is a more modern way of authenticating than X.509 certificates and keys stored into a hardware token, but unfortunately for the purpose of full disk encryption it is not entirely suitable as soon as you enroll multiple FIDO2 tokens: if you have N tokens enrolled and you happen to have the Nth token plugged in, the PIN will be prompted N times.
PKCS#11 on the other hand is a much older standard that defines generic cryptographic operations backed by a hardware token (smartcard, HSM, TPM) or even by software (SoftHSM2). The manufacturer of your token will typically provide a PKCS#11 module for you to use with your applications and everything is (mostly) transparent.
In the case of the Yubikey, the OpenSC project provides a PKCS#11 module that works with it (and with many other types of tokens) and is available in most distribution repositories.
It is pre-supposed that you are using systemd for the init system and dracut for the initramfs.
More details about the Yubikey
The Yubikey has the PIV applet for storing keys and certificates; it is a US government standard meant for personal identity verification: several kinds of keys and certificates are stored on the card and are meant to be used to identify the card holder, authenticate the card itself, encrypt and sign e-mails, etc.
Despite this intended use of PIV, thanks to the OpenSC PIV driver, the Yubikey PIV applet can be used as a generic PKCS#11 token for storing keys and certificates.
Due to the nature of PIV a key alone cannot be used, it has to have an associated certificate. We are going to use a self-signed certificate, the certificate itself (name, expiration, issuer) does not really matter.
A PIV applet has several slots, among which:
9A: PIV authentication: for authenticating the card and the cardholder9C: Digital signature: for signature9D: Key management: for encryption9E: Card authentication: for authenticating the card itself
We are going to use slot 9D as it is meant for key exchange keys and will require the PIN for every operation.
If you are already using these slots and want to use one of the retired slots 82, ..., 95 it is also possible
but the OpenSC driver will not find then, you will have to install the ykcs11 module into the initramfs
so they are found as well as make a corresponding p11-kit module file.
Required packages
Debian or Ubuntu will be assumed going on, you will need to adapt the commands and package names to other distributions.
Install the ykman command as well as the OpenSC machinery and the PC/SC daemon:
sudo apt install yubikey-manager opensc opensc-pkcs11 pcscd
Plug in your Yubikey and make sure it is detected by OpenSC:
pkcs11-tool -L
You should see something ressembling the following:
Available slots:
Slot 0 (0x0): Yubico YubiKey OTP+FIDO+CCID 01 00
token label : LUKS key
token manufacturer : piv_II
token model : PKCS#15 emulated
token flags : login required, rng, token initialized, PIN initialized
hardware version : 0.0
firmware version : 0.0
serial num : 87445d8b74e567ba
pin min/max : 4/8
uri : pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=87445d8b74e567ba;token=LUKS%20key
If it is not the case, make sure the PIV applet is enabled on your Yubikey:
ykman config usb --enable piv
Yubikey setup
You will first want to setup a PIN, PUK and management key on your Yubikey.
The default values are:
- PIN:
123456 - PUK:
12345678 - Management key:
010203040506070801020304050607080102030405060708
It is going to be most convenient if the new management key is stored on the Yubikey itsef, protected by the PIN:
# Follow the prompts, touch your key when prompted
ykman piv access --touch --generate --protect --algorithm aes256
Then change the PIN:
ykman piv access change-pin
And the PUK:
ykman piv access change-puk
Key and certificate generation
We are going to make a P-384 key (unfortunately X25519 is not supported by systemd-cryptsetup) into slot 9D:
ykman piv keys generate -a eccp384 --pin-policy always --touch-policy never 9D yubikey-luks-pubkey.pem
You will be prompted for the PIN and to touch your Yubikey.
Now a self-signed certificate needs to be signed and imported into slot 9D:
ykman piv certificates generate --subject 'LUKS key' --valid-days 3650 --hash-algorithm sha384 9D yubikey-luks-pubkey.pem
The expiration date does not matter, but the name will become the PKCS#11 name of your Yubikey as a side-effect of importing
it into key 9D.
Getting the PKCS#11 URI
Now the PKCS#11 URI to pass to systemd-cryptsetup needs to be obtained, so list all the public keys on your Yubikey:
pkcs11-tool -O -y pubkey
You will get something like this:
Using slot 0 with a present token (0x0)
Public Key Object; EC EC_POINT 384 bits
EC Point: 0461044a5d7e928b9c94b18fb6b51be9298e58dfc4212df3f0b09bd3a80e942b
19cdc1892e9580afce397cbc9eedeceae9cc3527700a259e4241c6c8980bd794
49ecc44637c072c33afe9d59b17ed772a9691a7758e416cdfdc0da707fc6bb82
b7c2a8
EC Params: 06:05:2b:81:04:00:22 ("secp384r1" OID:"1.3.132.0.34")
label: KEY MAN pubkey
ID: 3 (0x03)
Usage: derive
Access: none
uri: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=9094194173741dce;token=LUKS%20key;id=%03;object=KEY%20MAN%20pubkey;type=public
Now take the PKCS#11 URI and keep model, manufacturer, serial, id and type, for instance:
pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=9094194173741dce;id=%03;type=public
This will be what we give to systemd-cryptenroll.
Enrolling the token
In the following example, the encrypted disk is /dev/nvme0n1p2:
sudo systemd-cryptenroll --pkcs11-token-uri='pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=9094194173741dce;id=%03;type=public' /dev/nvme0n1p2
You will be prompted for the passphrase and the PIN.
A caveat about PKCS#11 key slots: you must always have a key slot of another type than PKCS#11, this is a limitation of systemd-cryptenroll (and in general a good idea to have at least a recovery passphrase keyslot that you print on paper for instance).
Taking care of the initramfs
Make sure dracut is the initramfs generator
Make sure to install dracut then create /etc/kernel/install.conf with the following contents:
initrd_generator=dracut
Making sure dracut loads the required modules
Create the file /etc/dracut.conf.d/pkcs11.conf with the following contents:
add_dracutmodules+=" systemd-cryptsetup pcsc pkcs11 "
Now if you're lucky and systemd did fix their initramfs mess it might work out of the box, asking for the token PIN on startup, and you can skip to the Regenerate the initramfs section.
Otherwise if you are on version 262 like me, keep reading:
Making sure the PC/SC daemon has time to start before systemd-cryptsetup starts looking for the token
This one is a regression introduced in recent systemd versions: the systemd-cryptsetup unit starts before pcscd has time to properly start and find the Yubikey.
Create the file /etc/systemd/system/systemd-cryptsetup@.service.d/override.conf with the following contents:
[Service]
ExecStartPre=/bin/sleep 2
Then open back /etc/dracut.conf.d/pkcs11.conf and append the line:
install_items+=" /etc/systemd/system/systemd-cryptsetup@.service.d/override.conf "
Regenerate the initramfs
Before that it might be wise to make a backup of the previous initramfs in case something went wrong; if you are using a unified kernel image
you can simply make a copy the .efi file for the current kernel; if you are using separate kernel and initramfs you can make a copy of
/boot/initrd.img-$(uname -r) which you will be able to select in GRUB or systemd-boot (press e when selecting the boot option to edit the
command-line and change the initramfs you want to use).
sudo dpkg-reconfigure linux-image-$(uname -r)
Now you can reboot; make sure the Yubikey is plugged into the computer before you start it (or more precisely, before the PC/SC daemon starts) and it should ask you for the PIN code.
You can repeat these operations (Key and certificate generation, Getting the PKCS#11 URI, Enrolling the token) with as many different Yubikeys as you want.
Other PKCS#11 tokens will work as well (for instance PGP cards), as long as they support either RSA keys with the OAEP mechanism or
ECC keys with the ECDH mechanism (you can run pkcs11-tool -M to list the supported mechanisms of a token).