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,116 @@
# Argon2id wrapper
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`argon2id.ts` is the OWASP-grade Argon2id derivation entry point for
`@suaveplan/crypto-vault`. It wraps `@suaveplan/crypto-hash`'s primitive
`argon2id` with two enterprise concerns the primitive itself does not
enforce: an OWASP-safe parameter floor (memoryKB ≥ 64 MiB, iterations ≥ 3,
parallelism ≥ 1) and a hard ceiling (memoryKB ≤ 1 GiB, outputLength ≤ 4 KiB)
that prevents accidental denial-of-service from misconfigured callers.
## Features
- `assertSafeArgon2idParams(params)` — pure validator usable in tests, in
config validation, and as a precondition for any Argon2id call.
- `deriveKeyWithArgon2id(password, salt, params?, options?)` — full
derivation pipeline with telemetry recording and structured errors.
- `DEFAULT_ARGON2ID_PARAMS` — frozen `{ memoryKB: 65536, iterations: 3,
parallelism: 4, outputLength: 32 }` constant.
- `OWASP_MIN_*` and `ARGON2ID_MAX_*` exported constants for callers that
build their own parameter UI.
## Usage: Basic Example
```ts
import {
DEFAULT_ARGON2ID_PARAMS,
deriveKeyWithArgon2id,
} from "@suaveplan/crypto-vault/argon2id/argon2id";
const password = new TextEncoder().encode("correct horse battery staple");
const salt = crypto.getRandomValues(new Uint8Array(16));
const key = await deriveKeyWithArgon2id(password, salt, DEFAULT_ARGON2ID_PARAMS);
console.log(key.length); // 32
```
## Usage: Advanced Example
```ts
import {
assertSafeArgon2idParams,
deriveKeyWithArgon2id,
} from "@suaveplan/crypto-vault/argon2id/argon2id";
import type { Argon2idParams } from "@suaveplan/crypto-vault/types";
// Stronger profile for highly sensitive accounts.
const strict: Argon2idParams = {
memoryKB: 131_072, // 128 MiB
iterations: 4,
parallelism: 4,
outputLength: 64,
};
assertSafeArgon2idParams(strict); // throws if any cell breaks the bands
const password = new TextEncoder().encode("super-strong-passphrase");
const salt = crypto.getRandomValues(new Uint8Array(16));
const key = await deriveKeyWithArgon2id(password, salt, strict, {
clock: () => 0, // deterministic telemetry timestamps for tests
});
console.log(key.length); // 64
```
## API Reference
### `deriveKeyWithArgon2id(password, salt, params?, options?)`
- `password: Uint8Array` — must be non-empty.
- `salt: Uint8Array` — must be non-empty (16+ bytes recommended).
- `params: Argon2idParams = DEFAULT_ARGON2ID_PARAMS` — validated through
`assertSafeArgon2idParams`.
- `options.clock?: () => number` — wall-clock source for the duration
histogram.
Returns `Promise<Uint8Array>` of length `params.outputLength`.
Throws `CryptoVaultError`:
- `ARGON2ID.INVALID_INPUT` for empty `password` or `salt`.
- `ARGON2ID.UNSAFE_PARAMETERS` for parameters outside the OWASP / hard-cap
bands.
### `assertSafeArgon2idParams(params)`
Throws `CryptoVaultError(ARGON2ID.UNSAFE_PARAMETERS)` when any of these
conditions are violated:
- `memoryKB` integer in `[OWASP_MIN_MEMORY_KB, ARGON2ID_MAX_MEMORY_KB]`.
- `iterations` integer ≥ `OWASP_MIN_ITERATIONS`.
- `parallelism` integer ≥ `OWASP_MIN_PARALLELISM`.
- `outputLength` integer in `(0, ARGON2ID_MAX_OUTPUT_LENGTH]`.
### Constants
- `OWASP_MIN_MEMORY_KB = 65_536`
- `OWASP_MIN_ITERATIONS = 3`
- `OWASP_MIN_PARALLELISM = 1`
- `ARGON2ID_MAX_MEMORY_KB = 1_048_576`
- `ARGON2ID_MAX_OUTPUT_LENGTH = 4_096`
- `DEFAULT_ARGON2ID_PARAMS` — frozen object.
## Implementation Notes
- The underlying primitive is sync (noble's `@noble/hashes/argon2`); the
wrapper exposes `Promise<Uint8Array>` to keep the public API stable
against future native or worker-backed implementations.
- The OWASP minimums encode the November 2024 password storage cheat sheet
guidance for Argon2id (m=64MiB, t=3, p=1+).
- The hard cap on memory cost (1 GiB) and output length (4 KiB) defends
against caller bugs and denial-of-service-by-misconfiguration. A caller
that legitimately needs more should open a follow-up change rather than
bypass the guard.
- Telemetry timing comes from the injected (or default) clock, not
`performance.now()`; this keeps the histogram reproducible under tests.