Files
skills/docs/identity/auth/webauthn/src/verifier/verify-attestation.md
2026-05-29 08:20:56 +01:00

3.1 KiB

Attestation verification dispatcher

Purpose

Implements verifyAttestation (W3C WebAuthn L3 §6.5.4) and defaultAttestationRegistry(). The dispatcher reads attestationObject.fmt, looks up the corresponding verifier in the registry, computes the clientDataHash once, and invokes the verifier. It is the single entry point into the per-format machinery — consumers do not import individual verifiers unless they want to override behaviour for a specific format.

Features

  • Built-in coverage of every IANA-registered format in W3C WebAuthn L3 §8: packed, fido-u2f, tpm, android-key, android-safetynet, apple, none.
  • WEBAUTHN.ATTESTATION_FORMAT_UNSUPPORTED thrown on missing registry entry — distinct from MALFORMED_RESPONSE so dashboards can distinguish policy denials from protocol violations.
  • webauthn.attestation_format_total{fmt} OTel counter incremented per dispatch with cardinality bounded to the seven registered formats plus unknown.
  • clientDataHash computed once via @suaveplan/crypto-hash#sha256 and passed to every verifier so the per-format code does not re-hash.

Usage: Basic Example

import {
    defaultAttestationRegistry,
    parseAttestationObject,
    verifyAttestation,
} from "@suaveplan/webauthn";

const env = parseAttestationObject(rawAttestationObject);
const result = await verifyAttestation({
    attestationObject: env,
    clientDataJSON,
    expectedRpIdHash,
    registry: defaultAttestationRegistry(),
});

Usage: Advanced Example

import {
    defaultAttestationRegistry,
    registerAttestationFormat,
    verifyAttestation,
} from "@suaveplan/webauthn";

const registry = defaultAttestationRegistry();
registerAttestationFormat(registry, "apple-appattest", async (input) => {
    // custom verifier for Apple's App Attest format
    return {
        trusted: true,
        attestationType: "anonCA",
        trustPath: [],
    };
});
const result = await verifyAttestation({
    attestationObject,
    clientDataJSON,
    expectedRpIdHash,
    registry,
});

API Reference

  • defaultAttestationRegistry(): AttestationFormatRegistry — registry pre-populated with every built-in format.
  • VerifyAttestationInput — { attestationObject, clientDataJSON, expectedRpIdHash, registry }.
  • verifyAttestation(input): Promise<AttestationVerifyResult> — dispatch + invoke + return.

Implementation Notes

  • The registry is a fresh Map per call so consumers can mutate it independently; production callers typically build it once and cache.
  • The OTel counter is emitted BEFORE the dispatch so a verifier that throws still contributes to the format count — this matters for diagnosing "X happened N times, M of which failed" without needing two counters.
  • clientDataHash is the SHA-256 of the raw clientDataJSON bytes, NOT of the parsed JSON. Authenticators sign over the wire-encoded bytes, so re-encoding would break the signature check.
  • The dispatcher does NOT validate the client data type / origin / challenge — that is the registration ceremony's job and lives in register. The dispatcher is a pure attestation primitive.