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 }