hub(R-1218): never replace a recovery package that seals the off-site key with one that seals none (409)
gates / gates (push) Successful in 1m54s

SaveHostEscrowWithIdentity refuses (ErrEscrowWouldDropRepoKey); the PUT answers 409. Red-proved (store: superseded=true;
API: 500). Two older tests pinned the old demotion and are updated. Commit only.
This commit is contained in:
admin committed 2026-10-26 20:21:08 +01:00
1 parent 99c0f5e310
commit 09b5f02fc3
7 files changed
+102 -13

No files matched your search

+8
View File
@@ -7,6 +7,7 @@ import (
"database/sql"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"log"
@@ -1426,6 +1427,13 @@ func (h *Handler) handleHostEscrowPut(w http.ResponseWriter, r *http.Request, pa
// when this upload supersedes a DIFFERENT-passphrase old blob, the old one is RETAINED (not
// overwritten) so its recovery-code-recoverable history survives (Viktor's data-first ruling).
superseded, prevPwSHA, serr := h.store.SaveHostEscrowWithIdentity(pathHostID, blob, idBlob, req.KeyFingerprint, req.Posture, createdAt, req.ResticPwSHA256)
if errors.Is(serr, store.ErrEscrowWouldDropRepoKey) {
// R-1218: the current package seals the off-site repository password and this one does not — keeping the good
// one current is the whole point; the box shows the ceremony as failed instead of silently weakening the code.
h.logger.Printf("[WARN] escrow for host %s REFUSED: the upload seals no off-site repository password while the current package does — the current package stays (R-1218)", pathHostID)
http.Error(w, "Refused: the new recovery package covers no off-site key while the stored one does; the stored package stays (R-1218)", http.StatusConflict)
return
}
if serr != nil {
h.logger.Printf("[ERROR] Failed to store escrow for host %s: %v", pathHostID, serr)
http.Error(w, "Internal error", http.StatusInternalServerError)