summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authoreranm <eranm@chromium.org>2016-01-12 08:00:48 -0800
committerCommit bot <commit-bot@chromium.org>2016-01-12 16:01:54 +0000
commit57259e076ec95ad4eaadae8f443bd6bdffdba616 (patch)
treedd6aa1f6cc70a02740fd1ced4956ea4c09120640 /net
parent238b112dd7fda58a408f0c351b8b207aaa33f142 (diff)
downloadchromium_src-57259e076ec95ad4eaadae8f443bd6bdffdba616.zip
chromium_src-57259e076ec95ad4eaadae8f443bd6bdffdba616.tar.gz
chromium_src-57259e076ec95ad4eaadae8f443bd6bdffdba616.tar.bz2
Certificate Transparency: Verify signed tree heads for empty trees.
According to RFC6962, Signed Tree Heads for trees of size 0 (i.e. the empty tree) must have, as the root hash, the hash of the empty string. This change adds this check to the STH verification function. BUG=563471 Review URL: https://codereview.chromium.org/1542753005 Cr-Commit-Position: refs/heads/master@{#368885}
Diffstat (limited to 'net')
-rw-r--r--net/cert/ct_log_verifier.cc17
-rw-r--r--net/cert/ct_log_verifier_unittest.cc16
-rw-r--r--net/test/ct_test_util.cc38
-rw-r--r--net/test/ct_test_util.h8
4 files changed, 75 insertions, 4 deletions
diff --git a/net/cert/ct_log_verifier.cc b/net/cert/ct_log_verifier.cc
index c33f978..3884600 100644
--- a/net/cert/ct_log_verifier.cc
+++ b/net/cert/ct_log_verifier.cc
@@ -4,6 +4,8 @@
#include "net/cert/ct_log_verifier.h"
+#include <string.h>
+
#include "base/logging.h"
#include "net/cert/ct_log_verifier_util.h"
#include "net/cert/ct_serialization.h"
@@ -12,6 +14,16 @@
namespace net {
+namespace {
+
+// The SHA-256 hash of the empty string.
+const unsigned char kSHA256EmptyStringHash[ct::kSthRootHashLength] = {
+ 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4,
+ 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b,
+ 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
+
+} // namespace
+
// static
scoped_refptr<const CTLogVerifier> CTLogVerifier::Create(
const base::StringPiece& public_key,
@@ -70,6 +82,11 @@ bool CTLogVerifier::VerifySignedTreeHead(
ct::EncodeTreeHeadSignature(signed_tree_head, &serialized_data);
if (VerifySignature(serialized_data,
signed_tree_head.signature.signature_data)) {
+ if (signed_tree_head.tree_size == 0) {
+ // Root hash must equate SHA256 hash of the empty string.
+ return (memcmp(signed_tree_head.sha256_root_hash, kSHA256EmptyStringHash,
+ ct::kSthRootHashLength) == 0);
+ }
return true;
}
return false;
diff --git a/net/cert/ct_log_verifier_unittest.cc b/net/cert/ct_log_verifier_unittest.cc
index 00412e5..b2cba58 100644
--- a/net/cert/ct_log_verifier_unittest.cc
+++ b/net/cert/ct_log_verifier_unittest.cc
@@ -274,19 +274,31 @@ TEST_F(CTLogVerifierTest, FailsInvalidLogID) {
EXPECT_FALSE(log_->Verify(cert_entry, *cert_sct.get()));
}
-TEST_F(CTLogVerifierTest, SetsValidSTH) {
+TEST_F(CTLogVerifierTest, VerifiesValidSTH) {
ct::SignedTreeHead sth;
ct::GetSampleSignedTreeHead(&sth);
ASSERT_TRUE(log_->VerifySignedTreeHead(sth));
}
-TEST_F(CTLogVerifierTest, DoesNotSetInvalidSTH) {
+TEST_F(CTLogVerifierTest, DoesNotVerifyInvalidSTH) {
ct::SignedTreeHead sth;
ct::GetSampleSignedTreeHead(&sth);
sth.sha256_root_hash[0] = '\x0';
ASSERT_FALSE(log_->VerifySignedTreeHead(sth));
}
+TEST_F(CTLogVerifierTest, VerifiesValidEmptySTH) {
+ ct::SignedTreeHead sth;
+ ct::GetSampleEmptySignedTreeHead(&sth);
+ ASSERT_TRUE(log_->VerifySignedTreeHead(sth));
+}
+
+TEST_F(CTLogVerifierTest, DoesNotVerifyInvalidEmptySTH) {
+ ct::SignedTreeHead sth;
+ ct::GetBadEmptySignedTreeHead(&sth);
+ ASSERT_FALSE(log_->VerifySignedTreeHead(sth));
+}
+
// Test that excess data after the public key is rejected.
TEST_F(CTLogVerifierTest, ExcessDataInPublicKey) {
std::string key = ct::GetTestPublicKey();
diff --git a/net/test/ct_test_util.cc b/net/test/ct_test_util.cc
index 9381934..f4ea619 100644
--- a/net/test/ct_test_util.cc
+++ b/net/test/ct_test_util.cc
@@ -5,7 +5,7 @@
#include "net/test/ct_test_util.h"
#include <stdint.h>
-#include <string>
+#include <string.h>
#include <vector>
#include "base/base64.h"
@@ -166,7 +166,7 @@ const char kSampleSTHTreeHeadSignature[] =
"6c7a20022100e38464f3c0fd066257b982074f7ac87655e0c8f714768a050b4be9a7b441cb"
"d3";
size_t kSampleSTHTreeSize = 21u;
-int64_t kSampleSTHTimestamp = 1396877277237u;
+int64_t kSampleSTHTimestamp = INT64_C(1396877277237);
} // namespace
@@ -266,6 +266,40 @@ void GetSampleSignedTreeHead(SignedTreeHead* sth) {
GetSampleSTHTreeHeadDecodedSignature(&(sth->signature));
}
+void GetSampleEmptySignedTreeHead(SignedTreeHead* sth) {
+ sth->version = SignedTreeHead::V1;
+ sth->timestamp = base::Time::UnixEpoch() +
+ base::TimeDelta::FromMilliseconds(INT64_C(1450443594920));
+ sth->tree_size = 0;
+ std::string empty_root_hash = HexToBytes(
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+ memcpy(sth->sha256_root_hash, empty_root_hash.c_str(), kSthRootHashLength);
+
+ std::string tree_head_signature = HexToBytes(
+ "040300463044022046c26401de9416403da54762dc1f1687c38eafd791b15e484ab4c5f7"
+ "f52721fe02201bf537a3bbea47109fc76c2273fe0f3349f493a07de9335c266330105fb0"
+ "2a4a");
+ base::StringPiece sp(tree_head_signature);
+ CHECK(DecodeDigitallySigned(&sp, &(sth->signature)));
+ CHECK(sp.empty());
+}
+
+void GetBadEmptySignedTreeHead(SignedTreeHead* sth) {
+ sth->version = SignedTreeHead::V1;
+ sth->timestamp = base::Time::UnixEpoch() +
+ base::TimeDelta::FromMilliseconds(INT64_C(1450870952897));
+ sth->tree_size = 0;
+ memset(sth->sha256_root_hash, 'f', kSthRootHashLength);
+
+ std::string tree_head_signature = HexToBytes(
+ "04030046304402207cab04c62dee5d1cbc95fec30cd8417313f71587b75f133ad2e6f324"
+ "74f164d702205e2f3a9bce46f87d7e20e951a4e955da3cb502f8717a22fabd7c5d7e1bef"
+ "46ea");
+ base::StringPiece sp(tree_head_signature);
+ CHECK(DecodeDigitallySigned(&sp, &(sth->signature)));
+ CHECK(sp.empty());
+}
+
std::string GetSampleSTHSHA256RootHash() {
return HexToBytes(kSampleSTHSHA256RootHash);
}
diff --git a/net/test/ct_test_util.h b/net/test/ct_test_util.h
index 72629ab..ef4643b 100644
--- a/net/test/ct_test_util.h
+++ b/net/test/ct_test_util.h
@@ -71,6 +71,14 @@ std::string GetDerEncodedFakeOCSPResponseIssuerCert();
// A sample, valid STH.
void GetSampleSignedTreeHead(SignedTreeHead* sth);
+// A valid STH for the empty tree.
+void GetSampleEmptySignedTreeHead(SignedTreeHead* sth);
+
+// An STH for an empty tree where the root hash is not the hash of the empty
+// string, but the signature over the STH is valid. Such an STH is not valid
+// according to RFC6962.
+void GetBadEmptySignedTreeHead(SignedTreeHead* sth);
+
// The SHA256 root hash for the sample STH.
std::string GetSampleSTHSHA256RootHash();