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

76 lines
6.9 KiB
Markdown

# Extend @suaveplan/crypto-vault — vault orchestration layer
**Package**: `@suaveplan/crypto-vault`
**Status**: PROPOSED
**Type**: feature (additive — no breaking changes)
- **Spec delta**: ./specs/crypto-vault/spec.md
## Why
`@suaveplan/crypto-vault` ships rich primitives (BIP39 / Argon2id / 2SKD / HKDF / SecureBuffer / OpaqueKeyHandle / SRP) but no high-level orchestration helpers — the layer that bundles "generate a root keychain + encrypt it into a vault file + write the signed public-key file" into one call. Upstream `../suaveplan/packages/universal/keys/src/lib/keys-common.ts` shipped that orchestration as part of the now-split `@suaveplan/keys` umbrella; on genesis it has no home yet.
`@suaveplan/server-keys` (in-flight; full-parity port of 18 operator CLI bins) is blocked on this layer: 7 of its bins (`create-root-keys`, `create-server-keys`, `roll-server-keys`, `view-keys`, `view-vault`, `view-public-key-file`, plus the orchestration half of the chain bins) call `createRootKeys` / `createServerKeys` / `rollServerKeys` / `openVault` / `packVault` / `viewPublicKeys` / `saveVaultToStorage` / `savePublicKeysToStorage` / `getDataFromStorage`. The 3 `*WithChain` variants already shipped in `@suaveplan/crypto-keys` (factory layer) demonstrate the pattern; this proposal adds the non-chain siblings to `crypto-vault` so server-keys can ship at parity.
The wire format is **genesis-native**, not byte-compatible with upstream vaults (upstream used the deprecated `BitPacker(schema)` layer; genesis uses the v1 derivation grammar `encodePublicKeychain` + AEAD regime-2 secret blob + regime-3 application data payload). Cross-version interop is explicitly out of scope — operators rotating from upstream to genesis use a one-shot migration command (in a separate follow-up package), not in-place vault parsing.
## What changes
All additive — no existing `crypto-vault` export changes shape. New module `src/orchestration/` exports 9 functions + supporting types.
### 9 new functions
- `createRootKeys(password, options?): Promise<RootKeysResult>` — generates a fresh root `Keychain` via `@suaveplan/crypto` `generateKeychain`, derives an Argon2id KEK from `password`, encrypts the full keychain into vault bytes (AEAD regime-2 secret blob), extracts public keys + canonical bytes via `encodePublicKeychain`. Returns `{ keychain, vaultBytes, publicKeyBytes, publicKeys }`.
- `createServerKeys(rootKeys, serverPassword, options?): Promise<ServerKeysResult>` — generates a child keychain derived from `rootKeys.keychain.master.private` (HD-key path), encrypts it under `serverPassword`. Returns `{ keychain, vaultBytes, publicKeyBytes, publicKeys, version }`.
- `rollServerKeys(rootKeys, currentVersion, serverPassword, options?): Promise<ServerKeysResult>` — deterministic re-derivation at `version = currentVersion + 1` using the same root master key + serverPassword. Re-encryption produces a fresh vault; the underlying private material is reproducible from `(rootMasterKey, version, serverPassword)`.
- `openVault(vaultBytes, password): Promise<Keychain>` — Argon2id-derive KEK from `password`, AEAD-decrypt `vaultBytes` to recover the full `Keychain`. Wrong password raises `CryptoVaultError(VAULT_DECRYPT_FAILED)`; tampered bytes raise the same.
- `packVault(keychain, password, options?): Promise<Uint8Array>` — inverse of `openVault`. Used by callers that already have a `Keychain` instance (e.g. after a rotation) and need to re-encrypt it.
- `viewPublicKeys(publicKeyBytes, optionalSignKey?): Promise<PublicKeychain>` — decode the signed public-key file. If `optionalSignKey` is provided, verify the embedded signature; mismatch raises `CryptoVaultError(PUBLIC_KEY_SIGNATURE_INVALID)`.
- `saveVaultToStorage(storage, key, vaultBytes): Promise<void>` — convenience wrapper over `@suaveplan/storage`'s `AbstractStorage.put(key, vaultBytes)`. Idempotent.
- `savePublicKeysToStorage(storage, key, publicKeyBytes): Promise<void>` — same shape, for public-key files.
- `getDataFromStorage(storage, key): Promise<Uint8Array>` — convenience wrapper over `AbstractStorage.get(key)`. Raises `CryptoVaultError(STORAGE_KEY_NOT_FOUND)` when absent (translates whatever the storage backend throws).
### Supporting types
- `RootKeysResult`, `ServerKeysResult` (interfaces declaring the return shapes)
- `CreateRootKeysOptions`, `CreateServerKeysOptions`, `RollServerKeysOptions`, `PackVaultOptions` (option bags — all optional; defaults for KDF parameters, algorithm choice, derivation path)
### New error codes (additive — registered alongside existing `CRYPTO_VAULT_*` codes)
- `CRYPTO_VAULT_DECRYPT_FAILED` — vault decryption failed (wrong password or tampered bytes)
- `CRYPTO_VAULT_PUBLIC_KEY_SIGNATURE_INVALID` — `viewPublicKeys` signature check failed
- `CRYPTO_VAULT_STORAGE_KEY_NOT_FOUND` — `getDataFromStorage` key absent
- `CRYPTO_VAULT_INVALID_VERSION` — `rollServerKeys` received `currentVersion < 0`
### Subpath export
`./orchestration/orchestration` — new entry in `package.json`'s `exports` map.
### Dependency additions
- `@suaveplan/crypto: workspace:^` (already used; ensure declared) — `generateKeychain`, `encodePublicKeychain`, `decodePublicKeychain`, `extractPublicKeys`, regime-2 seal/open helpers
- `@suaveplan/storage: workspace:^` (new dep) — `AbstractStorage` interface for the storage-aware helpers
## Out of scope
- **Byte-compat with upstream `@suaveplan/keys` vault format.** Upstream used `BitPacker(fullKeychainSchema)`; genesis uses the v1 derivation grammar. A migration command from old → new format belongs in a separate `@suaveplan/keys-migration` package.
- **`VaultManager` event-emitter class** (upstream `vault/vault.ts` 451-LOC orchestrator with `open`/`unlock`/`lock` FSM). Belongs in a follow-up extension; the 9 functions here are the primitives `VaultManager` would compose if/when it lands.
- **`VaultStreams`** (chunked streaming vault decryption). Belongs in a separate package once a real consumer exists.
- **Hardware-fingerprint binding for keychains.** Out of scope (application policy).
## Source mapping
| Capability | Upstream source | Notes |
|---|---|---|
| Public-API surface (9 functions) | `../suaveplan/packages/universal/keys/src/lib/keys-common.ts` L1-L469 | re-implemented on top of genesis v1 grammar (NOT byte-compat) |
| Argon2id KDF parameter defaults | `../suaveplan/enterprise/keys.md` | uses existing `DEFAULT_ARGON2ID_PARAMS` from `crypto-vault` |
| `CRYPTO_VAULT_*` code additions | genesis-original | matches existing crypto-vault error registry shape |
## Impact
### Affected dependents
`@suaveplan/server-keys` (in-flight) — this proposal unblocks 7 of its 18 bins. No other current consumers.
Genesis `crypto-vault` is foundation-tier: changes ripple through its existing consumers (`@suaveplan/vault`, `@suaveplan/crypto-vault` itself). The orchestration module is **additive** — no existing exports change — so dependent tests should not regress. The pre-archive `bunx turbo test --filter=...@suaveplan/crypto-vault` sweep will confirm.