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

Unified Diff: components/certificate_transparency/single_tree_tracker_unittest.cc

Issue 2017563002: Add Certificate Transparency logs auditing (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: (Re-upload) Created 4 years, 1 month 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: components/certificate_transparency/single_tree_tracker_unittest.cc
diff --git a/components/certificate_transparency/single_tree_tracker_unittest.cc b/components/certificate_transparency/single_tree_tracker_unittest.cc
index 43ea4fd446a7c7c450a8f7b0e0159b95a29bbb22..c7058b8bd5b9fb2bfa06605da1f3a6812bc471dc 100644
--- a/components/certificate_transparency/single_tree_tracker_unittest.cc
+++ b/components/certificate_transparency/single_tree_tracker_unittest.cc
@@ -7,25 +7,43 @@
#include <string>
#include <utility>
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
#include "base/strings/string_piece.h"
#include "base/test/histogram_tester.h"
+#include "components/base32/base32.h"
+#include "components/certificate_transparency/log_dns_client.h"
+#include "components/certificate_transparency/mock_log_dns_traffic.h"
+#include "crypto/sha2.h"
+#include "net/base/network_change_notifier.h"
#include "net/cert/ct_log_verifier.h"
#include "net/cert/ct_serialization.h"
+#include "net/cert/merkle_tree_leaf.h"
#include "net/cert/signed_certificate_timestamp.h"
#include "net/cert/signed_tree_head.h"
#include "net/cert/x509_certificate.h"
+#include "net/dns/dns_client.h"
+#include "net/log/net_log.h"
#include "net/test/ct_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
+using net::ct::SignedCertificateTimestamp;
+using net::ct::SignedTreeHead;
+
namespace certificate_transparency {
namespace {
const char kCanCheckForInclusionHistogramName[] =
"Net.CertificateTransparency.CanInclusionCheckSCT";
+const char kInclusionCheckResultHistogramName[] =
+ "Net.CertificateTransparency.InclusionCheckResult";
+
+base::TimeDelta kMoreThanMMD = base::TimeDelta::FromHours(25);
-bool GetOldSignedTreeHead(net::ct::SignedTreeHead* sth) {
- sth->version = net::ct::SignedTreeHead::V1;
+bool GetOldSignedTreeHead(SignedTreeHead* sth) {
+ sth->version = SignedTreeHead::V1;
sth->timestamp = base::Time::UnixEpoch() +
base::TimeDelta::FromMilliseconds(INT64_C(1348589665525));
sth->tree_size = 12u;
@@ -51,6 +69,81 @@ bool GetOldSignedTreeHead(net::ct::SignedTreeHead* sth) {
return DecodeDigitallySigned(&sp, &(sth->signature)) && sp.empty();
}
+scoped_refptr<SignedCertificateTimestamp> GetSCT() {
+ scoped_refptr<SignedCertificateTimestamp> sct;
+
+ // TODO(eranm): Move setting of the origin field to ct_test_util.cc
+ net::ct::GetX509CertSCT(&sct);
+ sct->origin = SignedCertificateTimestamp::SCT_FROM_OCSP_RESPONSE;
+ return sct;
+}
+
+std::string Base32LeafHash(const net::X509Certificate* cert,
+ const SignedCertificateTimestamp* sct) {
+ net::ct::MerkleTreeLeaf leaf;
+ if (!GetMerkleTreeLeaf(cert, sct, &leaf))
+ return std::string();
+
+ std::string leaf_hash;
+ if (!HashMerkleTreeLeaf(leaf, &leaf_hash))
+ return std::string();
+
+ return base32::Base32Encode(leaf_hash,
+ base32::Base32EncodePolicy::OMIT_PADDING);
+}
+
+template <typename... T>
+void AssertSCTsMatchingState(SingleTreeTracker* tree_tracker,
+ net::X509Certificate* chain,
+ SingleTreeTracker::SCTInclusionStatus status,
+ T... scts) {
Ryan Sleevi 2016/12/14 01:34:15 This seems like a lot of... undesirable template o
Eran Messeri 2016/12/22 16:12:11 Added documentation. I've created this function b
+ const int sct_list_size = sizeof...(scts);
+ SignedCertificateTimestamp* sct_list[sct_list_size] = {scts...};
+ for (const auto& sct : sct_list) {
+ EXPECT_EQ(status, tree_tracker->GetLogEntryInclusionStatus(chain, sct))
+ << "SCT age: " << sct->timestamp;
+ }
+};
+
+bool GetSignedTreeHeadForTreeOfSize2(SignedTreeHead* sth) {
+ sth->version = SignedTreeHead::V1;
+ sth->timestamp = base::Time::UnixEpoch() +
+ base::TimeDelta::FromMilliseconds(INT64_C(1365354256089));
Ryan Sleevi 2016/12/14 01:34:16 Document.
Eran Messeri 2016/12/22 16:12:11 Done.
+ sth->tree_size = 2;
+ const uint8_t kRootHash[] = {0x16, 0x80, 0xbd, 0x5a, 0x1b, 0xc1, 0xb6, 0xcf,
+ 0x1b, 0x7e, 0x77, 0x41, 0xeb, 0xed, 0x86, 0x8b,
+ 0x73, 0x81, 0x87, 0xf5, 0xab, 0x93, 0x6d, 0xb2,
+ 0x0a, 0x79, 0x0d, 0x9e, 0x40, 0x55, 0xc3, 0xe6};
Ryan Sleevi 2016/12/14 01:34:16 Document.
Eran Messeri 2016/12/22 16:12:11 Done.
+ std::string decoded_root_hash(reinterpret_cast<const char*>(kRootHash),
+ net::ct::kSthRootHashLength);
+
+ memcpy(sth->sha256_root_hash, decoded_root_hash.data(),
+ decoded_root_hash.size());
Ryan Sleevi 2016/12/14 01:34:15 Why is 117-121 necessary? Can't you just copy in d
Eran Messeri 2016/12/22 16:12:11 Not necessary, removed.
+ sth->log_id = net::ct::GetTestPublicKeyId();
+
+ const uint8_t kTreeHeadSignatureData[] = {
+ 0x04, 0x03, 0x00, 0x46, 0x30, 0x44, 0x02, 0x20, 0x25, 0xa1, 0x9d,
+ 0x7b, 0xf6, 0xe6, 0xfc, 0x47, 0xa7, 0x2d, 0xef, 0x6b, 0xf4, 0x84,
+ 0x71, 0xb7, 0x7b, 0x7e, 0xd4, 0x4c, 0x7a, 0x5c, 0x4f, 0x9a, 0xb7,
+ 0x04, 0x71, 0x6e, 0xd0, 0xa8, 0x0f, 0x53, 0x02, 0x20, 0x27, 0xe5,
+ 0xed, 0x7d, 0xc3, 0x5d, 0x4c, 0xf0, 0x67, 0x35, 0x5d, 0x8a, 0x10,
+ 0xae, 0x25, 0x87, 0x1a, 0xef, 0xea, 0xd2, 0xf7, 0xe3, 0x73, 0x2f,
+ 0x07, 0xb3, 0x4b, 0xea, 0x5b, 0xdd, 0x81, 0x2d};
Ryan Sleevi 2016/12/14 01:34:16 Document.
Eran Messeri 2016/12/22 16:12:12 Done.
+
+ base::StringPiece sp(reinterpret_cast<const char*>(kTreeHeadSignatureData),
+ sizeof(kTreeHeadSignatureData));
+ return DecodeDigitallySigned(&sp, &sth->signature);
+}
+
+void FillVectorWithValidAuditProofForTreeOfSize2(
+ std::vector<std::string>* out_proof) {
+ std::string node(crypto::kSHA256Length, '\0');
+ for (size_t i = 0; i < crypto::kSHA256Length; ++i) {
+ node[i] = static_cast<char>(0x0a);
+ }
+ out_proof->push_back(node);
+}
+
} // namespace
class SingleTreeTrackerTest : public ::testing::Test {
@@ -62,19 +155,57 @@ class SingleTreeTrackerTest : public ::testing::Test {
ASSERT_TRUE(log_);
ASSERT_EQ(log_->key_id(), net::ct::GetTestPublicKeyId());
- tree_tracker_.reset(new SingleTreeTracker(log_));
const std::string der_test_cert(net::ct::GetDerEncodedX509Cert());
chain_ = net::X509Certificate::CreateFromBytes(der_test_cert.data(),
der_test_cert.length());
ASSERT_TRUE(chain_.get());
net::ct::GetX509CertSCT(&cert_sct_);
+ cert_sct_->origin = SignedCertificateTimestamp::SCT_FROM_OCSP_RESPONSE;
+
+ ClearDnsConfig();
Ryan Sleevi 2016/12/14 01:34:16 Why is this necessary? Why can't it just be handle
Eran Messeri 2016/12/22 16:12:11 If I understand correctly, the question is why isn
Ryan Sleevi 2016/12/22 21:33:20 The latter - initialize the pointer in the ctor, a
+ // Default to throttling requests as it means observed log entries will
+ // be frozen in a pending state, simplifying testing of the
+ // SingleTreeTracker.
+ ASSERT_TRUE(ExpectLeafHashRequestAndThrottle(chain_, cert_sct_));
+ CreateTreeTracker();
}
protected:
+ void ClearDnsConfig() {
+ // Must clear the global one first.
+ net_change_notifier_.reset();
+ net_change_notifier_.reset(net::NetworkChangeNotifier::CreateMock());
Ryan Sleevi 2016/12/14 01:34:15 Every test creates a new instance, so the first .r
Eran Messeri 2016/12/22 16:12:11 It's true that every test creates a new instance.
+
+ mock_dns_.reset(new MockLogDnsTraffic);
+ mock_dns_->InitializeDnsConfig();
Ryan Sleevi 2016/12/14 01:34:16 Do these need to be unique? Can they just be membe
Eran Messeri 2016/12/22 16:12:12 Not sure about the question: Are you suggesting it
Ryan Sleevi 2016/12/22 21:33:20 Yes.
+ }
+
+ void CreateTreeTracker() {
Ryan Sleevi 2016/12/14 01:34:16 Doesn't seem necessary to have this helper functio
Eran Messeri 2016/12/22 16:12:11 Do you mean it seems like this function is not cal
Ryan Sleevi 2016/12/22 21:33:20 No. I meant more specifically: It does not seem ne
+ log_dns_client_ = base::MakeUnique<LogDnsClient>(
+ mock_dns_->CreateDnsClient(), net_log_, 1);
+
+ tree_tracker_.reset(new SingleTreeTracker(log_, log_dns_client_.get()));
+ }
+
+ bool ExpectLeafHashRequestAndThrottle(
Ryan Sleevi 2016/12/14 01:34:16 Document
Eran Messeri 2016/12/22 16:12:12 Done, also renamed to ExpectLeafIndexRequestAndThr
+ scoped_refptr<net::X509Certificate> chain,
+ scoped_refptr<SignedCertificateTimestamp> sct) {
Ryan Sleevi 2016/12/14 01:34:16 Doesn't seem like you need to be passing these byv
Eran Messeri 2016/12/22 16:12:12 Done (const-ref rather than naked pointers because
+ return mock_dns_->ExpectRequestAndSocketError(
+ Base32LeafHash(chain.get(), sct.get()) +
+ ".hash.dns.example."
Ryan Sleevi 2016/12/14 01:34:16 Mostly since you're adding it in this test, I thin
Eran Messeri 2016/12/22 16:12:11 Fixed throughout by adding a constant for the pref
Ryan Sleevi 2016/12/22 21:33:20 No, I think you're fine.
+ "com",
+ net::Error::ERR_TEMPORARILY_THROTTLED);
+ }
+
+ base::MessageLoopForIO message_loop_;
+ std::unique_ptr<MockLogDnsTraffic> mock_dns_;
scoped_refptr<const net::CTLogVerifier> log_;
+ std::unique_ptr<net::NetworkChangeNotifier> net_change_notifier_;
+ std::unique_ptr<LogDnsClient> log_dns_client_;
std::unique_ptr<SingleTreeTracker> tree_tracker_;
scoped_refptr<net::X509Certificate> chain_;
- scoped_refptr<net::ct::SignedCertificateTimestamp> cert_sct_;
+ scoped_refptr<SignedCertificateTimestamp> cert_sct_;
+ net::NetLogWithSource net_log_;
};
// Test that an SCT is classified as pending for a newer STH if the
@@ -104,7 +235,7 @@ TEST_F(SingleTreeTrackerTest, CorrectlyClassifiesUnobservedSCTNoSTH) {
TEST_F(SingleTreeTrackerTest, CorrectlyClassifiesUnobservedSCTWithRecentSTH) {
base::HistogramTester histograms;
// Provide an STH to the tree_tracker_.
- net::ct::SignedTreeHead sth;
+ SignedTreeHead sth;
net::ct::GetSampleSignedTreeHead(&sth);
tree_tracker_->NewSTHObserved(sth);
@@ -127,6 +258,9 @@ TEST_F(SingleTreeTrackerTest, CorrectlyClassifiesUnobservedSCTWithRecentSTH) {
// of a new SCT.
histograms.ExpectTotalCount(kCanCheckForInclusionHistogramName, 1);
histograms.ExpectBucketCount(kCanCheckForInclusionHistogramName, 2, 1);
+ // Nothing should be logged in the result histogram since inclusion check
+ // didn't finish.
+ histograms.ExpectTotalCount(kInclusionCheckResultHistogramName, 0);
}
// Test that the SingleTreeTracker correctly queues verified SCTs for inclusion
@@ -143,7 +277,7 @@ TEST_F(SingleTreeTrackerTest, CorrectlyUpdatesSCTStatusOnNewSTH) {
histograms.ExpectTotalCount(kCanCheckForInclusionHistogramName, 1);
// Provide with a fresh STH
- net::ct::SignedTreeHead sth;
+ SignedTreeHead sth;
net::ct::GetSampleSignedTreeHead(&sth);
tree_tracker_->NewSTHObserved(sth);
@@ -168,7 +302,7 @@ TEST_F(SingleTreeTrackerTest, DoesNotUpdatesSCTStatusOnOldSTH) {
tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
// Provide an old STH for the same log.
- net::ct::SignedTreeHead sth;
+ SignedTreeHead sth;
GetOldSignedTreeHead(&sth);
tree_tracker_->NewSTHObserved(sth);
@@ -184,7 +318,7 @@ TEST_F(SingleTreeTrackerTest, DoesNotUpdatesSCTStatusOnOldSTH) {
TEST_F(SingleTreeTrackerTest, LogsUMAForNewSCTAndOldSTH) {
base::HistogramTester histograms;
// Provide an old STH for the same log.
- net::ct::SignedTreeHead sth;
+ SignedTreeHead sth;
GetOldSignedTreeHead(&sth);
tree_tracker_->NewSTHObserved(sth);
@@ -199,4 +333,298 @@ TEST_F(SingleTreeTrackerTest, LogsUMAForNewSCTAndOldSTH) {
histograms.ExpectBucketCount(kCanCheckForInclusionHistogramName, 1, 1);
}
+// Test that an entry transitions to the "not found" state if the LogDnsClient
+// fails to get a leaf index.
+TEST_F(SingleTreeTrackerTest, TestEntryNotPendingAfterLeafIndexFetchFailure) {
+ ClearDnsConfig();
Ryan Sleevi 2016/12/14 01:34:16 Unnecessary?
Eran Messeri 2016/12/22 16:12:11 Done.
+ ASSERT_TRUE(mock_dns_->ExpectRequestAndSocketError(
+ Base32LeafHash(chain_.get(), cert_sct_.get()) + ".hash.dns.example."
+ "com",
+ net::Error::ERR_FAILED));
+
+ CreateTreeTracker();
+
+ tree_tracker_->OnSCTVerified(chain_.get(), cert_sct_.get());
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_PENDING_NEWER_STH,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+
+ // Provide with a fresh STH
+ SignedTreeHead sth;
+ net::ct::GetSampleSignedTreeHead(&sth);
+ tree_tracker_->NewSTHObserved(sth);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_NOT_OBSERVED,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+}
+
+// Test that an entry transitions to the "not found" state if the LogDnsClient
+// succeeds to get a leaf index but fails to get an inclusion proof.
+TEST_F(SingleTreeTrackerTest, TestEntryNotPendingAfterInclusionCheckFailure) {
+ ClearDnsConfig();
Ryan Sleevi 2016/12/14 01:34:16 Unnecessary?
Eran Messeri 2016/12/22 16:12:11 Acknowledged, see explanation below why we need to
+ ASSERT_TRUE(mock_dns_->ExpectLeafIndexRequestAndResponse(
+ Base32LeafHash(chain_.get(), cert_sct_.get()) + ".hash.dns.example."
+ "com",
+ 12));
+ ASSERT_TRUE(mock_dns_->ExpectRequestAndSocketError(
+ "0.12.21.tree.dns.example.com", net::Error::ERR_FAILED));
Ryan Sleevi 2016/12/14 01:34:16 Document why 0.12.21 is meaningful here.
Eran Messeri 2016/12/22 16:12:12 Done.
+
+ CreateTreeTracker();
+
+ tree_tracker_->OnSCTVerified(chain_.get(), cert_sct_.get());
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_PENDING_NEWER_STH,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+
+ // Provide with a fresh STH
+ SignedTreeHead sth;
+ net::ct::GetSampleSignedTreeHead(&sth);
+ tree_tracker_->NewSTHObserved(sth);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_NOT_OBSERVED,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+}
+
+// Test that an entry transitions to the "included" state if the LogDnsClient
+// succeeds to get a leaf index and an inclusion proof.
+TEST_F(SingleTreeTrackerTest, TestEntryIncludedAfterInclusionCheckSuccess) {
+ std::vector<std::string> audit_proof;
+ FillVectorWithValidAuditProofForTreeOfSize2(&audit_proof);
+
+ ClearDnsConfig();
Ryan Sleevi 2016/12/14 01:34:16 Unnecessary?
Eran Messeri 2016/12/22 16:12:12 Acknowledged, see explanation below.
+ ASSERT_TRUE(mock_dns_->ExpectLeafIndexRequestAndResponse(
+ Base32LeafHash(chain_.get(), cert_sct_.get()) + ".hash.dns.example."
+ "com",
+ 0));
+ ASSERT_TRUE(mock_dns_->ExpectAuditProofRequestAndResponse(
+ "0.0.2.tree.dns.example.com", audit_proof.begin(),
Ryan Sleevi 2016/12/14 01:34:16 Document why 0.0.2 is meaningful here
Eran Messeri 2016/12/22 16:12:11 Done.
+ audit_proof.begin() + 1));
+
+ CreateTreeTracker();
+
+ tree_tracker_->OnSCTVerified(chain_.get(), cert_sct_.get());
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_PENDING_NEWER_STH,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+
+ // Provide with a fresh STH
+ SignedTreeHead sth;
+ ASSERT_TRUE(GetSignedTreeHeadForTreeOfSize2(&sth));
+ ASSERT_TRUE(log_->VerifySignedTreeHead(sth));
+
+ tree_tracker_->NewSTHObserved(sth);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_INCLUDED_IN_LOG,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+}
+
+// Test that pending entries transition states correctly according to the
+// STHs provided:
+// * Start without an STH.
+// * Add a collection of entries with mixed timestamps (i.e. SCTs not added
+// in the order of their timestamps).
Ryan Sleevi 2016/12/14 01:34:16 Follow markdown style indents * Add a ... in the
Eran Messeri 2016/12/22 16:12:11 Done.
+// * Provide an STH that covers some of the entries, test these are audited.
+// * Provide another STH that covers more of the entries, test that the entries
+// already audited are not audited again and that those that need to be
+// audited are audited, while those that are not covered by that STH are
+// not audited.
Ryan Sleevi 2016/12/14 01:34:15 Is it wholly necessary to test this, as large as y
Eran Messeri 2016/12/22 16:12:12 I acknowledge this is a large and complex test. An
+TEST_F(SingleTreeTrackerTest, TestMultipleEntriesTransitionStateCorrectly) {
+ SignedTreeHead old_sth;
+ GetOldSignedTreeHead(&old_sth);
+
+ SignedTreeHead new_sth;
+ net::ct::GetSampleSignedTreeHead(&new_sth);
Ryan Sleevi 2016/12/14 01:34:15 You removed other net::ct:: qualifications, but ad
Eran Messeri 2016/12/22 16:12:12 Done.
+
+ base::TimeDelta kLessThanMMD = base::TimeDelta::FromHours(23);
+
+ // Assert the gap between the two timestamps is big enough so that
+ // all assumptions below on which SCT can be audited with the
+ // new STH are true.
+ ASSERT_LT(old_sth.timestamp + (kMoreThanMMD * 2), new_sth.timestamp);
+
+ // Oldest SCT - auditable by the old and new STHs.
+ scoped_refptr<SignedCertificateTimestamp> oldest_sct(GetSCT());
+ oldest_sct->timestamp = old_sth.timestamp - kMoreThanMMD;
+
+ // SCT that's older than the old STH's timestamp but by less than the MMD,
+ // so not auditable by old STH.
+ scoped_refptr<SignedCertificateTimestamp> not_auditable_by_old_sth_sct(
+ GetSCT());
+ not_auditable_by_old_sth_sct->timestamp = old_sth.timestamp - kLessThanMMD;
+
+ // SCT that's newer than the old STH's timestamp so is only auditable by
+ // the new STH.
+ scoped_refptr<SignedCertificateTimestamp> newer_than_old_sth_sct(GetSCT());
+ newer_than_old_sth_sct->timestamp = old_sth.timestamp + kLessThanMMD;
+
+ // SCT that's older than the new STH's timestamp but by less than the MMD,
+ // so isn't auditable by the new STH.
+ scoped_refptr<SignedCertificateTimestamp> not_auditable_by_new_sth_sct(
+ GetSCT());
+ not_auditable_by_new_sth_sct->timestamp = new_sth.timestamp - kLessThanMMD;
+
+ // SCT that's newer than the new STH's timestamp so isn't auditable by the
+ // the new STH.
+ scoped_refptr<SignedCertificateTimestamp> newer_than_new_sth_sct(GetSCT());
+ newer_than_new_sth_sct->timestamp = new_sth.timestamp + kLessThanMMD;
+
+ // Set up DNS expectations based on inclusion proof request order.
+ ClearDnsConfig();
Ryan Sleevi 2016/12/14 01:34:15 This is the only one that seems 'necessary' (e.g.
Eran Messeri 2016/12/22 16:12:11 Trying to clarify the relationship between the moc
+ ASSERT_TRUE(ExpectLeafHashRequestAndThrottle(chain_, oldest_sct));
+ ASSERT_TRUE(
+ ExpectLeafHashRequestAndThrottle(chain_, not_auditable_by_old_sth_sct));
+ ASSERT_TRUE(ExpectLeafHashRequestAndThrottle(chain_, newer_than_old_sth_sct));
+ CreateTreeTracker();
+
+ // Add SCTs in mixed order.
+ tree_tracker_->OnSCTVerified(chain_.get(), newer_than_new_sth_sct.get());
+ tree_tracker_->OnSCTVerified(chain_.get(), oldest_sct.get());
+ tree_tracker_->OnSCTVerified(chain_.get(),
+ not_auditable_by_new_sth_sct.get());
+ tree_tracker_->OnSCTVerified(chain_.get(), newer_than_old_sth_sct.get());
+ tree_tracker_->OnSCTVerified(chain_.get(),
+ not_auditable_by_old_sth_sct.get());
+
+ // Ensure all are in the PENDING_NEWER_STH state.
+ AssertSCTsMatchingState<SignedCertificateTimestamp*>(
+ tree_tracker_.get(), chain_.get(),
+ SingleTreeTracker::SCT_PENDING_NEWER_STH, oldest_sct.get(),
+ not_auditable_by_old_sth_sct.get(), newer_than_old_sth_sct.get(),
+ not_auditable_by_new_sth_sct.get(), newer_than_new_sth_sct.get());
+
+ // Provide the old STH, ensure only the oldest one is auditable.
+ tree_tracker_->NewSTHObserved(old_sth);
+ // Ensure all but the oldest are in the PENDING_NEWER_STH state.
+ AssertSCTsMatchingState<SignedCertificateTimestamp*>(
+ tree_tracker_.get(), chain_.get(),
+ SingleTreeTracker::SCT_PENDING_INCLUSION_CHECK, oldest_sct.get());
+
+ AssertSCTsMatchingState<SignedCertificateTimestamp*>(
+ tree_tracker_.get(), chain_.get(),
+ SingleTreeTracker::SCT_PENDING_NEWER_STH,
+ not_auditable_by_old_sth_sct.get(), newer_than_old_sth_sct.get(),
+ not_auditable_by_new_sth_sct.get(), newer_than_new_sth_sct.get());
+
+ // Provide the newer one, ensure two more are auditable but the
+ // rest aren't.
+ tree_tracker_->NewSTHObserved(new_sth);
+
+ AssertSCTsMatchingState<SignedCertificateTimestamp*>(
+ tree_tracker_.get(), chain_.get(),
+ SingleTreeTracker::SCT_PENDING_INCLUSION_CHECK, oldest_sct.get(),
+ not_auditable_by_old_sth_sct.get(), newer_than_old_sth_sct.get());
+
+ AssertSCTsMatchingState<SignedCertificateTimestamp*>(
+ tree_tracker_.get(), chain_.get(),
+ SingleTreeTracker::SCT_PENDING_NEWER_STH,
+ not_auditable_by_new_sth_sct.get(), newer_than_new_sth_sct.get());
+}
+
+// Test that if a request for an entry is throttled, it remains in a
+// pending state.
+
+// Test that if several entries are throttled, when the LogDnsClient notifies
+// of un-throttling all entries are handled.
+TEST_F(SingleTreeTrackerTest, TestThrottledEntryGetsHandledAfterUnthrottling) {
+ std::vector<std::string> audit_proof;
+ FillVectorWithValidAuditProofForTreeOfSize2(&audit_proof);
+
+ ClearDnsConfig();
Ryan Sleevi 2016/12/14 01:34:15 Unnecessary?
Eran Messeri 2016/12/22 16:12:11 Acknowledged, see above.
+ ASSERT_TRUE(mock_dns_->ExpectLeafIndexRequestAndResponse(
+ Base32LeafHash(chain_.get(), cert_sct_.get()) + ".hash.dns.example."
+ "com",
+ 0));
+ ASSERT_TRUE(mock_dns_->ExpectAuditProofRequestAndResponse(
+ "0.0.2.tree.dns.example.com", audit_proof.begin(),
+ audit_proof.begin() + 1));
+
+ scoped_refptr<SignedCertificateTimestamp> second_sct(GetSCT());
+ second_sct->timestamp -= base::TimeDelta::FromHours(1);
+
+ // Process request for |second_sct|
+ ASSERT_TRUE(mock_dns_->ExpectLeafIndexRequestAndResponse(
+ Base32LeafHash(chain_.get(), second_sct.get()) + ".hash.dns.example."
+ "com",
+ 1));
+ ASSERT_TRUE(mock_dns_->ExpectAuditProofRequestAndResponse(
+ "0.1.2.tree.dns.example.com", audit_proof.begin(),
+ audit_proof.begin() + 1));
+
+ CreateTreeTracker();
+
+ SignedTreeHead sth;
+ ASSERT_TRUE(GetSignedTreeHeadForTreeOfSize2(&sth));
+ tree_tracker_->NewSTHObserved(sth);
+
+ tree_tracker_->OnSCTVerified(chain_.get(), cert_sct_.get());
+ tree_tracker_->OnSCTVerified(chain_.get(), second_sct.get());
+
+ // Both entries should be in the pending state, the first because the
+ // LogDnsClient did not invoke the callback yet, the second one because
+ // the LogDnsClient is "busy" with the first entry and so would throttle.
+ ASSERT_EQ(
+ SingleTreeTracker::SCT_PENDING_INCLUSION_CHECK,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+ ASSERT_EQ(SingleTreeTracker::SCT_PENDING_INCLUSION_CHECK,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(),
+ second_sct.get()));
+
+ // Pump the message loop.
Ryan Sleevi 2016/12/14 01:34:16 It's obvious from line 579 that the message loop w
Eran Messeri 2016/12/22 16:12:11 Done - replaced the comment.
+ base::RunLoop().RunUntilIdle();
+
+ // Check that the first sct is included in the log.
+ ASSERT_EQ(
+ SingleTreeTracker::SCT_INCLUDED_IN_LOG,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+
+ // Check that the second SCT got an invalid proof and is not included, rather
+ // than being in the pending state.
+ ASSERT_EQ(SingleTreeTracker::SCT_NOT_OBSERVED,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(),
+ second_sct.get()));
+}
+
+// Test that proof fetching failure due to DNS config errors is handled
+// correctly:
+// (1) Entry removed from pending queue.
+// (2) UMA logged
+TEST_F(SingleTreeTrackerTest,
+ TestProofLookupDueToBadDNSConfigHandledCorrectly) {
+ base::HistogramTester histograms;
+ // Provide an STH to the tree_tracker_.
+ SignedTreeHead sth;
+ net::ct::GetSampleSignedTreeHead(&sth);
+
+ // Clear existing DNS configuration, so that the DnsClient created
+ // by the MockLogDnsTraffic has no valid DnsConfig.
+ net_change_notifier_.reset();
+ net_change_notifier_.reset(net::NetworkChangeNotifier::CreateMock());
+ CreateTreeTracker();
+
+ tree_tracker_->NewSTHObserved(sth);
+ tree_tracker_->OnSCTVerified(chain_.get(), cert_sct_.get());
+
+ // Make sure the SCT status indicates the entry has been removed from
+ // the SingleTreeTracker's internal queue as the DNS lookup failed
+ // synchronously.
+ EXPECT_EQ(
+ SingleTreeTracker::SCT_NOT_OBSERVED,
+ tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get()));
+
+ // Exactly one value should be logged, indicating the SCT can be checked for
+ // inclusion, as |tree_tracker_| did have a valid STH when it was notified
+ // of a new SCT.
+ histograms.ExpectTotalCount(kCanCheckForInclusionHistogramName, 1);
+ histograms.ExpectBucketCount(kCanCheckForInclusionHistogramName, 2, 1);
+ // Failure due to DNS configuration should be logged in the result histogram.
+ histograms.ExpectTotalCount(kInclusionCheckResultHistogramName, 1);
+ histograms.ExpectBucketCount(kInclusionCheckResultHistogramName, 3, 1);
+}
+
} // namespace certificate_transparency

Powered by Google App Engine
This is Rietveld 408576698