github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/integration/log.go (about) 1 // Copyright 2016 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package integration 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/hex" 21 "errors" 22 "fmt" 23 "math/rand" 24 "strings" 25 "time" 26 27 "github.com/golang/glog" 28 "github.com/google/trillian" 29 "github.com/google/trillian/client/backoff" 30 "github.com/google/trillian/merkle" 31 "github.com/google/trillian/merkle/rfc6962" 32 "github.com/google/trillian/types" 33 ) 34 35 // TestParameters bundles up all the settings for a test run 36 type TestParameters struct { 37 treeID int64 38 checkLogEmpty bool 39 queueLeaves bool 40 awaitSequencing bool 41 startLeaf int64 42 leafCount int64 43 uniqueLeaves int64 44 queueBatchSize int 45 sequencerBatchSize int 46 readBatchSize int64 47 sequencingWaitTotal time.Duration 48 sequencingPollWait time.Duration 49 rpcRequestDeadline time.Duration 50 customLeafPrefix string 51 } 52 53 // DefaultTestParameters builds a TestParameters object for a normal 54 // test of the given log. 55 func DefaultTestParameters(treeID int64) TestParameters { 56 return TestParameters{ 57 treeID: treeID, 58 checkLogEmpty: true, 59 queueLeaves: true, 60 awaitSequencing: true, 61 startLeaf: 0, 62 leafCount: 1000, 63 uniqueLeaves: 1000, 64 queueBatchSize: 50, 65 sequencerBatchSize: 100, 66 readBatchSize: 50, 67 sequencingWaitTotal: 10 * time.Second * 60, 68 sequencingPollWait: time.Second * 5, 69 rpcRequestDeadline: time.Second * 30, 70 customLeafPrefix: "", 71 } 72 } 73 74 type consistencyProofParams struct { 75 size1 int64 76 size2 int64 77 } 78 79 // inclusionProofTestIndices are the 0 based leaf indices to probe inclusion proofs at. 80 var inclusionProofTestIndices = []int64{5, 27, 31, 80, 91} 81 82 // consistencyProofTestParams are the intervals 83 // to test proofs at 84 var consistencyProofTestParams = []consistencyProofParams{{1, 2}, {2, 3}, {1, 3}, {2, 4}} 85 86 // consistencyProofBadTestParams are the intervals to probe for consistency proofs, none of 87 // these should succeed. Zero is not a valid tree size, nor is -1. 10000000 is outside the 88 // range we'll reasonably queue (multiple of batch size). 89 var consistencyProofBadTestParams = []consistencyProofParams{{0, 0}, {-1, 0}, {10000000, 10000000}} 90 91 // RunLogIntegration runs a log integration test using the given client and test 92 // parameters. 93 func RunLogIntegration(client trillian.TrillianLogClient, params TestParameters) error { 94 // Step 1 - Optionally check log starts empty then optionally queue leaves on server 95 if params.checkLogEmpty { 96 glog.Infof("Checking log is empty before starting test") 97 resp, err := getLatestSignedLogRoot(client, params) 98 if err != nil { 99 return fmt.Errorf("failed to get latest log root: %v %v", resp, err) 100 } 101 102 // TODO(gbelvin): Replace with VerifySignedLogRoot 103 var root types.LogRootV1 104 if err := root.UnmarshalBinary(resp.SignedLogRoot.GetLogRoot()); err != nil { 105 return fmt.Errorf("could not read current log root: %v", err) 106 } 107 108 if root.TreeSize > 0 { 109 return fmt.Errorf("expected an empty log but got tree head response: %v", resp) 110 } 111 } 112 113 var leafCounts map[string]int 114 var err error 115 if params.queueLeaves { 116 glog.Infof("Queueing %d leaves to log server ...", params.leafCount) 117 if leafCounts, err = queueLeaves(client, params); err != nil { 118 return fmt.Errorf("failed to queue leaves: %v", err) 119 } 120 } 121 122 // Step 2 - Wait for queue to drain when server sequences, give up if it doesn't happen (optional) 123 if params.awaitSequencing { 124 glog.Infof("Waiting for log to sequence ...") 125 if err := waitForSequencing(params.treeID, client, params); err != nil { 126 return fmt.Errorf("leaves were not sequenced: %v", err) 127 } 128 } 129 130 // Step 3 - Use get entries to read back what was written, check leaves are correct 131 glog.Infof("Reading back leaves from log ...") 132 leafMap, err := readbackLogEntries(params.treeID, client, params, leafCounts) 133 134 if err != nil { 135 return fmt.Errorf("could not read back log entries: %v", err) 136 } 137 138 // Step 4 - Cross validation between log and memory tree root hashes 139 glog.Infof("Checking log STH with our constructed in-memory tree ...") 140 tree, err := buildMemoryMerkleTree(leafMap, params) 141 if err != nil { 142 return err 143 } 144 if err := checkLogRootHashMatches(tree, client, params); err != nil { 145 return fmt.Errorf("log consistency check failed: %v", err) 146 } 147 148 // Now that the basic tree has passed validation we can start testing proofs 149 150 // Step 5 - Test some inclusion proofs 151 glog.Info("Testing inclusion proofs") 152 153 // Ensure log doesn't serve a proof for a leaf index outside the tree size 154 if err := checkInclusionProofLeafOutOfRange(params.treeID, client, params); err != nil { 155 return fmt.Errorf("log served out of range proof (index): %v", err) 156 } 157 158 // Ensure that log doesn't serve a proof for a valid index at a size outside the tree 159 if err := checkInclusionProofTreeSizeOutOfRange(params.treeID, client, params); err != nil { 160 return fmt.Errorf("log served out of range proof (tree size): %v", err) 161 } 162 163 // Probe the log at several leaf indices each with a range of tree sizes 164 for _, testIndex := range inclusionProofTestIndices { 165 if err := checkInclusionProofsAtIndex(testIndex, params.treeID, tree, client, params); err != nil { 166 return fmt.Errorf("log inclusion index: %d proof checks failed: %v", testIndex, err) 167 } 168 } 169 170 // TODO(al): test some inclusion proofs by Merkle hash too. 171 172 // Step 6 - Test some consistency proofs 173 glog.Info("Testing consistency proofs") 174 175 // Make some consistency proof requests that we know should not succeed 176 for _, consistParams := range consistencyProofBadTestParams { 177 if err := checkConsistencyProof(consistParams, params.treeID, tree, client, params, int64(params.queueBatchSize)); err == nil { 178 return fmt.Errorf("log consistency for %v: unexpected proof returned", consistParams) 179 } 180 } 181 182 // Probe the log between some tree sizes we know are included and check the results against 183 // the in memory tree. Request proofs at both STH and non STH sizes unless batch size is one, 184 // when these would be equivalent requests. 185 for _, consistParams := range consistencyProofTestParams { 186 if err := checkConsistencyProof(consistParams, params.treeID, tree, client, params, int64(params.queueBatchSize)); err != nil { 187 return fmt.Errorf("log consistency for %v: proof checks failed: %v", consistParams, err) 188 } 189 190 // Only do this if the batch size changes when halved 191 if params.queueBatchSize > 1 { 192 if err := checkConsistencyProof(consistParams, params.treeID, tree, client, params, int64(params.queueBatchSize/2)); err != nil { 193 return fmt.Errorf("log consistency for %v: proof checks failed (Non STH size): %v", consistParams, err) 194 } 195 } 196 } 197 return nil 198 } 199 200 func queueLeaves(client trillian.TrillianLogClient, params TestParameters) (map[string]int, error) { 201 if params.uniqueLeaves == 0 { 202 params.uniqueLeaves = params.leafCount 203 } 204 205 leaves := []*trillian.LogLeaf{} 206 207 uniqueLeaves := make([]*trillian.LogLeaf, 0, params.uniqueLeaves) 208 for i := int64(0); i < params.uniqueLeaves; i++ { 209 leafNumber := params.startLeaf + i 210 data := []byte(fmt.Sprintf("%sLeaf %d", params.customLeafPrefix, leafNumber)) 211 leaf := &trillian.LogLeaf{ 212 LeafValue: data, 213 ExtraData: []byte(fmt.Sprintf("%sExtra %d", params.customLeafPrefix, leafNumber)), 214 } 215 uniqueLeaves = append(uniqueLeaves, leaf) 216 } 217 218 // We'll shuffle the sent leaves around a bit to see if that breaks things, 219 // but record and log the seed we use so we can reproduce failures. 220 seed := time.Now().UnixNano() 221 rand.Seed(seed) 222 perm := rand.Perm(int(params.leafCount)) 223 glog.Infof("Queueing %d leaves total, built from %d unique leaves, using permutation seed %d", params.leafCount, len(uniqueLeaves), seed) 224 225 counts := make(map[string]int) 226 for l := int64(0); l < params.leafCount; l++ { 227 leaf := uniqueLeaves[int64(perm[l])%params.uniqueLeaves] 228 leaves = append(leaves, leaf) 229 counts[string(leaf.LeafValue)]++ 230 231 if len(leaves) >= params.queueBatchSize || (l+1) == params.leafCount { 232 glog.Infof("Queueing %d leaves...", len(leaves)) 233 234 ctx, cancel := getRPCDeadlineContext(params) 235 b := &backoff.Backoff{ 236 Min: 100 * time.Millisecond, 237 Max: 10 * time.Second, 238 Factor: 2, 239 Jitter: true, 240 } 241 242 err := b.Retry(ctx, func() error { 243 _, err := client.QueueLeaves(ctx, &trillian.QueueLeavesRequest{ 244 LogId: params.treeID, 245 Leaves: leaves, 246 }) 247 return err 248 }) 249 cancel() 250 251 if err != nil { 252 return nil, err 253 } 254 leaves = leaves[:0] // starting new batch 255 } 256 } 257 258 return counts, nil 259 } 260 261 func waitForSequencing(treeID int64, client trillian.TrillianLogClient, params TestParameters) error { 262 endTime := time.Now().Add(params.sequencingWaitTotal) 263 264 glog.Infof("Waiting for sequencing until: %v", endTime) 265 266 for endTime.After(time.Now()) { 267 req := trillian.GetSequencedLeafCountRequest{LogId: treeID} 268 ctx, cancel := getRPCDeadlineContext(params) 269 sequencedLeaves, err := client.GetSequencedLeafCount(ctx, &req) 270 cancel() 271 272 if err != nil { 273 return err 274 } 275 276 glog.Infof("Leaf count: %d", sequencedLeaves.LeafCount) 277 278 if sequencedLeaves.LeafCount >= params.leafCount+params.startLeaf { 279 return nil 280 } 281 282 glog.Infof("Leaves sequenced: %d. Still waiting ...", sequencedLeaves.LeafCount) 283 284 time.Sleep(params.sequencingPollWait) 285 } 286 287 return errors.New("wait time expired") 288 } 289 290 func readbackLogEntries(logID int64, client trillian.TrillianLogClient, params TestParameters, expect map[string]int) (map[int64]*trillian.LogLeaf, error) { 291 // Take a copy of the expect map, since we'll be modifying it: 292 expect = func(m map[string]int) map[string]int { 293 r := make(map[string]int) 294 for k, v := range m { 295 r[k] = v 296 } 297 return r 298 }(expect) 299 300 currentLeaf := int64(0) 301 leafMap := make(map[int64]*trillian.LogLeaf) 302 glog.Infof("Expecting %d unique leaves", len(expect)) 303 304 for currentLeaf < params.leafCount { 305 // We have to allow for the last batch potentially being a short one 306 numLeaves := params.leafCount - currentLeaf 307 308 if numLeaves > params.readBatchSize { 309 numLeaves = params.readBatchSize 310 } 311 312 glog.Infof("Reading %d leaves from %d ...", numLeaves, currentLeaf+params.startLeaf) 313 req := makeGetLeavesByIndexRequest(logID, currentLeaf+params.startLeaf, numLeaves) 314 ctx, cancel := getRPCDeadlineContext(params) 315 response, err := client.GetLeavesByIndex(ctx, req) 316 cancel() 317 318 if err != nil { 319 return nil, err 320 } 321 322 // Check we got the right leaf count 323 if len(response.Leaves) == 0 { 324 return nil, fmt.Errorf("expected %d leaves log returned none", numLeaves) 325 } 326 327 // Check the leaf contents make sense. Can't rely on exact ordering as queue timestamps will be 328 // close between batches and identical within batches. 329 for l := 0; l < len(response.Leaves); l++ { 330 // Check for duplicate leaf index in response data - should not happen 331 leaf := response.Leaves[l] 332 333 lk := string(leaf.LeafValue) 334 expect[lk]-- 335 336 if expect[lk] == 0 { 337 delete(expect, lk) 338 } 339 leafMap[leaf.LeafIndex] = leaf 340 341 hash, err := rfc6962.DefaultHasher.HashLeaf(leaf.LeafValue) 342 if err != nil { 343 return nil, fmt.Errorf("HashLeaf(%v): %v", leaf.LeafValue, err) 344 } 345 346 if got, want := hex.EncodeToString(hash), hex.EncodeToString(leaf.MerkleLeafHash); got != want { 347 return nil, fmt.Errorf("leaf %d hash mismatch expected got: %s want: %s", leaf.LeafIndex, got, want) 348 } 349 350 // Ensure that the ExtraData in the leaf made it through the roundtrip. This was set up when 351 // we queued the leaves. 352 if got, want := hex.EncodeToString(leaf.ExtraData), hex.EncodeToString([]byte(strings.Replace(string(leaf.LeafValue), "Leaf", "Extra", 1))); got != want { 353 return nil, fmt.Errorf("leaf %d extra data got: %s, want:%s (%v)", leaf.LeafIndex, got, want, leaf) 354 } 355 } 356 357 currentLeaf += int64(len(response.Leaves)) 358 } 359 360 // By this point we expect to have seen all the leaves so there should be nothing in the map 361 if len(expect) != 0 { 362 return nil, fmt.Errorf("incorrect leaves read back (+missing, -extra): %v", expect) 363 } 364 365 return leafMap, nil 366 } 367 368 func checkLogRootHashMatches(tree *merkle.InMemoryMerkleTree, client trillian.TrillianLogClient, params TestParameters) error { 369 // Check the STH against the hash we got from our tree 370 resp, err := getLatestSignedLogRoot(client, params) 371 if err != nil { 372 return err 373 } 374 var root types.LogRootV1 375 if err := root.UnmarshalBinary(resp.SignedLogRoot.GetLogRoot()); err != nil { 376 return err 377 } 378 379 // Hash must not be empty and must match the one we built ourselves 380 if got, want := root.RootHash, tree.CurrentRoot().Hash(); !bytes.Equal(got, want) { 381 return fmt.Errorf("root hash mismatch expected got: %x want: %x", got, want) 382 } 383 384 return nil 385 } 386 387 // checkInclusionProofLeafOutOfRange requests an inclusion proof beyond the current tree size. This 388 // should fail 389 func checkInclusionProofLeafOutOfRange(logID int64, client trillian.TrillianLogClient, params TestParameters) error { 390 // Test is a leaf index bigger than the current tree size 391 ctx, cancel := getRPCDeadlineContext(params) 392 proof, err := client.GetInclusionProof(ctx, &trillian.GetInclusionProofRequest{ 393 LogId: logID, 394 LeafIndex: params.leafCount + 1, 395 TreeSize: int64(params.leafCount), 396 }) 397 cancel() 398 399 if err == nil { 400 return fmt.Errorf("log returned proof for leaf index outside tree: %d v %d: %v", params.leafCount+1, params.leafCount, proof) 401 } 402 403 return nil 404 } 405 406 // checkInclusionProofTreeSizeOutOfRange requests an inclusion proof for a leaf within the tree size at 407 // a tree size larger than the current tree size. This should succeed but with an STH for the current 408 // tree and an empty proof, because it is a result of skew. 409 func checkInclusionProofTreeSizeOutOfRange(logID int64, client trillian.TrillianLogClient, params TestParameters) error { 410 // Test is an in range leaf index for a tree size that doesn't exist 411 ctx, cancel := getRPCDeadlineContext(params) 412 req := &trillian.GetInclusionProofRequest{ 413 LogId: logID, 414 LeafIndex: int64(params.sequencerBatchSize), 415 TreeSize: params.leafCount + int64(params.sequencerBatchSize), 416 } 417 proof, err := client.GetInclusionProof(ctx, req) 418 cancel() 419 if err != nil { 420 return fmt.Errorf("log returned error for tree size outside tree: %d v %d: %v", params.leafCount, req.TreeSize, err) 421 } 422 423 var root types.LogRootV1 424 if err := root.UnmarshalBinary(proof.SignedLogRoot.LogRoot); err != nil { 425 return fmt.Errorf("could not read current log root: %v", err) 426 } 427 428 if proof.Proof != nil { 429 return fmt.Errorf("log returned proof for tree size outside tree: %d v %d: %v", params.leafCount, req.TreeSize, proof) 430 } 431 if int64(root.TreeSize) >= req.TreeSize { 432 return fmt.Errorf("log returned bad root for tree size outside tree: %d v %d: %v", params.leafCount, req.TreeSize, proof) 433 } 434 435 return nil 436 } 437 438 // checkInclusionProofsAtIndex obtains and checks proofs at tree sizes from zero up to 2 x the sequencing 439 // batch size (or number of leaves queued if less). The log should only serve proofs for indices in a tree 440 // at least as big as the index where STHs where the index is a multiple of the sequencer batch size. All 441 // proofs returned should match ones computed by the alternate Merkle Tree implementation, which differs 442 // from what the log uses. 443 func checkInclusionProofsAtIndex(index int64, logID int64, tree *merkle.InMemoryMerkleTree, client trillian.TrillianLogClient, params TestParameters) error { 444 for treeSize := int64(0); treeSize < min(params.leafCount, int64(2*params.sequencerBatchSize)); treeSize++ { 445 ctx, cancel := getRPCDeadlineContext(params) 446 resp, err := client.GetInclusionProof(ctx, &trillian.GetInclusionProofRequest{ 447 LogId: logID, 448 LeafIndex: index, 449 TreeSize: int64(treeSize), 450 }) 451 cancel() 452 453 // If the index is larger than the tree size we cannot have a valid proof 454 shouldHaveProof := index < treeSize 455 if got, want := err == nil, shouldHaveProof; got != want { 456 return fmt.Errorf("GetInclusionProof(index: %d, treeSize %d): %v, want nil: %v", index, treeSize, err, want) 457 } 458 if !shouldHaveProof { 459 continue 460 } 461 462 // Verify inclusion proof. 463 root := tree.RootAtSnapshot(treeSize).Hash() 464 verifier := merkle.NewLogVerifier(rfc6962.DefaultHasher) 465 // Offset by 1 to make up for C++ / Go implementation differences. 466 merkleLeafHash := tree.LeafHash(index + 1) 467 if err := verifier.VerifyInclusionProof(index, treeSize, resp.Proof.Hashes, root, merkleLeafHash); err != nil { 468 return err 469 } 470 } 471 472 return nil 473 } 474 475 func checkConsistencyProof(consistParams consistencyProofParams, treeID int64, tree *merkle.InMemoryMerkleTree, client trillian.TrillianLogClient, params TestParameters, batchSize int64) error { 476 // We expect the proof request to succeed 477 ctx, cancel := getRPCDeadlineContext(params) 478 req := &trillian.GetConsistencyProofRequest{ 479 LogId: treeID, 480 FirstTreeSize: consistParams.size1 * int64(batchSize), 481 SecondTreeSize: consistParams.size2 * int64(batchSize), 482 } 483 resp, err := client.GetConsistencyProof(ctx, req) 484 cancel() 485 if err != nil { 486 return fmt.Errorf("GetConsistencyProof(%v) = %v %v", consistParams, err, resp) 487 } 488 489 if resp.SignedLogRoot == nil || resp.SignedLogRoot.LogRoot == nil { 490 return fmt.Errorf("received invalid response: %v", resp) 491 } 492 var root types.LogRootV1 493 if err := root.UnmarshalBinary(resp.SignedLogRoot.LogRoot); err != nil { 494 return fmt.Errorf("could not read current log root: %v", err) 495 } 496 497 if req.SecondTreeSize > int64(root.TreeSize) { 498 return fmt.Errorf("requested tree size %d > available tree size %d", req.SecondTreeSize, root.TreeSize) 499 } 500 501 verifier := merkle.NewLogVerifier(rfc6962.DefaultHasher) 502 root1 := tree.RootAtSnapshot(req.FirstTreeSize).Hash() 503 root2 := tree.RootAtSnapshot(req.SecondTreeSize).Hash() 504 return verifier.VerifyConsistencyProof(req.FirstTreeSize, req.SecondTreeSize, 505 root1, root2, resp.Proof.Hashes) 506 } 507 508 func makeGetLeavesByIndexRequest(logID int64, startLeaf, numLeaves int64) *trillian.GetLeavesByIndexRequest { 509 leafIndices := make([]int64, 0, numLeaves) 510 511 for l := int64(0); l < numLeaves; l++ { 512 leafIndices = append(leafIndices, l+startLeaf) 513 } 514 515 return &trillian.GetLeavesByIndexRequest{LogId: logID, LeafIndex: leafIndices} 516 } 517 518 func buildMemoryMerkleTree(leafMap map[int64]*trillian.LogLeaf, params TestParameters) (*merkle.InMemoryMerkleTree, error) { 519 // Build the same tree with two different Merkle implementations as an additional check. We don't 520 // just rely on the compact tree as the server uses the same code so bugs could be masked 521 compactTree := merkle.NewCompactMerkleTree(rfc6962.DefaultHasher) 522 merkleTree := merkle.NewInMemoryMerkleTree(rfc6962.DefaultHasher) 523 524 // We use the leafMap as we need to use the same order for the memory tree to get the same hash. 525 for l := params.startLeaf; l < params.leafCount; l++ { 526 compactTree.AddLeaf(leafMap[l].LeafValue, func(int, int64, []byte) error { 527 return nil 528 }) 529 if _, _, err := merkleTree.AddLeaf(leafMap[l].LeafValue); err != nil { 530 return nil, err 531 } 532 } 533 534 // If the two reference results disagree there's no point in continuing the checks. This is a 535 // "can't happen" situation. 536 if !bytes.Equal(compactTree.CurrentRoot(), merkleTree.CurrentRoot().Hash()) { 537 return nil, fmt.Errorf("different root hash results from merkle tree building: %v and %v", compactTree.CurrentRoot(), merkleTree.CurrentRoot()) 538 } 539 540 return merkleTree, nil 541 } 542 543 func getLatestSignedLogRoot(client trillian.TrillianLogClient, params TestParameters) (*trillian.GetLatestSignedLogRootResponse, error) { 544 req := trillian.GetLatestSignedLogRootRequest{LogId: params.treeID} 545 ctx, cancel := getRPCDeadlineContext(params) 546 resp, err := client.GetLatestSignedLogRoot(ctx, &req) 547 cancel() 548 549 return resp, err 550 } 551 552 // getRPCDeadlineTime calculates the future time an RPC should expire based on our config 553 func getRPCDeadlineContext(params TestParameters) (context.Context, context.CancelFunc) { 554 return context.WithDeadline(context.Background(), time.Now().Add(params.rpcRequestDeadline)) 555 } 556 557 func min(a, b int64) int64 { 558 if a < b { 559 return a 560 } 561 562 return b 563 }