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

3.0 KiB

REPORT — slice 10C: escrow consumption — productionize the spike (v0.17.0) (2026-06-10)

Overwrite-latest report. Cumulative history: 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 KConsume 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.