1. What are FIDO U2F and FIDO2?
FIDO U2F and FIDO2 are authentication standards from the FIDO Alliance that use public-key cryptography instead of reusable shared secrets. The private credential remains on the authenticator, such as a hardware security key, while the relying system stores only public registration data.
FIDO U2F
FIDO U2F, or Universal 2nd Factor, is the older model. It is primarily used as a second factor: the user enters a normal password first, then proves possession of the hardware token by touching or activating it.
FIDO2
FIDO2 is the newer model. It supports stronger authenticator behavior, including PIN-backed and potentially passwordless authentication. Depending on the token and software stack, FIDO2 can support a flow where the security key effectively replaces the account password.
| Feature | FIDO U2F | FIDO2 |
|---|---|---|
| Common use | Second factor after a password | Passwordless or strong hardware-backed login |
| Typical user flow | Password + token touch | Token PIN and/or biometric + token touch |
| Protocol family | CTAP1 / U2F | CTAP2 and WebAuthn |
| Linux PAM use in this guide | Not the preferred target | FIDO2 PIN + physical touch for selected users |
2. Authentication design used by this guide
This guide uses a group-gated local policy:
- Users in
fido-loginauthenticate with FIDO2 token PIN + physical touch only. - Users not in
fido-login, such as a controlled breakglass/local-admin account, remain password-only. - FIDO users do not fall back to Unix password if FIDO fails.
auth [success=ignore default=1] pam_succeed_if.so quiet user ingroup fido-login
auth [success=done default=die] pam_u2f.so authfile=/etc/security/u2f_keys cue="FIDO users: enter token PIN, then touch token when it flashes" pinverification=1 userverification=0
sufficient with nouserok. That combination can accidentally allow unmapped users to bypass password authentication. This guide avoids that pattern.3. Assumptions, placeholders, and safety preconditions
Replace these placeholders before running commands:
| Placeholder | Meaning |
|---|---|
FIDO_USER | The normal local user who will use the FIDO token. |
BREAKGLASS_USER | A separate, known-working, sudo-capable local account that remains password-only. |
/dev/hidraw1 | The FIDO token device path shown by fido2-token -L. The number may differ on your system (hidraw0, hidraw2, etc.) depending on other HID devices present — always confirm with fido2-token -L rather than assuming hidraw1. |
sudo -v. Keep that account out of fido-login.- Keep a root-capable terminal or TTY recovery path available while testing.
- Do not modify
/etc/pam.d/loginin this walkthrough. Console TTY password login is your recovery path. - Back up each PAM file before editing it. The commands below include backup steps.
- Do not close your working session until LightDM, sudo, and screen unlock have all been tested.
4. Command style in this guide
Command blocks are copy-pasteable and do not include shell prompt characters. The label above each block tells you whether it is intended for a normal user shell, a sudo command, or a PAM snippet.
VirtualBox: enable USB passthrough for the FIDO token
If you are running Debian inside VirtualBox, the FIDO token must be explicitly passed through to the VM before the guest OS can see it. VirtualBox does not pass USB devices to a VM by default.
fido2-token -L will return no devices even if the token is physically inserted.- With the VM powered off, open VirtualBox Manager and select your VM.
- Click Settings → USB.
- Make sure Enable USB Controller is checked. Choose USB 2.0 (EHCI) or USB 3.0 (xHCI) depending on your host and token. The xHCI controller requires the VirtualBox Extension Pack.
- Click the Add USB filter icon (plug with a green plus). Insert the FIDO token into the host first so it appears in the device list, then select it. This creates a filter that automatically routes that token to the VM whenever it is inserted.
- Click OK and start the VM.
- Once booted, insert the FIDO token and verify the guest sees it:
Normal user shell
lsusb fido2-token -Lfido2-token -Lshould list a device path such as/dev/hidraw1: vendor model FIDO+U2F. If it returns nothing, confirm the USB filter is saved and the correct controller type is selected.
5. Install required packages
Uses sudosudo apt update
sudo apt install libpam-u2f pamu2fcfg fido2-tools pamtester
6. Set or verify the FIDO2 PIN
Normal user shellfido2-token -L
Example output:
/dev/hidraw1: KEY-ID FIDO+U2F
Normal user shell
fido2-token -I /dev/hidraw1
Set a PIN if needed:
Normal user shellfido2-token -S /dev/hidraw1
Change an existing PIN if needed:
Normal user shellfido2-token -C /dev/hidraw1
fido-token-test.sh, that detects a key, dumps its identity, and runs a live touch/crypto round-trip before you enroll it. Ask your instructor to vet a new token first — the tool and its safe reset/identity features live in the FIDO2 Lab Reference, not this walkthrough.7. Pre-PAM FIDO2/PIN test
Before touching PAM, verify that the token accepts its PIN and requires physical presence.
Normal user shellrm -rf /tmp/fido2-login-test
mkdir -p /tmp/fido2-login-test
cd /tmp/fido2-login-test
Normal user shell
echo "debian local login test challenge" | openssl sha256 -binary | base64 > cred_param
echo "localhost" >> cred_param
echo FIDO_USER >> cred_param
dd if=/dev/urandom bs=32 count=1 status=none | base64 >> cred_param
Normal user shell
fido2-cred -M -t pin=true -i cred_param /dev/hidraw1 | fido2-cred -V -o cred
Expected behavior for a KEY-ID FIDO+U2F style token: enter the FIDO2 PIN, watch for the LED/button to flash, then press the token button. The command should return successfully and create cred.
ls -l cred
cat cred
If you intentionally enter a bad PIN, FIDO_ERR_PIN_INVALID is expected. If you run without -t pin=true and see FIDO_ERR_PIN_REQUIRED, rerun with -t pin=true.
cd
rm -rf /tmp/fido2-login-test
8. Create and populate the FIDO login group
The fido-login group is the selector. Members use FIDO2; non-members use password auth.
sudo groupadd --force fido-login
sudo usermod -aG fido-login FIDO_USER
id FIDO_USER
getent group fido-login
9. Create the central PAM mapping file
Use a central mapping file instead of ~/.config/Yubico/u2f_keys. This avoids encrypted-home and graphical-locker timing problems.
pamu2fcfg -N -u FIDO_USER > /tmp/u2f_keys-FIDO_USER
cat /tmp/u2f_keys-FIDO_USER
Expected: token PIN prompt, optional flashing/touch, and one mapping line beginning with FIDO_USER:.
sudo install -o root -g fido-login -m 0640 /tmp/u2f_keys-FIDO_USER /etc/security/u2f_keys
rm -f /tmp/u2f_keys-FIDO_USER
sudo python3 -c 'from pathlib import Path; p=Path("/etc/security/u2f_keys"); p.write_text(p.read_text().rstrip()+"\n")'
sudo ls -l /etc/security/u2f_keys
sudo grep "^FIDO_USER:" /etc/security/u2f_keys
Expected permissions:
-rw-r----- 1 root fido-login ... /etc/security/u2f_keys
0640 root:fido-login? Some graphical unlockers run PAM in the user’s session context, not as root. A root-only 0600 mapping file can work under sudo pamtester but fail during actual screen unlock. The mapping file contains public credential registration data, not the token private key, so group-readable by fido-login is appropriate for this design.Enroll a backup token
Insert the backup token, then append a second credential to the same user line.
Uses sudo and normal user commandssudo cp /etc/security/u2f_keys /tmp/u2f_keys-FIDO_USER.new
sudo chown FIDO_USER:fido-login /tmp/u2f_keys-FIDO_USER.new
chmod 0640 /tmp/u2f_keys-FIDO_USER.new
printf ':' >> /tmp/u2f_keys-FIDO_USER.new
pamu2fcfg -N -n >> /tmp/u2f_keys-FIDO_USER.new
sudo install -o root -g fido-login -m 0640 /tmp/u2f_keys-FIDO_USER.new /etc/security/u2f_keys
rm -f /tmp/u2f_keys-FIDO_USER.new
sudo python3 -c 'from pathlib import Path; p=Path("/etc/security/u2f_keys"); p.write_text(p.read_text().rstrip()+"\n")'
sudo ls -l /etc/security/u2f_keys
10. Create a shared PAM include file
Instead of copying the same FIDO logic into every PAM service, put it in one reusable include file.
success=done. Use PAM include/@include, not substack, so successful FIDO authentication stops before password auth.sudo test -e /etc/pam.d/fido2-local-auth && sudo cp -a /etc/pam.d/fido2-local-auth /etc/pam.d/fido2-local-auth.bak || true
sudo tee /etc/pam.d/fido2-local-auth >/dev/null <<'EOF'
{{EMBED:config/fido2-local-auth}}
EOF
Use one of these include styles depending on the surrounding PAM file:
Debian @include style@include fido2-local-auth
@include common-auth
Typed auth include styleauth include fido2-local-auth
auth include common-auth
11. Test PAM with a standalone service
Create a temporary PAM service before editing LightDM, sudo, su, or screen-locker files.
Backup if file exists, then write test servicesudo test -e /etc/pam.d/fido2-test && sudo cp -a /etc/pam.d/fido2-test /etc/pam.d/fido2-test.bak || true
sudo tee /etc/pam.d/fido2-test >/dev/null <<'EOF'
{{EMBED:config/fido2-test}}
EOF
Uses sudosudo pamtester fido2-test FIDO_USER authenticate
sudo pamtester fido2-test BREAKGLASS_USER authenticate
Expected: FIDO_USER gets FIDO PIN + touch only. BREAKGLASS_USER gets password only.
If pamtester says “Authentication service cannot retrieve authentication info”
Diagnostic commandswhoami
id FIDO_USER
getent group fido-login
sudo ls -l /etc/security/u2f_keys
sudo grep "^FIDO_USER:" /etc/security/u2f_keys
sudo cat /etc/pam.d/fido2-local-auth
sudo cat /etc/pam.d/fido2-test
None of those show pam_u2f's own reason for a failure — that goes to the journal, tagged by module name regardless of which program (pamtester, LightDM, sudo) triggered it:
sudo journalctl -g pam_u2f --since "5 minutes ago"
-g instead of -u. pamtester and LightDM aren't systemd units the way ssh.service is, so journalctl -u has nothing to filter on here. -g (--grep) searches the message text directly, so it finds pam_u2f's log line no matter what process asked PAM to authenticate. See the PAM Primer's journalctl section for the general technique.12. Configure LightDM graphical login
Edit /etc/pam.d/lightdm, not /etc/pam.d/lightdm-greeter, for normal user login authentication.
sudo cp -a /etc/pam.d/lightdm /etc/pam.d/lightdm.bak
sudo editor /etc/pam.d/lightdm
Find the service’s existing password include, usually:
@include common-auth
Insert the shared FIDO include immediately above it. Preserve unrelated lines such as pam_nologin, pam_env, SELinux, limits, loginuid, and keyring lines.
# FIDO2 local authentication policy
@include fido2-local-auth
@include common-auth
Do not modify lightdm-greeter on the first pass
/etc/pam.d/lightdm-greeter normally permits the greeter process itself to start. It is not the normal user-authentication path.
auth required pam_permit.so
Optional: prefer username-first graphical login
The group-gated PAM design needs to know the username before it selects the FIDO or password path. LightDM supports manual username entry when the greeter supports it.
Uses sudosudo mkdir -p /etc/lightdm/lightdm.conf.d
sudo tee /etc/lightdm/lightdm.conf.d/50-manual-login.conf >/dev/null <<'EOF'
{{EMBED:config/50-manual-login.conf}}
EOF
Expected presentation:
Username:
Password/PIN:
Pre-logout LightDM PAM test
Uses sudosudo pamtester lightdm FIDO_USER authenticate
sudo pamtester lightdm BREAKGLASS_USER authenticate
LightDM GTK greeter prompt quirk
LightDM GTK greeter may display blank prompt lines for the FIDO/PAM conversation until Enter is pressed. If the greeter shows blank lines instead of a clear PIN/password prompt, press Enter once. Then enter the FIDO token PIN and touch the token when it flashes.
13. Configure sudo
Backup, then editsudo cp -a /etc/pam.d/sudo /etc/pam.d/sudo.bak
sudo editor /etc/pam.d/sudo
Place the shared FIDO include immediately before @include common-auth.
session required pam_limits.so
@include fido2-local-auth
@include common-auth
@include common-account
@include common-session-noninteractive
Testsudo -k
sudo -v
sudo package owns /etc/pam.d/sudo as a dpkg conffile. Unattended or non-interactive upgrades — including unattended-upgrades, automated patching, or accepting a "replace modified conffile" prompt at an apt prompt — can silently replace your edited file with the package default, removing @include fido2-local-auth and disabling FIDO for sudo with no warning. Complete section 13.1 before proceeding.13.1 Protect the sudo PAM configuration from package updates
Install an apt post-invoke hook that checks for the FIDO include after every apt invocation and re-adds it if a package upgrade removed it.
Step 1: Create the guard script.
Uses sudosudo tee /usr/local/sbin/fido2-sudo-pam-guard >/dev/null <<'EOF'
{{EMBED:scripts/fido2-sudo-pam-guard}}
EOF
sudo chmod 755 /usr/local/sbin/fido2-sudo-pam-guard
Step 2: Install the apt hook.
Uses sudosudo tee /etc/apt/apt.conf.d/99-fido2-sudo-pam-guard >/dev/null <<'EOF'
{{EMBED:config/99-fido2-sudo-pam-guard}}
EOF
Step 3: Verify the guard works before relying on it.
Test# Confirm the include is present
sudo grep fido2-local-auth /etc/pam.d/sudo
# Simulate removal, run the guard, confirm it re-adds the line
sudo sed -i '/fido2-local-auth/d' /etc/pam.d/sudo
sudo /usr/local/sbin/fido2-sudo-pam-guard
sudo grep fido2-local-auth /etc/pam.d/sudo
# Confirm sudo still requires FIDO
sudo -k
sudo -v
@include fido2-local-auth is present in /etc/pam.d/sudo. If a package upgrade removed it, the script re-inserts it immediately before @include common-auth and logs the event via logger. The hook is a no-op when the include is already present. Trade-off: if a future sudo upgrade restructures the file and removes @include common-auth as the marker, the re-insertion will silently fail — check /var/log/syslog for fido2-sudo-pam-guard entries after major upgrades.sudo using your password (the FIDO include is gone but common-auth falls back to password). Re-run the guard manually — sudo /usr/local/sbin/fido2-sudo-pam-guard — then confirm with sudo -k && sudo -v that FIDO is active again.passwd -l. If a package upgrade removes the FIDO include and the password is locked, common-auth has nothing to fall back to — the FIDO user cannot run sudo at all. Your only recovery path is the password-only breakglass account. This is exactly why the breakglass account must remain password-capable, must stay outside the fido-login group, and must never have its password locked.14. Core FIDO login checkpoint
At this point, the core FIDO login design is complete only if the standalone PAM test, LightDM test, breakglass test, and real sudo -v test all pass.
| Core test | Command or action | Expected result |
|---|---|---|
| FIDO test service | sudo pamtester fido2-test FIDO_USER authenticate | FIDO PIN + touch only. |
| Breakglass test service | sudo pamtester fido2-test BREAKGLASS_USER authenticate | Password only. |
| LightDM FIDO user | sudo pamtester lightdm FIDO_USER authenticate | FIDO PIN + touch only. |
| LightDM breakglass user | sudo pamtester lightdm BREAKGLASS_USER authenticate | Password only. |
| Real sudo from FIDO session | sudo -k, then sudo -v | FIDO PIN + touch from the actual FIDO_USER session. |
The remaining sections are desktop-integration and hardening steps: optional su, required greeter-based lock behavior, token-removal greeter switching, password locking, keyring behavior, and rollback.
15. Configure su, optional
This is optional. Preserve pam_rootok.so before the FIDO include so root-originated su keeps normal behavior.
sudo cp -a /etc/pam.d/su /etc/pam.d/su.bak
sudo editor /etc/pam.d/su
The important ordering is:
PAM snippetauth sufficient pam_rootok.so
@include fido2-local-auth
@include common-auth
Testsu - FIDO_USER
su - BREAKGLASS_USER
16. Configure XFCE lock behavior: use the LightDM greeter, not the raw screen locker
The standard XFCE screen-locker path is not reliable for this FIDO-only login design. The required workstation-lock design is to send the user to the LightDM greeter instead of relying on the xfce4-screensaver unlock dialog as the primary FIDO unlock UI.
Tested behavior: configuring xflock4 to call dm-tool switch-to-greeter avoids the xfce4-screensaver FIDO-only flicker problem and preserves access to the password-only breakglass account.
xfce4-screensaver unlock dialog as the normal FIDO unlock path. Use the LightDM greeter for manual lock, switch-user behavior, and token-removal locking.Keep the XFCE screensaver PAM file sane as a fallback
Even though the final lock flow uses the LightDM greeter, keep /etc/pam.d/xfce4-screensaver valid. This prevents confusing PAM errors if XFCE still invokes the screensaver dialog during idle timeout or other edge cases.
sudo cp -a /etc/pam.d/xfce4-screensaver /etc/pam.d/xfce4-screensaver.bak.before-fido2-local-auth
sudo editor /etc/pam.d/xfce4-screensaver
Use this content:
PAM snippet#%PAM-1.0
auth include fido2-local-auth
auth include common-auth
auth optional pam_gnome_keyring.so
account required pam_permit.so
The explicit account required pam_permit.so line avoids the observed pam_unix(xfce4-screensaver:account): setuid failed account-management problem.
Test the PAM service directly:
Uses sudosudo pamtester xfce4-screensaver FIDO_USER authenticate
sudo pamtester xfce4-screensaver FIDO_USER acct_mgmt
sudo pamtester xfce4-screensaver BREAKGLASS_USER authenticate
sudo pamtester xfce4-screensaver BREAKGLASS_USER acct_mgmt
| Test | Expected result |
|---|---|
FIDO_USER authenticate | FIDO PIN + touch. |
FIDO_USER acct_mgmt | Success. |
BREAKGLASS_USER authenticate | Password. |
BREAKGLASS_USER acct_mgmt | Success. |
Make xflock4 switch to the LightDM greeter
The xflock4 script first checks the XFCE session setting /general/LockCommand. Set that command to switch to the LightDM greeter.
xfconf-query -c xfce4-session -p /general/LockCommand --create -t string -s "dm-tool switch-to-greeter"
Verify the setting:
Normal graphical sessionxfconf-query -c xfce4-session -p /general/LockCommand
Expected output:
Expected outputdm-tool switch-to-greeter
Test manual locking:
Normal graphical sessionxflock4
Expected behavior:
- The machine switches to the LightDM greeter instead of the
xfce4-screensaverunlock dialog. - The FIDO user authenticates through LightDM with FIDO PIN + touch.
- The breakglass user remains available with normal password authentication.
- Switch-user behavior remains available at the greeter.
If raw xflock4 fails from a terminal
Raw xflock4 only works from the original graphical XFCE session environment. It needs DISPLAY, XDG_RUNTIME_DIR, and DBUS_SESSION_BUS_ADDRESS. A nested login shell such as su - FIDO_USER may strip those variables.
echo "$DISPLAY"
echo "$XDG_RUNTIME_DIR"
echo "$DBUS_SESSION_BUS_ADDRESS"
If these are empty, exit the nested login shell and test from the original XFCE terminal session.
If idle locking still uses xfce4-screensaver
The xflock4 manual lock path may be fixed by /general/LockCommand, but idle timeout may still be controlled by xfce4-screensaver itself. This idle-timeout path must be tested on each installation before the setup is considered complete.
Check which lockers are running:
Normal graphical sessionpgrep -a 'xfce4-screensaver|light-locker|xscreensaver|xsecurelock'
If xfce4-screensaver continues to present its own unlock dialog during idle timeout and flickers when the token is absent, prefer one of these approaches:
- Disable
xfce4-screensaveridle locking and rely on LightDM greeter switching for manual lock and token-removal lock. - Replace the active idle locker with a LightDM-friendly path.
- Keep
xfce4-screensaverinstalled but avoid using it as the normal FIDO unlock surface.
17. Switch to the LightDM greeter when the FIDO key is removed
This section sends the machine to the LightDM greeter when the FIDO token is removed. It depends on the greeter-based lock behavior configured and tested in the previous section. This is part of the intended desktop behavior for this FIDO-only setup, but it is still not an authentication control; the real authentication control remains the FIDO/PAM login policy.
xflock4 or forcing the xfce4-screensaver unlock dialog. The greeter path preserves access to user switching and the password-only breakglass account.xflock4? Calling xflock4 on token removal can trap the user inside the current session's FIDO-only unlock dialog. If the token is absent, the unlock dialog may flicker or repeatedly restart, and the Switch User path may not be available until the token is reinserted. Switching to the LightDM greeter avoids that failure mode.17.1. Confirm the greeter switch command exists
Normal graphical sessioncommand -v dm-tool
Expected result:
Example output/usr/bin/dm-tool
From the real XFCE terminal session, test switching to the greeter:
Normal graphical sessiondm-tool switch-to-greeter
Expected behavior: the session switches to the LightDM greeter. From there, the FIDO user should be able to authenticate with FIDO PIN + touch, and the breakglass user should be able to authenticate with a normal password.
17.2. Find the token's udev identifiers
Insert the FIDO key, then monitor udev events:
Uses sudosudo udevadm monitor --udev --environment
Remove the FIDO key and look for the hidraw remove event. Prefer the event that includes ID_FIDO_TOKEN=1 and ID_SECURITY_TOKEN=1.
ACTION=remove
SUBSYSTEM=hidraw
DEVNAME=/dev/hidraw1
ID_FIDO_TOKEN=1
ID_SECURITY_TOKEN=1
ID_VENDOR_ID=096e
ID_MODEL_ID=0858
ID_MODEL=FIDO
Record ID_VENDOR_ID and ID_MODEL_ID. If your token exposes a truly unique serial number, you may also match on that, but some tokens expose a generic product serial string rather than a per-token serial.
17.3. Create the greeter-switch helper
This helper is run by root through udev/systemd, finds the active local graphical session, reconstructs the session environment, and runs dm-tool switch-to-greeter as the graphical user.
sudo tee /usr/local/sbin/fido-remove-switch-to-greeter >/dev/null <<'EOF'
{{EMBED:scripts/fido-remove-switch-to-greeter}}
EOF
sudo chown root:root /usr/local/sbin/fido-remove-switch-to-greeter
sudo chmod 0755 /usr/local/sbin/fido-remove-switch-to-greeter
17.4. Test the helper manually
Uses sudosudo /usr/local/sbin/fido-remove-switch-to-greeter
Expected behavior: the session switches to the LightDM greeter. After returning, inspect logs:
Troubleshootingsudo journalctl -b --since "3 minutes ago" | grep -Ei 'fido-switch-greeter|dm-tool|lightdm|greeter'
If dm-tool reports Not running inside a display manager, XDG_SEAT_PATH not defined, verify that the helper includes the XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0 environment variable.
17.5. Test a logging-only udev rule first
Before letting udev switch the greeter, confirm the rule fires on the correct remove event. Replace the vendor/model IDs with the values observed from your token.
Uses sudosudo tee /etc/udev/rules.d/85-fido-lock-on-remove-test.rules >/dev/null <<'EOF'
{{EMBED:config/85-fido-lock-on-remove-test.rules}}
EOF
sudo udevadm control --reload-rules
Remove the token, then check for the test log:
Troubleshootingsudo journalctl -b --since "2 minutes ago" | grep -i fido-udev-test
If the test log appears, remove the test rule:
Uses sudosudo rm -f /etc/udev/rules.d/85-fido-lock-on-remove-test.rules
sudo udevadm control --reload-rules
17.6. Create the real udev removal rule
Do not run dm-tool directly from udev. udev jobs should be short-lived. Use systemd-run --no-block to hand the work off to systemd.
sudo tee /etc/udev/rules.d/85-fido-lock-on-remove.rules >/dev/null <<'EOF'
{{EMBED:config/85-fido-lock-on-remove.rules}}
EOF
sudo udevadm control --reload-rules
17.7. Test token removal
- Stay logged into XFCE.
- Insert the FIDO key.
- Remove the FIDO key.
- The machine should switch to the LightDM greeter.
- Select the FIDO user and reinsert the token to authenticate with FIDO PIN + touch, or select the password-only breakglass user.
Check logs after the test:
Troubleshootingsudo journalctl -b --since "3 minutes ago" | grep -Ei 'fido-switch-greeter|fido-udev|udev|dm-tool|lightdm|greeter'
17.8. Notes on raw xflock4 testing
Raw xflock4 only works from the original graphical XFCE session environment. It needs DISPLAY, XDG_RUNTIME_DIR, and DBUS_SESSION_BUS_ADDRESS. If those variables are empty, such as inside a nested su - login shell, raw xflock4 may fail even though the desktop session is healthy.
echo "$DISPLAY"
echo "$XDG_RUNTIME_DIR"
echo "$DBUS_SESSION_BUS_ADDRESS"
The greeter-switch helper does not depend on the current shell environment; it reconstructs the graphical session environment from loginctl.
17.9. Rollback the removal-switch feature (optional)
sudo rm -f /etc/udev/rules.d/85-fido-lock-on-remove.rules
sudo rm -f /etc/udev/rules.d/85-fido-lock-on-remove-test.rules
sudo rm -f /usr/local/sbin/fido-remove-switch-to-greeter
sudo udevadm control --reload-rules
18. Extended test matrix before reboot
| Test | Command or action | Expected result |
|---|---|---|
| FIDO test service | sudo pamtester fido2-test FIDO_USER authenticate | FIDO PIN + touch only. |
| Breakglass test service | sudo pamtester fido2-test BREAKGLASS_USER authenticate | Password only. |
| LightDM FIDO user | sudo pamtester lightdm FIDO_USER authenticate | FIDO PIN + touch only. |
| LightDM breakglass user | sudo pamtester lightdm BREAKGLASS_USER authenticate | Password only. |
| Real sudo from FIDO session | sudo -k, then sudo -v | FIDO behavior for the actual FIDO_USER session. |
su, if configured | su - FIDO_USER and su - BREAKGLASS_USER | Correct path per account. If su includes fido2-local-auth, su - FIDO_USER may succeed by FIDO instead of password. |
| Manual lock | xflock4 | Switches to the LightDM greeter. FIDO user authenticates through LightDM with PIN + touch; breakglass user remains available. |
| Idle-timeout lock | Wait for the configured idle lock timeout. | Either switches to the LightDM greeter or uses a known-tested fallback. No flicker loop. Breakglass user remains reachable. |
| FIDO key removal | Remove the FIDO token from an active graphical session. | System switches to the LightDM greeter. User switching and breakglass login remain available. |
Reboot only after tests pass
Uses sudosudo reboot
19. Disable Unix password login for FIDO users
After the FIDO login path, LightDM greeter lock behavior, sudo behavior, breakglass account, and recovery path have been fully tested, the FIDO-enabled user can be made token-only by locking that user's Unix password. This prevents password-based authentication for that user while keeping the account itself valid for normal login, group membership, sudo policy, and PAM account/session checks.
BREAKGLASS_USER with a known-working password. Do not lock the breakglass account's password.passwd -l FIDO_USER, console TTY password recovery for that user is no longer available. Your password-based recovery path is BREAKGLASS_USER.Lock the password, not the account. Use passwd -l for the FIDO user. Do not expire the account, disable the shell, delete the user, or remove normal group memberships.
sudo passwd -l FIDO_USER
Verify the password state:
Uses sudosudo passwd -S FIDO_USER
sudo getent shadow FIDO_USER
The shadow password field should begin with !, indicating that password authentication is locked for that account. The account should still exist and should still be a member of fido-login.
Verify group membership:
Normal user shellid FIDO_USER
getent group fido-login
Retest after locking the password
Run the core tests again before logging out or rebooting. Prefer operational tests from the actual FIDO_USER graphical session over synthetic-only tests.
sudo -k
sudo -v
xflock4
Uses sudo
sudo pamtester lightdm FIDO_USER authenticate
sudo pamtester lightdm BREAKGLASS_USER authenticate
sudo pamtester xfce4-screensaver FIDO_USER authenticate
| Test | Expected result |
|---|---|
FIDO_USER through LightDM | FIDO PIN + touch only. |
FIDO_USER through real sudo -v | FIDO PIN + touch only. |
xflock4 | Switches to the LightDM greeter, then FIDO PIN + touch through LightDM. |
BREAKGLASS_USER through LightDM | Password only. |
Confirm the Unix password itself is locked
If su has been modified to include fido2-local-auth, then su - FIDO_USER may still succeed through FIDO. That is expected. To prove that the Unix password itself is locked, test a password-only path such as an unmodified TTY login for FIDO_USER, or perform this check before enabling FIDO for su.
FIDO-protected PAM services should continue to work because fido2-local-auth exits successfully before falling through to common-auth.
Restore password login later, if needed
Uses sudosudo passwd -u FIDO_USER
Only unlock the password if you intentionally want password-based authentication restored for that account.
20. Optional: GNOME Keyring and Chrome/Chromium prompts
After converting a user to FIDO-only login, Chrome or Chromium may ask for the GNOME Keyring password when it tries to access stored browser secrets. This is expected.
login keyring using the password entered during login. In the FIDO-only flow, the user authenticates with token PIN + touch, so there is no Unix password for pam_gnome_keyring.so to reuse.This means FIDO2 can authenticate the desktop login, but it does not automatically decrypt an existing password-protected keyring. The keyring password remains separate.
20.1. Options
| Option | Behavior | Tradeoff |
|---|---|---|
| Keep the keyring password | Chrome/Chromium prompts for the keyring password when needed | Most conservative; preserves separate protection for keyring secrets |
| Set the login keyring password blank | Chrome/Chromium stops prompting for the keyring password | Convenient, but keyring secrets are no longer protected by a separate keyring password after session start |
| Restore password login | GNOME Keyring may auto-unlock from the login password | Undermines the FIDO-only account goal |
20.2. Install Seahorse
On a minimal XFCE install, the graphical keyring editor may not already be installed. Install Seahorse:
Uses sudosudo apt install seahorse
seahorse may fail to install due to unresolvable GNOME library dependencies. If apt reports dependency errors, try the following:
sudo apt install --fix-brokensudo apt update && sudo apt full-upgradeIf dependencies still cannot be resolved, an alternative is to manage the keyring password from the command line using
secret-tool (from the libsecret-tools package) or by deleting and recreating the keyring file at ~/.local/share/keyrings/login.keyring. Note that deleting the keyring file will erase all stored keyring secrets.Launch it:
Normal graphical sessionseahorse
20.3. Set the login keyring password blank
- Open Passwords and Keys / Seahorse.
- Find the Login keyring.
- Right-click the Login keyring and choose Change Password.
- Enter the current keyring password.
- Leave the new password fields blank.
- Accept the warning about storing passwords unencrypted, if this matches your threat model.
After this, Chrome/Chromium should stop asking for the keyring password when launched inside the FIDO-only user session.
20.4. Do not automate this step
This walkthrough treats blanking the keyring password as an explicit user choice. Do not automate it in the PAM migration script, because it changes how local application secrets are protected.
21. Emergency rollback
If a PAM change breaks authentication and you still have a root-capable shell, remove the shared include references first:
Uses sudosudo sed -i '/fido2-local-auth/d' /etc/pam.d/lightdm /etc/pam.d/sudo /etc/pam.d/su /etc/pam.d/xfce4-screensaver 2>/dev/null || true
sudo rm -f /etc/pam.d/fido2-local-auth
sudo rm -f /etc/lightdm/lightdm.conf.d/50-manual-login.conf
sudo rm -f /etc/udev/rules.d/85-fido-lock-on-remove.rules
sudo rm -f /etc/udev/rules.d/85-fido-lock-on-remove-test.rules
sudo rm -f /usr/local/sbin/fido-remove-switch-to-greeter
sudo rm -f /usr/local/sbin/lock-on-fido-remove
sudo udevadm control --reload-rules
If you need to restore backed-up PAM files:
Uses sudosudo test -e /etc/pam.d/lightdm.bak && sudo cp -a /etc/pam.d/lightdm.bak /etc/pam.d/lightdm || true
sudo test -e /etc/pam.d/sudo.bak && sudo cp -a /etc/pam.d/sudo.bak /etc/pam.d/sudo || true
sudo test -e /etc/pam.d/su.bak && sudo cp -a /etc/pam.d/su.bak /etc/pam.d/su || true
sudo test -e /etc/pam.d/xfce4-screensaver.bak.before-fido2-local-auth && sudo cp -a /etc/pam.d/xfce4-screensaver.bak.before-fido2-local-auth /etc/pam.d/xfce4-screensaver || true
sudo test -e /etc/pam.d/xfce4-screensaver.bak && sudo cp -a /etc/pam.d/xfce4-screensaver.bak /etc/pam.d/xfce4-screensaver || true
Restart display manager only when ready
Uses sudosudo systemctl restart lightdm
If you are locked out entirely, boot recovery or a live USB, mount the system, and remove the fido2-local-auth includes from the PAM files.
22. Option notes and policy choices
Common pam_u2f options used here
| Option | Meaning |
|---|---|
authfile=/etc/security/u2f_keys | Use the central root-managed mapping file. |
cue="..." | Ask PAM/greeter to show a human cue. Graphical greeters may display it imperfectly. |
pinverification=1 | Require the FIDO2 token PIN during authentication. |
userverification=0 | Do not require a separate biometric or authenticator-level user-verification feature. The expected flow here is token PIN plus physical presence/touch. |
pinverification=1 requires the token PIN. userverification=0 avoids requiring a separate biometric or authenticator-level UV feature. This is appropriate for KEY-ID FIDO+U2F style behavior where the desired flow is PIN + flashing-button touch.pamu2fcfg options used here
| Option | Meaning |
|---|---|
-N | Enroll a credential that requires PIN verification. |
-u FIDO_USER | Create the mapping line for that user. |
-n | Output only credential data so a backup token can be appended. |
Why edit selected PAM services?
This guide edits LightDM, sudo, optional su, and the actual screen-locker PAM file. It does not edit common-auth globally, because a mistake there can break too many authentication paths at once.
Lock policy used here
This guide uses the LightDM greeter as the normal lock/unlock surface for FIDO users. The raw xfce4-screensaver unlock dialog is kept PAM-valid as a fallback, but it is not the preferred or tested primary unlock UI for FIDO-only use.
23. References and source notes
- Debian packages:
libpam-u2f,pamu2fcfg,fido2-tools, andpamtester. - Yubico
pam_u2fmanual: documentsauthfile,cue,pinverification,userverification,nouserok, and passwordless examples. - Yubico
pamu2fcfgmanual: documents-Nand-n. - libfido2
fido2-tokenandfido2-credmanuals: document listing tokens, setting/changing PINs, and make-credential tests with-t pin=true. pam_succeed_ifmanual: documents group-based selection withuser ingroup.- LightDM common configuration and sample config: document
greeter-hide-usersandgreeter-show-manual-login. loginctlmanual: documentslock-sessions.- Debian udev documentation: covers matching USB device add/remove events with udev rules.
24. Lab experiment: pkexec FIDO2 passwordless authentication
You have now configured FIDO2 PIN + touch for LightDM, sudo, optional su, and screen-lock. The next challenge is extending the same passwordless authentication policy to pkexec, the PolicyKit helper that graphical applications use to request elevated privileges.
BREAKGLASS_USER) can still authenticate with a password and that you have an open root-capable shell. The PAM stack for polkit-1 is separate from sudo — a mistake here will not lock you out of sudo, but you should still have a fallback.
Background: why pkexec is different
pkexec is driven by PolicyKit (polkit), which calls PAM using the service name polkit-1. Unlike sudo, which presents a terminal conversation, pkexec may invoke a graphical authentication agent (such as polkit-gnome-authentication-agent-1) that prompts the user. The agent mediates the PAM conversation. This means:
- The PAM file to edit is
/etc/pam.d/polkit-1. - Whether the agent can relay a FIDO2 PIN prompt depends on how it handles the PAM conversation.
- Token user-presence (the physical touch) is handled inside
pam_u2fand does not require special agent support.
Your task
Working from what you learned in sections 10–13, configure pkexec so that FIDO_USER authenticates with FIDO2 PIN + touch (no Unix password) while BREAKGLASS_USER continues to authenticate with a password.
- Which PAM file controls
pkexecauthentication? How would you verify this? - What is the minimum change needed to insert the shared
fido2-local-authinclude into that file? - How can you test the
polkit-1PAM service withpamtesterbefore invoking a real graphical app that callspkexec? - If the graphical agent does not forward the PIN prompt correctly, what alternative PAM conversation path could you use? (Hint: consider what happens when no graphical agent is running.)
- If a GUI app triggers
pkexecand the dialog never appears, how would you diagnose whether the problem is in the PAM stack, the polkit policy, or the agent?
Suggested approach
Work through these steps in order. Do not skip the pamtester step — it will tell you whether the PAM side is correct before you involve the graphical agent.
| # | Step | Goal |
|---|---|---|
| 1 | Locate and back up /etc/pam.d/polkit-1. | Preserve a known-good copy before any edit. |
| 2 | Read the current polkit-1 PAM file and identify where @include common-auth (or equivalent) appears. | Know what you are inserting above. |
| 3 | Insert @include fido2-local-auth immediately above the existing auth include, following the same pattern as sections 12–13. | Apply the group-gated FIDO policy to pkexec. |
| 4 | Run sudo pamtester polkit-1 FIDO_USER authenticate and sudo pamtester polkit-1 BREAKGLASS_USER authenticate. | Confirm the PAM stack is correct without invoking a real graphical app yet. |
| 5 | Trigger a real pkexec call from the FIDO_USER graphical session (for example, pkexec /usr/bin/id from a terminal, or open a graphical app that requires elevated privileges). | Observe whether the agent relays the PIN prompt and whether FIDO touch is accepted. |
| 6 | Record what you observe: does the PIN prompt appear? Where? Does the token flash? Does authentication succeed? | Document actual behavior, not assumed behavior. |
polkit-gnome-authentication-agent-1 or it may not be running at all. Run pgrep -a polkit to see what is active. If no agent is running, pkexec falls back to a terminal-based PAM conversation, which is much easier to observe.
Verification checklist
| Test | Expected result | Pass? |
|---|---|---|
sudo pamtester polkit-1 FIDO_USER authenticate | FIDO PIN + touch only. No Unix password prompt. | ☐ |
sudo pamtester polkit-1 BREAKGLASS_USER authenticate | Unix password only. No FIDO prompt. | ☐ |
pkexec /usr/bin/id as FIDO_USER (terminal or graphical) | Authentication succeeds with PIN + touch. Output shows uid=0(root). | ☐ |
Intentionally enter a wrong PIN via pamtester | Authentication fails. No fallback to password. | ☐ |
Remove token, then attempt pamtester polkit-1 FIDO_USER authenticate | Authentication fails cleanly. No password fallback. | ☐ |
polkit-1 on Debian ship with pam_permit.so as the only auth line — meaning any user is automatically authenticated for pkexec without a credential. If you observe this, discuss with your instructor why this is the default and what the correct production posture should be. Then apply the group-gated FIDO policy anyway, so that FIDO_USER is required to use FIDO2 and BREAKGLASS_USER falls through to common-auth.
25. Fix auto-suspend: polkit rules for power management
After configuring polkit-1 PAM in section 24, auto-suspend will stop working. The screen locks, the idle timer fires — and a polkit authentication prompt appears on the locked screen that the user cannot see and the graphical agent cannot complete. The system stays awake.
Why it fails
Both polkit actions that xfce4-power-manager uses for suspend have implicit active: yes — meaning polkit grants them without authentication when the session is active. The problem is that auto-suspend fires after the screen has locked. When light-locker activates, logind marks the session inactive. With an inactive session, both actions fall to auth_admin:
| Action | implicit active | implicit inactive |
|---|---|---|
org.freedesktop.login1.suspend | yes | auth_admin_keep |
org.xfce.power.xfce4-pm-helper | yes | auth_admin |
Polkit invokes the polkit-1 PAM stack to satisfy the auth_admin requirement. That stack now includes fido2-local-auth, which needs FIDO hardware. The graphical authentication agent cannot present a token challenge on a locked screen, so authentication fails and the suspend never completes.
xfce4-power-manager tries both action IDs in sequence — the logind action first, then its own setuid helper as a fallback. You can confirm both failures in the polkit journal immediately after a failed auto-suspend:
Diagnosticsudo journalctl -u polkit --since "5 minutes ago"
Expected output: two FAILED to authenticate entries — one for org.freedesktop.login1.suspend and one for org.xfce.power.xfce4-pm-helper.
The fix: a polkit rules file
Polkit's authorization layer has its own override mechanism, entirely separate from PAM: JavaScript rules files in /etc/polkit-1/rules.d/. A rules file can return polkit.Result.YES for a matching action and subject, bypassing both the implicit policy and the PAM authentication check.
polkit-1 PAM configuration does for any other polkit action.sudo tee /etc/polkit-1/rules.d/85-power-no-auth.rules << 'EOF'
polkit.addRule(function(action, subject) {
var pmActions = [
"org.freedesktop.login1.suspend",
"org.freedesktop.login1.suspend-multiple-sessions",
"org.freedesktop.login1.hibernate",
"org.freedesktop.login1.hibernate-multiple-sessions",
"org.xfce.power.xfce4-pm-helper"
];
if (pmActions.indexOf(action.id) >= 0 && subject.isInGroup("fido-login")) {
return polkit.Result.YES;
}
});
EOF
Polkit watches /etc/polkit-1/rules.d/ and reloads automatically — no service restart needed.
What the rule does
| Element | Purpose |
|---|---|
pmActions array | Covers both the logind suspend/hibernate actions and the XFCE power helper. The -multiple-sessions variants are included because logind uses them when more than one session is open, which is common on lab VMs. |
subject.isInGroup("fido-login") | Limits the grant to enrolled FIDO users. Non-members are not matched and fall through to the implicit policy unchanged. |
polkit.Result.YES | Authorization granted without invoking PAM. The locked-screen authentication failure cannot occur because no PAM challenge is issued. |
Verify
Test the full auto-suspend path: lock the screen with xflock4, then wait for the idle timeout configured in xfce4-power-manager. The system should suspend normally without a polkit prompt. To test immediately without waiting for the idle timer:
systemctl suspend
Expected: system suspends without an authentication prompt. If a prompt appears, the rules file was not picked up — verify the path and filename:
Diagnosticls -l /etc/polkit-1/rules.d/
Rollback
Uses sudosudo rm -f /etc/polkit-1/rules.d/85-power-no-auth.rules
Polkit reloads immediately. Auto-suspend will fail again for fido-login members until the file is restored. To include this in a single emergency cleanup, add the rm command to the rollback block in section 21.
26. Fix NetworkManager authentication: polkit rules for network settings
After configuring polkit-1 PAM in section 24, modifying network settings through the XFCE NetworkManager applet will trigger a polkit authentication dialog that FIDO2 cannot satisfy — and the change will be blocked.
Why it fails
The NetworkManager action for modifying system-wide connection profiles has implicit active: auth_admin_keep. When a user edits a connection in the NM applet, NetworkManager requests this action and polkit invokes the graphical authentication agent. The agent drives the polkit-1 PAM stack, which now requires FIDO2. Because the agent cannot present a PIN field that maps to token hardware, authentication fails:
| Action | implicit active | implicit inactive |
|---|---|---|
org.freedesktop.NetworkManager.settings.modify.system | auth_admin_keep | auth_admin |
The dialog reads "System policy prevents modification of network settings for all users" with a password field the user cannot complete via FIDO2. Clicking Authenticate fails; clicking Cancel leaves the network change unapplied.
Confirm the failure action in the polkit journal:
Diagnosticsudo journalctl -u polkit --since "5 minutes ago"
Expected output: a FAILED to authenticate entry for org.freedesktop.NetworkManager.settings.modify.system.
The fix: a polkit rules file
The same rules-file mechanism used in section 25 applies here. A rules file grants the NM action directly to fido-login group members, bypassing the PAM authentication challenge entirely.
org.freedesktop.NetworkManager.settings.modify.system and only for members of fido-login. It does not affect any other polkit action or any other user. Students outside the FIDO group are still subject to the normal auth_admin policy.sudo tee /etc/polkit-1/rules.d/85-nm-no-auth.rules << 'EOF'
polkit.addRule(function(action, subject) {
if (action.id === "org.freedesktop.NetworkManager.settings.modify.system" &&
subject.isInGroup("fido-login")) {
return polkit.Result.YES;
}
});
EOF
Polkit watches /etc/polkit-1/rules.d/ and reloads automatically — no service restart needed.
What the rule does
| Element | Purpose |
|---|---|
action.id === "org.freedesktop.NetworkManager.settings.modify.system" | Matches only the action that governs editing system-wide connection profiles — the one the NM applet requests when a user modifies a network connection. |
subject.isInGroup("fido-login") | Limits the grant to enrolled FIDO users, consistent with the power management rule in section 25. Non-members fall through to the normal auth_admin policy. |
polkit.Result.YES | Authorization granted without invoking PAM. The graphical authentication dialog never appears. |
Verify
As FIDO_USER, open the NetworkManager applet and edit an existing connection (for example, change the DNS server on the current connection and revert it). The change should apply without any polkit authentication dialog appearing.
To confirm via the journal that the action was granted rather than challenged:
Diagnosticsudo journalctl -u polkit --since "2 minutes ago" | grep -i network
Expected: an Authorized action or Operator of unix-process entry for the NM action — no FAILED entry.
Rollback
Uses sudosudo rm -f /etc/polkit-1/rules.d/85-nm-no-auth.rules
Polkit reloads immediately. NetworkManager settings changes will again require polkit authentication, which will fail for FIDO users until the file is restored. To include this in a single emergency cleanup, add the rm command to the rollback block in section 21.
24-I. pkexec FIDO2 — step-by-step instructor procedures
#show-instructor. Share the anchor URL only after the lab period ends, or use it for live demonstration. The section is not linked from the table of contents.
Step 1 — Confirm the current polkit-1 PAM file
Before any edit, students should read the existing file. On a stock Debian 13 XFCE install, /etc/pam.d/polkit-1 commonly contains only pam_permit.so, granting any user the right to authenticate polkit without any credential check:
sudo cat /etc/pam.d/polkit-1
Common stock content:
#%PAM-1.0
auth required pam_permit.so
account required pam_permit.so
session required pam_permit.so
pam_permit.so-only default is intentional on many distros because polkit's own .rules files are the primary authorization layer. However, for this lab's FIDO2 policy we are adding a real authentication layer on top of polkit's authorization check. Both layers are valid and complementary.
Step 2 — Back up and edit polkit-1
Uses sudosudo cp -a /etc/pam.d/polkit-1 /etc/pam.d/polkit-1.bak
sudo editor /etc/pam.d/polkit-1
Replace or augment the auth stack. The correct result:
Target polkit-1 content#%PAM-1.0
# FIDO2 local authentication policy for pkexec/polkit.
# Users in fido-login authenticate with FIDO2 PIN + touch.
# Users not in fido-login fall through to common-auth (password).
@include fido2-local-auth
auth required pam_permit.so
account required pam_permit.so
session required pam_permit.so
pam_permit.so? The existing auth required pam_permit.so line would succeed unconditionally for any user. Placing @include fido2-local-auth first means FIDO users hit the FIDO check; if they succeed, pam_u2f returns success=done and the pam_permit.so line is never reached. Non-FIDO users skip the include (because they are not in fido-login) and land on pam_permit.so, which accepts them exactly as before. This is the minimal-impact insertion strategy.
Step 3 — Test with pamtester before triggering a real pkexec call
Uses sudosudo pamtester polkit-1 FIDO_USER authenticate
Expected: FIDO PIN prompt, then token flash prompt, then success.
sudo pamtester polkit-1 BREAKGLASS_USER authenticate
Expected: Unix password prompt only.
If FIDO_USER gets a password prompt instead of FIDO, the most common causes are:
- The user is not in the
fido-logingroup (runid FIDO_USERandgetent group fido-login). - The
fido2-local-authinclude file has a syntax error. - The mapping file
/etc/security/u2f_keysis missing or unreadable by the PAM process.
Step 4 — Test a real pkexec invocation from the FIDO user session
From a terminal running as FIDO_USER inside the graphical session:
pkexec /usr/bin/id
Two possible observed behaviors depending on whether a polkit agent is running:
| Agent state | Observed behavior | Correct? |
|---|---|---|
| No agent running (common on minimal XFCE) | PAM conversation runs in the terminal. PIN prompt appears in the terminal. Token flashes. uid=0(root) output. | Yes — correct and easiest to observe. |
| polkit-gnome agent running | A graphical dialog appears. It may show a PIN field or a password field depending on agent version. After PIN entry, token flashes. uid=0(root) output. | Yes — if agent forwards the cue correctly. |
| polkit-gnome agent running, no PIN field visible | Dialog shows a blank or generic password field. Student must enter the FIDO token PIN there (not the Unix password). Token flashes. Authentication succeeds. | Functionally correct; graphical presentation is imperfect — same quirk as LightDM GTK greeter documented in section 12. |
Step 5 — Negative tests
Wrong PIN testsudo pamtester polkit-1 FIDO_USER authenticate
Enter an incorrect PIN deliberately. Expected: FIDO_ERR_PIN_INVALID, authentication failure, no password fallback.
# Remove token first, then:
sudo pamtester polkit-1 FIDO_USER authenticate
Expected: pam_u2f fails to find the device, authentication fails. No password fallback offered.
Step 6 — Diagnosis guide for common failure modes
| Symptom | Likely cause | Diagnostic command |
|---|---|---|
| pamtester shows password prompt for FIDO_USER | User not in fido-login group, or mapping file unreadable | id FIDO_USER, sudo ls -l /etc/security/u2f_keys |
| pkexec prompts for password graphically instead of PIN | Graphical agent not forwarding the cue — this is cosmetic; entering the PIN in the password field still works | Kill the agent with pkill polkit-gnome and retry in terminal for a cleaner test |
| pkexec says "Not authorized" even after FIDO succeeds | polkit authorization (.rules file) denies the action — separate from authentication | pkaction --action-id <action> --verbose; check /usr/share/polkit-1/actions/ |
| Authentication service cannot retrieve info | mapping file permissions wrong or fido2-local-auth has a typo | sudo cat /etc/pam.d/fido2-local-auth, sudo cat /etc/pam.d/polkit-1 |
| FIDO_ERR_NO_CREDENTIALS on a token with PIN | pamu2fcfg was run without -N; credential not registered for PIN | Re-run pamu2fcfg -N -u FIDO_USER and update /etc/security/u2f_keys |
Step 7 — Instructor provision: block non-FIDO users from pkexec authentication
By default the configuration above allows users outside fido-login to skip the FIDO check and land on pam_permit.so, which grants them authentication unconditionally. In most lab deployments this is acceptable because polkit's own rules engine controls what actions each user may perform. However, if the policy goal is to ensure that only FIDO-enrolled users can authenticate through pkexec at all, replace the permissive fallback with an explicit deny.
BREAKGLASS_USER and any other non-FIDO account will receive an authentication failure from polkit-1 regardless of the action being requested. Confirm that your recovery path does not depend on pkexec before applying this. Console TTY and direct sudo remain unaffected.
sudo cp -a /etc/pam.d/polkit-1 /etc/pam.d/polkit-1.bak-pre-deny
sudo tee /etc/pam.d/polkit-1 >/dev/null <<'EOF'
#%PAM-1.0
# Restricted pkexec policy: only fido-login members may authenticate.
# Non-members receive an explicit auth failure -- pam_permit fallback removed.
# FIDO users: PIN + touch, then done.
auth [success=ignore default=1] pam_succeed_if.so quiet user ingroup fido-login
auth [success=done default=die] pam_u2f.so authfile=/etc/security/u2f_keys cue="FIDO users: enter token PIN, then touch token when it flashes" pinverification=1 userverification=0
# Non-FIDO users: explicit deny -- no fallback.
auth required pam_deny.so
account required pam_permit.so
session required pam_permit.so
EOF
The control-flow for each user class:
| User | pam_succeed_if | pam_u2f | pam_deny | Result |
|---|---|---|---|---|
FIDO_USER (in fido-login) | success=ignore → continues | PIN + touch → success=done | Never reached | Authenticated |
FIDO_USER with wrong PIN | success=ignore → continues | Fails → default=die | Never reached | Denied |
BREAKGLASS_USER (not in fido-login) | default=1 → skips pam_u2f | Skipped | required → fails | Denied |
Verify the deny policy
Uses sudosudo pamtester polkit-1 FIDO_USER authenticate
sudo pamtester polkit-1 BREAKGLASS_USER authenticate
Expected: FIDO_USER succeeds with FIDO PIN + touch. BREAKGLASS_USER receives pamtester: Authentication failure immediately, with no password prompt offered.
FIDO_USER, the specific action being requested must still be authorized by polkit's .rules files. This PAM change controls who can authenticate to polkit; the rules files control what authenticated users may do. Both layers remain active.
Rollback the deny policy (if needed)
Uses sudosudo cp -a /etc/pam.d/polkit-1.bak-pre-deny /etc/pam.d/polkit-1
Step 8 — Rollback the full pkexec lab (if needed)
Uses sudosudo cp -a /etc/pam.d/polkit-1.bak /etc/pam.d/polkit-1
This restores the stock pam_permit.so-only behavior. No service restart is required — PAM reads its configuration at authentication time. If the deny policy backup also exists, remove it:
sudo rm -f /etc/pam.d/polkit-1.bak-pre-deny
Extension challenge — answer
The stock pam_permit.so-only default exists because polkit already performs per-action authorization through its rules engine (/usr/share/polkit-1/actions/ and /etc/polkit-1/rules.d/). Adding an authentication layer via pam_u2f provides a hardware-backed, PIN-gated credential check on top of polkit's policy checks. In a production environment requiring strong authentication for privilege escalation, this layered approach is appropriate.
The group-gated pattern from fido2-local-auth ensures non-FIDO accounts (such as service accounts or the breakglass account) are not blocked by the hardware requirement, so the policy applies only to intentionally enrolled users.
Instructor note: wheel vs sudo group and polkit
wheel and sudo are functionally equivalent privilege-escalation conventions that come from different Unix lineages. Neither has any inherent special meaning to polkit — they are only meaningful if a .rules file explicitly checks for them.
| Group | Origin | Default sudoers grant | Typical distros |
|---|---|---|---|
sudo | Debian/Ubuntu convention | %sudo ALL=(ALL:ALL) ALL | Debian, Ubuntu, Mint |
wheel | Red Hat/BSD lineage ("big wheel") | %wheel ALL=(ALL:ALL) ALL | Fedora, RHEL, Arch, SUSE |
On Debian, membership in sudo grants sudo access via /etc/sudoers — and nothing else. It has no effect on polkit unless a .rules file explicitly references it. The same is true of wheel.
.rules files copied from upstream or Red Hat-oriented documentation often check for wheel. On a Debian system where no users are in wheel, those rules silently grant nothing, causing actions to be denied even when they appear to be permitted by policy. If a student reports unexpected polkit denials, check the relevant .rules file for a wheel reference and either add the user to wheel or edit the rule to reference sudo instead.
Both groups can coexist harmlessly. In lab environments it is common practice to add privileged users to both sudo and wheel to avoid ambiguity when rules files from mixed sources are in play. Neither group interacts with the FIDO2 PAM policy configured in this lab — that policy is gated on fido-login membership exclusively.
pkexec uses the polkit-1 PAM service. The same shared include file (fido2-local-auth) that gates LightDM and sudo can be inserted into polkit-1 with minimal change. The graphical agent layer introduces a UX quirk (the cue may not render correctly) but does not prevent correct FIDO authentication. Always test with pamtester before testing with a real graphical application.