| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/certificate_transparency/single_tree_tracker.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/strings/string_piece.h" |
| #include "base/test/histogram_tester.h" |
| #include "net/cert/ct_log_verifier.h" |
| #include "net/cert/ct_serialization.h" |
| #include "net/cert/signed_certificate_timestamp.h" |
| #include "net/cert/signed_tree_head.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/test/ct_test_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace certificate_transparency { |
| |
| namespace { |
| |
| const char kCanCheckForInclusionHistogramName[] = |
| "Net.CertificateTransparency.CanInclusionCheckSCT"; |
| |
| bool GetOldSignedTreeHead(net::ct::SignedTreeHead* sth) { |
| sth->version = net::ct::SignedTreeHead::V1; |
| sth->timestamp = base::Time::UnixEpoch() + |
| base::TimeDelta::FromMilliseconds(INT64_C(1348589665525)); |
| sth->tree_size = 12u; |
| |
| const uint8_t kOldSTHRootHash[] = { |
| 0x18, 0x04, 0x1b, 0xd4, 0x66, 0x50, 0x83, 0x00, 0x1f, 0xba, 0x8c, |
| 0x54, 0x11, 0xd2, 0xd7, 0x48, 0xe8, 0xab, 0xbf, 0xdc, 0xdf, 0xd9, |
| 0x21, 0x8c, 0xb0, 0x2b, 0x68, 0xa7, 0x8e, 0x7d, 0x4c, 0x23}; |
| memcpy(sth->sha256_root_hash, kOldSTHRootHash, net::ct::kSthRootHashLength); |
| |
| sth->log_id = net::ct::GetTestPublicKeyId(); |
| |
| const uint8_t kOldSTHSignatureData[] = { |
| 0x04, 0x03, 0x00, 0x47, 0x30, 0x45, 0x02, 0x20, 0x15, 0x7b, 0x23, |
| 0x42, 0xa2, 0x5f, 0x88, 0xc9, 0x0b, 0x30, 0xa6, 0xb4, 0x49, 0x50, |
| 0xb3, 0xab, 0xf5, 0x25, 0xfe, 0x27, 0xf0, 0x3f, 0x9a, 0xbf, 0xc1, |
| 0x16, 0x5a, 0x7a, 0xc0, 0x62, 0x2b, 0xbb, 0x02, 0x21, 0x00, 0xe6, |
| 0x57, 0xa3, 0xfe, 0xfc, 0x5a, 0x82, 0x9b, 0x29, 0x46, 0x15, 0x1d, |
| 0xbc, 0xfd, 0x9e, 0x87, 0x7f, 0xd0, 0x00, 0x5d, 0x62, 0x4f, 0x9a, |
| 0x1a, 0x9f, 0x20, 0x79, 0xd0, 0xc1, 0x34, 0x2e, 0x08}; |
| base::StringPiece sp(reinterpret_cast<const char*>(kOldSTHSignatureData), |
| sizeof(kOldSTHSignatureData)); |
| return DecodeDigitallySigned(&sp, &(sth->signature)) && sp.empty(); |
| } |
| |
| } // namespace |
| |
| class SingleTreeTrackerTest : public ::testing::Test { |
| void SetUp() override { |
| log_ = |
| net::CTLogVerifier::Create(net::ct::GetTestPublicKey(), "testlog", |
| "https://ct.example.com", "dns.example.com"); |
| |
| 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_); |
| } |
| |
| protected: |
| scoped_refptr<const net::CTLogVerifier> log_; |
| std::unique_ptr<SingleTreeTracker> tree_tracker_; |
| scoped_refptr<net::X509Certificate> chain_; |
| scoped_refptr<net::ct::SignedCertificateTimestamp> cert_sct_; |
| }; |
| |
| // Test that an SCT is classified as pending for a newer STH if the |
| // SingleTreeTracker has not seen any STHs so far. |
| TEST_F(SingleTreeTrackerTest, CorrectlyClassifiesUnobservedSCTNoSTH) { |
| base::HistogramTester histograms; |
| // First make sure the SCT has not been observed at all. |
| EXPECT_EQ( |
| SingleTreeTracker::SCT_NOT_OBSERVED, |
| tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get())); |
| |
| tree_tracker_->OnSCTVerified(chain_.get(), cert_sct_.get()); |
| |
| // Since no STH was provided to the tree_tracker_ the status should be that |
| // the SCT is pending a newer STH. |
| EXPECT_EQ( |
| SingleTreeTracker::SCT_PENDING_NEWER_STH, |
| tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get())); |
| |
| // Expect logging of a value indicating a valid STH is required. |
| histograms.ExpectTotalCount(kCanCheckForInclusionHistogramName, 1); |
| histograms.ExpectBucketCount(kCanCheckForInclusionHistogramName, 0, 1); |
| } |
| |
| // Test that an SCT is classified as pending an inclusion check if the |
| // SingleTreeTracker has a fresh-enough STH to check inclusion against. |
| TEST_F(SingleTreeTrackerTest, CorrectlyClassifiesUnobservedSCTWithRecentSTH) { |
| base::HistogramTester histograms; |
| // Provide an STH to the tree_tracker_. |
| net::ct::SignedTreeHead sth; |
| net::ct::GetSampleSignedTreeHead(&sth); |
| tree_tracker_->NewSTHObserved(sth); |
| |
| // Make sure the SCT status is the same as if there's no STH for |
| // this log. |
| EXPECT_EQ( |
| SingleTreeTracker::SCT_NOT_OBSERVED, |
| tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get())); |
| |
| tree_tracker_->OnSCTVerified(chain_.get(), cert_sct_.get()); |
| |
| // The status for this SCT should be 'pending inclusion check' since the STH |
| // provided at the beginning of the test is newer than the SCT. |
| EXPECT_EQ( |
| SingleTreeTracker::SCT_PENDING_INCLUSION_CHECK, |
| 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); |
| } |
| |
| // Test that the SingleTreeTracker correctly queues verified SCTs for inclusion |
| // checking such that, upon receiving a fresh STH, it changes the SCT's status |
| // from pending newer STH to pending inclusion check. |
| TEST_F(SingleTreeTrackerTest, CorrectlyUpdatesSCTStatusOnNewSTH) { |
| base::HistogramTester histograms; |
| // Report an observed SCT and make sure it's in the pending newer STH |
| // state. |
| tree_tracker_->OnSCTVerified(chain_.get(), cert_sct_.get()); |
| EXPECT_EQ( |
| SingleTreeTracker::SCT_PENDING_NEWER_STH, |
| tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get())); |
| histograms.ExpectTotalCount(kCanCheckForInclusionHistogramName, 1); |
| |
| // Provide with a fresh STH |
| net::ct::SignedTreeHead sth; |
| net::ct::GetSampleSignedTreeHead(&sth); |
| tree_tracker_->NewSTHObserved(sth); |
| |
| // Test that its status has changed. |
| EXPECT_EQ( |
| SingleTreeTracker::SCT_PENDING_INCLUSION_CHECK, |
| tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get())); |
| // Check that no additional UMA was logged for this case as the histogram is |
| // only supposed to measure the state of newly-observed SCTs, not pending |
| // ones. |
| histograms.ExpectTotalCount(kCanCheckForInclusionHistogramName, 1); |
| } |
| |
| // Test that the SingleTreeTracker does not change an SCT's status if an STH |
| // from the log it was issued by is observed, but that STH is too old to check |
| // inclusion against. |
| TEST_F(SingleTreeTrackerTest, DoesNotUpdatesSCTStatusOnOldSTH) { |
| // Notify of an SCT and make sure it's in the 'pending newer STH' state. |
| tree_tracker_->OnSCTVerified(chain_.get(), cert_sct_.get()); |
| EXPECT_EQ( |
| SingleTreeTracker::SCT_PENDING_NEWER_STH, |
| tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get())); |
| |
| // Provide an old STH for the same log. |
| net::ct::SignedTreeHead sth; |
| GetOldSignedTreeHead(&sth); |
| tree_tracker_->NewSTHObserved(sth); |
| |
| // Make sure the SCT's state hasn't changed. |
| EXPECT_EQ( |
| SingleTreeTracker::SCT_PENDING_NEWER_STH, |
| tree_tracker_->GetLogEntryInclusionStatus(chain_.get(), cert_sct_.get())); |
| } |
| |
| // Test that the SingleTreeTracker correctly logs that an SCT is pending a new |
| // STH, when it has a valid STH, but the observed SCT is not covered by the |
| // STH. |
| TEST_F(SingleTreeTrackerTest, LogsUMAForNewSCTAndOldSTH) { |
| base::HistogramTester histograms; |
| // Provide an old STH for the same log. |
| net::ct::SignedTreeHead sth; |
| GetOldSignedTreeHead(&sth); |
| tree_tracker_->NewSTHObserved(sth); |
| |
| histograms.ExpectTotalCount(kCanCheckForInclusionHistogramName, 0); |
| |
| // Notify of an SCT and make sure it's in the 'pending newer STH' state. |
| tree_tracker_->OnSCTVerified(chain_.get(), cert_sct_.get()); |
| |
| // Exactly one value should be logged, indicating the SCT cannot be checked |
| // for inclusion as the STH is too old. |
| histograms.ExpectTotalCount(kCanCheckForInclusionHistogramName, 1); |
| histograms.ExpectBucketCount(kCanCheckForInclusionHistogramName, 1, 1); |
| } |
| |
| } // namespace certificate_transparency |