Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
# Client data — parse + validate
## Purpose
Decodes the `clientDataJSON` byte string returned by `navigator.credentials.create` / `get` into a typed object and validates its `type`, `origin`, and `challenge` fields against the relying-party's expectations. The `clientDataJSON` is the authenticator's anti-replay anchor — getting this validation right is the difference between a real WebAuthn deployment and a token-passing protocol that any phishing site can replay.
## Features
- UTF-8 decode with `fatal: true` so any non-UTF-8 byte rejects with `WEBAUTHN.MALFORMED_RESPONSE`.
- JSON parse with cause-chain preservation.
- Zod `ClientDataJSONSchema` enforces `type`, `challenge`, `origin` fields; extra fields pass through for forward compatibility with future W3C revisions (`topOrigin`, `crossOrigin`, payment-extension fields).
- Discriminated `expectedType` argument — `"webauthn.create"` for registration, `"webauthn.get"` for authentication — so callers cannot accidentally accept the wrong ceremony's response.
- Constant-time `origin` and `challenge` compares via `constantTimeEqual` from `@suaveplan/crypto-utils`.
- Base64url decode of `challenge` before compare so the byte-level comparison happens after canonicalisation.
## Usage: Basic Example
```ts
import { parseClientDataJSON } from "@suaveplan/webauthn/parser";
const data = parseClientDataJSON(response.clientDataJSON);
console.log(data.type, data.origin);
```
## Usage: Advanced Example
```ts
import {
CLIENT_DATA_TYPE_CREATE,
validateClientDataJSON,
} from "@suaveplan/webauthn/parser";
validateClientDataJSON({
clientDataJSON: response.clientDataJSON,
expectedType: CLIENT_DATA_TYPE_CREATE,
expectedOrigin: "https://example.com",
expectedChallenge: storedChallengeBytes,
});
```
## API Reference
- `CLIENT_DATA_TYPE_CREATE = "webauthn.create"` — registration discriminator.
- `CLIENT_DATA_TYPE_GET = "webauthn.get"` — assertion discriminator.
- `parseClientDataJSON(bytes: Uint8Array): ClientDataJSON` — decode + schema validate; throws `WEBAUTHN.MALFORMED_RESPONSE`.
- `validateClientDataJSON(input: { clientDataJSON, expectedType, expectedOrigin, expectedChallenge }): ClientDataJSON` — full validation; throws `WEBAUTHN.MALFORMED_RESPONSE` (type), `WEBAUTHN.ORIGIN_MISMATCH`, or `WEBAUTHN.CHALLENGE_MISMATCH` on the appropriate failure.
## Implementation Notes
- The challenge is compared byte-for-byte after base64url decode rather than string-for-string so authenticators that emit base64url with or without padding (some Yubikey firmware does the latter on long challenges) all canonicalise to the same byte string.
- `origin` is compared as UTF-8 bytes via `constantTimeEqual` rather than via `===` so a mismatch in the high-bit-set portion of an IDN origin (e.g. punycode-encoded host) takes time independent of the byte index of the difference.
- The `passthrough()` on `ClientDataJSONSchema` is deliberate — the W3C spec reserves the right to add fields (`tokenBinding`, `topOrigin`, payment extension fields) and we do not want the parser to reject conforming new browsers.
- `parseClientDataJSON` is the lower-level building block; consumers that already validated `type` / `origin` / `challenge` elsewhere can call it directly. `validateClientDataJSON` is the one-shot entry point used by `register` and `authenticate`.