aboutsummaryrefslogtreecommitdiff
path: root/main.go
blob: d08cdbd978cc59597a10caf144f7e79d63c6d09c (plain)
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
}