Files
felhom-agent/REPORT.md
T
admin 89e9f98a95 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>
2026-06-10 22:17:56 +02:00

51 lines
3.0 KiB
Markdown

# REPORT — slice 10C: escrow consumption — productionize the spike (v0.17.0) (2026-06-10)
> Overwrite-latest report. Cumulative history: [CHANGELOG.md](CHANGELOG.md).
## What was implemented
The 10C spike proved the crypto + real-data restore on a key-less box (recover `K` from `(blob, R)`,
restore real data, GO). 10C turns that throwaway harness into a real, tested **`Consume`** path.
**Agent-only — no hub change** (10C reads its four inputs as parameters; 10D sources blob/fingerprint/
PBS-connection from the hub and prompts for R).
### `escrow.Consume(ctx, blob, R, expectedFingerprint, keyDest)`
The consumption contract, baking in the spike findings:
1. **Unwrap** the blob (operating on a copy — **F-C6**: the input blob is read-only, so 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** (the cheap correctness
check *before* any multi-GB restore).
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); the
input blob is read-only; `K` is never mutated. **Zero-knowledge holds**: the hub serves the blob +
expected fingerprint + PBS connection; **R is by-hand from the customer, never the hub** — a hub
compromise alone still cannot decrypt.
### `--selftest=escrow-consume`
`-blob -fingerprint -keydest`, R via env `FELHOM_RECOVERY_CODE` (off the command line / `ps`). 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. `go test ./...` green (integration
round-trip gated to a host with `proxmox-backup-client`).
## Versioning / docs
- Version `0.16.0 → 0.17.0`; `CHANGELOG.md`. Doc 03 §8a (consumption implemented; zero-knowledge) +
§9 (10C done) updated. No cross-repo golden (no wire change).
## Out of scope (per the task)
- DR orchestration (re-enroll in restore mode, *which* guests, identity restore) → **10D**;
populating/serving the restore directive from the hub → **10D**.
## Pending
- **Live validation** on the demo: create a fresh escrow from the live `K``Consume` on a key-less
scratch → restore a real encrypted backup with the consumed key; **wrong R → clean failure, nothing
installed**; the live `K` stays byte-unchanged.