first commit
This commit is contained in:
50
docs/identity/auth/webauthn/src/verifier/sign-counter.md
Normal file
50
docs/identity/auth/webauthn/src/verifier/sign-counter.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Sign-counter regression detector
|
||||
|
||||
## Purpose
|
||||
|
||||
Implements the W3C WebAuthn L3 §6.1.1 sign-counter regression check. Authenticators that maintain a monotonically-increasing counter MUST surface a larger value on every successful assertion; a counter that fails to increase indicates either a cloned authenticator or an authenticator software bug. The check is deliberately a pure function so the policy decision (accept-and-warn vs revoke-and-reject) lives in the caller's identity layer.
|
||||
|
||||
## Features
|
||||
|
||||
- Three-state semantics: stored=0 & asserted=0 (skip), asserted<=stored & at least one non-zero (regression), asserted>stored (ok).
|
||||
- Pure synchronous function — no IO, no observability emission, no exceptions.
|
||||
- Boundary-correct comparison: `<=` so the equal-counter case is also a regression, matching the W3C-defined behaviour for authenticators that never rotate their counter.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { detectSignCounterRegression } from "@suaveplan/webauthn/verifier";
|
||||
|
||||
if (detectSignCounterRegression(stored.signCounter, result.signCount)) {
|
||||
revokeCredential(stored.id);
|
||||
throw new Error("sign-counter regression");
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { detectSignCounterRegression } from "@suaveplan/webauthn/verifier";
|
||||
|
||||
function classifyAuthenticatorCounter(
|
||||
stored: number,
|
||||
asserted: number
|
||||
): "static-counter-ok" | "regression" | "advanced" {
|
||||
if (stored === 0 && asserted === 0) {
|
||||
return "static-counter-ok";
|
||||
}
|
||||
return detectSignCounterRegression(stored, asserted)
|
||||
? "regression"
|
||||
: "advanced";
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `detectSignCounterRegression(stored: number, asserted: number): boolean` — `true` if the assertion's counter regressed, `false` otherwise.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- W3C deliberately keeps the policy ambiguous: an RP MAY treat regression as a soft warning (for testing-tier authenticators) or as a hard revoke (for production). The package surfaces the boolean and leaves the policy to the caller; `verifyAssertion` consumes this and returns `{ ok: false, code: SIGN_COUNTER_REGRESSION }` so the caller can branch.
|
||||
- The zero-zero exception matters: several major authenticators (Touch ID, Windows Hello on platform-bound credentials) deliberately emit `signCount === 0` on every assertion to avoid leaking usage frequency to the RP. Without the zero-zero exception every such login would falsely flag as a regression.
|
||||
- The function is intentionally branchless except for the two arithmetic comparisons so its execution time is invariant of the inputs — there is no plausible side-channel here, but the discipline is part of the package's no-naive-equality posture.
|
||||
Reference in New Issue
Block a user