Skip to content

ctap2, fidohid: support silent GetAssertion pre-flight, MakeCredential blink probe, and CTAPHID_CANCEL - #42

Open
HmFlashy wants to merge 1 commit into
matejsmycka:mainfrom
HmFlashy:fix/ctap2-firefox-preflight-and-blink
Open

HmFlashy wants to merge 1 commit into
matejsmycka:mainfrom
HmFlashy:fix/ctap2-firefox-preflight-and-blink

Conversation

@HmFlashy

@HmFlashy HmFlashy commented Nov 3, 2026

Copy link
Copy Markdown

Summary

Fixes FIDO2 / Passkey authentication in Firefox (authenticator-rs) and improves CTAP 2.1 / CTAPHID compliance for Chromium-based browsers when using linux-id (tested end-to-end with hardware TPM 2.0 + --auth fprintd against Google Passkeys on Linux).

Root Cause & Changes

  1. Silent authenticatorGetAssertion pre-flight (options.up = false, CTAP 2.1 §6.2.2)

    • During WebAuthn sign-in with an allowList, Firefox first probes authenticators silently with options: {"up": false} to discover which token holds a matching credential before prompting the user.
    • Previously, handleGetAssertion ignored options.up and immediately invoked VerifyUser (blocking on fprintd / pinentry), causing Firefox's silent pre-flight to time out or consume a fingerprint touch prematurely.
    • Fix:
      • Parse options.up (defaulting to true).
      • Reject invalid up=false && uv=true combinations with CTAP2_ERR_INVALID_OPTION (0x2C).
      • When up == false, skip VerifyUser, clear UP/UV flags (0x00), and omit the user entity (response[4]) so PII is never leaked without user presence.
      • Check the resident credential cache (FindByRPID) first on allowList lookups to avoid an unnecessary TPM round-trip during pre-flight, and select the most recently saved resident credential when multiple exist for the same RP.
  2. Dummy authenticatorMakeCredential blink probe (pinUvAuthParam, CTAP 2.1 §6.1.2 steps 2 & 4)

    • Firefox (rp.id = "make.me.blink") and Chromium (rp.id = ".dummy") send MakeCredential with a zero-length pinUvAuthParam (h"") to trigger a user-presence check and expect CTAP2_ERR_PIN_NOT_SET (0x35) when clientPin is not supported/set.
    • Previously, pinUvAuthParam was ignored and linux-id minted and stored a dummy credential returning StatusOK (0x00).
    • Fix:
      • For len(pinUvAuthParam) == 0, perform user-presence verification via VerifyUser and return StatusPinNotSet (0x35) without minting or persisting a credential.
      • For len(pinUvAuthParam) > 0, return StatusPinNotSet (0x35) immediately without prompting for user presence.
  3. CTAPHID_CANCEL (0x11) & unknown command error framing (CTAPHID §11.2.9.1.4 & §11.2.9.1.6)

    • Previously, CTAPHID_CANCEL (0x11) hit default: in SoftToken.Run and wrote a 0x6D00 payload back on command 0x11, violating CTAPHID §11.2.9.1.4 ("CTAPHID_CANCEL has no response") and confusing browser HID state machines when cancelling sibling probes.
    • Fix: Ignore CmdCancel (0x11) without writing a response frame, echo CmdPing (0x01), and return CmdError (0x3F) with ERR_INVALID_CMD (0x01) for unknown CTAPHID commands.

Test Plan

  • go test ./... passes, including 4 new unit tests:
    • TestRealWorld_FirefoxSilentPreFlightThenUVAssertion
    • TestGetAssertion_RejectUVTrueWithUPFalse
    • TestMakeCredential_ZeroLengthPinUvAuthParamBlinkProbe
    • TestMakeCredential_NonEmptyPinUvAuthParamRejectedWithoutUP
  • Verified live on Manjaro Linux (KDE Plasma 6, TPM 2.0 /dev/tpmrm0, Goodix fingerprint sensor via --auth fprintd) registering and signing in with Passkeys on accounts.google.com in Firefox.

…l blink probe, and CTAPHID_CANCEL

Browsers (notably Firefox via authenticator-rs and Chromium) perform
several CTAP 2.1 / CTAPHID probes during WebAuthn authentication that
previously caused Passkey sign-in to time out or fail:

1. Silent GetAssertion pre-flight (CTAP 2.1 §6.2.2):
   Firefox probes allowList credentials with options.up = false before
   requesting interactive verification. Previously, handleGetAssertion
   ignored options.up and blocked on VerifyUser (e.g. fprintd), causing
   the browser's silent pre-flight to time out or consume a fingerprint
   scan prematurely.
   - Parse options.up (default true) and reject invalid up=false + uv=true
     combinations with CTAP2_ERR_INVALID_OPTION (0x2C).
   - Skip VerifyUser, clear UP/UV flags, and omit the user entity (key 4)
     when options.up == false.
   - Match allowList entries against the resident credential store first
     before falling back to TPM dummy signing, and select the most
     recently stored credential when multiple exist for an RP.

2. Dummy MakeCredential blink probe (CTAP 2.1 §6.1.2 steps 2 & 4):
   Firefox ("make.me.blink") and Chromium (".dummy") send MakeCredential
   with a zero-length pinUvAuthParam to collect user presence and expect
   CTAP2_ERR_PIN_NOT_SET (0x35) when clientPin is not configured.
   Previously, pinUvAuthParam was ignored and a spurious credential was
   minted with StatusOK (0x00).
   - Handle zero-length pinUvAuthParam by requesting user presence and
     returning StatusPinNotSet (0x35) without persisting a credential.
   - Reject non-empty pinUvAuthParam immediately with StatusPinNotSet
     (0x35) without requesting user presence.

3. CTAPHID_CANCEL and unknown command framing (CTAPHID §11.2.9.1.4 / .6):
   - Handle CmdCancel (0x11) silently with no response frame instead of
     writing an invalid U2F 0x6D00 status word on the cancel command byte.
   - Support CmdPing (0x01) echo and return CmdError (0x3F) with
     ERR_INVALID_CMD (0x01) for unsupported HID commands.

Includes unit tests covering all three behaviors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant