PAM FIDO2 Vault
Hardware-agnostic Linux PAM login and KeePassXC vault auto-unlock powered by FIDO2 (hmac-secret with User Verification / clientPin). Compatible with all standard FIDO2 / CTAP 2.0+ authenticators (YubiKey 5 / Bio Series, FEITIAN BioPass, Thetis, SoloKey 2, Nitrokey 3, etc.).
Security Architecture & Threat Model
- Hardware Agnostic:
Operates on the standard FIDO2 / CTAP 2.0+ protocol via
libfido2. Automatically discovers any plugged-in FIDO2 authenticator regardless of vendor (Yubico, Feitian, Thetis, SoloKeys, Nitrokey, etc.). - Stolen Laptop Immunity:
The FIDO2 credential strictly enforces
clientPin(User Verification) in the security key's secure element. If an attacker steals the laptop and boots a live USB recovery disk or resets the Linux user password, they cannot decrypt~/.fido2-vault.enc. The physical hardware token strictly refuses to release the HMAC secret without physical presence and the hardware-verified PIN. - Zero Persistent Disk Footprint:
Neither the FIDO2 PIN nor the KeePassXC master key is ever written to persistent storage (NVMe/SSD). Decrypted secrets reside transiently in RAM (
/run/user/<uid>/kpxc.keyontmpfs) and are immediately wiped (shred -u) after KeePassXC consumes the key via standard input (--pw-stdin). A 15-second background watchdog guarantees cleanup even if KeePassXC fails to start. - Fail-Safe Fallback:
If the security key is unplugged or absent,
pam_fido2_vaultquietly yields (PAM_AUTHINFO_UNAVAIL), cleanly falling back to the standard Linux user password prompt. You will never be locked out.
Directory Layout
pam-fido2-vault/
├── helpers/
│ ├── keepassxc-autounlock.sh # Autounlock script with safety watchdog
│ └── org.keepassxc.KeePassXC.desktop # Desktop autostart entry
├── src/
│ ├── crypto_common.c / .h # HKDF-SHA256 & AES-256-GCM authenticated crypto
│ ├── fido2-vault-setup.c # Enrollment CLI and status diagnostic tool
│ ├── pam_fido2_vault.c # PAM shared module (auth + session)
│ └── vault_format.h # Vault file format definitions
├── .gitignore
├── LICENSE # MIT License
├── Makefile # Universal build into build/
├── install.sh # Universal distro & service installer
└── README.md
Installation & Setup
1. Build and Install (Requires sudo)
Auto-detects PAM directory (Ubuntu, Debian, Fedora, Arch, openSUSE, RHEL, ARM64) and display manager:
cd /home/steve/src/pam-fido2-vault
sudo ./install.sh
Optional: You can target a specific PAM service, e.g. sudo ./install.sh sddm or sudo ./install.sh lightdm.
2. Enroll FIDO2 Key & Master Password
Run the setup utility as your normal user:
fido2-vault-setup
- Verifies KeePassXC binary, helper scripts, and symlinks desktop autostart.
- Prompts to confirm or select your
.kdbxdatabase location. - Prompts if you use a key file with your database and verifies its path.
- Enter your FIDO2 security key PIN.
- Enter and confirm your KeePassXC master password.
- Touch your FIDO2 key when prompted to register the credential.
- Touch your FIDO2 key a second time to verify HMAC derivation and encrypt
~/.fido2-vault.enc.
3. Check System Status Anytime
Run the built-in diagnostic checklist:
fido2-vault-setup --status
Example output:
=========================================================
PAM FIDO2 Vault Status Check
=========================================================
[✓] FIDO2 Key: Detected (/dev/hidraw0 - Security Key(F829) by Thetis)
[✓] Enrollment: Active (/home/steve/.fido2-vault.enc, cred_id=96 bytes)
[✓] PAM Module: Installed (/lib/x86_64-linux-gnu/security/pam_fido2_vault.so)
[✓] PAM Service: Configured (/etc/pam.d/gdm-password)
[✓] KeePassXC Binary: Found (/home/steve/.local/bin/keepassxc)
[✓] Unlock Helper: Installed (/usr/local/bin/keepassxc-autounlock.sh)
[✓] Autostart Link: Active (/home/steve/.config/autostart/org.keepassxc.KeePassXC.desktop)
[✓] Target Database: Configured (/home/steve/Documents/steve.kdbx)
[-] Key File: None (password only)
=========================================================
How Login Works
- At the GDM3 (or SDDM/LightDM) login screen, you are prompted:
FIDO2 PIN: - Enter your PIN and press Enter.
- The prompt displays:
Touch your FIDO2 security key to unlock...and the security key flashes. - Tap the security key once.
- Desktop logs in!
pam_fido2_vaultwrites the decrypted key to/run/user/<uid>/kpxc.keyin RAM (tmpfs).- Desktop autostart launches
keepassxc-autounlock.sh, which pipes the key intokeepassxc --pw-stdin, unlocking your database, and shreds/run/user/<uid>/kpxc.key.
PAM Configuration Deep-Dive (/etc/pam.d/gdm-password)
The automated installer (sudo ./install.sh) inserts two specific hooks into /etc/pam.d/gdm-password (or your display manager's service file):
Exact Configuration
#%PAM-1.0
auth requisite pam_nologin.so
auth required pam_succeed_if.so user != root quiet_success
auth sufficient pam_fido2_vault.so
...
@include common-auth
...
@include common-session
session optional pam_fido2_vault.so
...
Why This Architecture Works
1. Authentication Phase (auth sufficient pam_fido2_vault.so)
- Placement: Inserted before
@include common-auth(and before any legacypam_u2f.sorule). sufficientControl Flag:- When a FIDO2 key is plugged in and the user has enrolled
~/.fido2-vault.enc, the module prompts for the FIDO2 PIN and cued touch. - Upon successful verification, the token returns the hardware-derived HMAC secret. The module decrypts the master password in memory and returns
PAM_SUCCESS. - Because the flag is
sufficient, PAM considers authentication fully satisfied and skips subsequent password prompts.
- When a FIDO2 key is plugged in and the user has enrolled
- Fail-Safe Fallback:
- If the FIDO2 key is unplugged or the user is not enrolled, the module immediately returns
PAM_AUTHINFO_UNAVAILwithout prompting. - PAM cleanly falls through to
@include common-auth, seamlessly presenting the standard Linux user password prompt.
- If the FIDO2 key is unplugged or the user is not enrolled, the module immediately returns
- In-Memory Secret Handoff:
- The decrypted secret is held transiently in PAM transaction memory (
pam_set_data) accompanied by a zeroing destructor (crypto_cleanse). - On screen unlock (where
/run/user/<uid>already exists from the active session), the authentication module writes/run/user/<uid>/kpxc.keydirectly.
- The decrypted secret is held transiently in PAM transaction memory (
2. Session Phase (session optional pam_fido2_vault.so)
- Placement: Placed after
@include common-session. - Systemd Runtime Lifecycle:
- On a cold boot / fresh graphical login, the user's volatile runtime directory
/run/user/<uid>does not exist during theauthphase. pam_systemd.so(inside@include common-session) is responsible for allocating the user session and mounting the per-usertmpfsdirectory/run/user/<uid>.- Placing
session optional pam_fido2_vault.soafter@include common-sessionguarantees that/run/user/<uid>is mounted and writable before the module attempts to write the key file.
- On a cold boot / fresh graphical login, the user's volatile runtime directory
- Transient Key Materialization:
- The session hook retrieves the decrypted secret from the PAM transaction data (
pam_get_data), writes it to/run/user/<uid>/kpxc.keywith strict0600permissions owned by<uid>, and securely zeros PAM memory.
- The session hook retrieves the decrypted secret from the PAM transaction data (
optionalControl Flag:- If the user logged in using fallback password authentication (meaning no FIDO2 secret was decrypted), the session hook exits cleanly without impeding the session.
3. Desktop Autounlock (keepassxc-autounlock.sh)
- When the graphical desktop starts, XDG Autostart executes
/usr/local/bin/keepassxc-autounlock.sh. - The helper script:
- Checks for
/run/user/<uid>/kpxc.key. - Spawns an asynchronous 15-second safety watchdog to ensure the key is shredded even if KeePassXC hangs or encounters an error.
- Pipes the key into KeePassXC via standard input:
(If a key file was enrolled during setup, it also automatically passes
keepassxc --pw-stdin [options] "$DB" < "$KEY_FILE"--keyfile <path>). - Immediately overwrites and removes the key file using
shred -u.
- Checks for
Supporting Other Display Managers & Services
The same configuration pattern applies to other PAM service files:
- SDDM (KDE):
/etc/pam.d/sddm - LightDM (XFCE/Cinnamon):
/etc/pam.d/lightdm - Console / TTY:
/etc/pam.d/login
You can target any service during installation:
sudo ./install.sh sddm