Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,261 @@
# crypto-vault — Cryptographic Foundation Layer
- **Change ID**: crypto-vault
- **Package**: `@suaveplan/crypto-vault`
- **Status**: PROPOSED
- **Type**: migration-redesign
- **Date**: 2026-04-29
## Why
The Vaulted password-manager ecosystem requires a dedicated cryptographic foundation that `@suaveplan/keys` (keypair management) and `@suaveplan/crypto-*` (primitives) do not provide. `crypto-vault` fills the gap between raw crypto primitives and vault orchestration: BIP39 mnemonic generation, Argon2id HD key derivation, two-secret key derivation (2SKD), SRP-6a authentication, and a hierarchical key path (AUK → personal keyset → vault key → item key). Without this package, vault key management would be scattered across `keys` and `vault`, violating the invariant that key material never leaves its memory boundary.
## What changes
- ADDED: `generateMnemonic(strength?)` / `entropyToMnemonic` / `mnemonicToEntropy` — BIP39 English word-list support
- ADDED: `deriveKeyWithArgon2id(password, salt, params)` — Argon2id with OWASP-safe minimum parameter enforcement
- ADDED: `deriveMasterKeyFromMnemonic(mnemonic, passphrase?)` — BIP39 seed + BIP32-like HD derivation
- ADDED: `deriveAuk(password, secretKey, email, salt, params)` — 2SKD combining password + Secret Key + email
- ADDED: `derivePersonalKeyset(auk)` / `deriveVaultKey(personalKeyset, vaultId)` / `deriveItemKey(vaultKey, itemId)` — hierarchical HKDF key tree
- ADDED: `SrpClient` class — full SRP-6a client (salt, private key, verifier, ephemeral keypair, session key, server proof verification)
- ADDED: `SecureBuffer` — `Uint8Array`-based secure buffer with eager zeroization via `fill(0)` on close, `toString` masking, and clone isolation
- ADDED: `zeroizeAll()` — global zeroization of all live secure buffers
- ADDED: `CryptoVaultError` with structured error codes for every failure mode
## Current-state audit
Package exists at `packages/crypto/crypto-vault` (v0.0.0) with `mnemonic`, `vault`, `chain-signer`, `errors` modules. This change restructures and expands the package.
## Gap analysis: sub-standard → enterprise
Most password-manager cryptographic implementations fall well below enterprise standards:
| Sub-standard practice | Enterprise replacement |
|---|---|
| PBKDF2 with low iteration counts (often < 100k) | Argon2id with OWASP-safe minimum parameters (≥ 64 MiB memory, ≥ 3 iterations, parallelism ≥ 1) |
| Single-factor key derivation (password only) | Two-secret key derivation (2SKD): password + Secret Key + email salt |
| No constant-time comparison of MACs or passwords | Constant-time `timingSafeEqual` for all sensitive comparisons |
| Key material scattered across modules, leaked in strings | `SecureBuffer` with eager zeroization via `fill(0)`, and masked `toString` |
| Flat key hierarchy (one key encrypts everything) | Hierarchical HKDF tree: AUK → personal keyset → vault key → item key |
| No secure memory zeroization on lock | Global `zeroizeAll()` + per-buffer `zeroize()` with clone isolation |
| Mnemonic generation without checksum validation | Full BIP39 English word-list support with checksum validation |
| Weak or non-standard authentication protocols | SRP-6a with full client-side proof verification and tamper rejection |
This package migrates the entire cryptographic foundation from ad-hoc, copy-pasted primitives to a rigorously designed, audited, and tested enterprise-grade layer.
## Security model
### Threat model
| Attacker | Capability | Mitigation |
|---|---|---|
| **Local attacker** (malware, keylogger) | Can read process memory, intercept system calls | `SecureBuffer` eager zeroization via `fill(0)`; key material never leaves crypto-vault boundary; zeroization on lock |
| **Remote attacker** (network MITM, compromised API) | Can intercept sync payloads, replay auth | SRP-6a mutual auth; E2EE via XChaCha20-Poly1305 handled in vault-sync |
| **Compromised sync server** | Can read stored blobs, modify sync data | Client-side encryption; server never sees plaintext, keys, or SRP verifier entropy |
| **Forensic recovery** (stolen device, cold boot) | Can extract RAM contents post-unlock | SecureBuffer zeroization on lock; no key material persisted unencrypted; Argon2id slow brute-force |
### Encryption boundaries
- **Plaintext lives only inside** `SecureBuffer` instances within the `crypto-vault` memory boundary.
- **Keys live only inside** `SecureBuffer` instances derived from the hierarchical tree.
- No key material is serialized to JSON, strings, or plain `Uint8Array`s that survive garbage collection without zeroization.
- The AUK (Account Unlock Key) is ephemeral: computed on unlock, zeroized on lock.
### Key hierarchy and derivation
```text
Account Password + Secret Key + Email + Salt ──► Argon2id ──► AUK
│
▼
HKDF-Expand ──► Personal Keyset
│
├──► HKDF ──► Vault Key (per vaultId)
│ │
│ └──► HKDF ──► Item Key (per itemId)
│
└──► HKDF ──► SRP Private Key / Verifier
```
- **AUK**: Ephemeral root derived from 2SKD. Never persisted.
- **Personal Keyset**: Long-lived HKDF-derived branch for vault and auth sub-keys.
- **Vault Key**: Per-vault symmetric key for vault-level encryption.
- **Item Key**: Per-item symmetric key for item-level encryption.
### Two-secret key derivation (2SKD) threat model
2SKD splits the entropy requirement across two factors:
1. **Something you know** (password) — vulnerable to phishing, keylogging, brute-force.
2. **Something you have** (Secret Key) — a high-entropy device-local or paper key.
An attacker must compromise **both** factors to derive the AUK. Compromise of the password alone is insufficient because Argon2id parameters make brute-force infeasible. Compromise of the Secret Key alone is insufficient because it is high-entropy and useless without the password and email salt. The email salt binds the derivation to the account identity, preventing cross-account pre-computation.
### Secure memory
- **`SecureBuffer`**: Uses `Uint8Array` with eager zeroization via `fill(0)` on close; memory locking via `mlock` is deferred to a future native binding.
- **Clone isolation**: Every clone creates a distinct allocation; zeroizing one does not affect siblings.
- **`toString` masking**: Returns `[SecureBuffer]` instead of hex/base64 to prevent accidental logging.
- **Global `zeroizeAll()`**: Emergency zeroization of all live `SecureBuffer`s on lock, crash, or security event.
### Side-channel resistance
- **Constant-time comparison**: All MAC verification, password comparison, and SRP proof checks use `timingSafeEqual`.
- **Timing-safe MAC**: HMAC-SHA256 and Poly1305 tag verification are constant-time.
- **Argon2id memory-hardness**: Memory-access pattern is data-dependent, mitigating cache-timing attacks.
### Compliance
- **NIST SP 800-63B**: Memorized secret verifier requirements (salted, iterated, memory-hard). Argon2id satisfies the latest NIST guidance for password hashing.
- **OWASP Password Storage Guidelines**: Recommends Argon2id as the primary choice. Minimum parameters enforced at runtime (memory ≥ 64 MiB, iterations ≥ 3).
## Performance SLOs
| Metric | Target | Measurement |
|---|---|---|
| **Unlock latency (Argon2id)** | P99 ≤ 1.5s on target hardware | End-to-end from password entry to decrypted vault key |
| **HD key derivation (AUK → item key)** | P99 ≤ 10 ms | Single HKDF-expand per key level |
| **SecureBuffer allocation** | P99 ≤ 1 ms | Pinned memory allocation + zero-fill |
| **Zeroization latency** | P99 ≤ 5 ms | Global zeroize of 1,000 live buffers |
| **Memory bound (10K items)** | ≤ 128 MiB resident | All live SecureBuffers + key hierarchy |
| **Argon2id parameterization** | Memory = 64 MiB, iterations = 3, parallelism = 4 | Default; higher tiers configurable |
| **Scalability: max vault size** | 100,000 items | Hierarchical key derivation is O(1) per item |
| **Scalability: max attachment size** | 100 MiB | Encryption streaming handled by drive layer; keys are fixed-size |
## Observability plan
All telemetry is **safe-by-construction**: no key material, no plaintext, no vault metadata, no item counts, and no user-identifiable data in spans, metrics, or logs.
### OTel spans and metrics
- `crypto-vault.derive.argon2id.duration` — histogram of Argon2id derivation time (no params, no salt)
- `crypto-vault.derive.hkdf.duration` — histogram of HKDF derivation time
- `crypto-vault.secure-buffer.count` — gauge of live SecureBuffers (count only, no addresses)
- `crypto-vault.zeroize.duration` — histogram of zeroization latency
- `crypto-vault.srp.handshake.duration` — histogram of SRP client handshake time
- Span names: `derive.deriveAuk`, `derive.deriveVaultKey`, `srp.handshake`, `securebuffer.allocate`
### Structured logging
Safe-to-log fields only:
- `event: "auk_derived"` — no password, no secret key, no salt
- `event: "vault_key_derived"` — `vaultIdHash: sha256(vaultId)` (one-way), no key
- `event: "secure_buffer_zeroized"` — `bufferCount: number`
- `event: "srp_handshake_complete"` — `success: boolean`, `durationMs: number`
### Error telemetry
- Aggregate error counts by `CryptoVaultError` code (e.g., `ARGON2ID_PARAMS_UNSAFE`, `SRP_PROOF_MISMATCH`).
- No vault ID, item ID, salt, or user context attached.
### Health checks
- `health.crypto-vault.derivation` — periodic Argon2id derivation of a test vector; fails if latency > 2× SLO.
- `health.crypto-vault.memory` — verifies SecureBuffer allocation and zeroization succeed.
### Audit logging
- `audit.crypto-vault.unlock` — `timestamp`, `deviceIdHash`, `success` (no password, no key material).
- `audit.crypto-vault.lock` — `timestamp`, `deviceIdHash`, `zeroizedBufferCount`.
- Retention: 90 days hot, 1 year cold.
## Dependency audit
| Dependency | Justification | Supply-chain risk | Tree-shakeable | License |
|---|---|---|---|---|
| `@suaveplan/crypto-hash` | Argon2id and HKDF primitives | Internal monorepo; audited in CI | Yes | MIT |
| `@suaveplan/random` | CSPRNG for salts and ephemeral keys | Internal monorepo; audited in CI | Yes | MIT |
| `@suaveplan/encoding` | Base64/hex for test vectors and interop | Internal monorepo | Yes | MIT |
| `@suaveplan/error` | Base error class | Internal monorepo | Yes | MIT |
**No external runtime dependencies.** All primitives are either internal packages or standard Web Crypto / Node.js crypto APIs. This minimizes supply-chain attack surface and eliminates hidden transitive dependencies.
## Operational readiness
### Graceful degradation
- If Argon2id parameter validation fails (e.g., caller supplies unsafe params), derivation rejects immediately with `CryptoVaultError` — no silent downgrades.
- `SecureBuffer` always uses standard `Uint8Array` with eager zeroization via `fill(0)` on close; a warning telemetry event is emitted if a future native `mlock` binding is unavailable.
### Resource limits
- **Argon2id max memory**: 1 GiB (enforced at runtime; reject params exceeding this).
- **Max concurrent derivation operations**: 4 (prevents memory exhaustion from parallel unlocks).
- **SecureBuffer max size**: 64 KiB (keys and secrets only; large data handled by streaming layers).
### Error recovery
- Derivation failures are non-retryable by design (bad password, bad params). Caller receives structured error code.
- SRP handshake failures are non-retryable beyond standard TCP retry; caller must re-initiate.
- SecureBuffer allocation failures trigger global zeroization and a fatal health-check failure.
### Backup/restore compatibility
- Mnemonic generation produces standard BIP39 seed phrases — compatible with any BIP39 wallet or backup tool.
- Key hierarchy is fully deterministic from mnemonic + passphrase; restore re-derives identical keys.
- No proprietary key formats; all serialized data uses standard encoding.
### Upgrade/downgrade safety
- Argon2id parameters are versioned in the salt/header. New versions can increase costs; old versions can still derive.
- HKDF info strings include a version prefix (`"v1/..."`) for forward-compatible derivation paths.
- No schema migrations required for pure crypto — math does not change.
### Key rotation without full re-encryption
- **Account password rotation**: Re-derive AUK with new password; re-encrypt personal keyset wrapper only. Vault keys and item keys remain unchanged.
- **Secret Key rotation**: Requires re-derivation of AUK and re-encryption of personal keyset wrapper. Vault keys and item keys remain unchanged.
- **Vault key rotation**: Derive new vault key from existing personal keyset; re-encrypt vault header only. Item keys remain unchanged.
- **Item key rotation**: Derive new item key from existing vault key; re-encrypt single item only.
## Out of scope
- Server-side SRP-6a verifier storage or session management — belongs in `@suaveplan/api` gateway layer
- BIP39 word lists other than English — defer until i18n requirement is concrete
- Hardware security module (HSM) or secure-enclave key storage — deferred to `@suaveplan/biometrics` and platform-specific packages
- Argon2id WASM or native bindings — pure-TS/Bun implementation using `@suaveplan/crypto-hash` primitives
## Verification approach
- `src/bip39/bip39.test.ts` — BIP39 generation, round-trip, checksum validation, error paths
- `src/argon2id/argon2id.test.ts` — Argon2id derivation, parameter safety, salt uniqueness
- `src/hd-key/hd-key.test.ts` — HD master key derivation, passphrase sensitivity
- `src/twoskd/twoskd.test.ts` — 2SKD determinism, factor separation, empty-input rejection
- `src/key-hierarchy/key-hierarchy.test.ts` — hierarchical derivation determinism, branch isolation
- `src/srp/srp-client.test.ts` — full handshake, wrong-password failure, tampered proof rejection
- `src/secure/secure-buffer.test.ts` — zeroization, masking, clone isolation
- `src/error/error-codes.test.ts` — every error code is thrown by at least one production path
- `tests/dep-graph.test.ts` — pins zero `@suaveplan/*` runtime deps for `testing` packages
### Coverage and quality targets
- **100% coverage target**: All source files must achieve 100% statement and branch coverage.
- **Property-based tests**: `fast-check` properties for BIP39 round-trip, Argon2id parameter enforcement, 2SKD commutativity, HKDF branch isolation, and SRP handshake correctness.
- **Load/stress tests**: 1,000 concurrent Argon2id derivations to verify memory bounds and contention; 10,000 hierarchical key derivations to verify latency SLOs.
- **Security audit checklist**:
- [ ] Argon2id memory cost ≥ 64 MiB enforced at runtime
- [ ] Argon2id iteration count ≥ 3 enforced at runtime
- [ ] All key material zeroized on lock (`zeroizeAll()` + per-buffer `zeroize()`)
- [ ] No plaintext key material in any log, error message, or `toString()` output
- [ ] All MAC and comparison operations use constant-time `timingSafeEqual`
- [ ] SRP-6a server proof verified before session key use
- [ ] SecureBuffer clone isolation verified (zeroize one, verify sibling intact)
- [ ] Salt uniqueness verified across 10,000 generations (collision probability < 2⁻¹²⁸)
- **Penetration test scenarios**:
- [ ] Dump process memory after `vault.lock()` — verify no AUK, vault key, or item key material present
- [ ] Fuzz Argon2id parameters — verify rejection of all unsafe combinations
- [ ] Fuzz SRP handshake messages — verify no crashes or incorrect proof acceptance
- [ ] Attempt timing attack on MAC comparison — verify distribution is statistically uniform
## Impact
Existing consumers of `@suaveplan/crypto-vault` (v0.0.0) will be affected as follows:
- The current `vault` and `chain-signer` subpath exports will be restructured to align with the new hierarchy.
- New subpath exports (`bip39`, `argon2id`, `twoskd`, `srp`, `secure`) will be added; existing exports remain available during the migration window.
- Internal APIs may change shape as the package moves from a narrow mnemonic+vault utility to a full cryptographic foundation.
- All downstream packages (`vault`, `vault-sync`, `keys`) must update their imports to use the new module layout.
## Migration impact
`VaultManager` and `VaultFactory` consumers in `@suaveplan/keys` migrate to `@suaveplan/vault`. `mnemonic` and `chain-signer` consumers update imports to the new subpath layout. A deprecation window of one minor version is provided for removed exports.