Skip to content

test(webauthn): exercise the attestation formats against real conformance statements - #254

Open
Bccorb wants to merge 1 commit into
mainfrom
test/attestation-format-fixtures
Open

test(webauthn): exercise the attestation formats against real conformance statements#254
Bccorb wants to merge 1 commit into
mainfrom
test/attestation-format-fixtures

Conversation

@Bccorb

@Bccorb Bccorb commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Addresses #210. Does not close it: the Android SafetyNet criterion cannot be met
as written, see below.

#168 and #169 wired up attestation and the metadata service, but nothing drove
the individual statement formats. Hand-rolling a statement is close to
reimplementing the authenticator, so these were captured from a FIDO2
conformance run and committed as fixtures under tests/fixtures/attestation/.
They replay offline, with no network and no metadata blob.

What is covered

Packed, TPM and fido-u2f each verify end to end through the same
verifyRegistrationResponse call the WebAuthn controller makes, with the same
SUPPORTED_ALGORITHM_IDS, and each reports the format and AAGUID it came from.
Each is then refused twice: once with the attestation signature altered, which
fails the signature check, and once with the authenticator data altered, which
fails the relying party check.

Android Key rides along because it pins the vendor root behaviour from #253. It
is refused against the real Google roots and verifies once the preset roots are
cleared, which is exactly what conformance mode does.

The metadata-absent path is covered in both requireKnownAuthenticator
postures. Initializing the metadata service with no MDS servers and no
statements makes the fixture's model unlisted by construction rather than by
network luck, so strict refuses it and permissive registers it with no
statement.

Android SafetyNet, and why it is a todo

Two independent reasons, either of which is enough:

  1. The tools never send one. Three conformance runs, the last with every
    test group selected, produced no SafetyNet statement at all across 347
    captured requests, though its metadata statement loads with the other 24.
    Google turned down the SafetyNet Attestation API in favour of Play Integrity,
    which is the likely reason it has left the server test suite.
  2. A fixture could not work anyway. The verifier refuses a SafetyNet
    statement more than sixty seconds after the authenticator produced it, so a
    committed fixture is dead on arrival and could only pass against a faked
    clock, which proves nothing about the format.

Left as it.todo with the reasoning recorded beside it, rather than quietly
dropped. #210's acceptance needs amending, which is a call for the repo owner,
so this PR deliberately does not close it.

One thing worth knowing

fido-u2f signs an explicit list of fields rather than the authenticator data
as a whole, and the signature counter is not on that list, so altering the
counter verifies happily. That looks like a bug and is not. The RP ID hash is
altered instead, and the comment in the test says why.

Checks

Full suite green: 1232 passed, 1 skipped, 1 todo. Lint, typecheck, format and
coverage thresholds all clean. No changeset, matching how test-only changes have
landed here before.

…ance statements

Registration attestation was wired up in #168 and #169, but no test drove the
individual statement formats. Hand-rolling one is close to reimplementing the
authenticator, so these statements were captured from a FIDO2 conformance run
and committed as fixtures.

Packed, TPM and fido-u2f each verify end to end through the same call the
WebAuthn controller makes, reporting the format and the model they came from.
Each is then refused twice over, once with the attestation signature altered and
once with the authenticator data altered.

Android Key is covered alongside them because it proves the vendor root handling
from #253: it is refused against the real Google roots and verifies once the
preset roots are cleared, which is what conformance mode does.

The metadata-absent path is covered in both requireKnownAuthenticator postures,
offline. Passing no MDS servers and no statements makes the fixture's model
unlisted by construction rather than by network luck, so strict refuses it and
permissive registers it with no statement.

Android SafetyNet is left as a todo with the reasoning recorded next to it. The
conformance tools never emit one, and the verifier refuses any statement more
than sixty seconds old, so a committed fixture could only pass against a faked
clock.

One thing worth stating, since it looks like a bug and is not: fido-u2f signs an
explicit list of fields rather than the authenticator data as a whole, and the
signature counter is not among them, so altering the counter verifies happily.
The RP ID hash is altered instead.
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