Home Transient PAM Authentication Failures
Post
Cancel

Transient PAM Authentication Failures

Introduction

I use a YubiKey as a second factor to log in to my Fedora Hyprland setup, for my lock screen and for all of my sudo prompts. For a long time it worked well and didn’t have to think about it at all.

Then one day it started failing on certain boots. This post covers how I tracked down the cause.

Understanding PAM

PAM stands for Pluggable Authentication Modules. It’s a framework on Linux that handles authentication for programs, so each program doesn’t have to implement its own login logic. Without PAM, every program that needs to verify who you are (login, sudo, the display manager) would need its own code for checking passwords and for talking to hardware keys like a YubiKey. With PAM, those programs hand the question “is this person who they claim to be?” to PAM, and PAM decides how to answer it based on configuration files. The program doesn’t need to know whether you proved your identity with a password, a hardware key, or something else.

openssh implements its own PAM logic and the configuration is done in the usual ssh config files so the ssh server is the one hooking to external modules like OIDC or a hardware key. I’ve setup OIDC authentication on my Fedora CoreOS VMs with opkssh in my infrastructure as code on github if you’re interested to know how I did it. Everytime I want to login to my VM it would open up a browser page, ask me to login to my identity provider and if successful, download a public/private key pair with a configured expiry date.

Looking back at our Wi-Fi setup network based authentication with freeradius and the active directory we could have used PAM as well but we opted for ntlm_auth instead. Freeradius calls ntlm_auth itself (via the exec module), and ntlm_auth talks to winbindd over the /var/lib/samba/winbindd_privileged pipe. That path is Samba’s own mechanism, which is why I had to fix permissions on that pipe for the freerad user rather than touching anything in /etc/pam.d/. Ntlm being a controversial protocol that should have been deprecated because of it’s security flaws I might one day go back to that setup and migrate it to something else.

The setup

I’m running Fedora with Hyprland and use SDDM for the login so the authentication is delegated to pam_u2f via PAM.

I installed the packages and registered the key like this:

1
2
sudo dnf install pam-u2f pamu2fcfg
pamu2fcfg -n >> ~/.config/Yubico/u2f_keys

That writes the credential information pam_u2f needs into a file in my home directory. Then I pointed PAM at that file. For sudo, the relevant line looked like this in the /etc/pam.d/sudo

auth required pam_u2f.so authfile=/home/hugo/.config/Yubico/u2f_keys cue

To configure SDDM: /etc/pam.d/sddm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
auth     [success=done ignore=ignore default=bad] pam_selinux_permit.so
auth        substack	 password-auth
auth        required	 pam_u2f.so authfile=/home/hugo/.config/Yubico/u2f_keys
-auth        optional	  pam_gnome_keyring.so
-auth        optional	  pam_kwallet5.so
-auth        optional	  pam_kwallet.so
auth        include	 postlogin

account     required	 pam_nologin.so
account     include	 password-auth

password    include	 password-auth

session     required	 pam_selinux.so close
session     required	 pam_loginuid.so
-session    optional    pam_ck_connector.so
session     required	 pam_selinux.so open
session     optional	 pam_keyinit.so force revoke
session     required	 pam_namespace.so
session     include	 password-auth
-session     optional	  pam_gnome_keyring.so auto_start
-session     optional	  pam_kwallet5.so auto_start
-session     optional	  pam_kwallet.so auto_start
session     include	 postlogin

and lastly hyprlock: /etc/pam.d/hyprlock

1
2
auth required pam_u2f.so authfile=/home/hugo/.config/Yubico/u2f_keys cue
auth required pam_unix.so try_first_pass nullok

It was a standard setup, copied from the usual guides, and it worked fine for a long time.

The problem

After a while, I noticed the YubiKey would sometimes get rejected on certain boots. The inconsistency made it more confusing than a plain failure would have been:

  • Some boots, everything worked as usual.
  • Other boots, the key was rejected.
  • The key blinked when asked, and the system clearly detected it.

So the hardware and the USB connection were fine, and pam_u2f could see the device. The authentication step itself was failing.

