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,74 @@
# `server-finish`
## Purpose
Complete the SRP-6a handshake on the server side after receiving the client's public value `A` and proof `M1`. Validates `A mod N != 0` (RFC 5054 §2.5.4 abort condition), derives the server-side shared secret `S = (A * v^u)^b mod N`, hashes it into the session key `K = H(S)`, recomputes the expected `M1`, and constant-time-compares against the client's `M1`. On match emits `M2 = H(A | M1 | K)` per RFC 2945 §3. On mismatch throws `SrpError(SRP.AUTH_FAILED)`.
## Features
- `A mod N == 0` gate per RFC 5054 §2.5.4.
- `u ≡ 0 mod N` gate inherited from `computeU`.
- Constant-time `M1` comparison via `@suaveplan/crypto-utils.constantTimeEqual`.
- Returns `K`, `M2`, and `S` so consumers can chain into a KDF on `S` directly.
- Emits `srp.handshake_duration_ms{role=server,phase=finish}` plus the appropriate outcome counter (`ok` / `auth_failed` / `public_key_invalid`).
## Usage: Basic Example
```ts
import { serverFinishHandshake } from "@suaveplan/srp/server";
import { params2048Sha256 } from "@suaveplan/srp/params";
import { nobleHashBridge } from "@suaveplan/srp/hash";
const params = params2048Sha256(nobleHashBridge("SHA256"));
const salt = new Uint8Array(16);
const A = new Uint8Array(256);
A[255] = 1;
const B = new Uint8Array(256);
B[255] = 2;
const b = new Uint8Array(32);
b[31] = 1;
const v = new Uint8Array(256);
v[255] = 1;
const M1Client = new Uint8Array(32); // server expects to recompute this and compare
try {
serverFinishHandshake("alice", salt, A, B, b, v, M1Client, params);
} catch (err) {
console.log("auth failed:", (err as Error).message);
}
```
## Usage: Advanced Example
```ts
import { serverFinishHandshake, serverStartHandshake } from "@suaveplan/srp/server";
import { clientFinishHandshake, clientStartHandshake } from "@suaveplan/srp/client";
import { computeVerifier } from "@suaveplan/srp/verifier";
import { params2048Sha256 } from "@suaveplan/srp/params";
import { nobleHashBridge } from "@suaveplan/srp/hash";
const params = params2048Sha256(nobleHashBridge("SHA256"));
const salt = new Uint8Array(16);
const rng = {
nextUint32: () => 0,
nextFloat: () => 0,
nextBytes: (n: number) => crypto.getRandomValues(new Uint8Array(n)),
};
const { v } = computeVerifier("alice", "password123", salt, params);
const { a, A } = clientStartHandshake(rng, params);
const { b, B } = serverStartHandshake(v, rng, params);
const client = clientFinishHandshake("alice", "password123", salt, A, B, a, params);
const server = serverFinishHandshake("alice", salt, A, B, b, v, client.M1, params);
console.log(Buffer.from(client.K).equals(Buffer.from(server.K))); // true
```
## API Reference
### `serverFinishHandshake(I, salt, A, B, b, v, M1Client, params): { K, M2, S }`
Throws `SrpError(SRP.PUBLIC_KEY_INVALID)` when `A mod N == 0` or `u ≡ 0 mod N`. Throws `SrpError(SRP.AUTH_FAILED)` when the recomputed `M1` does not constant-time-match `M1Client`. On success returns the session key `K`, the server proof `M2`, and the raw shared secret `S`.
## Implementation Notes
The order `validate A mod N → compute u → compute S → compute K → compute expectedM1 → compare M1Client` is intentional: the cheap modular check (`A mod N == 0`) happens first so an adversary cannot force the server to spend a `modPow` on garbage; `computeU` runs next because it has its own abort gate (`u ≡ 0`); only then do we pay the full `modPow` chain. This ordering minimises wasted server CPU on adversarial inputs without leaking any new timing channel (every legitimate handshake takes the same path).
The constant-time `M1` compare is the last gate before emitting `M2`. We use `expectedM1` (the server's locally-computed proof) inside `computeM2` rather than `M1Client` — even though they are byte-identical when the compare succeeds — because reusing the server-side computation eliminates a class of subtle bugs where a malicious client substitutes a near-miss `M1` that happens to match by the time the compare runs.