Files
2026-05-27 23:29:53 +01:00

262 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.