Ruling things out

I started with the obvious suspects:

  • The key itself. It was detected every time, and it worked again later without my changing anything.
  • The credential file. It hadn’t changed between boots.
  • The PAM config. The sudo line was the same either way.

Nothing was different between a working boot and a failing one, at least nothing I had changed. Whatever varied from boot to boot was something I wasn’t looking at. That meant I needed more information, so I turned on pam_u2f’s debug output.

1
2
sudo install -o root -g root -m 0600 /dev/null /var/log/pam-u2f-sudo.log
sudoedit /etc/pam.d/sudo 

and in the /etc/pam.d/sudo

auth sufficient pam_u2f.so authfile=/home/hugo/.config/Yubico/u2f_keys cue debug debug_file=/var/log/pam-u2f-sudo.log

The clue in the logs

The debug log had the answer:

1
2
3
4
5
Origin not specified, using "pam://fedora.fritz.box"
Appid not specified, using value of origin
Key not found in authenticator 0
Key not found
Authentication failure

The line that caught my attention was the first one. I had never specified an origin, and pam_u2f had picked one on its own: pam://fedora.fritz.box. That looked odd.

On other failing boots, the origin was different. The hostname had resolved to an ISP reverse-DNS name (generated PTR record). The value kept changing, and it was not the one I expected.

Understanding what was happening

A U2F credential is registered against an origin and an application ID. When you authenticate, you have to present the same ones, or else the authenticator can’t find the credential.

Since I never set either value, pam_u2f fell back on defaults derived from the system hostname. The application ID defaults to the origin.

With a hostname of fedora, the defaults are:

1
2
origin = pam://fedora
appid  = pam://fedora

With a hostname of fedora.fritz.box, they become:

1
2
origin = pam://fedora.fritz.box
appid  = pam://fedora.fritz.box

To pam_u2f, these are different identities. My key had been registered under the first one, pam://fedora. When PAM asked for a credential under a different origin, the YubiKey returned CTAP2_ERR_NO_CREDENTIALS. In the log, that shows up as Key not found in authenticator 0.

So the key was never broken. It was being asked for a credential under the wrong name.

That also explained the intermittency. Depending on what the hostname resolved to at that moment, the origin was either the one I had enrolled with or a different one. On good boots they matched, and on bad boots they didn’t.

A hostname-dependent default is a poor choice for a system whose hostname can change.

What I did wrong

The mistake was in my original enrollment command:

1
pamu2fcfg -n >> ~/.config/Yubico/u2f_keys

I relied on the defaults and don’t have a fixed hostname so it did not enroll a stable origin or application ID. Everything worked as long as the hostname was correct, and it broke once I switched gateways as part of my homelab endeavours.

If I were enrolling a new key today, I would set those values explicitly:

1
pamu2fcfg -n -o pam://fedora -i pam://fedora

That ties the credential to a fixed identity that doesn’t depend on the network.

The fix

Either I set a static hostname or change all PAM commands to pin them so that the origin doesn’t change.

1
sudo hostnamectl set-hostname fedora

For sudo:

auth required pam_u2f.so authfile=/home/hugo/.config/Yubico/u2f_keys origin=pam://fedora appid=pam://fedora cue

For SDDM, the same values go on the corresponding line:

auth required pam_u2f.so authfile=/home/hugo/.config/Yubico/u2f_keys origin=pam://fedora appid=pam://fedora

I’m using hyprlock for the lock screen so that needed to be changed as well.

Lessons learned

  1. Intermittent failures usually mean something is changing somewhere else Here it was a derived value because I have two gateways running on DHCP which generates different PTR records as my infered hostname.
  2. Don’t rely on defaults for identity. If a value ties a credential to something, set it explicitly.
  3. Debug logs are worth the effort. The whole story was in one line: Origin not specified, using ....

If you use pam_u2f on a machine whose hostname might change (DHCP, a router’s local domain, a hostname set at boot), pin your origin and appid and you can avoid this class of problem entirely.

This post is licensed under CC BY 4.0 by the author.