aboutsummaryrefslogtreecommitdiff
path: root/sth.go
diff options
context:
space:
mode:
Diffstat (limited to 'sth.go')
-rw-r--r--sth.go159
1 files changed, 159 insertions, 0 deletions
diff --git a/sth.go b/sth.go
new file mode 100644
index 0000000..e251b98
--- /dev/null
+++ b/sth.go
@@ -0,0 +1,159 @@
+package stfe
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "reflect"
+ "sync"
+
+ "github.com/golang/glog"
+ "github.com/google/certificate-transparency-go/schedule"
+ "github.com/google/trillian"
+ "github.com/google/trillian/types"
+)
+
+// SthSource provides access to the log's STHs.
+type SthSource interface {
+ // Latest returns the most reccent signed_tree_head_v*.
+ Latest(context.Context) (*StItem, error)
+ // Stable returns the most recent signed_tree_head_v* that is stable for
+ // some period of time, e.g., 10 minutes.
+ Stable(context.Context) (*StItem, error)
+ // Cosigned returns the most recent cosigned_tree_head_v*.
+ Cosigned(context.Context) (*StItem, error)
+ // AddCosignature attempts to add a cosignature to the stable STH. The
+ // passed cosigned_tree_head_v* must have a single verified cosignature.
+ AddCosignature(context.Context, *StItem) error
+ // Run keeps the STH source updated until cancelled
+ Run(context.Context)
+}
+
+// ActiveSthSource implements the SthSource interface for an STFE instance that
+// accepts new logging requests, i.e., the log is running in read+write mode.
+type ActiveSthSource struct {
+ client trillian.TrillianLogClient
+ logParameters *LogParameters
+ currSth *StItem // current cosigned_tree_head_v1 (already finalized)
+ nextSth *StItem // next cosigned_tree_head_v1 (under preparation)
+ cosignatureFrom map[string]bool // track who we got cosignatures from
+ mutex sync.RWMutex
+}
+
+// NewActiveSthSource returns an initialized ActiveSthSource
+func NewActiveSthSource(cli trillian.TrillianLogClient, lp *LogParameters) (*ActiveSthSource, error) {
+ s := ActiveSthSource{
+ client: cli,
+ logParameters: lp,
+ }
+
+ ctx, _ := context.WithTimeout(context.Background(), lp.Deadline)
+ sth, err := s.Latest(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("Latest: %v", err)
+ }
+ // TODO: load peristed cosigned STH?
+ s.currSth = NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil)
+ s.nextSth = NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil)
+ s.cosignatureFrom = make(map[string]bool)
+ return &s, nil
+}
+
+func (s *ActiveSthSource) Latest(ctx context.Context) (*StItem, error) {
+ trsp, err := s.client.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{
+ LogId: s.logParameters.TreeId,
+ })
+ var lr types.LogRootV1
+ if errInner := checkGetLatestSignedLogRoot(s.logParameters, trsp, err, &lr); errInner != nil {
+ return nil, fmt.Errorf("invalid signed log root response: %v", errInner)
+ }
+ return s.logParameters.genV1Sth(NewTreeHeadV1(&lr))
+}
+
+func (s *ActiveSthSource) Stable(_ context.Context) (*StItem, error) {
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+ if s.nextSth == nil {
+ return nil, fmt.Errorf("no stable sth available")
+ }
+ return &StItem{
+ Format: StFormatSignedTreeHeadV1,
+ SignedTreeHeadV1: &s.nextSth.CosignedTreeHeadV1.SignedTreeHeadV1,
+ }, nil
+}
+
+func (s *ActiveSthSource) Cosigned(_ context.Context) (*StItem, error) {
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+ if s.currSth == nil || len(s.currSth.CosignedTreeHeadV1.SignatureV1) == 0 {
+ return nil, fmt.Errorf("no cosigned sth available")
+ }
+ return s.currSth, nil
+}
+
+func (a *SignedTreeHeadV1) Equals(b *SignedTreeHeadV1) bool {
+ return bytes.Equal(a.LogId, b.LogId) &&
+ bytes.Equal(a.Signature, b.Signature) &&
+ a.TreeHead.Timestamp == b.TreeHead.Timestamp &&
+ a.TreeHead.TreeSize == b.TreeHead.TreeSize &&
+ bytes.Equal(a.TreeHead.RootHash.Data, b.TreeHead.RootHash.Data) &&
+ bytes.Equal(a.TreeHead.Extension, b.TreeHead.Extension)
+ // TODO: why reflect.DeepEqual(a, b) gives a different result? Fixme.
+}
+
+func (s *ActiveSthSource) AddCosignature(_ context.Context, costh *StItem) error {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ //if !reflect.DeepEqual(s.nextSth.CosignedTreeHeadV1.SignedTreeHeadV1, costh.CosignedTreeHeadV1.SignedTreeHeadV1) {
+ if !(&s.nextSth.CosignedTreeHeadV1.SignedTreeHeadV1).Equals(&costh.CosignedTreeHeadV1.SignedTreeHeadV1) {
+ return fmt.Errorf("cosignature covers a different tree head")
+ }
+ witness := costh.CosignedTreeHeadV1.SignatureV1[0].Namespace.String()
+ if _, ok := s.cosignatureFrom[witness]; ok {
+ return nil // duplicate
+ }
+ s.cosignatureFrom[witness] = true
+ s.nextSth.CosignedTreeHeadV1.SignatureV1 = append(s.nextSth.CosignedTreeHeadV1.SignatureV1, costh.CosignedTreeHeadV1.SignatureV1[0])
+ return nil
+}
+
+func (s *ActiveSthSource) Run(ctx context.Context) {
+ schedule.Every(ctx, s.logParameters.Interval, func(ctx context.Context) {
+ // get the next stable sth
+ ictx, _ := context.WithTimeout(ctx, s.logParameters.Deadline)
+ sth, err := s.Latest(ictx)
+ if err != nil {
+ glog.Warningf("cannot rotate without new sth: Latest: %v", err)
+ return
+ }
+ // rotate
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ s.rotate(sth)
+ // TODO: persist cosigned STH?
+ })
+}
+
+// rotate rotates the log's cosigned and stable STH. The caller must aquire the
+// source's read-write lock if there are concurrent reads and/or writes.
+func (s *ActiveSthSource) rotate(fixedSth *StItem) {
+ // rotate stable -> cosigned
+ if reflect.DeepEqual(&s.currSth.CosignedTreeHeadV1.SignedTreeHeadV1, &s.nextSth.CosignedTreeHeadV1.SignedTreeHeadV1) {
+ for _, sigv1 := range s.currSth.CosignedTreeHeadV1.SignatureV1 {
+ witness := sigv1.Namespace.String()
+ if _, ok := s.cosignatureFrom[witness]; !ok {
+ s.cosignatureFrom[witness] = true
+ s.nextSth.CosignedTreeHeadV1.SignatureV1 = append(s.nextSth.CosignedTreeHeadV1.SignatureV1, sigv1)
+ }
+ }
+ }
+ s.currSth.CosignedTreeHeadV1.SignedTreeHeadV1 = s.nextSth.CosignedTreeHeadV1.SignedTreeHeadV1
+ s.currSth.CosignedTreeHeadV1.SignatureV1 = make([]SignatureV1, len(s.nextSth.CosignedTreeHeadV1.SignatureV1))
+ copy(s.currSth.CosignedTreeHeadV1.SignatureV1, s.nextSth.CosignedTreeHeadV1.SignatureV1)
+
+ // rotate new stable -> stable
+ if !reflect.DeepEqual(&s.nextSth.CosignedTreeHeadV1.SignedTreeHeadV1, fixedSth.SignedTreeHeadV1) {
+ s.nextSth = NewCosignedTreeHeadV1(fixedSth.SignedTreeHeadV1, nil)
+ s.cosignatureFrom = make(map[string]bool)
+ }
+}