Hardware-Enforced Authentication · Zero Password Fallback · Full Privilege Path Coverage · Pop!_OS COSMIC
FIDO2 Sovereign Auth is the physical identity layer of the VERITAS & Sovereign Ecosystem (Omega Universe). It covers every privilege escalation path — lock screen, sudo, su, shell change, user info change — and removes the password from the authentication stack entirely rather than treating it as a fallback.
The goal is to make physical hardware presence a hard requirement for access, not an optional second factor. This guide documents the configuration decisions, the non-obvious bugs, and the reasoning behind each choice so the setup can be understood and reproduced cleanly.
Design principle: No password fallback on any privilege path. Hardware presence is the only credential.
After debugging FIDO2 failures that no existing documentation explains, three critical issues were identified:
u2f_keyssingle-line bug:pam_u2fonly reads the first matching line per user. Two lines = second key silently fails. Both keys must be on one line.sufficientfreezes cosmic-greeter: Usingsufficientcauses cosmic-greeter to hang after FIDO2 timeout — it falls through topam_unixand receives a password conversation request it has no UI to handle. The fix isrequiredwith@include common-authremoved entirely.- Stale FIDO2 credentials:
blob[0]=0x2emeans the key has oldpam://hostnameentries.ykman fido credentials listreveals them. - Inbound SSH
pam_u2fon a peer laptop: SSH to machine B runs PAM on B — your key on machine A cannot satisfy it. Use outbound client guard + inbound pubkey for mesh peers (see mesh docs).
No other guide covers the cosmic-greeter freeze, the single-line bug, two-laptop mesh SSH, or hardening every PAM service end to end.
- Any FIDO2-capable security key (YubiKey or equivalent)
- Pop!_OS 24.04 / Ubuntu 24.04 / Debian-based
sudoaccess- A second terminal or TTY open before making PAM changes — never edit PAM without a fallback session open
sudo apt update
sudo apt install libpam-u2f yubikey-managerGenerate credentials for each key. -n disables PIN verification (touch-only — required for lock screen since the greeter cannot prompt for a PIN).
# Key 1 — touch when LED blinks, copy the full output line
pamu2fcfg -n
# Key 2 — repeat
pamu2fcfg -nmkdir -p ~/.config/Yubico
# Format: username:Key1Handle,Key1PubKey,es256,+presence:Key2Handle,Key2PubKey,es256,+presence
# Both keys separated by : on a SINGLE LINE
nano ~/.config/Yubico/u2f_keys
chmod 600 ~/.config/Yubico/u2f_keysWrong (what most guides show — Key 2 will silently fail):
yourusername:<KEY1_DATA>,es256,+presence
yourusername:<KEY2_DATA>,es256,+presence
Correct (single line, colon-separated):
yourusername:<KEY1_DATA>,es256,+presence:<KEY2_DATA>,es256,+presence
pam_u2f reads line 1, finds a match for the username, and stops. It never reaches line 2.
sufficient: If FIDO2 succeeds, skip the rest. If it times out or fails, fall through topam_unix(password). cosmic-greeter freezes after the timeout because it receives apam_unixpassword conversation request it has no UI to handle.required: FIDO2 must succeed. No fallback, no freeze.
common-auth includes pam_unix — the password module. Keeping it means password auth is still stacked below FIDO2. Removing it eliminates the password path entirely.
sudo cp /etc/pam.d/cosmic-greeter /etc/pam.d/cosmic-greeter.bak
sudo tee /etc/pam.d/cosmic-greeter > /dev/null << 'EOF'
#%PAM-1.0
auth requisite pam_nologin.so
auth requisite pam_succeed_if.so user != root quiet_success
auth required pam_u2f.so cue
auth optional pam_gnome_keyring.so
@include common-account
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
session required pam_loginuid.so
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open
session optional pam_keyinit.so force revoke
session required pam_limits.so
session required pam_env.so readenv=1
session required pam_env.so readenv=1 user_readenv=1 envfile=/etc/default/locale
@include common-session
session optional pam_gnome_keyring.so auto_start
@include common-password
EOFcosmic-greeter needs access to the key's HID interface:
# Permanent (requires re-login)
sudo usermod -aG plugdev $USER
# Immediate (current session)
sudo setfacl -m u:$USER:rw /dev/hidraw*sudo cp /etc/pam.d/sudo /etc/pam.d/sudo.bak
sudo tee /etc/pam.d/sudo > /dev/null << 'EOF'
#%PAM-1.0
auth required pam_u2f.so cue
@include common-account
session required pam_limits.so
session required pam_env.so readenv=1 user_readenv=0
session required pam_env.so readenv=1 envfile=/etc/default/locale user_readenv=0
@include common-session-noninteractive
EOFThese are the overlooked paths. Without hardening, an attacker with your session can su to root with just a password.
sudo cp /etc/pam.d/su /etc/pam.d/su.bak
sudo cp /etc/pam.d/chsh /etc/pam.d/chsh.bak
sudo cp /etc/pam.d/chfn /etc/pam.d/chfn.bak
sudo tee /etc/pam.d/su > /dev/null << 'EOF'
#%PAM-1.0
auth sufficient pam_rootok.so
auth required pam_u2f.so cue
@include common-account
session required pam_env.so readenv=1
session required pam_env.so readenv=1 envfile=/etc/default/locale
session optional pam_mail.so nopen
session required pam_limits.so
@include common-session
EOF
sudo tee /etc/pam.d/chsh > /dev/null << 'EOF'
#%PAM-1.0
auth required pam_shells.so
auth sufficient pam_rootok.so
auth required pam_u2f.so cue
@include common-account
@include common-session
EOF
sudo tee /etc/pam.d/chfn > /dev/null << 'EOF'
#%PAM-1.0
auth sufficient pam_rootok.so
auth required pam_u2f.so cue
@include common-account
@include common-session
EOFpam_rootok.so sufficient exempts root — intentional. All non-root users require FIDO2.
Do not modify /etc/pam.d/login. This is TTY login — accessible via Ctrl+Alt+F2. It is the recovery path if something goes wrong. Leave it untouched.
NOPASSWD entries bypass PAM entirely. A broad NOPASSWD list destroys everything above.
sudo cat /etc/sudoers.d/*Dangerous — gives root shell with zero auth:
yourusername ALL=(ALL) NOPASSWD: /usr/bin/bash, /bin/sh, /usr/bin/cp, /usr/bin/rm, /usr/bin/tee, /usr/bin/curl
Safe subset for automation only:
yourusername ALL=(ALL) NOPASSWD: /usr/bin/apt, /usr/bin/apt-get, /usr/bin/dpkg, /usr/bin/systemctl, /bin/systemctl, /usr/bin/restic, /usr/bin/tailscale, /usr/bin/journalctl
Two-laptop mesh (2026-06): If you SSH between your own machines over Tailscale with one FIDO2 key per laptop, do not require inbound
pam_u2fonsshd— the remote machine cannot see your key when you travel. Use outbound local touch + inbound pubkey-only instead. See docs/MESH-SSH-TWO-LAPTOP.md andscripts/mesh-*.sh.
Single-machine or Tailscale-only baseline:
sudo tee /etc/ssh/sshd_config.d/hardening.conf > /dev/null << 'EOF'
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
AuthenticationMethods publickey
PermitUserEnvironment no
MaxAuthTries 3
LoginGraceTime 20
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
GatewayPorts no
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers <yourusername>
ListenAddress <your-tailscale-ip>
EOF
sudo sshd -t && sudo systemctl reload sshListenAddress set to Tailscale IP — SSH only accepts connections over the encrypted mesh.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow in on tailscale0
sudo ufw --force enable
sudo ufw status verbosesudo tee /etc/sysctl.d/99-custom-hardening.conf > /dev/null << 'EOF'
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
EOF
sudo sysctl --systemsudo apt install auditd audispd-plugins
sudo tee /etc/audit/rules.d/hardening.rules > /dev/null << 'EOF'
-w /etc/audit/ -p wa -k audit-config
-w /etc/audit/rules.d/ -p wa -k audit-config
-w /etc/pam.d/ -p wa -k pam-config
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
-w /etc/ssh/sshd_config -p wa -k ssh-config
-w /etc/ssh/sshd_config.d/ -p wa -k ssh-config
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/gshadow -p wa -k identity
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset -k privileged-sudo
-a always,exit -F path=/bin/su -F perm=x -F auid>=1000 -F auid!=unset -k privileged-su
-w /var/log/wtmp -p wa -k session
-w /var/log/btmp -p wa -k session
-w /var/run/utmp -p wa -k session
-w /sbin/insmod -p x -k module-load
-w /sbin/modprobe -p x -k module-load
-w /sbin/rmmod -p x -k module-load
-e 2
EOF
sudo systemctl enable --now auditd
sudo augenrules --loadAdd /var/log/audit to your backup solution so logs go off-machine nightly.
sudo apt install unattended-upgrades
sudo tee /etc/apt/apt.conf.d/20auto-upgrades > /dev/null << 'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
EOF
sudo tee /etc/apt/apt.conf.d/50unattended-upgrades > /dev/null << 'EOF'
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::DevRelease "false";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
EOF
sudo systemctl enable --now unattended-upgradesStale pam://hostname credentials on the key.
ykman fido credentials list
ykman fido credentials delete <CRED_ID>Re-register with pamu2fcfg -n after clearing.
You have sufficient instead of required. Change to required and remove @include common-auth.
chmod 600 ~/.config/Yubico/u2f_keyssudo setfacl -m u:$USER:rw /dev/hidraw*Ctrl+Alt+F2 → TTY login with password → restore from /etc/pam.d/*.bak.
| Script | Purpose |
|---|---|
scripts/mesh-ssh-outbound-guard |
Local FIDO2 touch before ssh to a mesh peer |
scripts/mesh-install-outbound-ssh.sh |
Install guard into ~/bin + .bashrc |
scripts/mesh-sshd-inbound-tailnet-pubkey.sh |
Inbound sshd: pubkey only; drop pam_u2f from sshd |
scripts/mesh-ssh-yubikey-doctor.sh |
Shielded health check |
docs/MESH-SSH-TWO-LAPTOP.md |
Full policy, troubleshooting, lessons learned |
| File | Purpose |
|---|---|
~/.config/Yubico/u2f_keys |
FIDO2 credentials — single line per user |
/etc/pam.d/cosmic-greeter |
Lock screen auth |
/etc/pam.d/sudo |
sudo auth |
/etc/pam.d/su |
su auth |
/etc/pam.d/chsh |
Shell change auth |
/etc/pam.d/chfn |
User info change auth |
/etc/pam.d/login |
TTY login — leave untouched (recovery path) |
/etc/ssh/sshd_config.d/hardening.conf |
SSH restrictions |
/etc/sysctl.d/99-custom-hardening.conf |
Kernel hardening |
/etc/audit/rules.d/hardening.rules |
Audit rules |
/etc/sudoers.d/<username> |
Per-user sudo rules |
- Long-tap vs tap: On multi-configuration keys, short tap = slot 1, long tap = slot 2. Both work with FIDO2.
- OTP disabled on slot 2: Common on FIPS variants. Use FIDO2 for everything — consistent across all slots.
- Two keys, one line: Register both, put both on the same line in
u2f_keys. Test both before closing your backup session.
FIDO2 Sovereign Auth is the physical identity layer of the Omega Universe. No component of this stack trusts software alone.
┌─────────────────────┐
│ Ω AdGuard DNS │
│ DNS filtering + │
│ telemetry block │
└──────────┬──────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌─────────▼──────┐ ┌───────▼──────┐ ┌──────▼───────┐
│ Ω Primary │ │ Ω Secondary │ │ Ω Mobile │
│ Pop!_OS 24.04 │ │ Pop!_OS │ │ SSH Client │
│ COSMIC │ │ │ │ │
└─────────┬──────┘ └───────┬──────┘ └──────┬───────┘
│ │ │
└────────────────┼────────────────┘
│
┌──────────▼──────────┐
│ Ω Tailscale │
│ Encrypted Mesh VPN │
│ All devices │
└─────────────────────┘
| Layer | Tool | Purpose |
|---|---|---|
| Identity | FIDO2 Security Key | Hardware-enforced presence on all privilege paths |
| Network | Tailscale | Encrypted mesh, stable IPs across all networks |
| DNS | AdGuard | Network-wide filtering, centralized control |
| Remote Access | SSH (outbound FIDO2 + inbound pubkey on mesh) | Touch key on the machine you are at; peers trust Tailscale + SSH keys |
| OS | Pop!_OS 24.04 COSMIC | Sovereign baseline on all machines |
MIT