1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
package main
import (
"bytes"
"fmt"
"io"
"log"
"net/http"
"strconv"
"strings"
"golang.org/x/mod/sumdb/note"
)
func main() {
dbs := []*db{
&db{host: "sum.golang.org", key: "sum.golang.org+033de0ae+Ac4zctda0e5eza+HJyk9SxEdh+s3Ux18htTTAD8OuAn8"},
//&db{host: "sum.golang.org", key: "sum.golang.org+033de0ae+BADBADBADBADBADBADBADBADBADBADBADBADBADBADBA"},
}
for _, d := range dbs {
if err := audit(d); err != nil {
log.Printf("AUDIT FAIL (%s): %s", d.host, err.Error())
}
}
}
func audit(d *db) error {
log.Printf("starting audit of %s...", d.host)
size, hash, err := d.getLatest()
if err != nil {
return err
}
log.Printf("db size %d", size)
log.Printf("db hash %s", hash)
return nil
}
type db struct {
host string
key string
}
// httpGet makes a GET request to the specified path of the database and
// returns a byte slice of the response body.
func (d *db) httpGet(path string) ([]byte, error) {
client := &http.Client{}
resp, err := client.Get("https://" + d.host + path)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var body bytes.Buffer
if _, err := io.Copy(&body, resp.Body); err != nil {
return nil, fmt.Errorf("could not read response body: %w", err)
}
return body.Bytes(), nil
}
// verifyNote takes a signed byte slice, verifies the signature against the
// db's public key. If successful, the note content is returned, otherwise, an
// error.
func (d *db) verifyNote(b []byte) (string, error) {
verifier, err := note.NewVerifier(d.key)
if err != nil {
return "", err
}
verifiers := note.VerifierList(verifier)
msg, err := note.Open(b, verifiers)
if err != nil {
return "", err
}
return msg.Text, nil
}
// getLatest fetches and verifies the latest signed tree head hash and database
// size.
func (d *db) getLatest() (int, string, error) {
body, err := d.httpGet("/latest")
if err != nil {
return 0, "", fmt.Errorf("could not fetch latest: %w", err)
}
msg, err := d.verifyNote(body)
if err != nil {
return 0, "", fmt.Errorf("could not verify note: %w", err)
}
parts := strings.Split(msg, "\n")
if len(parts) != 4 {
return 0, "", fmt.Errorf("could not parse latest: expected %d lines but got %d", 4, len(parts))
}
size, err := strconv.Atoi(parts[1])
if err != nil {
return 0, "", fmt.Errorf("could not parse tree size: %w", err)
}
hash := parts[2]
return size, hash, nil
}
|