package monitor import ( "fmt" "io/ioutil" "log" "net/http" "golang.org/x/mod/sumdb/note" "golang.org/x/mod/sumdb/tlog" ) const userAgent = "gosumdbaudit/1.0 (+https://bnbl.io/gosumdbaudit)" type Database struct { URL string Key string c http.Client } func (db *Database) readRemote(path string) ([]byte, error) { log.Printf("GET %s", path) req, err := http.NewRequest(http.MethodGet, "https://"+db.URL+path, nil) if err != nil { return nil, err } req.Header.Set("user-agent", userAgent) resp, err := db.c.Do(req) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("get %s: expected HTTP OK but got %s", path, resp.Status) } if resp.ContentLength > 1e6 { return nil, fmt.Errorf("get %s: body too large (%d bytes)", path, resp.ContentLength) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("get %s: %v", path, err) } return body, nil } func (db *Database) VerifyNote(msg []byte) (*note.Note, error) { verifier, err := note.NewVerifier(db.Key) if err != nil { return nil, err } return note.Open(msg, note.VerifierList(verifier)) } func (db *Database) FetchSTH() (tlog.Tree, error) { resp, err := db.readRemote("/latest") if err != nil { return tlog.Tree{}, err } note, err := db.VerifyNote(resp) if err != nil { return tlog.Tree{}, err } return tlog.ParseTree([]byte(note.Text)) }