Files
felhom.eu/hub/internal/store/offsite_seal_test.go
T
admin f417cdede1
gates / gates (push) Successful in 29s
hub v0.127.0: off-site key registrar (box never gets the storage password), password sealed at rest, daily key check, clean-up window (shipped off) — decisions 68-69, R-820/R-821/R-822
Part A evidence (migration spike, sftp-written repo through the pinned rclone key) and the hub
red-proofs under documentation/audits/offsite-lock-build-2026-10-03/. Manifest bump follows after
the image is built and Secret/offsite-secret-key exists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-03 16:57:04 +02:00

107 lines
3.2 KiB
Go

package store
import (
"database/sql"
"log"
"os"
"path/filepath"
"strings"
"testing"
)
func sealTestStore(t *testing.T) *Store {
t.Helper()
st, err := New(filepath.Join(t.TempDir(), "hub.db"), log.New(os.Stderr, "", 0))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
return st
}
func rawValue(t *testing.T, st *Store, id string) string {
t.Helper()
var v string
if err := st.db.QueryRow(`SELECT value FROM one_time_secrets WHERE customer_id = ?`, id).Scan(&v); err != nil {
t.Fatal(err)
}
return v
}
// R-821: a copy of the database alone does not reveal the password — asserted on the raw column.
func TestOffsiteSecret_RawRowHoldsNoPassword(t *testing.T) {
st := sealTestStore(t)
if err := st.SaveOneTimeSecret("c1", "Sup3rSecretPw%"); err != nil {
t.Fatal(err)
}
raw := rawValue(t, st, "c1")
if strings.Contains(raw, "Sup3rSecretPw") || !strings.HasPrefix(raw, sealPrefix) {
t.Fatalf("raw row is not sealed: %q", raw)
}
if pw, err := st.OffsitePassword("c1"); err != nil || pw != "Sup3rSecretPw%" {
t.Fatalf("OffsitePassword = %q, %v", pw, err)
}
if pw, err := st.ConsumeOneTimeSecret("c1"); err != nil || pw != "Sup3rSecretPw%" {
t.Fatalf("Consume = %q, %v", pw, err)
}
}
// A different key cannot open what was sealed (the key is the secret, not the format).
func TestOffsiteSecret_WrongKeyCannotOpen(t *testing.T) {
st := sealTestStore(t)
if err := st.SaveOneTimeSecret("c1", "Sup3rSecretPw%"); err != nil {
t.Fatal(err)
}
if err := st.SetOffsiteSecretKey([]byte("another-key-of-exactly-32-bytes!")); err != nil {
t.Fatal(err)
}
if pw, err := st.OffsitePassword("c1"); err == nil || pw != "" {
t.Fatalf("wrong key opened the secret: %q", pw)
}
}
// Fail-closed: no key → nothing saved, nothing read, never plaintext.
func TestOffsiteSecret_NoKeyRefusesToSave(t *testing.T) {
st := sealTestStore(t)
st.sealer = nil
if err := st.SaveOneTimeSecret("c1", "Sup3rSecretPw%"); err != ErrNoSealKey {
t.Fatalf("save without key = %v, want ErrNoSealKey", err)
}
var n int
_ = st.db.QueryRow(`SELECT COUNT(*) FROM one_time_secrets`).Scan(&n)
if n != 0 {
t.Fatalf("%d row(s) written without a key", n)
}
}
// Rows written by a pre-v0.127.0 hub are sealed in place at start-up; idempotent.
func TestSealLegacyOffsiteSecrets_SealsPlaintextRows(t *testing.T) {
st := sealTestStore(t)
if _, err := st.db.Exec(`INSERT INTO one_time_secrets (customer_id, value) VALUES ('old', 'LegacyPlain1%')`); err != nil {
t.Fatal(err)
}
if err := st.SaveOneTimeSecret("new", "AlreadySealed1%"); err != nil {
t.Fatal(err)
}
sealedNew := rawValue(t, st, "new")
n, err := st.SealLegacyOffsiteSecrets()
if err != nil || n != 1 {
t.Fatalf("sealed %d, %v; want 1", n, err)
}
if raw := rawValue(t, st, "old"); strings.Contains(raw, "LegacyPlain") {
t.Fatalf("legacy row still plaintext: %q", raw)
}
if rawValue(t, st, "new") != sealedNew {
t.Fatal("an already-sealed row was re-sealed")
}
if pw, err := st.OffsitePassword("old"); err != nil || pw != "LegacyPlain1%" {
t.Fatalf("legacy row does not open: %q %v", pw, err)
}
if n, _ := st.SealLegacyOffsiteSecrets(); n != 0 {
t.Fatalf("second run sealed %d", n)
}
if _, err := st.OffsitePassword("absent"); err != sql.ErrNoRows {
t.Fatalf("absent = %v, want ErrNoRows", err)
}
}