My Yubikey Debian Setup

My Goals

Warning

Disclaimer

As with everything related to security: do not follow these instructions blindly. Take time to understand exactly why and if you need to follow some step. I take no responsibility for you locking yourself out of your data, or leaking everything to a third-party.

You have been warned.

I am documenting my goals to make you understand why I did something and why it is good enough in my scenario. Check if you really need the same provisions.

You might need to be more strict or more lax, depending on your use case. I wanted to achieve reasonable security and ergonomics, without sacrificing usability.

In particular, I try to use different passwords almost everywhere. This means having one hardware token which enables me to perform passwordless logins and acts as a 2FA token is a big improvement. If it gets stolen, I expect to realize that very fast, and be able to revoke everything linked to it, without losing full access to the associated services.

This is why:

  • I want to be able to unlock my disk volumes with the hardware key without a password or PIN, but just with the Yubikey presence.

  • I want to be able to login into my account and become root without checking for user presence or inputting a password if the Yubikey is inserted.

  • I want to use the Yubikey to securely prove my identity on different computers / laptops under my full control.

  • I want to be able to sign emails and git commits with my GPG key without going around with the master key.

  • I want to be able to get 2FA codes for a range of different Internet services to improve security over the usual username-and-password authentication flow. I also have my smartphone setup with the same codes.

  • I want to be able to still get access to all services and quickly react if the Yubikey gets lost or stolen (or forgotten at home!).

Other applications

Note that the Yubikey is also not my only 2FA provider. I tend to duplicate all TOTP account data using Authenticator Pro on my phone. I don’t always have my Yubikey with me, and I might need to access a 2FA-protected account! Doing it like this, has the nice property that I can export and backup all secrets to my Nextcloud instance. This might or might not be a good idea in your scenario. I still recommend checking all recovery keys for accounts with 2FA enabled inside a password manager.

As for my password manager: I use pass along with the corresponding Android application. I export a copy of the password store via git to my server, and sync with that.

You might want to simply rely on Firefox to store your password, and use the Firefox Sync account to keep them actual on all your devices. Remember to use 2FA with your Firefox account, then!

Preparing the Key

apt install yubikey-manager flatpak

flatpak remote-add --if-not-exists \
    flathub https://flathub.org/repo/flathub.flatpakrepo

flatpak install com.yubico.yubioath

Now you should proceed to enable a PIN for critical apps on the key if it is not already on.

Importing the GPG subkeys

GPG interface is… byzantine. While you can generate multiple subkeys on a per-device basis (and this is the most secure setup), it has the drawback that data encrypted with one encryption key cannot be decrypted by the master (well, this is not really a GPG problem, it’s a design choice).

Since I want to be able to sign and decrypt data using always the same keys, I decided to manually export the subkeys to the Yubikey while retaining them on a desktop PC I own. This is normally not permitted (and for good reason!) by GPG. In case the Yubikey is lost or stolen, it would be my job to immediately revoke and rotate keys on the desktop PC!

But from an egonomic perspective, it’s a really good thing. The desktop PC is unlikely to get stolen. The Yubikey acts as a backup.

Start by setting the Yubikey PIN with gpg --card-edit (factory defaults are 123456 and 12345678 for admin):

gpg/card> admin
gpg/card> passwd

1 - change PIN
3 - change Admin PIN
Q - quit

gpg/card> name

Name Lastname

gpg/card> lang

e.g. en

gpg/card> login

email@email.org

Now to generate the key with gpg --expert --full-gen-key:

(9) ECC and ECC
(1) Curve 25519
0 = key does not expire

...

pub   ed25519 2023-03-15 [SC]
     DF3D5AD589481FC3448BB3D33AA9819FF4CB5B7C
uid                      Temporary <ciccio@pasticcio.com>
sub   cv25519 2023-03-15 [E]

Then gpg --expert --edit-key DF3D5AD589481FC3448BB3D33AA9819FF4CB5B7C:

uid
# (optional) if you have other email addresses to add

addkey

(10) ECC (sign only)
(1) Curve 25519
0 = key does not expire

addkey

(11) ECC (set your own capabilities)
(A) Toggle the authenticate capability
(S) Toggle the sign capability
(Q) Finished
(1) Curve 25519
0 = key does not expire

sec  ed25519/3AA9819FF4CB5B7C
    created: 2023-03-15  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  cv25519/5811300B293014C8
    created: 2023-03-15  expires: never       usage: E
ssb  ed25519/92167215DD1721AD
    created: 2023-03-15  expires: never       usage: S
ssb  ed25519/1D988BD1FB3879E0
    created: 2023-03-15  expires: never       usage: A
