oficina / home Public

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

Plain git — no account needed to clone.

Download

Download this file

1// Package sealtest is the plaintext gate: it proves, by reading raw
2// bytes, that a database file never holds a sealed value in the clear.
3//
4// It lives in its own package — not seal, and not a _test.go file — so
5// that store and handler tests, which are the ones that actually write
6// rows, can import it directly. AssertNoPlaintext takes a *testing.T, so
7// this package must never be imported from non-test code: doing so would
8// drag the testing package into the production binary. That rule is a
9// guard, not a comment — see internal/guardstest.
10package sealtest
11
12import (
13 "bytes"
14 "fmt"
15 "os"
16 "path/filepath"
17 "testing"
18)
19
20// CanaryAddress is the address a test signs in with before running
21// AssertNoPlaintext.
22//
23// It is lowercase and address-shaped on purpose, and both halves matter.
24// Lowercase, because the blind index and signin's rate limiter both key
25// on strings.ToLower(address), so the lowercased copy is the one likeliest
26// to reach a store or an error string — an upper-case canary cannot see
27// it. Address-shaped, because the gate signs in with this value, and an
28// address parser rejects a string with two @ signs, which would make the
29// gate fail for the wrong reason.
30//
31// example.invalid is reserved by RFC 2606 and can never be delivered to.
32const CanaryAddress = "canary-8f3a@example.invalid"
33
34// Canaries are the strings a test seeds into a database before running
35// AssertNoPlaintext. Any of them appearing in the raw file bytes is a
36// plaintext leak.
37//
38// One per sealed purpose. A canary that is not in this slice is a value
39// AssertNoPlaintext never scans for, so a gate seeded with it passes
40// while proving nothing.
41var Canaries = [][]byte{
42 []byte(CanaryAddress),
43 []byte("CANARY-TEAM-NAME-2b71"),
44 []byte("CANARY-QUEUE-PAYLOAD-c40d"),
45}
46
47// leaked reports the first canary found in b, or nil if none is present.
48// Split out from AssertNoPlaintext so the detection logic can be
49// unit-tested directly, without needing a live *testing.T failure to
50// observe.
51//
52// The scan folds case. CanaryAddress is already lowercase, because the
53// blind index and signin's limiter key on strings.ToLower(address); but
54// the team-name and queue canaries are not, and a team name is
55// normalised for reservation, so the copy that reaches a store may be
56// lowercased on the way. A case-sensitive scan would walk straight past
57// it — which is the same class of miss that made an upper-case address
58// canary wrong in the first place.
59func leaked(b []byte) []byte {
60 lower := bytes.ToLower(b)
61 for _, c := range Canaries {
62 if bytes.Contains(lower, bytes.ToLower(c)) {
63 return c
64 }
65 }
66 return nil
67}
68
69// findLeak scans dbPath's main file, its WAL and its SHM for a canary. It
70// checks the WAL because Litestream ships WAL frames continuously, and a
71// value written plainly reaches the bucket before any delete would remove
72// it from the main file. A nil canary means no leak was found.
73//
74// Split out from AssertNoPlaintext so the scan itself — including the
75// WAL/SHM handling — can be unit-tested directly, without routing a
76// deliberately-failing case through a live *testing.T (a failing subtest
77// always fails its parent, which would make this package's own tests
78// unable to ever go green).
79func findLeak(dbPath string) (canary []byte, file string, err error) {
80 for _, suffix := range []string{"", "-wal", "-shm"} {
81 p := dbPath + suffix
82 b, err := os.ReadFile(p)
83 if os.IsNotExist(err) {
84 continue
85 }
86 if err != nil {
87 return nil, "", fmt.Errorf("read %s: %w", filepath.Base(p), err)
88 }
89 if c := leaked(b); c != nil {
90 return c, filepath.Base(p), nil
91 }
92 }
93 return nil, "", nil
94}
95
96// AssertNoPlaintext greps a database file, its WAL and its SHM for
97// canaries. Any of them appearing in raw bytes is a plaintext leak.
98func AssertNoPlaintext(t *testing.T, dbPath string) {
99 t.Helper()
100 c, f, err := findLeak(dbPath)
101 if err != nil {
102 t.Fatal(err)
103 }
104 if c != nil {
105 t.Fatalf("plaintext %q found in %s", c, f)
106 }
107}
108