From dea6ee40f067e0a3ef81972cfcfac1761eeb27d4 Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Tue, 24 Sep 2019 14:06:09 -0400 Subject: Implement monitor package --- monitor/database.go | 56 ++++++++++++++++++++++ monitor/monitor.go | 117 ++++++++++++++++++++++++++++++++++++++++++++++ monitor/security_error.go | 7 +++ 3 files changed, 180 insertions(+) create mode 100644 monitor/database.go create mode 100644 monitor/monitor.go create mode 100644 monitor/security_error.go (limited to 'monitor') diff --git a/monitor/database.go b/monitor/database.go new file mode 100644 index 0000000..7a0ead1 --- /dev/null +++ b/monitor/database.go @@ -0,0 +1,56 @@ +package monitor + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + + "golang.org/x/mod/sumdb/note" + "golang.org/x/mod/sumdb/tlog" +) + +type Database struct { + URL string + Key string +} + +func (db *Database) readRemote(path string) ([]byte, error) { + log.Printf("GET %s", path) + resp, err := http.Get("https://" + db.URL + path) + 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", 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)) +} diff --git a/monitor/monitor.go b/monitor/monitor.go new file mode 100644 index 0000000..e291a63 --- /dev/null +++ b/monitor/monitor.go @@ -0,0 +1,117 @@ +package monitor + +import ( + "fmt" + "log" + "sync" + "time" + + "golang.org/x/mod/sumdb/tlog" +) + +type Monitor struct { + db *Database + + // the latest verified tree + latest tlog.Tree +} + +func NewMonitor(db *Database) *Monitor { + return &Monitor{db: db} +} + +// Watch polls the database to check for a new Signed Tree Head every interval. +// It runs indefinitely; only returning an error if an inconsistency is found. +func (m *Monitor) Watch(interval time.Duration) error { + for { + log.Printf("checking for new STH") + t, err := m.db.FetchSTH() + if err != nil { + return err + } + log.Printf("got STH: %#v", t) + + if m.latest.N == t.N { + log.Printf("current STH matches latest proved") + } else { + if err := m.TreeProof(m.latest, t); err != nil { + return err + } + log.Printf("proof succeeded") + m.latest = t + } + + time.Sleep(interval) + } + return nil +} + +// TreeProof proves that tree older is a prefix of tree newer, returning an +// error if the proof fails. +func (m *Monitor) TreeProof(older, newer tlog.Tree) error { + log.Printf("proving that N=%d is a prefix of N'=%d", older.N, newer.N) + if older.N == 0 { + log.Printf("N=0, so using bootstrap proof") + return m.BootstrapProof(newer) + } + thr := tlog.TileHashReader(newer, m) + p, err := tlog.ProveTree(newer.N, older.N, thr) + if err != nil { + return err + } + log.Printf("Proof: %#v", p) + return nil +} + +func (m *Monitor) BootstrapProof(t tlog.Tree) error { + thr := tlog.TileHashReader(t, m) + h, err := tlog.TreeHash(t.N, thr) + if err != nil { + return err + } + log.Printf("computed hash: %v", h) + log.Printf("expected hash: %v", t.Hash) + if h != t.Hash { + return fmt.Errorf("computed hash %v does not match expected: %v", h, t.Hash) + } + return nil +} + +func (m *Monitor) Height() int { + return 8 +} + +// ReadTiles reads and returns the requested tiles, +// either from the on-disk cache or the server. +func (m *Monitor) ReadTiles(tiles []tlog.Tile) ([][]byte, error) { + log.Printf("reading %d tiles", len(tiles)) + // Read all the tiles in parallel. + data := make([][]byte, len(tiles)) + errs := make([]error, len(tiles)) + var wg sync.WaitGroup + for i, tile := range tiles { + wg.Add(1) + go func(i int, tile tlog.Tile) { + defer wg.Done() + log.Printf("reading tile %v", tile) + data[i], errs[i] = m.readTile(tile) + }(i, tile) + } + wg.Wait() + + for _, err := range errs { + if err != nil { + return nil, err + } + } + + return data, nil +} + +func (m *Monitor) readTile(tile tlog.Tile) ([]byte, error) { + return m.db.readRemote("/" + tile.Path()) +} + +func (m *Monitor) SaveTiles(tiles []tlog.Tile, data [][]byte) { + // noop +} diff --git a/monitor/security_error.go b/monitor/security_error.go new file mode 100644 index 0000000..aa036eb --- /dev/null +++ b/monitor/security_error.go @@ -0,0 +1,7 @@ +package monitor + +type SecurityError string + +func (e SecurityError) Error() string { + return string(e) +} -- cgit v1.2.3