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,133 @@
# SRP-6a client
## Purpose
`srp-client.ts` implements the client side of the SRP-6a zero-knowledge
password-authenticated key exchange per RFC 5054, using SHA-256 as the
one-way hash and the 2048-bit safe-prime group `N`/`g` from Appendix A.
The class hands the caller everything needed for an interoperable
handshake against any conformant server: salt generation, private key
derivation `x = H(s, H(I:p))`, verifier `v = g^x mod N`, ephemeral
keypair `(a, A = g^a mod N)`, session key derivation, and constant-time
server-proof verification.
## Features
- Salt generation via `generateSalt(length?)` honouring an injected RNG.
- Private key, verifier, and ephemeral keypair primitives — pure,
deterministic given inputs (or RNG).
- `deriveSessionKey(...)` produces `K = SHA-256(S)` and the client proof
`M1 = H(A || B || K)` per the RFC.
- Tamper-resistant `B` validation: `B mod N == 0` aborts with
`SRP.INVALID_B`. The `u = H(A || B) == 0` case is excluded via the
collision-resistance of SHA-256 and is documented in code rather than
re-checked at runtime.
- `verifyServerProof` is constant-time (`constantTimeEqual`) and throws on
mismatch with `CRYPTOVAULT.SRP.AUTHENTICATION_FAILED`.
- Reject groups with `N` shorter than 2048 bits at construction time.
- OTel histograms for handshake duration and counters for success / failure.
## Usage: Basic Example
```ts
import { SrpClient } from "@suaveplan/crypto-vault/srp/srp-client";
const client = new SrpClient();
const salt = client.generateSalt();
const privateKey = client.derivePrivateKey(
"alice@example.com",
"correct horse battery staple",
salt
);
const verifier = client.deriveVerifier(privateKey);
console.log(verifier.length); // 256
```
## Usage: Advanced Example
```ts
import { SrpClient } from "@suaveplan/crypto-vault/srp/srp-client";
import {
SRP_G,
SRP_N_2048,
} from "@suaveplan/crypto-vault/srp/srp-group";
import { pow } from "@noble/curves/abstract/modular";
// Symmetric handshake fixture: same client + a hand-rolled server share.
const client = new SrpClient();
const salt = client.generateSalt();
const x = client.derivePrivateKey("alice", "pw", salt);
const v = BigInt("0x" + Buffer.from(client.deriveVerifier(x)).toString("hex"));
// Server side computes B = (k*v + g^b) mod N (hand-rolled for the example).
const k = 3n; // simplified — production: H(N||g)
const b = 7n;
const B = ((k * v) + pow(SRP_G, b, SRP_N_2048)) % SRP_N_2048;
// Client side derives session key and proof.
const { a, A } = client.generateEphemeralKeypair();
const result = client.deriveSessionKey(a, A, B, x);
console.log(result.K.length); // 32
```
## API Reference
### `class SrpClient`
#### `new SrpClient(options?)`
`SrpClientOptions` accepts:
- `clock?: () => number` — defaults to `Date.now`.
- `rng?: Rng` — overrides the default CSPRNG for salt and ephemeral
generation.
- `N?: bigint`, `g?: bigint` — group overrides for tests. Construction
rejects any `N` shorter than 2048 bits with `SRP.WEAK_PARAMETERS`.
#### `generateSalt(length = 16): Uint8Array`
Random salt. Honours `rng?.nextBytes` if provided; otherwise uses
`crypto.getRandomValues` via `@suaveplan/crypto-utils`.
#### `derivePrivateKey(identity, password, salt): Uint8Array`
Computes `x = H(salt, H(identity:password))`. Throws
`SRP.INVALID_PRIVATE_KEY` for empty inputs.
#### `deriveVerifier(privateKey): Uint8Array`
Returns a 256-byte big-endian `v = g^x mod N`.
#### `generateEphemeralKeypair(): EphemeralKeypair`
`{ a: bigint; A: bigint }` — `a` is the private exponent (kept by the
client), `A` the public form sent to the server. Reseeds when the entropy
collapses to zero.
#### `deriveSessionKey(a, A, B, privateKey): SessionKeyResult`
Returns `{ K, S, M1 }` per the RFC. Throws `SRP.INVALID_B` when `B mod N`
is zero.
#### `verifyServerProof(A, M1, K, serverProof): boolean`
Constant-time compare with the recomputed `M2 = H(A || M1 || K)`. Throws
`SRP.AUTHENTICATION_FAILED` on mismatch; otherwise returns `true`.
#### `computeServerProof(A, M1, K): Uint8Array`
Helper exposed for symmetric tests and server-side adapters.
## Implementation Notes
- The implementation deliberately keeps every state transition pure —
callers thread `(a, A, B, x)` through the methods rather than relying
on instance fields.
- All `bigint` ↔ byte conversions are big-endian and zero-padded to the
group byte length so that hashing is deterministic regardless of the
numeric value.
- `k = H(N || g)` and `u = H(A || B)` use SHA-256 with both inputs padded
to 256 bytes per RFC 5054 §2.5.3 / §2.6.
- Constant-time comparison protects against timing-based proof guessing,
matching the broader Suaveplan posture of using `constantTimeEqual` for
every MAC / proof check.