74009014aa
A reinstall mints a new PBS key K while R-241 keeps the restic password, so the supersession rule (restic sha only) overwrote the only copy of the old K and every pre-reinstall whole-guest archive became unopenable for good. The current row is now also retained when the key fingerprint changes (case and space ignored; an empty fingerprint is unknown, not a change). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
66 lines
2.8 KiB
Go
66 lines
2.8 KiB
Go
package store
|
|
|
|
import "testing"
|
|
|
|
// R-366 — a reinstall mints a NEW whole-guest backup key K (`felhom-pbs-apply`: `--encryption-key
|
|
// autogen`) while, since R-241's mint guard, the box keeps its restic password. The escrow PUT then
|
|
// carries the SAME restic sha and a NEW key fingerprint. Until this fix the supersession rule looked
|
|
// at the restic sha only, so the row holding the OLD K (wrapped under the customer's recovery code)
|
|
// was OVERWRITTEN — every whole-guest archive written before the reinstall became unopenable for
|
|
// good, not only for the new box. The old row must be RETAINED when K changes.
|
|
//
|
|
// COMPANION RED-PROOF (observed, `audits/night-burndown-2026-10-06/r366/red-key-change.txt`): on the
|
|
// pre-fix rule (`curSHA != resticPwSHA256` alone) this fails with "a new backup key with the same
|
|
// restic password must supersede (retain the old K)". Restored.
|
|
func TestSaveHostEscrow_R366_NewBackupKeySameResticPasswordRetainsOldKey(t *testing.T) {
|
|
st := newTestStore(t)
|
|
const h = "h1"
|
|
if _, _, err := st.SaveHostEscrow(h, []byte("K-old-wrapped"), "3f:4f:65:c0", "zk", "2026-08-18T00:00:00Z", "SHA_SAME"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sup, _, err := st.SaveHostEscrow(h, []byte("K-new-wrapped"), "dd:d1:d8:53", "zk", "2026-08-21T00:00:00Z", "SHA_SAME")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !sup {
|
|
t.Fatal("a new backup key with the same restic password must supersede (retain the old K)")
|
|
}
|
|
old, err := st.ListSupersededEscrow(h)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(old) != 1 || string(old[0].Blob) != "K-old-wrapped" || old[0].KeyFingerprint != "3f:4f:65:c0" {
|
|
t.Fatalf("the old K is not retained: %+v", old)
|
|
}
|
|
cur, _ := st.GetHostEscrow(h)
|
|
if cur == nil || string(cur.Blob) != "K-new-wrapped" || cur.KeyFingerprint != "dd:d1:d8:53" {
|
|
t.Fatalf("the current row must be the new K: %+v", cur)
|
|
}
|
|
}
|
|
|
|
// The same key in another letter case, or a legacy row with no fingerprint, is NOT a key change:
|
|
// a re-upload of the same K must stay idempotent (no retained row per re-ceremony).
|
|
func TestSaveHostEscrow_R366_SameKeyOrUnknownFingerprintDoesNotSupersede(t *testing.T) {
|
|
st := newTestStore(t)
|
|
const h = "h1"
|
|
if _, _, err := st.SaveHostEscrow(h, []byte("a"), "AB:CD", "zk", "2026-08-18T00:00:00Z", "SHA"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i, fp := range []string{"ab:cd", " AB:CD ", ""} {
|
|
sup, _, err := st.SaveHostEscrow(h, []byte("b"), fp, "zk", "2026-08-19T00:00:00Z", "SHA")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if sup {
|
|
t.Fatalf("case %d (%q): the same or an unknown fingerprint must not supersede", i, fp)
|
|
}
|
|
// keep the stored fingerprint known for the next case
|
|
if _, _, err := st.SaveHostEscrow(h, []byte("a"), "AB:CD", "zk", "2026-08-18T00:00:00Z", "SHA"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if n, _ := st.CountSupersededEscrow(h); n != 0 {
|
|
t.Fatalf("no key change happened, yet %d rows were retained", n)
|
|
}
|
|
}
|