oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1package sealtest
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "amadan.net/oficina/home/internal/home/seal"
10)
11
12func testDEK() []byte {
13 d := make([]byte, 32)
14 for i := range d {
15 d[i] = byte(i)
16 }
17 return d
18}
19
20// leaked is the detection core AssertNoPlaintext relies on. Exercising it
21// directly is the permanent proof that the gate CAN fail: these two tests
22// show it correctly flags a canary and correctly ignores clean bytes,
23// without needing to crash the test process to observe a failure.
24func TestLeakedDetectsCanary(t *testing.T) {
25 for _, c := range Canaries {
26 if got := leaked(c); got == nil {
27 t.Fatalf("leaked(%q) = nil, want a match", c)
28 }
29 }
30}
31
32func TestLeakedIgnoresCleanBytes(t *testing.T) {
33 if got := leaked([]byte("nothing here but person refs and blind index digests")); got != nil {
34 t.Fatalf("leaked(clean) = %q, want nil", got)
35 }
36}
37
38// The address canary is the one the gate signs in with, so it has to be
39// in the slice the gate scans, and it has to survive an address parser.
40// Both of those have been got wrong before: a canary outside Canaries
41// makes the gate pass while proving nothing, and an upper-case one cannot
42// see the lowercased copy that the blind index and signin's limiter key
43// on.
44func TestCanaryAddressIsScannedLowercaseAndAddressShaped(t *testing.T) {
45 if leaked([]byte(CanaryAddress)) == nil {
46 t.Error("CanaryAddress is not in Canaries, so the gate would never scan for it")
47 }
48 if CanaryAddress != strings.ToLower(CanaryAddress) {
49 t.Errorf("CanaryAddress %q is not lowercase", CanaryAddress)
50 }
51 if n := strings.Count(CanaryAddress, "@"); n != 1 {
52 t.Errorf("CanaryAddress %q has %d @ signs, want exactly 1", CanaryAddress, n)
53 }
54 if !strings.HasSuffix(CanaryAddress, ".invalid") {
55 t.Errorf("CanaryAddress %q is not in a reserved domain, so it could be delivered to", CanaryAddress)
56 }
57}
58
59// TestAssertNoPlaintextPassesOnSealedData is the gate's normal-operation
60// path: a canary sealed through the real Sealer must not be readable in
61// the raw bytes the gate scans.
62func TestAssertNoPlaintextPassesOnSealedData(t *testing.T) {
63 s, err := seal.New(testDEK())
64 if err != nil {
65 t.Fatal(err)
66 }
67 ct, err := s.Seal("signin.address", []byte(CanaryAddress))
68 if err != nil {
69 t.Fatal(err)
70 }
71 dbPath := filepath.Join(t.TempDir(), "sealed.db")
72 if err := os.WriteFile(dbPath, ct, 0o600); err != nil {
73 t.Fatal(err)
74 }
75 AssertNoPlaintext(t, dbPath)
76}
77
78// TestAssertNoPlaintextToleratesMissingSidecars proves the gate does not
79// require a WAL or SHM file to exist (most callers won't have one), while
80// still being willing to read them when they do — see TestFindLeakChecksWAL.
81func TestAssertNoPlaintextToleratesMissingSidecars(t *testing.T) {
82 dbPath := filepath.Join(t.TempDir(), "clean.db")
83 if err := os.WriteFile(dbPath, []byte("not a leak"), 0o600); err != nil {
84 t.Fatal(err)
85 }
86 AssertNoPlaintext(t, dbPath)
87}
88
89// TestFindLeakChecksWAL proves the gate's detection core reads the WAL
90// sidecar, not just the main file. Litestream ships WAL frames
91// continuously, so a value written plainly reaches the bucket before any
92// delete would remove it from the main file — a leak that only ever
93// touched the WAL must still be caught. This test seeds the canary into
94// the WAL only; a scan that checked just the main file would wrongly miss
95// it. (This is tested against findLeak rather than AssertNoPlaintext
96// itself: a failing subtest always fails its parent in Go's testing
97// package, so routing a deliberately-positive leak case through a live
98// *testing.T would leave this package permanently red.)
99func TestFindLeakChecksWAL(t *testing.T) {
100 dir := t.TempDir()
101 dbPath := filepath.Join(dir, "leaky.db")
102 if err := os.WriteFile(dbPath, []byte("clean main file"), 0o600); err != nil {
103 t.Fatal(err)
104 }
105 if err := os.WriteFile(dbPath+"-wal", Canaries[1], 0o600); err != nil {
106 t.Fatal(err)
107 }
108
109 c, f, err := findLeak(dbPath)
110 if err != nil {
111 t.Fatal(err)
112 }
113 if c == nil {
114 t.Fatal("did not detect a canary present only in the WAL")
115 }
116 if f != "leaky.db-wal" {
117 t.Fatalf("found in %q, want leaky.db-wal", f)
118 }
119}
120
121// A canary that only ever appears lowercased must still be caught. The
122// team-name and queue canaries are written upper-case, and a team name
123// is normalised for reservation, so the copy that reaches a store may
124// not be the copy Canaries holds.
125func TestLeakedFoldsCase(t *testing.T) {
126 for _, c := range Canaries {
127 lower := []byte(strings.ToLower(string(c)))
128 if leaked(lower) == nil {
129 t.Errorf("leaked(%q) = nil; a lowercased canary walked past the scan", lower)
130 }
131 upper := []byte(strings.ToUpper(string(c)))
132 if leaked(upper) == nil {
133 t.Errorf("leaked(%q) = nil; an uppercased canary walked past the scan", upper)
134 }
135 }
136}
137