3.1 KiB
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_UNSUPPORTEDthrown on missing registry entry — distinct fromMALFORMED_RESPONSEso 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 plusunknown.clientDataHashcomputed once via@suaveplan/crypto-hash#sha256and 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
Mapper 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.
clientDataHashis 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.