[ultimate] (1). Temporary <ciccio@pasticcio.com>

save

Now we have all we need: an encryption, a signing, and an auth subkey. I strongly recommend that you also generate a revocation certificate for the whole master key at this point, print it in a font that makes it easy to use OCR later on (look for good differentiation of O and 0, e.g. via Fira Code), and store it offline. This is left as an exercise to the reader.

At this point, we want to export the full key, including subkeys, so that we can re-import it later.

gpg --export-secret-keys DF3D5AD589481FC3448BB3D33AA9819FF4CB5B7C > temp.priv
gpg --export DF3D5AD589481FC3448BB3D33AA9819FF4CB5B7C > temp.pub
gpg --expert --edit-key DF3D5AD589481FC3448BB3D33AA9819FF4CB5B7C

Now select the subkeys in turn via key and then issue a keytocard to move them to the Yubikey.

gpg> key 1
gpg> keytocard
gpg> key 2
gpg> keytocard
gpg> key 3
gpg> keytocard
gpg> save

The subkeys will be marked as “moved”. This is not what we want: we want a local copy. Hence it is time to throw away our key and restore it from backup.

gpg --delete-secret-keys DF3D5AD589481FC3448BB3D33AA9819FF4CB5B7C
gpg --import temp.priv

This should be enough. After checking everything is back to normal, you can delete the temp.priv file.

SSH

See directly Setting up OpenSSH for FIDO2 Authentication » Discoverable Key Instructions on the Yubico website.

System Integration

Automated LUKS disk unlocking

apt install dracut

At the time of this writing, some files were missing from the dracut-generated images. I forced these to be included by adding a corresponding configuration file and then running dpkg-reconfigure dracut:

/etc/dracut.conf.d/99-yubikey.conf
install_optional_items+=" /lib/x86_64-linux-gnu/libfido2.so* "
install_items+=" /usr/bin/fido2-token "
install_items+=" /usr/lib/udev/rules.d/60-fido-id.rules /usr/lib/udev/fido_id "

I expect the above file to be or become obsolete quite soon. For me it was still necessary to ensure all needed files were present in the initrd image. But it should be fixed in Debian.

Now it’s finally time to enroll our keys. After a quick check at blkid | grep crypto_LUKS, we can enroll our Yubikey against volumes we want to unlock through it:

systemd-cryptenroll \
  --fido2-device=auto \
  --fido2-with-user-presence=false \
  --fido2-with-client-pin=false \
  /dev/sda1

At the moment, --fido2-with-user-presence=false does not seem to work. I don’t know if this is by design or if it is a bug. You will need to touch your key during boot when it starts blinking.

We now need to tell the system using fido2 at boot for unlocking is an option. For instance, for me:

# <target name> <source device>                                 <key file>      <options>
rootfs          UUID="13bd3015-55c4-40d3-a805-4d5f2b90ac1f"     -               fido2-device=auto

If you do not have your key, after 30 seconds you will be prompted for the password. You can lower this timeout in /etc/crypttab, check its man page.

Configuring PAM

Let’s start by installing the needed module:

apt install libpam-yubico

Unfortunately the default installed PAM module goes through the Internet and to the Yubico server. This requires an account and an Internet connection. Since I often travel with my laptop and I don’t want to depend on an external service, I set up the key to perform a challenge-response authentication locally.

Let’s create a custom configuration file:

/usr/share/pam-configs/yubico-sufficient
Name: Yubico authentication with YubiKey (challenge-response)
Default: no
Priority: 704
Auth-Type: Primary
Auth:
        sufficient      pam_yubico.so   mode=challenge-response chalresp_path=/var/yubico
Auth-Initial:
        sufficient      pam_yubico.so   mode=challenge-response chalresp_path=/var/yubico

Now run pam-auth-update and enable the corresponding module we just created. Disable the default “Yubico authentication with Yubikey” module if it is enabled.

The rest of the guide about generating the challenge-response slot on the Yubikey can be read here.

Teaching SSH to Prefer the Yubikey

~/.ssh/config
Host *
    IdentityFile ~/.ssh/id_ed25519_sk
    ControlMaster auto
    ControlPath ~/.ssh/S.%r@%h:%p
    ControlPersist 5m

Making GPG Slightly More Happy

~/.gnupg/scdaemon.conf
reader-port Yubico Yubi
disable-ccid

Configuring git with the Subkey

~/.gitconfig
...

[user]
    signingkey = 8576CC1AD97D42DF!

[commit]
    gpgsign = true

Note the exclamation mark (!) after the subkey id to force its selection.

Previous: A new blog