first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
# 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
```ts
import {
defaultAttestationRegistry,
parseAttestationObject,
verifyAttestation,
} from "@suaveplan/webauthn";
const env = parseAttestationObject(rawAttestationObject);
const result = await verifyAttestation({
attestationObject: env,
clientDataJSON,
expectedRpIdHash,
registry: defaultAttestationRegistry(),
});
```
## Usage: Advanced Example
```ts
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.