Damus
ghost profile picture
ghost
@ghost

YubiKey 5C NFC: The Ultimate Ubuntu 25.10 Hardening Guide

Article header image

From Boot to Shell: Zero Passwords, Maximum Security

#YubiKey#FIDO2#Ubuntu#Linux#Security#ZFS#SSH#Privacy#Infosec

I just achieved something I've wanted for years: a fully hardware-backed authentication flow from the moment my laptop turns on to the final SSH session. No passwords floating around in memory, no typing the same passphrase 50 times a day, and most importantly—if someone steals my laptop, they get a brick. If they steal my YubiKey, they get a useless piece of plastic without my PIN.

Here's how I turned my Ubuntu 25.10 machine into a fortress using two YubiKey 5C NFCs (Firmware 5.7)—one primary, one backup.

Prerequisites: Set your FIDO2 PIN first if you haven't already:

sudo apt -y install yubikey-manager
ykman fido access change-pin

Without this, FIDO2 operations won't work.


Part 1: SSH - Auto-Detecting Your YubiKey

Modern OpenSSH supports FIDO2 resident keys. These are true hardware-backed keys—the private key lives only on the YubiKey's secure element and never touches your disk.

Generate your keys (one per YubiKey):

# Primary YubiKey
ssh-keygen -t ed25519-sk -O resident -O verify-required -O application=ssh:primary -f ~/.ssh/id_yk1

# Backup YubiKey (swap devices)
ssh-keygen -t ed25519-sk -O resident -O verify-required -O application=ssh:backup -f ~/.ssh/id_yk2

Key flags explained:

  • -O resident: Stores the key on the YubiKey (consumes 1 of 25 slots)

  • -O verify-required: Forces PIN entry (something you know) + touch (something you do)

  • -O application=ssh:primary: Allows multiple keys on one YubiKey (distinguishes by RP ID)

Smart SSH Config (~/.ssh/config):

# ~/.ssh/config

# Non-YubiKey servers (example: GitHub with traditional key)
Host github.com
    HostName github.com
    IdentityFile ~/.ssh/id_ed25519
    IdentityAgent $SSH_AUTH_SOCK
    IdentitiesOnly yes

# Auto-detect YubiKey by serial number instead of Host *
# Get serial: ykman info | grep "Serial number"
# Primary YubiKey
Match exec "ykman info 2>/dev/null | grep -q 'Serial number: REDACTED'"
    IdentityAgent none
    IdentityFile ~/.ssh/id_yk1
    IdentitiesOnly yes
    AddKeysToAgent no

# Backup YubiKey  
Match exec "ykman info 2>/dev/null | grep -q 'Serial number: REDACTED'"
    IdentityAgent none
    IdentityFile ~/.ssh/id_yk2
    IdentitiesOnly yes
    AddKeysToAgent no

Why IdentityAgent none? The ssh-agent can't handle FIDO2 PIN prompts properly—it will return "agent refused operation." Disabling the agent for these keys forces SSH to handle the hardware interaction directly.


Part 2: Local Auth - PAM U2F

Why type your login password when you can just touch a button?

Install and register:

sudo apt install libpam-u2f
sudo mkdir -p /etc/Yubico

# Register with your username (do this for each YubiKey)
pamu2fcfg -u user123 >> /etc/Yubico/u2f_keys
# Touch the YubiKey when it blinks

Configure PAM (/etc/pam.d/sudo, /etc/pam.d/sudo-i, /etc/pam.d/gdm-password):

auth sufficient pam_u2f.so cue authfile=/etc/Yubico/u2f_keys [cue_prompt=Tap the YubiKey]
@include common-auth  # this line must go after this ^

sufficient means: if YubiKey works, skip password; if not (or YubiKey missing), fall back to password. This is the safe approach—complex passwords remain as backup, but you rarely need them.

Now sudo -i, screen lock (Win+L), and GNOME login all require a simple touch.


Part 3: The Boss Fight - ZFS Boot Encryption

This is where most guides fail. Ubuntu's default initramfs-tools uses traditional cryptsetup, which does not support FIDO2. It ignores fido2-device=auto with a warning.

The solution: Switch to Dracut (now default in Ubuntu 25.10), which uses systemd-cryptsetup natively.

1. Install Dracut

sudo apt install dracut zfs-dracut
# This removes initramfs-tools - ensure you have a backup kernel/initrd files under /boot/ !

2. Configure /etc/crypttab

Add your ZFS keystore with the FIDO2 flag:

keystore-rpool /dev/zvol/rpool/keystore none luks,discard,fido2-device=auto

3. Enroll Your YubiKeys (Can Be Done Live!)

Unlike some guides suggest, you don't need to unmount or close the keystore:

# Check current slots (should show slot 0: password)
sudo cryptsetup luksDump /dev/zvol/rpool/keystore

# Enroll primary YubiKey (adds to slot 1)
sudo systemd-cryptenroll --fido2-device=auto /dev/zvol/rpool/keystore

# Swap to backup YubiKey and enroll (adds to slot 2)
sudo systemd-cryptenroll --fido2-device=auto /dev/zvol/rpool/keystore

