oficina / home Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package provisioner
4
5import (
6 "encoding/base64"
7 "os"
8 "path/filepath"
9 "testing"
10
11 family "amadan.net/rastrillo/rastrillo/crypto"
12)
13
14func testToken(n byte) string {
15 b := make([]byte, 32)
16 for i := range b {
17 b[i] = n
18 }
19 return base64.RawURLEncoding.EncodeToString(b)
20}
21func floorFixture(t *testing.T) (*nativeFloor, *family.Keypair) {
22 t.Helper()
23 path := persistentDirectory(t)
24 dir, e := openPrivateDirectory(path)
25 if e != nil {
26 t.Fatal(e)
27 }
28 t.Cleanup(func() { dir.close() })
29 key, e := family.Generate()
30 if e != nil {
31 t.Fatal(e)
32 }
33 floor, e := newNativeFloor(dir, floorIdentity{testToken(1), testToken(2), base64.RawURLEncoding.EncodeToString(key.SignPub())})
34 if e != nil {
35 t.Fatal(e)
36 }
37 return floor, key
38}
39func TestFloorDurableCompareAndSwap(t *testing.T) {
40 f, k := floorFixture(t)
41 if _, _, e := f.current(); e != ErrRefused {
42 t.Fatal("absent floor accepted", e)
43 }
44 if e := f.initialize(k); e != nil {
45 t.Fatal(e)
46 }
47 if e := f.initialize(k); e != ErrRefused {
48 t.Fatal("floor reinitialized", e)
49 }
50 zero, _, e := f.current()
51 if e != nil || zero != (floorPoint{}) {
52 t.Fatal(e)
53 }
54 one := floorPoint{1, testToken(3)}
55 if e = f.advance(k, zero, one); e != nil {
56 t.Fatal(e)
57 }
58 if e = f.advance(k, zero, floorPoint{1, testToken(4)}); e != ErrRefused {
59 t.Fatal("stale registry rolled floor back", e)
60 }
61 again, _, e := f.current()
62 if e != nil || again != one {
63 t.Fatal("floor failed durable scan", e)
64 }
65 // Reopening with a competing device or key never creates a fresh genesis.
66 wrong, e := newNativeFloor(f.dir, floorIdentity{f.identity.Registry, testToken(9), f.identity.Public})
67 if e != nil {
68 t.Fatal(e)
69 }
70 if _, _, e = wrong.current(); e != ErrRefused {
71 t.Fatal("competing device accepted", e)
72 }
73}
74func TestFloorRefusesPartialTamperedAndMissingHistory(t *testing.T) {
75 for _, which := range []string{"partial", "tamper", "gap"} {
76 t.Run(which, func(t *testing.T) {
77 f, k := floorFixture(t)
78 if e := f.initialize(k); e != nil {
79 t.Fatal(e)
80 }
81 one := floorPoint{1, testToken(3)}
82 if e := f.advance(k, floorPoint{}, one); e != nil {
83 t.Fatal(e)
84 }
85 switch which {
86 case "partial":
87 if e := f.dir.create(floorName(2), []byte("{")); e != nil {
88 t.Fatal(e)
89 }
90 case "tamper":
91 path := filepath.Join(f.dir.file.Name(), floorName(1))
92 raw, e := os.ReadFile(path)
93 if e != nil {
94 t.Fatal(e)
95 }
96 raw[len(raw)-5] ^= 1
97 if e = os.WriteFile(path, raw, 0600); e != nil {
98 t.Fatal(e)
99 }
100 case "gap":
101 if e := f.advance(k, one, floorPoint{2, testToken(4)}); e != nil {
102 t.Fatal(e)
103 }
104 if e := os.Remove(filepath.Join(f.dir.file.Name(), floorName(1))); e != nil {
105 t.Fatal(e)
106 }
107 }
108 if _, _, e := f.current(); e != ErrRefused {
109 t.Fatal("damaged history accepted", e)
110 }
111 })
112 }
113}
114
115func TestFloorScanSeesDurableRecordBeforeMarkerUpdate(t *testing.T) {
116 f, k := floorFixture(t)
117 if e := f.initialize(k); e != nil {
118 t.Fatal(e)
119 }
120 old, previous, e := f.current()
121 if e != nil {
122 t.Fatal(e)
123 }
124 next := floorPoint{1, testToken(8)}
125 raw, e := f.signed(k, next, previous)
126 if e != nil {
127 t.Fatal(e)
128 }
129 if e = f.dir.create(floorName(1), raw); e != nil {
130 t.Fatal(e)
131 }
132 // The marker remains genesis, as after power loss between durable record and
133 // marker replacement. It cannot authorize reusing the previous sequence.
134 current, _, e := f.current()
135 if e != nil || current != next {
136 t.Fatal("old marker hid durable record", e)
137 }
138 if e = f.advance(k, old, next); e != ErrRefused {
139 t.Fatal("old marker allowed replay", e)
140 }
141 marker, e := canonicalJSON(next)
142 if e != nil {
143 t.Fatal(e)
144 }
145 if e = f.dir.create("head.tmp", marker); e != nil {
146 t.Fatal(e)
147 }
148 if e = f.updateMarker(next); e != nil {
149 t.Fatal("matching interrupted marker did not resume", e)
150 }
151}
152
153func TestCanonicalMetadataPreservesTypedIntegerPrecision(t *testing.T) {
154 value := struct {
155 Signed int64 `json:"signed"`
156 Unsigned uint64 `json:"unsigned"`
157 }{9223372036854775807, 18446744073709551615}
158 raw, e := canonicalJSON(value)
159 if e != nil {
160 t.Fatal(e)
161 }
162 if string(raw) != `{"signed":9223372036854775807,"unsigned":18446744073709551615}` {
163 t.Fatal("integer precision lost")
164 }
165 var decoded struct {
166 Signed int64 `json:"signed"`
167 Unsigned uint64 `json:"unsigned"`
168 }
169 if closedJSON(raw, &decoded) != nil || decoded != value {
170 t.Fatal("typed integer did not round trip")
171 }
172}
173
174func TestFloorMarkerRecoveryRefusesUnsafeScratch(t *testing.T) {
175 for _, kind := range []string{"symlink", "hardlink", "public", "directory"} {
176 t.Run(kind, func(t *testing.T) {
177 f, k := floorFixture(t)
178 if e := f.initialize(k); e != nil {
179 t.Fatal(e)
180 }
181 path := filepath.Join(f.dir.file.Name(), "head.tmp")
182 target := filepath.Join(persistentDirectory(t), "target")
183 original := []byte("must remain unchanged")
184 if e := os.WriteFile(target, original, 0600); e != nil {
185 t.Fatal(e)
186 }
187 var e error
188 switch kind {
189 case "symlink":
190 e = os.Symlink(target, path)
191 case "hardlink":
192 e = os.Link(target, path)
193 case "public":
194 e = os.WriteFile(path, original, 0644)
195 case "directory":
196 e = os.Mkdir(path, 0700)
197 }
198 if e != nil {
199 t.Fatal(e)
200 }
201 if e = f.advance(k, floorPoint{}, floorPoint{1, testToken(3)}); e != ErrRefused {
202 t.Fatal("unsafe scratch accepted", e)
203 }
204 got, e := os.ReadFile(target)
205 if e != nil || string(got) != string(original) {
206 t.Fatal("target changed", e)
207 }
208 if _, e := os.Lstat(path); e != nil {
209 t.Fatal("unsafe scratch removed", e)
210 }
211 })
212 }
213}
214