aboutsummaryrefslogtreecommitdiff
path: root/monitor
diff options
context:
space:
mode:
authorBen Burwell <ben@benburwell.com>2019-09-24 14:06:09 -0400
committerBen Burwell <ben@benburwell.com>2019-09-24 14:06:09 -0400
commitdea6ee40f067e0a3ef81972cfcfac1761eeb27d4 (patch)
tree5fb7e48ab9ee8b250058ff9f9ba28c21268e552d /monitor
parenta583fb71e1a3c4a99ecb8742168ad71c3b289b55 (diff)
Implement monitor package
Diffstat (limited to 'monitor')
-rw-r--r--monitor/database.go56
-rw-r--r--monitor/monitor.go117
-rw-r--r--monitor/security_error.go7
3 files changed, 180 insertions, 0 deletions
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)
+}