1
2
3
4
5 package sumdb
6
7 import (
8 "bytes"
9 "fmt"
10 "strings"
11 "sync"
12 "testing"
13
14 "golang.org/x/mod/sumdb/note"
15 "golang.org/x/mod/sumdb/tlog"
16 )
17
18 const (
19 testName = "localhost.localdev/sumdb"
20 testVerifierKey = "localhost.localdev/sumdb+00000c67+AcTrnkbUA+TU4heY3hkjiSES/DSQniBqIeQ/YppAUtK6"
21 testSignerKey = "PRIVATE+KEY+localhost.localdev/sumdb+00000c67+AXu6+oaVaOYuQOFrf1V59JK1owcFlJcHwwXHDfDGxSPk"
22 )
23
24 func TestClientLookup(t *testing.T) {
25 tc := newTestClient(t)
26 tc.mustHaveLatest(1)
27
28
29 tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
30 tc.mustHaveLatest(3)
31
32
33 tc.getOK = false
34 tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
35 tc.mustLookup("rsc.io/sampler", "v1.3.0/go.mod", "rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=")
36 tc.mustHaveLatest(3)
37 tc.getOK = true
38 tc.getTileOK = false
39
40
41 tc.mustLookup("rsc.io/quote", "v1.5.2", "rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=\nrsc.io/quote v1.5.2 h2:xyzzy")
42 tc.mustHaveLatest(3)
43
44
45
46
47 tc.getTileOK = true
48 tc.mustLookup("rsc.io/Quote", "v1.5.2", "rsc.io/Quote v1.5.2 h1:uppercase!=")
49 tc.mustHaveLatest(4)
50 }
51
52 func TestClientBadTiles(t *testing.T) {
53 tc := newTestClient(t)
54
55 flipBits := func() {
56 for url, data := range tc.remote {
57 if strings.Contains(url, "/tile/") {
58 for i := range data {
59 data[i] ^= 0x80
60 }
61 }
62 }
63 }
64
65
66 tc.mustHaveLatest(1)
67 flipBits()
68 _, err := tc.client.Lookup("rsc.io/sampler", "v1.3.0")
69 tc.mustError(err, "rsc.io/sampler@v1.3.0: initializing sumdb.Client: checking tree#1: downloaded inconsistent tile")
70 flipBits()
71 tc.newClient()
72 tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
73
74
75 flipBits()
76 _, err = tc.client.Lookup("rsc.io/Quote", "v1.5.2")
77 tc.mustError(err, "rsc.io/Quote@v1.5.2: checking tree#3 against tree#4: downloaded inconsistent tile")
78 flipBits()
79 tc.newClient()
80 tc.mustLookup("rsc.io/Quote", "v1.5.2", "rsc.io/Quote v1.5.2 h1:uppercase!=")
81
82
83 tc.newClient()
84 text := tlog.FormatTree(tlog.Tree{N: 1, Hash: tlog.Hash{}})
85 data, err := note.Sign(¬e.Note{Text: string(text)}, tc.signer)
86 if err != nil {
87 tc.t.Fatal(err)
88 }
89 tc.config[testName+"/latest"] = data
90 _, err = tc.client.Lookup("rsc.io/sampler", "v1.3.0")
91 tc.mustError(err, "rsc.io/sampler@v1.3.0: initializing sumdb.Client: checking tree#1: downloaded inconsistent tile")
92 }
93
94 func TestClientFork(t *testing.T) {
95 tc := newTestClient(t)
96 tc2 := tc.fork()
97
98 tc.addRecord("rsc.io/pkg1@v1.5.2", `rsc.io/pkg1 v1.5.2 h1:hash!=
99 `)
100 tc.addRecord("rsc.io/pkg1@v1.5.4", `rsc.io/pkg1 v1.5.4 h1:hash!=
101 `)
102 tc.mustLookup("rsc.io/pkg1", "v1.5.2", "rsc.io/pkg1 v1.5.2 h1:hash!=")
103
104 tc2.addRecord("rsc.io/pkg1@v1.5.3", `rsc.io/pkg1 v1.5.3 h1:hash!=
105 `)
106 tc2.addRecord("rsc.io/pkg1@v1.5.4", `rsc.io/pkg1 v1.5.4 h1:hash!=
107 `)
108 tc2.mustLookup("rsc.io/pkg1", "v1.5.4", "rsc.io/pkg1 v1.5.4 h1:hash!=")
109
110 key := "/lookup/rsc.io/pkg1@v1.5.2"
111 tc2.remote[key] = tc.remote[key]
112 _, err := tc2.client.Lookup("rsc.io/pkg1", "v1.5.2")
113 tc2.mustError(err, ErrSecurity.Error())
114
115
139
140 wants := []string{
141 "SECURITY ERROR",
142 "go.sum database server misbehavior detected!",
143 "old database:\n\tgo.sum database tree\n\t5\n",
144 "— localhost.localdev/sumdb AAAMZ5/2FVAd",
145 "new database:\n\tgo.sum database tree\n\t6\n",
146 "— localhost.localdev/sumdb AAAMZ6oRNswl",
147 "proof of misbehavior:\n\tT7i+H/8ER4nXOiw4Bj0k",
148 }
149 text := tc2.security.String()
150 for _, want := range wants {
151 if !strings.Contains(text, want) {
152 t.Fatalf("cannot find %q in security text:\n%s", want, text)
153 }
154 }
155 }
156
157 func TestClientGONOSUMDB(t *testing.T) {
158 tc := newTestClient(t)
159 tc.client.SetGONOSUMDB("p,*/q")
160 tc.client.Lookup("rsc.io/sampler", "v1.3.0")
161 tc.getOK = false
162
163 ok := []string{
164 "abc",
165 "a/p",
166 "pq",
167 "q",
168 "n/o/p/q",
169 }
170 skip := []string{
171 "p",
172 "p/x",
173 "x/q",
174 "x/q/z",
175 }
176
177 for _, path := range ok {
178 _, err := tc.client.Lookup(path, "v1.0.0")
179 if err == ErrGONOSUMDB {
180 t.Errorf("Lookup(%q): ErrGONOSUMDB, wanted failed actual lookup", path)
181 }
182 }
183 for _, path := range skip {
184 _, err := tc.client.Lookup(path, "v1.0.0")
185 if err != ErrGONOSUMDB {
186 t.Errorf("Lookup(%q): %v, wanted ErrGONOSUMDB", path, err)
187 }
188 }
189 }
190
191
192 type testClient struct {
193 t *testing.T
194 client *Client
195 tileHeight int
196 getOK bool
197 getTileOK bool
198 treeSize int64
199 hashes []tlog.Hash
200 remote map[string][]byte
201 signer note.Signer
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 mu sync.Mutex
218 config map[string][]byte
219 cache map[string][]byte
220 security bytes.Buffer
221 }
222
223
224
225 func newTestClient(t *testing.T) *testClient {
226 tc := &testClient{
227 t: t,
228 tileHeight: 2,
229 getOK: true,
230 getTileOK: true,
231 config: make(map[string][]byte),
232 cache: make(map[string][]byte),
233 remote: make(map[string][]byte),
234 }
235
236 tc.config["key"] = []byte(testVerifierKey + "\n")
237 var err error
238 tc.signer, err = note.NewSigner(testSignerKey)
239 if err != nil {
240 t.Fatal(err)
241 }
242
243 tc.newClient()
244
245 tc.addRecord("rsc.io/quote@v1.5.2", `rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
246 rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
247 rsc.io/quote v1.5.2 h2:xyzzy
248 `)
249
250 tc.addRecord("golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c", `golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
251 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
252 `)
253 tc.addRecord("rsc.io/sampler@v1.3.0", `rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
254 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
255 `)
256 tc.config[testName+"/latest"] = tc.signTree(1)
257
258 tc.addRecord("rsc.io/!quote@v1.5.2", `rsc.io/Quote v1.5.2 h1:uppercase!=
259 `)
260 return tc
261 }
262
263
264
265
266 func (tc *testClient) newClient() {
267 tc.client = NewClient(tc)
268 tc.client.SetTileHeight(tc.tileHeight)
269 }
270
271
272 func (tc *testClient) mustLookup(path, vers, want string) {
273 tc.t.Helper()
274 lines, err := tc.client.Lookup(path, vers)
275 if err != nil {
276 tc.t.Fatal(err)
277 }
278 if strings.Join(lines, "\n") != want {
279 tc.t.Fatalf("Lookup(%q, %q):\n\t%s\nwant:\n\t%s", path, vers, strings.Join(lines, "\n\t"), strings.Replace(want, "\n", "\n\t", -1))
280 }
281 }
282
283
284
285 func (tc *testClient) mustHaveLatest(n int64) {
286 tc.t.Helper()
287
288 latest := tc.config[testName+"/latest"]
289 lines := strings.Split(string(latest), "\n")
290 if len(lines) < 2 || lines[1] != fmt.Sprint(n) {
291 tc.t.Fatalf("/latest should have tree %d, but has:\n%s", n, latest)
292 }
293 }
294
295
296 func (tc *testClient) mustError(err error, text string) {
297 tc.t.Helper()
298 if err == nil || !strings.Contains(err.Error(), text) {
299 tc.t.Fatalf("err = %v, want %q", err, text)
300 }
301 }
302
303
304
305 func (tc *testClient) fork() *testClient {
306 tc2 := &testClient{
307 t: tc.t,
308 getOK: tc.getOK,
309 getTileOK: tc.getTileOK,
310 tileHeight: tc.tileHeight,
311 treeSize: tc.treeSize,
312 hashes: append([]tlog.Hash{}, tc.hashes...),
313 signer: tc.signer,
314 config: copyMap(tc.config),
315 cache: copyMap(tc.cache),
316 remote: copyMap(tc.remote),
317 }
318 tc2.newClient()
319 return tc2
320 }
321
322 func copyMap(m map[string][]byte) map[string][]byte {
323 m2 := make(map[string][]byte)
324 for k, v := range m {
325 m2[k] = v
326 }
327 return m2
328 }
329
330
331
332 func (tc *testClient) ReadHashes(indexes []int64) ([]tlog.Hash, error) {
333 var list []tlog.Hash
334 for _, id := range indexes {
335 list = append(list, tc.hashes[id])
336 }
337 return list, nil
338 }
339
340
341 func (tc *testClient) addRecord(key, data string) {
342 tc.t.Helper()
343
344
345 id := tc.treeSize
346 tc.treeSize++
347 rec, err := tlog.FormatRecord(id, []byte(data))
348 if err != nil {
349 tc.t.Fatal(err)
350 }
351 hashes, err := tlog.StoredHashesForRecordHash(id, tlog.RecordHash([]byte(data)), tc)
352 if err != nil {
353 tc.t.Fatal(err)
354 }
355 tc.hashes = append(tc.hashes, hashes...)
356
357
358 tc.remote["/lookup/"+key] = append(rec, tc.signTree(tc.treeSize)...)
359
360
361 tiles := tlog.NewTiles(tc.tileHeight, id, tc.treeSize)
362 for _, tile := range tiles {
363 data, err := tlog.ReadTileData(tile, tc)
364 if err != nil {
365 tc.t.Fatal(err)
366 }
367 tc.remote["/"+tile.Path()] = data
368
369 }
370 }
371
372
373 func (tc *testClient) signTree(size int64) []byte {
374 h, err := tlog.TreeHash(size, tc)
375 if err != nil {
376 tc.t.Fatal(err)
377 }
378 text := tlog.FormatTree(tlog.Tree{N: size, Hash: h})
379 data, err := note.Sign(¬e.Note{Text: string(text)}, tc.signer)
380 if err != nil {
381 tc.t.Fatal(err)
382 }
383 return data
384 }
385
386
387 func (tc *testClient) ReadRemote(path string) ([]byte, error) {
388
389
390 if !tc.getOK {
391 return nil, fmt.Errorf("disallowed remote read %s", path)
392 }
393 if strings.Contains(path, "/tile/") && !tc.getTileOK {
394 return nil, fmt.Errorf("disallowed remote tile read %s", path)
395 }
396
397 data, ok := tc.remote[path]
398 if !ok {
399 return nil, fmt.Errorf("no remote path %s", path)
400 }
401 return data, nil
402 }
403
404
405 func (tc *testClient) ReadConfig(file string) ([]byte, error) {
406 tc.mu.Lock()
407 defer tc.mu.Unlock()
408
409 data, ok := tc.config[file]
410 if !ok {
411 return nil, fmt.Errorf("no config %s", file)
412 }
413 return data, nil
414 }
415
416
417 func (tc *testClient) WriteConfig(file string, old, new []byte) error {
418 tc.mu.Lock()
419 defer tc.mu.Unlock()
420
421 data := tc.config[file]
422 if !bytes.Equal(old, data) {
423 return ErrWriteConflict
424 }
425 tc.config[file] = new
426 return nil
427 }
428
429
430 func (tc *testClient) ReadCache(file string) ([]byte, error) {
431 tc.mu.Lock()
432 defer tc.mu.Unlock()
433
434 data, ok := tc.cache[file]
435 if !ok {
436 return nil, fmt.Errorf("no cache %s", file)
437 }
438 return data, nil
439 }
440
441
442 func (tc *testClient) WriteCache(file string, data []byte) {
443 tc.mu.Lock()
444 defer tc.mu.Unlock()
445
446 tc.cache[file] = data
447 }
448
449
450 func (tc *testClient) Log(msg string) {
451 tc.t.Log(msg)
452 }
453
454
455 func (tc *testClient) SecurityError(msg string) {
456 tc.mu.Lock()
457 defer tc.mu.Unlock()
458
459 fmt.Fprintf(&tc.security, "%s\n", strings.TrimRight(msg, "\n"))
460 }
461
View as plain text