f417cdede1
gates / gates (push) Successful in 29s
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
107 lines
3.2 KiB
Go
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)
|
|
}
|
|
}
|