diff options
Diffstat (limited to 'internal')
-rw-r--r-- | internal/ioutil/ioutil.go | 32 | ||||
-rw-r--r-- | internal/logutil/logutil.go | 108 | ||||
-rw-r--r-- | internal/manager/manager.go | 1 | ||||
-rw-r--r-- | internal/monitor/monitor.go | 7 | ||||
-rw-r--r-- | internal/monitor/tail.go | 72 |
5 files changed, 163 insertions, 57 deletions
diff --git a/internal/ioutil/ioutil.go b/internal/ioutil/ioutil.go index 7fe6cfc..8e98c65 100644 --- a/internal/ioutil/ioutil.go +++ b/internal/ioutil/ioutil.go @@ -1,6 +1,7 @@ package ioutil import ( + "crypto/sha256" "encoding/json" "fmt" "os" @@ -54,3 +55,34 @@ func DirectoriesExist(paths []string) error { } return nil } + +func CopyHashes(hashes [][sha256.Size]byte) (ret [][sha256.Size]byte) { + for _, hash := range hashes { + var dst [sha256.Size]byte + copy(dst[:], hash[:]) + ret = append(ret, dst) + } + return +} + +func SliceHashes(hashes [][sha256.Size]byte) (ret [][]byte) { + for _, hash := range hashes { + dst := hash + ret = append(ret, dst[:]) + } + return +} + +// UnsliceHashes panics unless all hashes are 32 bytes +func UnsliceHashes(hashes [][]byte) (ret [][sha256.Size]byte) { + for _, hash := range hashes { + if got, want := len(hash), sha256.Size; got != want { + panic(fmt.Sprintf("bug: invalid hash: size %d", got)) + } + + var dst [sha256.Size]byte + copy(dst[:], hash) + ret = append(ret, dst) + } + return +} diff --git a/internal/logutil/logutil.go b/internal/logutil/logutil.go new file mode 100644 index 0000000..27c3a73 --- /dev/null +++ b/internal/logutil/logutil.go @@ -0,0 +1,108 @@ +// Package logutil wraps functions related to a log's Merkle tree. All log +// queries use a context where the deadline is set to a hardcoded value. +package logutil + +import ( + "context" + "crypto/sha256" + "fmt" + "time" + + ct "github.com/google/certificate-transparency-go" + "github.com/google/certificate-transparency-go/client" + "github.com/transparency-dev/merkle/compact" + "github.com/transparency-dev/merkle/rfc6962" + "gitlab.torproject.org/rgdd/ct/pkg/merkle" + "rgdd.se/silent-ct/internal/ioutil" +) + +const timeout = 10 * time.Second + +func GetSignedTreeHead(ctx context.Context, cli client.CheckLogClient) (*ct.SignedTreeHead, error) { + rctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return cli.GetSTH(rctx) +} + +func GetConsistencyProof(ctx context.Context, cli client.CheckLogClient, oldSize, newSize uint64) ([][sha256.Size]byte, error) { + if oldSize > newSize { + return nil, fmt.Errorf("old size %d is larger than new size %d", oldSize, newSize) + } + if oldSize == 0 || oldSize == newSize { + return [][sha256.Size]byte{}, nil + } + + rctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + proof, err := cli.GetSTHConsistency(rctx, oldSize, newSize) + if err != nil { + return nil, err + } + return ioutil.UnsliceHashes(proof), nil +} + +func GetEntry(ctx context.Context, cli *client.LogClient, index uint64) (*ct.LeafEntry, error) { + rctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + rsp, err := cli.GetRawEntries(rctx, int64(index), int64(index)) + if err != nil { + return nil, err + } + if got, want := len(rsp.Entries), 1; got != want { + return nil, fmt.Errorf("too many log entries: %d (want one)", got) + } + return &rsp.Entries[0], nil +} + +// GetCompactRange constructs the compact range [0, index) by doing a +// get-proof-by-hash query to obtain the necessary tree hashes +func GetCompactRange(ctx context.Context, cli *client.LogClient, entry *ct.LeafEntry, index uint64) (*compact.Range, error) { + rctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + leafHash := merkle.HashLeafNode(entry.LeafInput) + proof, err := cli.GetProofByHash(rctx, leafHash[:], index+1) + if err != nil { + return nil, err + } + if got, want := uint64(proof.LeafIndex), index; got != want { + return nil, fmt.Errorf("invalid leaf index: %d (want %d)", got, want) + } + tree := ioutil.UnsliceHashes(reverse(proof.AuditPath)) + return AppendCompactRange(tree, index, [][sha256.Size]byte{leafHash}) +} + +// RootHash panics if the compact range is malformed +func RootHash(cr *compact.Range) (ret [sha256.Size]byte) { + rootHash, err := cr.GetRootHash(nil) + if err != nil { + panic(fmt.Sprintf("bug: compact: %v", err)) + } + if got, want := len(rootHash), sha256.Size; got != want { + panic(fmt.Sprintf("bug: invalid root hash: size %d", got)) + } + copy(ret[:], rootHash[:]) + return +} + +// AppendCompactRange appends a list of leaf hashes to the compact range [0, size) +func AppendCompactRange(tree [][sha256.Size]byte, size uint64, pending [][sha256.Size]byte) (*compact.Range, error) { + rf := compact.RangeFactory{Hash: rfc6962.DefaultHasher.HashChildren} + cr, err := rf.NewRange(0, size, ioutil.SliceHashes(tree)) + if err != nil { + return nil, fmt.Errorf("compact: %v", err) + } + for _, leafHash := range pending { + dst := leafHash + cr.Append(dst[:], nil) + } + return cr, nil +} + +func reverse(hashes [][]byte) (ret [][]byte) { + for i := len(hashes) - 1; i >= 0; i-- { + ret = append(ret, hashes[i]) + } + return +} diff --git a/internal/manager/manager.go b/internal/manager/manager.go index bf1ad92..ce31b1b 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -51,6 +51,7 @@ func New(cfg Config, fch chan []feedback.Event, mch chan monitor.Event, cch chan s, err := storage.New(storage.Config{ Bootstrap: cfg.Bootstrap, Directory: cfg.Directory, + Logger: cfg.Logger, AlertDelay: cfg.AlertDelay, StaticLogs: cfg.Policy.StaticLogs, RemoveLogs: cfg.Policy.RemoveLogs, diff --git a/internal/monitor/monitor.go b/internal/monitor/monitor.go index 6de7193..2fe4d88 100644 --- a/internal/monitor/monitor.go +++ b/internal/monitor/monitor.go @@ -11,6 +11,7 @@ package monitor import ( "context" + "crypto/sha256" "crypto/x509" "encoding/base64" "fmt" @@ -32,10 +33,12 @@ type MonitoredLog struct { } // State is the latest append-only state the monitor observed from its local -// vantage point. The next entry to download is specified by NextIndex. +// vantage point. The compact range covers [0, NextIndex). The next entry to +// download from the log is at index NextIndex. type State struct { ct.SignedTreeHead `json:"latest_sth"` - NextIndex uint64 `json:"next_index"` + CompactRange [][sha256.Size]byte `json:"compact_range"` + NextIndex uint64 `json:"next_index"` } // Event carries the latest consistent monitor state, found matches, as well as diff --git a/internal/monitor/tail.go b/internal/monitor/tail.go index 0e16476..6be165b 100644 --- a/internal/monitor/tail.go +++ b/internal/monitor/tail.go @@ -2,15 +2,14 @@ package monitor import ( "context" - "crypto/sha256" "fmt" "sync" - "time" - ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/scanner" "gitlab.torproject.org/rgdd/ct/pkg/merkle" + "rgdd.se/silent-ct/internal/ioutil" + "rgdd.se/silent-ct/internal/logutil" ) type tail struct { @@ -118,7 +117,7 @@ func (t *tail) nextState(ctx context.Context, state State, c *chunk) (State, err } func (t *tail) nextConsistentState(ctx context.Context, state State) (State, error) { - sth, err := getSignedTreeHead(ctx, t.checker) + sth, err := logutil.GetSignedTreeHead(ctx, t.checker) if err != nil { return State{}, fmt.Errorf("%s: get-sth: %v", t.checker.BaseURI(), err) } @@ -128,73 +127,36 @@ func (t *tail) nextConsistentState(ctx context.Context, state State) (State, err newSize := sth.TreeSize newRoot := sth.SHA256RootHash - proof, err := getConsistencyProof(ctx, t.checker, oldSize, newSize) + proof, err := logutil.GetConsistencyProof(ctx, t.checker, oldSize, newSize) if err != nil { return State{}, fmt.Errorf("%s: get-consistency: %v", t.checker.BaseURI(), err) } - if err := merkle.VerifyConsistency(oldSize, newSize, oldRoot, newRoot, unslice(proof)); err != nil { + if err := merkle.VerifyConsistency(oldSize, newSize, oldRoot, newRoot, proof); err != nil { return State{}, fmt.Errorf("%s: verify consistency: %v", t.checker.BaseURI(), err) } - return State{SignedTreeHead: *sth, NextIndex: state.NextIndex}, nil + return State{SignedTreeHead: *sth, CompactRange: ioutil.CopyHashes(state.CompactRange), NextIndex: state.NextIndex}, nil } func (t *tail) nextIncludedState(ctx context.Context, state State, c *chunk) (State, error) { - leafHash := c.leafHashes[0] - oldSize := state.NextIndex + uint64(len(c.leafHashes)) - iproof, err := getInclusionProof(ctx, t.checker, leafHash, oldSize) // FIXME: set leaf index in ctx to hack into tile API - if err != nil { - return State{}, fmt.Errorf("%s: get-inclusion: %v", t.checker.BaseURI(), err) - } - if got, want := uint64(iproof.LeafIndex), state.NextIndex; got != want { - return State{}, fmt.Errorf("%s: wrong index for get-inclusion proof query %x:%d", t.checker.BaseURI(), leafHash[:], oldSize) - } - oldRoot, err := merkle.TreeHeadFromRangeProof(c.leafHashes, state.NextIndex, unslice(iproof.AuditPath)) + cr, err := logutil.AppendCompactRange(state.CompactRange, state.NextIndex, c.leafHashes) if err != nil { - return State{}, fmt.Errorf("%s: range proof: %v", t.checker.BaseURI(), err) + panic(fmt.Sprintf("bug: %v", err)) } - - newSize := state.TreeSize + oldRoot := logutil.RootHash(cr) + oldSize := state.NextIndex + uint64(len(c.leafHashes)) newRoot := state.SHA256RootHash - cproof, err := getConsistencyProof(ctx, t.checker, oldSize, newSize) + newSize := state.TreeSize + + proof, err := logutil.GetConsistencyProof(ctx, t.checker, oldSize, newSize) if err != nil { - return State{}, fmt.Errorf("%s: get-consistency: %v", t.checker.BaseURI(), err) + return State{}, fmt.Errorf("%s: tree: get-consistency: %v", t.checker.BaseURI(), err) } - if err := merkle.VerifyConsistency(oldSize, newSize, oldRoot, newRoot, unslice(cproof)); err != nil { - return State{}, fmt.Errorf("%s: verify consistency: %v", t.checker.BaseURI(), err) + if err := merkle.VerifyConsistency(oldSize, newSize, oldRoot, newRoot, proof); err != nil { + return State{}, fmt.Errorf("%s: tree: verify consistency: %v", t.checker.BaseURI(), err) } state.NextIndex += uint64(len(c.leafHashes)) + state.CompactRange = ioutil.UnsliceHashes(cr.Hashes()) return state, nil } - -func getInclusionProof(ctx context.Context, cli client.CheckLogClient, leafHash [sha256.Size]byte, size uint64) (*ct.GetProofByHashResponse, error) { - rctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - return cli.GetProofByHash(rctx, leafHash[:], size) -} - -func getConsistencyProof(ctx context.Context, cli client.CheckLogClient, oldSize, newSize uint64) ([][]byte, error) { - if oldSize == 0 || oldSize >= newSize { - return [][]byte{}, nil - } - rctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - return cli.GetSTHConsistency(rctx, oldSize, newSize) -} - -func getSignedTreeHead(ctx context.Context, cli client.CheckLogClient) (*ct.SignedTreeHead, error) { - rctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - return cli.GetSTH(rctx) -} - -func unslice(hashes [][]byte) [][sha256.Size]byte { - var ret [][sha256.Size]byte - for _, hash := range hashes { - var h [sha256.Size]byte - copy(h[:], hash) - ret = append(ret, h) - } - return ret -} |