Skip to content

PAM module (pam-ssh-agent-webauthn)

pam-ssh-agent-webauthn is the PAM module that lets a target host accept a WebAuthn passkey (forwarded over the SSH agent) as a sudo / login factor. For the guide, see Guides → PAM module. This page is the reference.

Source: github.com/rado0x54/pam-ssh-agent-webauthn. Standalone — no runtime dependency on ShellWatch. (The shared library still installs under the historical pam_ssh_webauthn.so filename so existing /etc/pam.d/ configs keep working.)

auth sufficient pam_ssh_webauthn.so [args]
ArgumentDefaultNotes
file=<path>/etc/security/authorized_keysAuthorized public-keys file. WebAuthn keys only — other types are silently ignored.
socket=<path>$SSH_AUTH_SOCKOverride the SSH agent socket path.

Requires Rust 1.70+ and PAM headers.

Terminal window
sudo apt install libpam0g-dev # Debian/Ubuntu
sudo dnf install pam-devel # Fedora/RHEL
git clone https://github.com/rado0x54/pam-ssh-agent-webauthn.git
cd pam-ssh-agent-webauthn
cargo build --release

Output:

PlatformPath
Linuxtarget/release/libpam_ssh_webauthn.so
macOStarget/release/libpam_ssh_webauthn.dylib

Default: pure-Rust p256 crate.

For FIPS / OpenSSL-backed verification:

Terminal window
cargo build --release --features native-crypto

Requires libssl-dev / openssl-devel.

Key blob (webauthn-sk-ecdsa-sha2-nistp256@openssh.com)

Section titled “Key blob (webauthn-sk-ecdsa-sha2-nistp256@openssh.com)”
string "webauthn-sk-ecdsa-sha2-nistp256@openssh.com"
string "nistp256"
string ec_point (65 bytes: 0x04 || x || y, uncompressed P-256)
string application (relying-party ID, e.g. "shellwatch.example.com")
string algorithm ("sk-ecdsa-..." or "webauthn-sk-ecdsa-...")
string ecdsa_signature (mpint R || mpint S)
byte flags
uint32 counter
string origin (WebAuthn origin URL)
string clientDataJSON (contains base64url-encoded challenge)
string extensions (CBOR, typically empty)
signed_data = SHA256(application) || flags || counter || extensions || SHA256(clientDataJSON)

ECDSA P-256 signature is verified over signed_data using the public key from the authorized-keys file.

Mirrors OpenSSH’s webauthn_check_prepare_hash:

CheckRequirement
Algorithmwebauthn-sk-ecdsa-sha2-nistp256@openssh.com or sk-ecdsa-sha2-nistp256@openssh.com
User Presence (UP)flag bit 0x01 set
Attested Credential Data (AD)flag bit 0x40 NOT set (assertion, not registration)
Extension Data (ED)flag bit 0x80 consistent with extensions presence
Originno quote characters; matches between signature blob and clientDataJSON
clientDataJSON.type"webauthn.get"
clientDataJSON.challengebase64url-decodes to the PAM-issued challenge
clientDataJSON.originmatches signature-blob origin
Trailing datarejected — no bytes after extensions
  • Standard SSH key types (RSA, Ed25519, ECDSA) — use pam-ssh-agent.
  • Standard FIDO2 SK keys (sk-ecdsa-... with application=ssh:).
  • Ed25519 SK keys.
  • SSH certificates.
  • authorized_keys_command (file-only).

Non-WebAuthn entries in the authorized-keys file are silently ignored.

  • No counter / replay protection. The counter is integrity-protected but not checked for monotonic increase. A captured signature still can’t be replayed (each invocation uses a fresh challenge), but a cloned authenticator with a reset counter would not be detected.
  • No User Verification (UV) enforcement. UP is required, UV is not.
  • Agent timeout: 60 s. Enough for a real user to respond to the browser approval.
LayerTrust
Authenticator hardware, browser origin check, this verifierTrusted
The SSH agent forwarder (ShellWatch, OpenSSH, etc.)Transport only — can deny / delay, cannot forge
Verification on the targetECDSA P-256 over a fresh challenge against /etc/security/authorized_keys

A compromised broker can decline to forward a sign request; it cannot manufacture one. The PAM verifier on the target is the authoritative gate.

Syslog facility AUTH, identifier pam_ssh_webauthn. Set log level via PAM stack or syslog config.

  • authenticator.rs — runs the full flow without PAM. Useful for sanity checks.
  • list_keys.rs — dumps the agent’s exposed key blobs (matching them against your authorized-keys file).
Terminal window
SSH_AUTH_SOCK=/path/to/agent.sock cargo run --example authenticator -- /etc/security/authorized_keys
SSH_AUTH_SOCK=/path/to/agent.sock cargo run --example list_keys