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.

View File

@@ -0,0 +1,71 @@
# SRP-6a 2048-bit group
## Purpose
`srp-group.ts` exports the canonical 2048-bit MODP group from RFC 5054
Appendix A as `bigint` constants. The SRP-6a client uses this group to
exchange ephemeral keys, derive the shared secret, and produce mutual
proofs. The constants are pinned because they are part of the protocol
contract — switching groups silently would break interoperability with
every server using the same RFC.
## Features
- `SRP_N_2048` — the 2048-bit safe prime `N` as a positive `bigint`.
- `SRP_G` — the generator `g = 2`.
- `SRP_N_BITLEN = 2048`, `SRP_N_BYTELEN = 256` — convenience constants for
byte-padding and validation.
## Usage: Basic Example
```ts
import { SRP_G, SRP_N_2048 } from "@suaveplan/crypto-vault/srp/srp-group";
console.log(SRP_N_2048.toString(2).length); // 2048
console.log(SRP_G); // 2n
```
## Usage: Advanced Example
```ts
import {
SRP_G,
SRP_N_2048,
SRP_N_BITLEN,
SRP_N_BYTELEN,
} from "@suaveplan/crypto-vault/srp/srp-group";
import { pow } from "@noble/curves/abstract/modular";
// Compute v = g^x mod N for an arbitrary private key x.
function makeVerifier(x: bigint): bigint {
return pow(SRP_G, x, SRP_N_2048);
}
console.log(SRP_N_BITLEN); // 2048
console.log(SRP_N_BYTELEN); // 256
console.log(typeof makeVerifier(123n)); // bigint
```
## API Reference
### `SRP_N_2048: bigint`
The exact 2048-bit safe prime defined in RFC 5054 Appendix A, parsed from
the canonical hex constant.
### `SRP_G: bigint`
The generator: `2n`.
### `SRP_N_BITLEN: number`, `SRP_N_BYTELEN: number`
Bit length (`2048`) and byte length (`256`) of the prime.
## Implementation Notes
- The hex string is laid out as a TypeScript string concatenation matching
the line breaks in the RFC for diffability against the source.
- The constant is parsed once at module load; subsequent imports share the
resulting `bigint`.
- The 1024-bit and 1536-bit groups from the same RFC are intentionally
omitted. RFC 5054 §1 states they should be deprecated for new
deployments, and the proposal pins us to ≥ 2048 bits.