first commit
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# `extra-data` — `TPMS_ATTEST.extraData` constant-time match
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`extraDataMatches` compares a TPM-emitted `extraData` blob (the value the
|
||||
TPM signed alongside the attest body) against the relying-party-recomputed
|
||||
`attToBeSigned` digest per W3C WebAuthn Level 3 §8.3 step 8. The function
|
||||
is intentionally tiny: it short-circuits on length mismatch (length is
|
||||
public, not a secret) and then delegates the content comparison to a
|
||||
caller-injected constant-time equality function. Keeping the constant-
|
||||
time primitive injectable lets `@suaveplan/tpm-struct` stay universal-
|
||||
tier (no `@suaveplan/crypto-utils` runtime dep) while letting consumers
|
||||
wire in whichever hardened equality their crypto stack ships.
|
||||
|
||||
## Features
|
||||
|
||||
- Length short-circuit (length is not a secret).
|
||||
- Pluggable equality function via the `ConstantTimeEqual` type.
|
||||
- Zero-allocation: works directly over the supplied `Uint8Array`s.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { extraDataMatches } from "@suaveplan/tpm-struct/extra-data";
|
||||
|
||||
declare const constantTimeEqual: (a: Uint8Array, b: Uint8Array) => boolean;
|
||||
|
||||
if (extraDataMatches(att.extraData, recomputedDigest, constantTimeEqual)) {
|
||||
// attToBeSigned matched.
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { constantTimeEqual } from "@suaveplan/crypto-utils";
|
||||
import { extraDataMatches } from "@suaveplan/tpm-struct/extra-data";
|
||||
|
||||
// Compose with the rest of the WebAuthn `tpm`-attestation chain.
|
||||
function verifyExtraDataAndName(
|
||||
att: { extraData: Uint8Array; attested: { name: Uint8Array } },
|
||||
recomputedAttToBeSigned: Uint8Array,
|
||||
recomputedName: Uint8Array
|
||||
): { extraDataOk: boolean; nameOk: boolean } {
|
||||
return {
|
||||
extraDataOk: extraDataMatches(
|
||||
att.extraData,
|
||||
recomputedAttToBeSigned,
|
||||
constantTimeEqual
|
||||
),
|
||||
nameOk: extraDataMatches(
|
||||
att.attested.name,
|
||||
recomputedName,
|
||||
constantTimeEqual
|
||||
),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- **`extraDataMatches(actual: Uint8Array, expected: Uint8Array, equal: ConstantTimeEqual): boolean`**
|
||||
— `true` if and only if `actual.length === expected.length` and
|
||||
`equal(actual, expected)` returns true. Returns `false` on length
|
||||
mismatch without invoking `equal`.
|
||||
- **`ConstantTimeEqual`** — `(a: Uint8Array, b: Uint8Array) => boolean`.
|
||||
Implementations MUST NOT short-circuit on first byte mismatch — the
|
||||
wall-time MUST depend only on the longer input's length.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The function deliberately does not own a constant-time-equality fallback.
|
||||
A naive `for` loop short-circuiting on mismatch would leak content
|
||||
equality through timing; a sample implementation that XOR-accumulates
|
||||
every byte is correct but lives in `@suaveplan/crypto-utils`. By keeping
|
||||
the primitive injectable, this module avoids importing a crypto package
|
||||
from a universal parser while still surfacing the right API shape
|
||||
for the WebAuthn step 8 check.
|
||||
Reference in New Issue
Block a user