# Verify all three methods: password (0), YubiKey 1 (1), YubiKey 2 (2)
sudo cryptsetup luksDump /dev/zvol/rpool/keystore

4. Rebuild Initramfs

sudo dracut --force --hostonly /boot/initrd.img-$(uname -r) $(uname -r)

Boot Experience

Power on. Instead of "Please unlock disk...", you see:

🔐 Please enter LUKS2 token PIN:
Asking FIDO2 token for authentication.
Please confirm presence on security token to unlock.

Enter PIN, touch the YubiKey (it will blink), and ZFS imports automatically.

Fallback: If YubiKey is missing or dead, wait a few seconds or hit Enter—it falls back to the passphrase prompt (Slot 0).


Security Model & Threat Analysis

StagePrimary AuthFallbackRisk if YubiKey Lost
BootYubiKey FIDO2 (PIN + Touch)LUKS passphrase (Slot 0)Can still boot with password
LoginYubiKey U2F (Touch)PasswordCan login with password
SudoYubiKey U2F (Touch)PasswordCan sudo with password
SSHYubiKey FIDO2 (PIN + Touch)NoneLocked out (key is hardware-only)

Important: For SSH, there is no password fallback—the private key exists only on the YubiKey. This is by design. For local access, you keep passwords as emergency backup.

Removing Password Fallback (Advanced)

You can remove the passphrase slot for maximum security:

sudo cryptsetup luksKillSlot /dev/zvol/rpool/keystore 0

⚠️ Warning: If you do this and lose BOTH YubiKeys, your data is permanently lost. You cannot enroll new FIDO2 tokens without at least one working keyslot (FIDO2 or password).

Can you manage LUKS with only FIDO2 keyslots? Yes—you can add/remove FIDO2 tokens as long as you have at least one working token enrolled. But if all FIDO2 tokens fail (broken, lost, or PIN forgotten), you're locked out without a password slot.


Audit Your Setup

List resident credentials (SSH keys stored on YubiKey):

ykman fido credentials list

Shows only SSH resident keys (application: ssh:primary, etc.). PAM U2F and LUKS FIDO2 use non-resident credentials—the YubiKey generates these on-the-fly using its master secret + the credential ID stored on disk/LUKS header. These don't consume your 25 slots.

> You cannot clone resident keys between YubiKeys. Each device generates unique cryptographic material in its secure element. This is a feature—it prevents credential copying attacks. Your backup YubiKey must have its own independently generated key pair.


What Doesn't Work (And Why)

GNOME Keyring: Still wants a password because it's encrypted with your login password, not the YubiKey. Options:

  • Disable keyring password (if you don't store browser passwords)

  • Use YubiKey Slot 2 (long touch) programmed with a static password to auto-type

  • Accept one password entry per GNOME session

To disable GNOME Keyring Password:

seahorse

1. Right-click "Login" keyring (under Passwords)

2. Click "Change Password"

3. Enter current password

4. Leave new password blank (press Enter/OK)

5. Confirm "Use unsafe storage"

Done.


Final Thoughts

Moving from passwords to hardware-backed keys changes your threat model fundamentally:

Malware on your laptop can no longer steal SSH keys—they don't exist in files, only in the YubiKey's secure element. Even a rootkit with full memory access can't extract FIDO2 private keys.

Physical theft: If you removed the passphrase slot, a stolen laptop is a brick without your YubiKey. If you kept the passphrase slot (recommended), they still need your complex password.

The "Briked" Scenario: If someone steals your YubiKey, they get a device that locks after 8 failed PIN attempts, then wipes itself. They cannot clone it, extract keys, or use it without your PIN.

The setup took hours of debugging initramfs scripts and wrestling with cryptsetup vs systemd-cryptsetup, but the result is worth it: cryptographic certainty from the first microsecond of boot to the final SSH handshake.

Hardware-backed, phishing-resistant, unclonable. That's the YubiKey difference.

n/1 for the full config files and troubleshooting notes.

Stay paranoid, stay secure.


Appendix: Using Existing Resident Keys on a New Machine

Download your resident keys to a new machine:

cd ~/.ssh
ssh-keygen -K  # Insert primary YubiKey, enter PIN, touch
# Repeat with backup YubiKey

Update your ~/.ssh/config:

Match exec "ykman info 2>/dev/null | grep -q 'Serial number: REDACTED'"
    IdentityAgent none
    IdentityFile ~/.ssh/id_ed25519_sk_rk_primary
    IdentitiesOnly yes

Done. The private keys never leave the YubiKey—you're just downloading stubs that reference them.


Appendix: YubiKey Nano vs. 5C NFC vs. Bio Series

Feature5C Nano5C NFCBio Series
User Presence (Touch)✅ Capacitive✅ Capacitive✅ Fingerprint
NFC❌ No✅ Yes❌ No
PIN Required✅ Yes✅ Yes❌ No (biometric)
Form FactorFlush, always inSticks out, removableSticks out, removable
Behavioral RiskLeft plugged inPrompts removalPrompts removal

Verdict: 5C Nano has touch but trains permanent insertion—attacker can touch while you step away. 5C NFC/Bio protrude, forcing intentional "something you have." Bio Series replaces PIN with biometric for same security, less friction.