slice 10C: escrow consumption — productionize the spike (v0.17.0)
Add escrow.Consume(blob, R, expectedFingerprint, keyDest): Unwrap -> fingerprint gate -> atomic 0600 install. Bakes in the spike findings — wrong R fails closed (no write), the fingerprint gate runs BEFORE any restore (no install on mismatch), the input blob is read-only (retryable), K is never mutated, R/key bytes never logged. Zero-knowledge holds: the hub serves all but R (by hand). --selftest=escrow-consume invokes the real path live. Agent-only; no hub change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,38 @@
|
||||
All notable changes to **felhom-agent** are recorded here. Update on every code
|
||||
change that gets pushed.
|
||||
|
||||
## v0.17.0 — slice 10C: escrow consumption (productionize the spike) (2026-06-10)
|
||||
|
||||
Turns the throwaway 10C spike harness into a real, tested **`Consume`** path: recover the PBS key
|
||||
`K` from an R-wrapped escrow blob, **gate it on the expected fingerprint**, and install it for the
|
||||
restore. The spike already proved the crypto + real-data restore; this bakes its findings into
|
||||
production code. **Agent-only** — 10C *reads* the four inputs as parameters (so it stays
|
||||
standalone-testable); 10D sources blob/fingerprint/PBS-connection from the hub and prompts for R.
|
||||
**Zero-knowledge holds**: the hub serves everything except **R** (by hand from the customer), so a
|
||||
hub compromise alone still can't decrypt.
|
||||
|
||||
### Added
|
||||
- **`escrow.Consume(ctx, blob, R, expectedFingerprint, keyDest)`** — the consumption contract:
|
||||
1. **Unwrap** the blob (a copy — F-C6: the input blob is read-only → a failed Consume is
|
||||
**retryable**) with `R`; a **wrong R fails closed** at the scrypt KDF (F-C3) → a clear,
|
||||
R-free error, **nothing written**.
|
||||
2. **Fingerprint gate (F-C4)** — `KeyFingerprint(recovered)` must equal the expected (the hub
|
||||
knows it); a mismatch **fails fast + loud, no install, no restore attempted**.
|
||||
3. **Atomic install (F-C2)** at `keyDest` (`0600`, write-temp-sibling→rename); any failure leaves
|
||||
**no partial install**. The recovered key lives only in a `0700` tempdir that is always removed.
|
||||
**Secret discipline:** `R` and key bytes are never logged/persisted (only fingerprint prefixes);
|
||||
`K` is never mutated.
|
||||
- **`--selftest=escrow-consume`** (`-blob -fingerprint -keydest`, R via env `FELHOM_RECOVERY_CODE`
|
||||
to keep it off the command line) — invokes the real `Consume` live (the spike's S3 via the
|
||||
production path, not a harness).
|
||||
|
||||
### Tests (non-hollow)
|
||||
- valid → key installed + `KeyFingerprint(dest) == expected` + `0600` + blob byte-unchanged;
|
||||
**wrong R** → error, **no file at dest**, blob unchanged; **fingerprint mismatch** → fail fast,
|
||||
**no install** (the gate runs before any restore); input validation; format-tolerant fingerprint
|
||||
compare (no empty-fingerprint gate-bypass); atomic-install permissions (integration tests gated to
|
||||
a host with `proxmox-backup-client`).
|
||||
|
||||
## v0.16.0 — slice 10B: operator-signed destructive completion (offline key + signing CLI) (2026-06-10)
|
||||
|
||||
The security centerpiece: a destructive op runs ONLY on a verified, operator-signed authorization
|
||||
|
||||
Reference in New Issue
Block a user