aboutsummaryrefslogtreecommitdiff
path: root/sth.go
blob: 1399241aedc1a2d7b5f27a4f389c83f990f45a2d (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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package stfe

import (
	"context"
	"crypto/ed25519"
	"fmt"
	"reflect"
	"sync"

	"github.com/golang/glog"
	"github.com/google/certificate-transparency-go/schedule"
	"github.com/google/trillian"
	ttypes "github.com/google/trillian/types"
	"github.com/system-transparency/stfe/types"
)

// SthSource provides access to the log's (co)signed tree heads
type SthSource interface {
	Latest(context.Context) (*types.SignedTreeHead, error)
	Stable(context.Context) (*types.SignedTreeHead, error)
	Cosigned(context.Context) (*types.SignedTreeHead, error)
	AddCosignature(context.Context, ed25519.PublicKey, *[types.SignatureSize]byte) error
	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
	sync.RWMutex

	// cosigned is the current cosigned tree head that is served
	cosigned types.SignedTreeHead

	// tosign is the current tree head that is being cosigned
	tosign types.SignedTreeHead

	// cosignature keeps track of all collected cosignatures for tosign
	cosignature map[[types.HashSize]byte]*types.SigIdent
}

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)
	}

	s.cosigned = *sth
	s.tosign = *sth
	s.cosignature = make(map[[types.HashSize]byte]*types.SigIdent)
	return &s, 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.Lock()
		defer s.Unlock()
		s.rotate(sth)
	})
}

func (s *ActiveSthSource) Latest(ctx context.Context) (*types.SignedTreeHead, error) {
	trsp, err := s.client.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{
		LogId: s.logParameters.TreeId,
	})
	var lr ttypes.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.Sign(NewTreeHeadFromLogRoot(&lr))
}

func (s *ActiveSthSource) Stable(_ context.Context) (*types.SignedTreeHead, error) {
	s.RLock()
	defer s.RUnlock()
	return &s.tosign, nil
}

func (s *ActiveSthSource) Cosigned(_ context.Context) (*types.SignedTreeHead, error) {
	s.RLock()
	defer s.RUnlock()
	return &s.cosigned, nil
}

func (s *ActiveSthSource) AddCosignature(_ context.Context, vk ed25519.PublicKey, sig *[types.SignatureSize]byte) error {
	s.Lock()
	defer s.Unlock()

	if msg := s.tosign.TreeHead.Marshal(); !ed25519.Verify(vk, msg, sig[:]) {
		return fmt.Errorf("Invalid signature for tree head with timestamp: %d", s.tosign.TreeHead.Timestamp)
	}
	witness := types.Hash(vk[:])
	if _, ok := s.cosignature[*witness]; ok {
		glog.V(3).Infof("received cosignature again (duplicate)")
		return nil // duplicate
	}
	s.cosignature[*witness] = &types.SigIdent{
		Signature: sig,
		KeyHash:   witness,
	}
	glog.V(3).Infof("accepted new cosignature")
	return nil
}

// 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(next *types.SignedTreeHead) {
	if reflect.DeepEqual(s.cosigned.TreeHead, s.tosign.TreeHead) {
		for _, sigident := range s.cosigned.SigIdent[1:] { // skip log sigident
			if _, ok := s.cosignature[*sigident.KeyHash]; !ok {
				s.cosignature[*sigident.KeyHash] = sigident
			}
		}
	}
	var cosignatures []*types.SigIdent
	for _, sigident := range s.cosignature {
		cosignatures = append(cosignatures, sigident)
	} // cosignatures contains all cosignatures, even if repeated tree head

	// Update cosigned tree head
	s.cosigned.TreeHead = s.tosign.TreeHead
	s.cosigned.SigIdent = append(s.tosign.SigIdent, cosignatures...)

	// Update to-sign tree head
	s.tosign = *next
	s.cosignature = make(map[[types.HashSize]byte]*types.SigIdent)
	glog.V(3).Infof("rotated sth")
}