Skip to content

fix(conformance): load the metadata the tools ship, echo only requested extensions - #253

Merged
Bccorb merged 1 commit into
mainfrom
fix/fido-conformance-metadata-and-extensions
Sep 3, 2026
Merged

fix(conformance): load the metadata the tools ship, echo only requested extensions#253
Bccorb merged 1 commit into
mainfrom
fix/fido-conformance-metadata-and-extensions

Conversation

@Bccorb

@Bccorb Bccorb commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Closes #172.

The FIDO2 conformance test tools were run against a local instance for the first
time. The first run reported 15 failures. This fixes the three defects behind
them, and a run now reports zero failures.

All three are confined to FIDO_CONFORMANCE_MODE, which is refused under a
production NODE_ENV, so no deployed behaviour changes.

Metadata statements were never loaded

The tools' "DOWNLOAD SERVER METADATA" archive unzips to a nested
metadataStatements/ directory, and the loader only read JSON files at the top
level of FIDO_CONFORMANCE_METADATA_DIR. It silently found none.

With requireKnownAuthenticator set, the metadata service runs in strict mode,
so every conformance authenticator was refused as unlisted and every
registration failed. That cascaded into the tests which depend on a registration
having already succeeded, so the failures pointed away from the actual cause.
Confirmed directly against the library before and after:

before: REFUSE packed Secp256R1 -> No metadata statement found for aaguid "326adcf0-..."
after:  OK     packed Secp256R1 -> Virtual Secp256R1 FIDO2 Conformance Testing CTAP2 Authenticator

The directory is now read recursively, so the archive can be dropped in unedited
as the documentation already promised.

Vendor attestation roots blocked their own tests

The tools sign their Apple, Android Key and SafetyNet statements with their own
test roots, so validating those against the real vendor roots could never
succeed. Those preset roots are now cleared in conformance mode, which lets the
roots carried in the metadata statement apply instead. Android Key attestation
verifies for the first time as a result.

Registration options advertised an extension nobody asked for

generateRegistrationOptions always appends its own credProps, and the tools
compare the echoed extensions against the requested set for exact equality:

AssertionError: Response.extensions MUST be set to the requested extensions!
Expected "{"example.extension.bool":true,"credProps":true}"
to equal "{"example.extension.bool":true}"

The requested set is now echoed verbatim over the library's answer. The
authentication options path was already correct, since the library passes
extensions through there unchanged.

Verification

Three conformance runs, 347 captured requests in the last one. Every options
call answered ok, no ceremony was lost, and every remaining refusal is a
negative test the tools intend to fail: corrupted attestation internals, bad
origins, unsupported formats.

Attestation formats verified end to end during a run: packed, tpm, fido-u2f,
android-key and none. Android SafetyNet is never exercised by the tools even
with every test group selected, which is worth confirming before closing #210.

Full suite green: 1219 passed, 1 skipped. Lint, typecheck, format and coverage
thresholds all clean.

…ed extensions

The first real FIDO2 conformance runs surfaced three defects, all confined to
FIDO_CONFORMANCE_MODE, which is refused under a production NODE_ENV.

The tools' metadata archive unzips to a nested metadataStatements/ directory and
the loader only read the top level, so it silently loaded nothing. With
requireKnownAuthenticator set the metadata service runs strict, so every
conformance authenticator was refused as unlisted and every registration failed.
That cascaded into the tests which depend on a registration having succeeded,
rather than showing up as a metadata failure. The directory is now read
recursively, so the archive can be dropped in unedited as the docs already said.

The tools sign their Apple, Android Key and SafetyNet statements with their own
test roots, so validating those against the real vendor roots could never
succeed. Those preset roots are now cleared in conformance mode, which lets the
roots carried in the metadata statement apply instead. Android Key attestation
verifies for the first time as a result.

generateRegistrationOptions always appends its own credProps, and the tools
compare the echoed extensions against the requested set for exact equality, so
every options request carrying extensions failed. The requested set is now
echoed verbatim over the library's answer. The authentication options path was
already correct, since the library passes extensions through there unchanged.

A conformance run now reports zero failures.
@Bccorb
Bccorb merged commit 5efcf5c into main Sep 3, 2026
5 checks passed
@Bccorb
Bccorb deleted the fix/fido-conformance-metadata-and-extensions branch September 3, 2026 04:06
Bccorb added a commit that referenced this pull request Sep 3, 2026
…ance statements (#254)

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.

Co-authored-by: Brandon Corbett <Bccorb@users.noreply.github.com>
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.

Run FIDO2 conformance self-validation and remediate findings

1 participant