Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(665)

Unified Diff: net/cert/ct_log_verifier_unittest.cc

Issue 2182533002: Adds a VerifyAuditProof method to CTLogVerifier (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addresses some of Ryan Sleevi's comments Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/cert/ct_log_verifier_unittest.cc
diff --git a/net/cert/ct_log_verifier_unittest.cc b/net/cert/ct_log_verifier_unittest.cc
index fb342f4b1116d50ac03676e160101249f0b78cf1..5be67a237ed7823256335ebb7fbb269a878bf6eb 100644
--- a/net/cert/ct_log_verifier_unittest.cc
+++ b/net/cert/ct_log_verifier_unittest.cc
@@ -10,11 +10,13 @@
#include <string>
#include <vector>
+#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "crypto/secure_hash.h"
#include "net/base/hash_value.h"
#include "net/cert/ct_log_verifier_util.h"
+#include "net/cert/merkle_audit_proof.h"
#include "net/cert/merkle_consistency_proof.h"
#include "net/cert/signed_certificate_timestamp.h"
#include "net/cert/signed_tree_head.h"
@@ -53,6 +55,17 @@ struct ProofTestVector {
const char* const kSHA256EmptyTreeHash =
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+// Hashes of the leaf data for the sample tree (8 leaves).
+const char* const kSHA256LeafHashes[8] = {
+ "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d",
+ "96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7",
+ "0298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7",
+ "07506a85fd9dd2f120eb694f86011e5bb4662e5c415a62917033d4a9624487e7",
+ "bc1a0643b12e4d2d7c77918f44e0f4f79a838b6cf9ec5b5c283e1f4d88599e6b",
+ "4271a26be0d8a84f0bd54c8c302e7cb3a3b5d1fa6780a40bcce2873477dab658",
+ "b08693ec2e721597130641e8211e7eedccb4c26413963eee6c1e2ed16ffb1a5f",
+ "46f6ffadd3d06a09ff3c5860d2755c8b9819db7df44251788c7d8e3180de8eb1"};
+
// Root hashes from building the sample tree of size 8 leaf-by-leaf.
// The first entry is the root at size 0, the last is the root at size 8.
const char* const kSHA256Roots[8] = {
@@ -94,6 +107,45 @@ const ProofTestVector kSHA256Proofs[4] = {
{"5f083f0a1a33ca076a95279832580db3e0ef4584bdff1f54c8a360f50de3031e",
"bc1a0643b12e4d2d7c77918f44e0f4f79a838b6cf9ec5b5c283e1f4d88599e6b", ""}}};
+// A single audit proof. Contains the leaf index (leaf), tree size (snapshot),
+// the length of the proof (proof_length) and at most 3 proof nodes (all test
+// proofs will be for a tree of size 8).
+struct PathTestVector {
+ uint64_t leaf;
+ uint64_t snapshot;
+ size_t path_length;
+ const char* const path[3];
+};
+
+// A collection of audit proofs for various leaves and sub-trees of the tree
+// defined by |kSHA256Roots|.
+const PathTestVector kSHA256Paths[6] = {
+ {0, 0, 0, {"", "", ""}},
+ {1, 1, 0, {"", "", ""}},
+ {1,
+ 8,
+ 3,
+ {"96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7",
+ "5f083f0a1a33ca076a95279832580db3e0ef4584bdff1f54c8a360f50de3031e",
+ "6b47aaf29ee3c2af9af889bc1fb9254dabd31177f16232dd6aab035ca39bf6e4"}},
+ {6,
+ 8,
+ 3,
+ {"bc1a0643b12e4d2d7c77918f44e0f4f79a838b6cf9ec5b5c283e1f4d88599e6b",
+ "ca854ea128ed050b41b35ffc1b87b8eb2bde461e9e3b5596ece6b9d5975a0ae0",
+ "d37ee418976dd95753c1c73862b9398fa2a2cf9b4ff0fdfe8b30cd95209614b7"}},
+ {3,
+ 3,
+ 1,
+ {"fac54203e7cc696cf0dfcb42c92a1d9dbaf70ad9e621f4bd8d98662f00e3c125", "",
+ ""}},
+ {2,
+ 5,
+ 3,
+ {"6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d",
+ "5f083f0a1a33ca076a95279832580db3e0ef4584bdff1f54c8a360f50de3031e",
+ "bc1a0643b12e4d2d7c77918f44e0f4f79a838b6cf9ec5b5c283e1f4d88599e6b"}}};
+
// Decodes a hexadecimal string into the binary data it represents.
std::string HexToBytes(const std::string& hex_data) {
std::vector<uint8_t> output;
@@ -122,6 +174,21 @@ bool VerifyConsistencyProof(scoped_refptr<const CTLogVerifier> log,
old_tree_root, new_tree_root);
}
+bool VerifyAuditProof(scoped_refptr<const CTLogVerifier> log,
Ryan Sleevi 2016/08/25 18:56:24 STYLE: https://chromium.googlesource.com/chromium/
Rob Percival 2016/10/03 17:39:26 Done.
+ uint64_t leaf,
+ uint64_t tree_size,
+ const std::vector<std::string>& proof,
+ const std::string& tree_root,
+ const std::string& leaf_hash) {
+ // Test vectors use a 1-based leaf index, but CTLogVerifier uses a 0-based
+ // index. We need to map |leaf| to a 0-based index but there is no mapping for
+ // 0, so reject it. Subtract 1 from any other value.
+ if (leaf == 0)
+ return false;
+ return log->VerifyAuditProof(ct::MerkleAuditProof(leaf - 1, tree_size, proof),
+ tree_root, leaf_hash);
+}
+
class CTLogVerifierTest : public ::testing::Test {
public:
CTLogVerifierTest() {}
@@ -135,6 +202,76 @@ class CTLogVerifierTest : public ::testing::Test {
ASSERT_EQ("ct.example.com", log_->dns_domain());
}
+ // Given an audit proof for a leaf in the tree, asserts that it verifies and
+ // no other combination of leaves, snapshots and proof nodes verifies.
+ void VerifierCheck(int leaf,
+ int tree_size,
+ const std::vector<std::string>& path,
+ const std::string& root,
+ const std::string& leaf_hash) {
+ // Verify the original path.
+ EXPECT_TRUE(VerifyAuditProof(log_, leaf, tree_size, path, root, leaf_hash));
+
+ // Wrong leaf index.
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf - 1, tree_size, path, root, leaf_hash));
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf + 1, tree_size, path, root, leaf_hash));
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf ^ 2, tree_size, path, root, leaf_hash));
+
+ // Wrong tree height.
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf, tree_size * 2, path, root, leaf_hash));
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf, tree_size / 2, path, root, leaf_hash));
+
+ // Wrong root.
+ EXPECT_FALSE(VerifyAuditProof(log_, leaf, tree_size, path,
+ GetEmptyTreeHash(), leaf_hash));
+
+ // Wrong paths.
+ std::vector<std::string> wrong_path;
+
+ // Modify a single element on the path.
+ for (size_t j = 0; j < path.size(); ++j) {
+ wrong_path = path;
+ wrong_path[j] = HexToBytes(kSHA256EmptyTreeHash);
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf, tree_size, wrong_path, root, leaf_hash));
+ }
+
+ // Add garbage at the end of the path.
+ wrong_path = path;
+ wrong_path.push_back(std::string());
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf, tree_size, wrong_path, root, leaf_hash));
+ wrong_path.pop_back();
+
+ wrong_path.push_back(root);
Ryan Sleevi 2016/08/25 18:56:24 PERFORMANCE: Since you were concerned about perf i
Rob Percival 2016/10/03 17:39:25 Done.
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf, tree_size, wrong_path, root, leaf_hash));
+ wrong_path.pop_back();
+
+ // Remove a node from the end.
+ if (!wrong_path.empty()) {
+ wrong_path.pop_back();
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf, tree_size, wrong_path, root, leaf_hash));
+ }
+
+ // Add garbage in the beginning of the path.
+ wrong_path.clear();
+ wrong_path.push_back(std::string());
+ wrong_path.insert(wrong_path.end(), path.begin(), path.end());
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf, tree_size, wrong_path, root, leaf_hash));
+
+ wrong_path[0] = root;
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, leaf, tree_size, wrong_path, root, leaf_hash));
+ }
+
// Given a consistency proof between two snapshots of the tree, asserts that
// it verifies and no other combination of snapshots and proof nodes verifies.
void VerifierConsistencyCheck(int snapshot1,
@@ -361,27 +498,28 @@ TEST_F(CTLogVerifierTest,
empty_tree_hash, proof));
}
-TEST_F(CTLogVerifierTest, VerifiesValidConsistencyProofs) {
+class CTLogVerifierConsistencyProofTest
+ : public CTLogVerifierTest,
+ public ::testing::WithParamInterface<size_t> {};
Ryan Sleevi 2016/08/25 18:56:24 <size_t /* tree_size */> ?
Rob Percival 2016/10/03 17:39:25 Done.
+
+TEST_P(CTLogVerifierConsistencyProofTest, VerifiesValidConsistencyProof) {
+ const size_t i = GetParam();
std::vector<std::string> proof;
- std::string root1, root2;
-
- // Known good proofs.
- for (size_t i = 0; i < arraysize(kSHA256Proofs); ++i) {
- SCOPED_TRACE(i);
- proof.clear();
- for (size_t j = 0; j < kSHA256Proofs[i].proof_length; ++j) {
- const char* const v = kSHA256Proofs[i].proof[j];
- proof.push_back(HexToBytes(v));
- }
- const uint64_t snapshot1 = kSHA256Proofs[i].snapshot1;
- const uint64_t snapshot2 = kSHA256Proofs[i].snapshot2;
- const char* const old_root = kSHA256Roots[snapshot1 - 1];
- const char* const new_root = kSHA256Roots[snapshot2 - 1];
- VerifierConsistencyCheck(snapshot1, snapshot2, HexToBytes(old_root),
- HexToBytes(new_root), proof);
+ for (size_t j = 0; j < kSHA256Proofs[i].proof_length; ++j) {
+ proof.push_back(HexToBytes(kSHA256Proofs[i].proof[j]));
}
+ const uint64_t snapshot1 = kSHA256Proofs[i].snapshot1;
+ const uint64_t snapshot2 = kSHA256Proofs[i].snapshot2;
+ const char* const old_root = kSHA256Roots[snapshot1 - 1];
+ const char* const new_root = kSHA256Roots[snapshot2 - 1];
+ VerifierConsistencyCheck(snapshot1, snapshot2, HexToBytes(old_root),
+ HexToBytes(new_root), proof);
}
+INSTANTIATE_TEST_CASE_P(KnownGoodProofs,
+ CTLogVerifierConsistencyProofTest,
+ ::testing::Range(0ul, arraysize(kSHA256Proofs)));
+
const char kLeafPrefix[] = {'\x00'};
// Reference implementation of RFC6962.
@@ -421,6 +559,36 @@ std::string ReferenceMerkleTreeHash(std::string* inputs, uint64_t input_size) {
ReferenceMerkleTreeHash(&inputs[split], input_size - split));
}
+// Reference implementation of Merkle paths. Path from leaf to root,
+// excluding the leaf and root themselves. Returns an audit proof for the tree
+// leaf with index |leaf| (1-based). The tree is designated by |inputs|.
Ryan Sleevi 2016/08/25 18:56:24 I'm completely struggling to understand what this
Rob Percival 2016/10/03 17:39:26 Don't worry about "piling on", I didn't even write
+std::vector<std::string> ReferenceMerklePath(std::string* inputs,
Ryan Sleevi 2016/08/25 18:56:24 DESIGN: Do you need to pass as std::string? Or wou
Rob Percival 2016/10/03 17:39:26 Each element of |inputs| can be an arbitrary strin
+ uint64_t input_size,
+ uint64_t leaf) {
Ryan Sleevi 2016/08/25 18:56:24 STYLE: These should be size_t, not uint64_t - http
Rob Percival 2016/10/03 17:39:25 Done.
+ std::vector<std::string> path;
+ if (leaf > input_size || leaf == 0)
+ return path;
+
+ if (input_size == 1)
+ return path;
+
+ const uint64_t split = CalculateNearestPowerOfTwo(input_size);
+
+ std::vector<std::string> subpath;
+ if (leaf <= split) {
+ subpath = ReferenceMerklePath(&inputs[0], split, leaf);
+ path.insert(path.end(), subpath.begin(), subpath.end());
+ path.push_back(ReferenceMerkleTreeHash(&inputs[split], input_size - split));
+ } else {
+ subpath =
+ ReferenceMerklePath(&inputs[split], input_size - split, leaf - split);
+ path.insert(path.end(), subpath.begin(), subpath.end());
+ path.push_back(ReferenceMerkleTreeHash(&inputs[0], split));
+ }
Ryan Sleevi 2016/08/25 18:56:24 You should add more documentation here as to expla
Rob Percival 2016/10/03 17:39:25 Done.
+
+ return path;
+}
+
// Reference implementation of snapshot consistency. Returns a
// consistency proof between two snapshots of the tree designated
// by |inputs|.
@@ -468,20 +636,50 @@ std::vector<std::string> ReferenceSnapshotConsistency(std::string* inputs,
return proof;
}
+TEST_F(CTLogVerifierTest, VerifiesAuditProofEdgeCases_InvalidLeafIndex) {
+ std::vector<std::string> path;
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, 1, 0, path, std::string(), std::string()));
+ EXPECT_FALSE(
+ VerifyAuditProof(log_, 2, 1, path, std::string(), std::string()));
+
+ const std::string empty_hash = HexToBytes(kSHA256EmptyTreeHash);
+ EXPECT_FALSE(VerifyAuditProof(log_, 1, 0, path, empty_hash, std::string()));
+ EXPECT_FALSE(VerifyAuditProof(log_, 2, 1, path, empty_hash, std::string()));
+}
+
+class CTLogVerifierAuditProofTest
+ : public CTLogVerifierTest,
+ public ::testing::WithParamInterface<size_t> {};
+
+TEST_P(CTLogVerifierAuditProofTest, VerifiesValidAuditProofs) {
+ size_t i = GetParam();
+ // Construct the path.
+ std::vector<std::string> path;
+ for (size_t j = 0; j < kSHA256Paths[i].path_length; ++j)
+ path.push_back(HexToBytes(kSHA256Paths[i].path[j]));
+ const char* const root_hash = kSHA256Roots[kSHA256Paths[i].snapshot - 1];
+ VerifierCheck(kSHA256Paths[i].leaf, kSHA256Paths[i].snapshot, path,
+ HexToBytes(root_hash),
+ HexToBytes(kSHA256LeafHashes[kSHA256Paths[i].leaf - 1]));
+}
+
+// kSHA256Paths[0] is an invalid path, so only use elements 1-5.
+INSTANTIATE_TEST_CASE_P(KnownGoodPaths,
+ CTLogVerifierAuditProofTest,
+ ::testing::Range(1ul, arraysize(kSHA256Paths)));
+
class CTLogVerifierTestUsingReferenceGenerator
: public CTLogVerifierTest,
public ::testing::WithParamInterface<uint64_t> {};
const uint64_t kReferenceTreeSize = 256;
-// Tests that every possible valid consistency proof for a tree of a given size
-// verifies correctly. Also checks that invalid variations of each proof fail to
-// verify (see VerifierConsistencyCheck).
TEST_P(CTLogVerifierTestUsingReferenceGenerator,
VerifiesValidConsistencyProof) {
std::vector<std::string> data;
for (uint64_t i = 0; i < kReferenceTreeSize; ++i)
- data.push_back(std::string(1, static_cast<char>(i)));
+ data.emplace_back(1, static_cast<char>(i));
const uint64_t tree_size = GetParam();
const std::string tree_root = ReferenceMerkleTreeHash(data.data(), tree_size);
@@ -497,8 +695,27 @@ TEST_P(CTLogVerifierTestUsingReferenceGenerator,
}
}
-// Test verification of consistency proofs between all tree sizes from 1 to 128.
-INSTANTIATE_TEST_CASE_P(RangeOfTreeSizesAndSnapshots,
+TEST_P(CTLogVerifierTestUsingReferenceGenerator, VerifiesValidAuditProofs) {
+ std::vector<std::string> data;
+ for (uint64_t i = 0; i < kReferenceTreeSize; ++i)
+ data.emplace_back(1, static_cast<char>(i));
+
+ // More tests with reference path generator.
+ const uint64_t tree_size = GetParam();
+ const std::string root = ReferenceMerkleTreeHash(data.data(), tree_size);
+ // Repeat for each leaf in range.
+ for (uint64_t leaf = 1; leaf <= tree_size; ++leaf) {
+ SCOPED_TRACE(leaf);
+ std::vector<std::string> path =
+ ReferenceMerklePath(data.data(), tree_size, leaf);
+ VerifierCheck(leaf, tree_size, path, root,
+ TreeHasher::HashLeaf(data[leaf - 1]));
+ }
+}
+
+// Test verification of consistency proofs and audit proofs for all tree sizes
+// from 1 to |kReferenceTreeSize / 2|.
+INSTANTIATE_TEST_CASE_P(RangeOfTreeSizes,
CTLogVerifierTestUsingReferenceGenerator,
testing::Range(UINT64_C(1),
(kReferenceTreeSize / 2) + 1));

Powered by Google App Engine
This is Rietveld 408576698