Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.

Commit 9b99af5

Browse files
authored
Monitor verification logic (#768)
* reintroduce verification * monitor integration tests * quickfix for mutator bug
1 parent 3a237b6 commit 9b99af5

File tree

8 files changed

+361
-39
lines changed

8 files changed

+361
-39
lines changed

CONTRIBUTORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ Antonio Marcedone <a.marcedone@gmail.com>
1313
Cesar Ghali <cghali@uci.edu>
1414
Daniel Ziegler <dmz@yahoo-inc.com>
1515
Gary Belvin <gdbelvin@gmail.com>
16+
Ismail Khoffi <Ismail.Khoffi@gmail.com>

cmd/keytransparency-monitor/main.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ import (
4242
spb "github.com/google/keytransparency/impl/proto/keytransparency_v1_service"
4343
mopb "github.com/google/keytransparency/impl/proto/monitor_v1_service"
4444
mupb "github.com/google/keytransparency/impl/proto/mutation_v1_service"
45-
_ "github.com/google/trillian/merkle/coniks" // Register coniks
45+
tlogcli "github.com/google/trillian/client"
46+
"github.com/google/trillian/crypto/keys/der"
47+
_ "github.com/google/trillian/merkle/coniks" // Register coniks
48+
"github.com/google/trillian/merkle/hashers"
4649
_ "github.com/google/trillian/merkle/objhasher" // Register objhasher
4750
)
4851

@@ -141,13 +144,22 @@ func main() {
141144
// Insert handlers for other http paths here.
142145
mux := http.NewServeMux()
143146
mux.Handle("/", gwmux)
147+
logHasher, err := hashers.NewLogHasher(logTree.GetHashStrategy())
148+
if err != nil {
149+
glog.Fatalf("Could not initialize log hasher: %v", err)
150+
}
151+
logPubKey, err := der.UnmarshalPublicKey(logTree.GetPublicKey().GetDer())
152+
if err != nil {
153+
glog.Fatalf("Failed parsing Log public key: %v", err)
154+
}
155+
logVerifier := tlogcli.NewLogVerifier(logHasher, logPubKey)
144156

145-
// initialize the mutations API client and feed the responses it got
146-
// into the monitor:
147-
mon, err := cmon.New(logTree, mapTree, crypto.NewSHA256Signer(key), store)
157+
mon, err := cmon.New(logVerifier, mapTree, crypto.NewSHA256Signer(key), store)
148158
if err != nil {
149159
glog.Exitf("Failed to initialize monitor: %v", err)
150160
}
161+
// initialize the mutations API client and feed the responses it got
162+
// into the monitor:
151163
mutCli := client.New(mcc, *pollPeriod)
152164
responses, errs := mutCli.StartPolling(1)
153165
go func() {

core/monitor/monitor.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,39 +25,39 @@ import (
2525
ktpb "github.com/google/keytransparency/core/proto/keytransparency_v1_types"
2626

2727
"github.com/google/trillian"
28+
"github.com/google/trillian/client"
2829
tcrypto "github.com/google/trillian/crypto"
29-
"github.com/google/trillian/merkle"
30+
"github.com/google/trillian/crypto/keys/der"
3031
"github.com/google/trillian/merkle/hashers"
3132
)
3233

3334
// Monitor holds the internal state for a monitor accessing the mutations API
3435
// and for verifying its responses.
3536
type Monitor struct {
36-
hasher hashers.MapHasher
37-
logPubKey crypto.PublicKey
37+
mapID int64
38+
mapHasher hashers.MapHasher
3839
mapPubKey crypto.PublicKey
39-
logVerifier merkle.LogVerifier
40+
logVerifier client.LogVerifier
4041
signer *tcrypto.Signer
41-
// TODO(ismail): update last trusted signed log root
42-
//trusted trillian.SignedLogRoot
43-
store *storage.Storage
42+
trusted *trillian.SignedLogRoot
43+
store *storage.Storage
4444
}
4545

4646
// New creates a new instance of the monitor.
47-
func New(logTree, mapTree *trillian.Tree, signer *tcrypto.Signer, store *storage.Storage) (*Monitor, error) {
48-
logHasher, err := hashers.NewLogHasher(logTree.GetHashStrategy())
49-
if err != nil {
50-
return nil, fmt.Errorf("Failed creating LogHasher: %v", err)
51-
}
47+
func New(logverifierCli client.LogVerifier, mapTree *trillian.Tree, signer *tcrypto.Signer, store *storage.Storage) (*Monitor, error) {
5248
mapHasher, err := hashers.NewMapHasher(mapTree.GetHashStrategy())
5349
if err != nil {
5450
return nil, fmt.Errorf("Failed creating MapHasher: %v", err)
5551
}
52+
mapPubKey, err := der.UnmarshalPublicKey(mapTree.GetPublicKey().GetDer())
53+
if err != nil {
54+
return nil, fmt.Errorf("Could not unmarshal map public key: %v", err)
55+
}
5656
return &Monitor{
57-
hasher: mapHasher,
58-
logVerifier: merkle.NewLogVerifier(logHasher),
59-
logPubKey: logTree.GetPublicKey(),
60-
mapPubKey: mapTree.GetPublicKey(),
57+
logVerifier: logverifierCli,
58+
mapID: mapTree.TreeId,
59+
mapHasher: mapHasher,
60+
mapPubKey: mapPubKey,
6161
signer: signer,
6262
store: store,
6363
}, nil
@@ -70,7 +70,7 @@ func (m *Monitor) Process(resp *ktpb.GetMutationsResponse) error {
7070
var smr *trillian.SignedMapRoot
7171
var err error
7272
seen := time.Now().Unix()
73-
errs := m.verifyMutationsResponse(resp)
73+
errs := m.VerifyMutationsResponse(resp)
7474
if len(errs) == 0 {
7575
glog.Infof("Successfully verified mutations response for epoch: %v", resp.Epoch)
7676
smr, err = m.signMapRoot(resp)

core/monitor/verify.go

Lines changed: 180 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,190 @@
1818
package monitor
1919

2020
import (
21+
"bytes"
22+
"encoding/json"
23+
"errors"
24+
"math/big"
25+
26+
"github.com/google/keytransparency/core/mutator/entry"
27+
28+
"github.com/golang/glog"
29+
tcrypto "github.com/google/trillian/crypto"
30+
"github.com/google/trillian/merkle"
31+
"github.com/google/trillian/storage"
32+
2133
ktpb "github.com/google/keytransparency/core/proto/keytransparency_v1_types"
2234
)
2335

24-
// verifyMutationsResponse verifies a response received by the GetMutations API.
36+
var (
37+
// ErrInconsistentProofs occurs when the server returned different hashes
38+
// for the same inclusion proof node in the tree.
39+
ErrInconsistentProofs = errors.New("inconsistent inclusion proofs")
40+
// ErrInvalidLogConsistencyProof occurs when the log consistency proof does
41+
// not verify.
42+
ErrInvalidLogConsistencyProof = errors.New("invalid log consistency proof")
43+
// ErrInvalidLogInclusion occurs if the inclusion proof for the signed map
44+
// root into the log does not verify.
45+
ErrInvalidLogInclusion = errors.New("invalid log inclusion proof")
46+
// ErrInvalidLogSignature occurs if the log roots signature does not verify.
47+
ErrInvalidLogSignature = errors.New("invalid signature on log root")
48+
// ErrInvalidMapSignature occurs if the map roots signature does not verify.
49+
ErrInvalidMapSignature = errors.New("invalid signature on map root")
50+
// ErrInvalidMutation occurs when verification failed because of an invalid
51+
// mutation.
52+
ErrInvalidMutation = errors.New("invalid mutation")
53+
// ErrNotMatchingMapRoot occurs when the reconstructed root differs from the
54+
// one we received from the server.
55+
ErrNotMatchingMapRoot = errors.New("recreated root does not match")
56+
)
57+
58+
// VerifyMutationsResponse verifies a response received by the GetMutations API.
2559
// Additionally to the response it takes a complete list of mutations. The list
2660
// of received mutations may differ from those included in the initial response
27-
// because of the max. page size. If any verification check failed it returns
28-
// an error.
29-
func (m *Monitor) verifyMutationsResponse(in *ktpb.GetMutationsResponse) []error {
61+
// because of the max. page size.
62+
func (m *Monitor) VerifyMutationsResponse(in *ktpb.GetMutationsResponse) []error {
63+
errList := make([]error, 0)
64+
65+
if m.trusted == nil {
66+
m.trusted = in.GetLogRoot()
67+
}
68+
69+
if err := m.logVerifier.VerifyRoot(m.trusted, in.GetLogRoot(), in.GetLogConsistency()); err != nil {
70+
// this could be one of ErrInvalidLogSignature, ErrInvalidLogConsistencyProof
71+
errList = append(errList, err)
72+
}
73+
// updated trusted log root
74+
m.trusted = in.GetLogRoot()
75+
76+
b, err := json.Marshal(in.GetSmr())
77+
if err != nil {
78+
glog.Errorf("json.Marshal(): %v", err)
79+
errList = append(errList, ErrInvalidMapSignature)
80+
}
81+
leafIndex := in.GetSmr().GetMapRevision()
82+
treeSize := in.GetLogRoot().GetTreeSize()
83+
err = m.logVerifier.VerifyInclusionAtIndex(in.GetLogRoot(), b, leafIndex, in.GetLogInclusion())
84+
if err != nil {
85+
glog.Errorf("m.logVerifier.VerifyInclusionAtIndex((%v, %v, _): %v", leafIndex, treeSize, err)
86+
errList = append(errList, ErrInvalidLogInclusion)
87+
}
88+
89+
// copy of singed map root
90+
smr := *in.GetSmr()
91+
// reset to the state before it was signed:
92+
smr.Signature = nil
93+
// verify signature on map root:
94+
if err := tcrypto.VerifyObject(m.mapPubKey, smr, in.GetSmr().GetSignature()); err != nil {
95+
glog.Infof("couldn't verify signature on map root: %v", err)
96+
errList = append(errList, ErrInvalidMapSignature)
97+
}
98+
99+
// we need the old root for verifying the inclusion of the old leafs in the
100+
// previous epoch. Storage always stores the mutations response independent
101+
// from if the checks succeeded or not.
102+
var oldRoot []byte
103+
// mutations happen after epoch 1 which is stored in storage:
104+
if m.store.LatestEpoch() > 0 {
105+
// retrieve the old root hash from storage
106+
monRes, err := m.store.Get(in.Epoch - 1)
107+
if err != nil {
108+
glog.Infof("Could not retrieve previous monitoring result: %v", err)
109+
}
110+
oldRoot = monRes.Response.GetSmr().GetRootHash()
111+
112+
if err := m.verifyMutations(in.GetMutations(), oldRoot,
113+
in.GetSmr().GetRootHash(), in.GetSmr().GetMapId()); len(err) > 0 {
114+
errList = append(errList, err...)
115+
}
116+
}
117+
118+
return errList
119+
}
120+
121+
func (m *Monitor) verifyMutations(muts []*ktpb.Mutation, oldRoot, expectedNewRoot []byte, mapID int64) []error {
122+
errList := make([]error, 0)
123+
mutator := entry.New()
124+
oldProofNodes := make(map[string][]byte)
125+
newLeaves := make([]merkle.HStar2LeafHash, 0, len(muts))
126+
glog.Infof("verifyMutations() called with %v mutations.", len(muts))
127+
128+
for _, mut := range muts {
129+
oldLeaf, err := entry.FromLeafValue(mut.GetProof().GetLeaf().GetLeafValue())
130+
if err != nil {
131+
errList = append(errList, ErrInvalidMutation)
132+
}
133+
134+
// verify that the provided leaf’s inclusion proof goes to epoch e-1:
135+
index := mut.GetProof().GetLeaf().GetIndex()
136+
leaf := mut.GetProof().GetLeaf().GetLeafValue()
137+
if err := merkle.VerifyMapInclusionProof(mapID, index,
138+
leaf, oldRoot, mut.GetProof().GetInclusion(), m.mapHasher); err != nil {
139+
glog.Infof("VerifyMapInclusionProof(%x): %v", index, err)
140+
errList = append(errList, ErrInvalidMutation)
141+
}
142+
143+
// compute the new leaf
144+
newLeaf, err := mutator.Mutate(oldLeaf, mut.GetUpdate())
145+
if err != nil {
146+
glog.Infof("Mutation did not verify: %v", err)
147+
errList = append(errList, ErrInvalidMutation)
148+
}
149+
newLeafnID := storage.NewNodeIDFromPrefixSuffix(index, storage.Suffix{}, m.mapHasher.BitLen())
150+
newLeafHash := m.mapHasher.HashLeaf(mapID, index, newLeaf)
151+
newLeaves = append(newLeaves, merkle.HStar2LeafHash{
152+
Index: newLeafnID.BigInt(),
153+
LeafHash: newLeafHash,
154+
})
155+
156+
// store the proof hashes locally to recompute the tree below:
157+
sibIDs := newLeafnID.Siblings()
158+
proofs := mut.GetProof().GetInclusion()
159+
for level, sibID := range sibIDs {
160+
proof := proofs[level]
161+
if p, ok := oldProofNodes[sibID.String()]; ok {
162+
// sanity check: for each mut overlapping proof nodes should be
163+
// equal:
164+
if !bytes.Equal(p, proof) {
165+
// this is really odd and should never happen
166+
errList = append(errList, ErrInconsistentProofs)
167+
}
168+
} else {
169+
if len(proof) > 0 {
170+
oldProofNodes[sibID.String()] = proof
171+
}
172+
}
173+
}
174+
}
175+
176+
if err := m.validateMapRoot(expectedNewRoot, mapID, newLeaves, oldProofNodes); err != nil {
177+
errList = append(errList, err)
178+
}
179+
180+
return errList
181+
}
182+
183+
func (m *Monitor) validateMapRoot(expectedRoot []byte, mapID int64, mutatedLeaves []merkle.HStar2LeafHash, oldProofNodes map[string][]byte) error {
184+
// compute the new root using local intermediate hashes from epoch e
185+
// (above proof hashes):
186+
hs2 := merkle.NewHStar2(mapID, m.mapHasher)
187+
newRoot, err := hs2.HStar2Nodes([]byte{}, m.mapHasher.BitLen(), mutatedLeaves,
188+
func(depth int, index *big.Int) ([]byte, error) {
189+
nID := storage.NewNodeIDFromBigInt(depth, index, m.mapHasher.BitLen())
190+
if p, ok := oldProofNodes[nID.String()]; ok {
191+
return p, nil
192+
}
193+
return nil, nil
194+
}, nil)
195+
196+
if err != nil {
197+
glog.Errorf("hs2.HStar2Nodes(_): %v", err)
198+
return ErrNotMatchingMapRoot
199+
}
200+
201+
// verify rootHash
202+
if !bytes.Equal(newRoot, expectedRoot) {
203+
return ErrNotMatchingMapRoot
204+
}
205+
30206
return nil
31207
}

core/mutation/mutation.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,18 +106,24 @@ func (s *Server) GetMutations(ctx context.Context, in *tpb.GetMutationsRequest)
106106
}
107107
// Get leaf proofs.
108108
// TODO: allow leaf proofs to be optional.
109-
proofs, err := s.inclusionProofs(ctx, indexes, in.Epoch)
109+
var epoch int64
110+
if in.Epoch > 1 {
111+
epoch = in.Epoch - 1
112+
} else {
113+
epoch = 1
114+
}
115+
proofs, err := s.inclusionProofs(ctx, indexes, epoch)
110116
if err != nil {
111117
return nil, err
112118
}
113119
for i, p := range proofs {
114120
mutations[i].Proof = p
115121
}
116122

117-
// MapRevisions start at 1. Log leave's index starts at 0.
123+
// MapRevisions start at 1. Log leave's index starts at 1.
118124
// MapRevision should be at least 1 since the Signer is
119125
// supposed to create at least one revision on startup.
120-
respEpoch := resp.GetMapRoot().GetMapRevision() - 1
126+
respEpoch := resp.GetMapRoot().GetMapRevision()
121127
// Fetch log proofs.
122128
logRoot, logConsistency, logInclusion, err := s.logProofs(ctx, in.GetFirstTreeSize(), respEpoch)
123129
if err != nil {

impl/monitor/client/client.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ func (c *Client) StartPolling(startEpoch int64) (<-chan *ktpb.GetMutationsRespon
6464
glog.Infof("Polling: %v", now)
6565
// time out if we exceed the poll period:
6666
ctx, _ := context.WithTimeout(context.Background(), c.pollPeriod)
67-
monitorResp, err := c.pollMutations(ctx, epoch)
67+
monitorResp, err := c.PollMutations(ctx, epoch)
6868
if err != nil {
69-
glog.Infof("pollMutations(_): %v", err)
69+
glog.Infof("PollMutations(_): %v", err)
7070
errChan <- err
7171
} else {
7272
// only write to results channel and increment epoch
@@ -80,7 +80,9 @@ func (c *Client) StartPolling(startEpoch int64) (<-chan *ktpb.GetMutationsRespon
8080
return response, errChan
8181
}
8282

83-
func (c *Client) pollMutations(ctx context.Context,
83+
// PollMutations polls GetMutationsResponses from the configured
84+
// key-transparency server's mutations API.
85+
func (c *Client) PollMutations(ctx context.Context,
8486
queryEpoch int64,
8587
opts ...grpc.CallOption) (*ktpb.GetMutationsResponse, error) {
8688
response, err := c.client.GetMutations(ctx, &ktpb.GetMutationsRequest{

0 commit comments

Comments
 (0)