first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user