1
2
3
4
5 package sumdb
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "strings"
12 "sync"
13 "sync/atomic"
14
15 "golang.org/x/mod/module"
16 "golang.org/x/mod/sumdb/note"
17 "golang.org/x/mod/sumdb/tlog"
18 )
19
20
21
22
23 type ClientOps interface {
24
25
26
27
28
29
30 ReadRemote(path string) ([]byte, error)
31
32
33
34
35
36
37
38
39
40
41 ReadConfig(file string) ([]byte, error)
42
43
44
45
46
47
48
49 WriteConfig(file string, old, new []byte) error
50
51
52
53
54
55
56 ReadCache(file string) ([]byte, error)
57
58
59 WriteCache(file string, data []byte)
60
61
62 Log(msg string)
63
64
65
66
67
68 SecurityError(msg string)
69 }
70
71
72 var ErrWriteConflict = errors.New("write conflict")
73
74
75 var ErrSecurity = errors.New("security error: misbehaving server")
76
77
78
79 type Client struct {
80 ops ClientOps
81
82 didLookup uint32
83
84
85 initOnce sync.Once
86 initErr error
87 name string
88 verifiers note.Verifiers
89 tileReader tileReader
90 tileHeight int
91 nosumdb string
92
93 record parCache
94 tileCache parCache
95
96 latestMu sync.Mutex
97 latest tlog.Tree
98 latestMsg []byte
99
100 tileSavedMu sync.Mutex
101 tileSaved map[tlog.Tile]bool
102 }
103
104
105 func NewClient(ops ClientOps) *Client {
106 return &Client{
107 ops: ops,
108 }
109 }
110
111
112
113 func (c *Client) init() error {
114 c.initOnce.Do(c.initWork)
115 return c.initErr
116 }
117
118
119 func (c *Client) initWork() {
120 defer func() {
121 if c.initErr != nil {
122 c.initErr = fmt.Errorf("initializing sumdb.Client: %v", c.initErr)
123 }
124 }()
125
126 c.tileReader.c = c
127 if c.tileHeight == 0 {
128 c.tileHeight = 8
129 }
130 c.tileSaved = make(map[tlog.Tile]bool)
131
132 vkey, err := c.ops.ReadConfig("key")
133 if err != nil {
134 c.initErr = err
135 return
136 }
137 verifier, err := note.NewVerifier(strings.TrimSpace(string(vkey)))
138 if err != nil {
139 c.initErr = err
140 return
141 }
142 c.verifiers = note.VerifierList(verifier)
143 c.name = verifier.Name()
144
145 data, err := c.ops.ReadConfig(c.name + "/latest")
146 if err != nil {
147 c.initErr = err
148 return
149 }
150 if err := c.mergeLatest(data); err != nil {
151 c.initErr = err
152 return
153 }
154 }
155
156
157
158
159
160
161 func (c *Client) SetTileHeight(height int) {
162 if atomic.LoadUint32(&c.didLookup) != 0 {
163 panic("SetTileHeight used after Lookup")
164 }
165 if height <= 0 {
166 panic("invalid call to SetTileHeight")
167 }
168 if c.tileHeight != 0 {
169 panic("multiple calls to SetTileHeight")
170 }
171 c.tileHeight = height
172 }
173
174
175
176
177
178
179 func (c *Client) SetGONOSUMDB(list string) {
180 if atomic.LoadUint32(&c.didLookup) != 0 {
181 panic("SetGONOSUMDB used after Lookup")
182 }
183 if c.nosumdb != "" {
184 panic("multiple calls to SetGONOSUMDB")
185 }
186 c.nosumdb = list
187 }
188
189
190
191
192 var ErrGONOSUMDB = errors.New("skipped (listed in GONOSUMDB)")
193
194 func (c *Client) skip(target string) bool {
195 return module.MatchPrefixPatterns(c.nosumdb, target)
196 }
197
198
199
200
201 func (c *Client) Lookup(path, vers string) (lines []string, err error) {
202 atomic.StoreUint32(&c.didLookup, 1)
203
204 if c.skip(path) {
205 return nil, ErrGONOSUMDB
206 }
207
208 defer func() {
209 if err != nil {
210 err = fmt.Errorf("%s@%s: %v", path, vers, err)
211 }
212 }()
213
214 if err := c.init(); err != nil {
215 return nil, err
216 }
217
218
219 epath, err := module.EscapePath(path)
220 if err != nil {
221 return nil, err
222 }
223 evers, err := module.EscapeVersion(strings.TrimSuffix(vers, "/go.mod"))
224 if err != nil {
225 return nil, err
226 }
227 remotePath := "/lookup/" + epath + "@" + evers
228 file := c.name + remotePath
229
230
231
232
233
234
235 type cached struct {
236 data []byte
237 err error
238 }
239 result := c.record.Do(file, func() interface{} {
240
241 writeCache := false
242 data, err := c.ops.ReadCache(file)
243 if err != nil {
244 data, err = c.ops.ReadRemote(remotePath)
245 if err != nil {
246 return cached{nil, err}
247 }
248 writeCache = true
249 }
250
251
252 id, text, treeMsg, err := tlog.ParseRecord(data)
253 if err != nil {
254 return cached{nil, err}
255 }
256 if err := c.mergeLatest(treeMsg); err != nil {
257 return cached{nil, err}
258 }
259 if err := c.checkRecord(id, text); err != nil {
260 return cached{nil, err}
261 }
262
263
264
265 if writeCache {
266 c.ops.WriteCache(file, data)
267 }
268
269 return cached{data, nil}
270 }).(cached)
271 if result.err != nil {
272 return nil, result.err
273 }
274
275
276
277 prefix := path + " " + vers + " "
278 var hashes []string
279 for _, line := range strings.Split(string(result.data), "\n") {
280 if strings.HasPrefix(line, prefix) {
281 hashes = append(hashes, line)
282 }
283 }
284 return hashes, nil
285 }
286
287
288
289
290
291
292
293
294
295
296 func (c *Client) mergeLatest(msg []byte) error {
297
298 when, err := c.mergeLatestMem(msg)
299 if err != nil {
300 return err
301 }
302 if when != msgFuture {
303
304
305 return nil
306 }
307
308
309
310
311
312 for {
313 msg, err := c.ops.ReadConfig(c.name + "/latest")
314 if err != nil {
315 return err
316 }
317 when, err := c.mergeLatestMem(msg)
318 if err != nil {
319 return err
320 }
321 if when != msgPast {
322
323
324 return nil
325 }
326
327
328 c.latestMu.Lock()
329 latestMsg := c.latestMsg
330 c.latestMu.Unlock()
331 if err := c.ops.WriteConfig(c.name+"/latest", msg, latestMsg); err != ErrWriteConflict {
332
333 return err
334 }
335 }
336 }
337
338 const (
339 msgPast = 1 + iota
340 msgNow
341 msgFuture
342 )
343
344
345
346
347
348
349
350
351
352 func (c *Client) mergeLatestMem(msg []byte) (when int, err error) {
353 if len(msg) == 0 {
354
355 c.latestMu.Lock()
356 latest := c.latest
357 c.latestMu.Unlock()
358 if latest.N == 0 {
359 return msgNow, nil
360 }
361 return msgPast, nil
362 }
363
364 note, err := note.Open(msg, c.verifiers)
365 if err != nil {
366 return 0, fmt.Errorf("reading tree note: %v\nnote:\n%s", err, msg)
367 }
368 tree, err := tlog.ParseTree([]byte(note.Text))
369 if err != nil {
370 return 0, fmt.Errorf("reading tree: %v\ntree:\n%s", err, note.Text)
371 }
372
373
374
375
376 c.latestMu.Lock()
377 latest := c.latest
378 latestMsg := c.latestMsg
379 c.latestMu.Unlock()
380
381 for {
382
383 if tree.N <= latest.N {
384 if err := c.checkTrees(tree, msg, latest, latestMsg); err != nil {
385 return 0, err
386 }
387 if tree.N < latest.N {
388 return msgPast, nil
389 }
390 return msgNow, nil
391 }
392
393
394 if err := c.checkTrees(latest, latestMsg, tree, msg); err != nil {
395 return 0, err
396 }
397
398
399
400 c.latestMu.Lock()
401 installed := false
402 if c.latest == latest {
403 installed = true
404 c.latest = tree
405 c.latestMsg = msg
406 } else {
407 latest = c.latest
408 latestMsg = c.latestMsg
409 }
410 c.latestMu.Unlock()
411
412 if installed {
413 return msgFuture, nil
414 }
415 }
416 }
417
418
419
420
421
422 func (c *Client) checkTrees(older tlog.Tree, olderNote []byte, newer tlog.Tree, newerNote []byte) error {
423 thr := tlog.TileHashReader(newer, &c.tileReader)
424 h, err := tlog.TreeHash(older.N, thr)
425 if err != nil {
426 if older.N == newer.N {
427 return fmt.Errorf("checking tree#%d: %v", older.N, err)
428 }
429 return fmt.Errorf("checking tree#%d against tree#%d: %v", older.N, newer.N, err)
430 }
431 if h == older.Hash {
432 return nil
433 }
434
435
436
437 var buf bytes.Buffer
438 fmt.Fprintf(&buf, "SECURITY ERROR\n")
439 fmt.Fprintf(&buf, "go.sum database server misbehavior detected!\n\n")
440 indent := func(b []byte) []byte {
441 return bytes.Replace(b, []byte("\n"), []byte("\n\t"), -1)
442 }
443 fmt.Fprintf(&buf, "old database:\n\t%s\n", indent(olderNote))
444 fmt.Fprintf(&buf, "new database:\n\t%s\n", indent(newerNote))
445
446
447
448
449
450
451
452
453
454
455
456 fmt.Fprintf(&buf, "proof of misbehavior:\n\t%v", h)
457 if p, err := tlog.ProveTree(newer.N, older.N, thr); err != nil {
458 fmt.Fprintf(&buf, "\tinternal error: %v\n", err)
459 } else if err := tlog.CheckTree(p, newer.N, newer.Hash, older.N, h); err != nil {
460 fmt.Fprintf(&buf, "\tinternal error: generated inconsistent proof\n")
461 } else {
462 for _, h := range p {
463 fmt.Fprintf(&buf, "\n\t%v", h)
464 }
465 }
466 c.ops.SecurityError(buf.String())
467 return ErrSecurity
468 }
469
470
471 func (c *Client) checkRecord(id int64, data []byte) error {
472 c.latestMu.Lock()
473 latest := c.latest
474 c.latestMu.Unlock()
475
476 if id >= latest.N {
477 return fmt.Errorf("cannot validate record %d in tree of size %d", id, latest.N)
478 }
479 hashes, err := tlog.TileHashReader(latest, &c.tileReader).ReadHashes([]int64{tlog.StoredHashIndex(0, id)})
480 if err != nil {
481 return err
482 }
483 if hashes[0] == tlog.RecordHash(data) {
484 return nil
485 }
486 return fmt.Errorf("cannot authenticate record data in server response")
487 }
488
489
490
491
492 type tileReader struct {
493 c *Client
494 }
495
496 func (r *tileReader) Height() int {
497 return r.c.tileHeight
498 }
499
500
501
502 func (r *tileReader) ReadTiles(tiles []tlog.Tile) ([][]byte, error) {
503
504 data := make([][]byte, len(tiles))
505 errs := make([]error, len(tiles))
506 var wg sync.WaitGroup
507 for i, tile := range tiles {
508 wg.Add(1)
509 go func(i int, tile tlog.Tile) {
510 defer wg.Done()
511 defer func() {
512 if e := recover(); e != nil {
513 errs[i] = fmt.Errorf("panic: %v", e)
514 }
515 }()
516 data[i], errs[i] = r.c.readTile(tile)
517 }(i, tile)
518 }
519 wg.Wait()
520
521 for _, err := range errs {
522 if err != nil {
523 return nil, err
524 }
525 }
526
527 return data, nil
528 }
529
530
531 func (c *Client) tileCacheKey(tile tlog.Tile) string {
532 return c.name + "/" + tile.Path()
533 }
534
535
536 func (c *Client) tileRemotePath(tile tlog.Tile) string {
537 return "/" + tile.Path()
538 }
539
540
541 func (c *Client) readTile(tile tlog.Tile) ([]byte, error) {
542 type cached struct {
543 data []byte
544 err error
545 }
546
547 result := c.tileCache.Do(tile, func() interface{} {
548
549 data, err := c.ops.ReadCache(c.tileCacheKey(tile))
550 if err == nil {
551 c.markTileSaved(tile)
552 return cached{data, nil}
553 }
554
555
556
557
558 full := tile
559 full.W = 1 << uint(tile.H)
560 if tile != full {
561 data, err := c.ops.ReadCache(c.tileCacheKey(full))
562 if err == nil {
563 c.markTileSaved(tile)
564 return cached{data[:len(data)/full.W*tile.W], nil}
565 }
566 }
567
568
569 data, err = c.ops.ReadRemote(c.tileRemotePath(tile))
570 if err == nil {
571 return cached{data, nil}
572 }
573
574
575
576
577
578 if tile != full {
579 data, err := c.ops.ReadRemote(c.tileRemotePath(full))
580 if err == nil {
581
582
583
584
585
586 return cached{data[:len(data)/full.W*tile.W], nil}
587 }
588 }
589
590
591
592 return cached{nil, err}
593 }).(cached)
594
595 return result.data, result.err
596 }
597
598
599
600 func (c *Client) markTileSaved(tile tlog.Tile) {
601 c.tileSavedMu.Lock()
602 c.tileSaved[tile] = true
603 c.tileSavedMu.Unlock()
604 }
605
606
607 func (r *tileReader) SaveTiles(tiles []tlog.Tile, data [][]byte) {
608 c := r.c
609
610
611
612 save := make([]bool, len(tiles))
613 c.tileSavedMu.Lock()
614 for i, tile := range tiles {
615 if !c.tileSaved[tile] {
616 save[i] = true
617 c.tileSaved[tile] = true
618 }
619 }
620 c.tileSavedMu.Unlock()
621
622 for i, tile := range tiles {
623 if save[i] {
624
625
626
627
628 c.ops.WriteCache(c.name+"/"+tile.Path(), data[i])
629 }
630 }
631 }
632
View as plain text