// Copyright 2013 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 "net/cert/multi_log_ct_verifier.h" #include #include #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/metrics/histogram.h" #include "base/metrics/histogram_samples.h" #include "base/metrics/statistics_recorder.h" #include "base/values.h" #include "net/base/net_errors.h" #include "net/cert/ct_log_verifier.h" #include "net/cert/ct_serialization.h" #include "net/cert/pem.h" #include "net/cert/sct_status_flags.h" #include "net/cert/signed_certificate_timestamp.h" #include "net/cert/signed_certificate_timestamp_and_status.h" #include "net/cert/x509_certificate.h" #include "net/log/net_log_source_type.h" #include "net/log/net_log_with_source.h" #include "net/log/test_net_log.h" #include "net/log/test_net_log_util.h" #include "net/test/cert_test_util.h" #include "net/test/ct_test_util.h" #include "net/test/test_data_directory.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using testing::_; using testing::Mock; namespace net { namespace { const char kHostname[] = "example.com"; const char kLogDescription[] = "somelog"; class DoNothingLogProvider : public MultiLogCTVerifier::CTLogProvider { public: DoNothingLogProvider() = default; ~DoNothingLogProvider() = default; }; class MultiLogCTVerifierTest : public ::testing::Test { public: void SetUp() override { scoped_refptr log( CTLogVerifier::Create(ct::GetTestPublicKey(), kLogDescription)); ASSERT_TRUE(log); log_verifiers_.push_back(log); DoNothingLogProvider notifier; verifier_ = std::make_unique(¬ifier); verifier_->SetLogs(log_verifiers_); std::string der_test_cert(ct::GetDerEncodedX509Cert()); chain_ = X509Certificate::CreateFromBytes( base::as_bytes(base::make_span(der_test_cert))); ASSERT_TRUE(chain_.get()); embedded_sct_chain_ = CreateCertificateChainFromFile(GetTestCertsDirectory(), "ct-test-embedded-cert.pem", X509Certificate::FORMAT_AUTO); ASSERT_TRUE(embedded_sct_chain_.get()); } bool CheckForEmbeddedSCTInNetLog( const RecordingNetLogObserver& net_log_observer) { auto entries = net_log_observer.GetEntries(); if (entries.size() != 2) return false; auto embedded_scts = GetOptionalStringValueFromParams(entries[0], "embedded_scts"); if (!embedded_scts || embedded_scts->empty()) return false; const NetLogEntry& parsed = entries[1]; if (!parsed.params.is_dict()) return false; const base::Value* scts = parsed.params.FindListPath("scts"); if (!scts || scts->GetListDeprecated().size() != 1) return false; const base::Value& the_sct = scts->GetListDeprecated()[0]; if (!the_sct.is_dict()) return false; const std::string* origin = the_sct.FindStringPath("origin"); if (!origin || *origin != "Embedded in certificate") return false; const std::string* verification_status = the_sct.FindStringPath("verification_status"); if (!verification_status || *verification_status != "Verified") return false; return true; } // Returns true if |chain| is a certificate with embedded SCTs that can be // successfully extracted. bool VerifySinglePrecertificateChain(scoped_refptr chain) { SignedCertificateTimestampAndStatusList scts; verifier_->Verify(kHostname, chain.get(), base::StringPiece(), base::StringPiece(), &scts, NetLogWithSource()); return !scts.empty(); } // Returns true if |chain| is a certificate with a single embedded SCT that // can be successfully extracted and matched to the test log indicated by // |kLogDescription|. bool CheckPrecertificateVerification(scoped_refptr chain) { SignedCertificateTimestampAndStatusList scts; RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault); NetLogWithSource net_log = NetLogWithSource::Make( NetLog::Get(), NetLogSourceType::SSL_CONNECT_JOB); verifier_->Verify(kHostname, chain.get(), base::StringPiece(), base::StringPiece(), &scts, net_log); return ct::CheckForSingleVerifiedSCTInResult(scts, kLogDescription) && ct::CheckForSCTOrigin( scts, ct::SignedCertificateTimestamp::SCT_EMBEDDED) && CheckForEmbeddedSCTInNetLog(net_log_observer); } // Histogram-related helper methods int GetValueFromHistogram(const std::string& histogram_name, int sample_index) { base::Histogram* histogram = static_cast( base::StatisticsRecorder::FindHistogram(histogram_name)); if (histogram == nullptr) return 0; std::unique_ptr samples = histogram->SnapshotSamples(); return samples->GetCount(sample_index); } int NumEmbeddedSCTsInHistogram() { return GetValueFromHistogram("Net.CertificateTransparency.SCTOrigin", ct::SignedCertificateTimestamp::SCT_EMBEDDED); } int NumValidSCTsInStatusHistogram() { return GetValueFromHistogram("Net.CertificateTransparency.SCTStatus", ct::SCT_STATUS_OK); } protected: std::unique_ptr verifier_; scoped_refptr chain_; scoped_refptr embedded_sct_chain_; std::vector> log_verifiers_; }; TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCT) { ASSERT_TRUE(CheckPrecertificateVerification(embedded_sct_chain_)); } TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithPreCA) { scoped_refptr chain( CreateCertificateChainFromFile(GetTestCertsDirectory(), "ct-test-embedded-with-preca-chain.pem", X509Certificate::FORMAT_AUTO)); ASSERT_TRUE(chain.get()); ASSERT_TRUE(CheckPrecertificateVerification(chain)); } TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithIntermediate) { scoped_refptr chain(CreateCertificateChainFromFile( GetTestCertsDirectory(), "ct-test-embedded-with-intermediate-chain.pem", X509Certificate::FORMAT_AUTO)); ASSERT_TRUE(chain.get()); ASSERT_TRUE(CheckPrecertificateVerification(chain)); } TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithIntermediateAndPreCA) { scoped_refptr chain(CreateCertificateChainFromFile( GetTestCertsDirectory(), "ct-test-embedded-with-intermediate-preca-chain.pem", X509Certificate::FORMAT_AUTO)); ASSERT_TRUE(chain.get()); ASSERT_TRUE(CheckPrecertificateVerification(chain)); } TEST_F(MultiLogCTVerifierTest, VerifiesSCTOverX509Cert) { std::string sct_list = ct::GetSCTListForTesting(); SignedCertificateTimestampAndStatusList scts; verifier_->Verify(kHostname, chain_.get(), base::StringPiece(), sct_list, &scts, NetLogWithSource()); ASSERT_TRUE(ct::CheckForSingleVerifiedSCTInResult(scts, kLogDescription)); ASSERT_TRUE(ct::CheckForSCTOrigin( scts, ct::SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION)); } TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromUnknownLog) { std::string sct_list = ct::GetSCTListWithInvalidSCT(); SignedCertificateTimestampAndStatusList scts; verifier_->Verify(kHostname, chain_.get(), base::StringPiece(), sct_list, &scts, NetLogWithSource()); EXPECT_EQ(1U, scts.size()); EXPECT_EQ("", scts[0].sct->log_description); EXPECT_EQ(ct::SCT_STATUS_LOG_UNKNOWN, scts[0].status); } TEST_F(MultiLogCTVerifierTest, CountsValidSCTsInStatusHistogram) { int num_valid_scts = NumValidSCTsInStatusHistogram(); ASSERT_TRUE(VerifySinglePrecertificateChain(embedded_sct_chain_)); EXPECT_EQ(num_valid_scts + 1, NumValidSCTsInStatusHistogram()); } TEST_F(MultiLogCTVerifierTest, CountsInvalidSCTsInStatusHistogram) { std::string sct_list = ct::GetSCTListWithInvalidSCT(); SignedCertificateTimestampAndStatusList scts; int num_valid_scts = NumValidSCTsInStatusHistogram(); int num_invalid_scts = GetValueFromHistogram( "Net.CertificateTransparency.SCTStatus", ct::SCT_STATUS_LOG_UNKNOWN); verifier_->Verify(kHostname, chain_.get(), base::StringPiece(), sct_list, &scts, NetLogWithSource()); ASSERT_EQ(num_valid_scts, NumValidSCTsInStatusHistogram()); ASSERT_EQ(num_invalid_scts + 1, GetValueFromHistogram("Net.CertificateTransparency.SCTStatus", ct::SCT_STATUS_LOG_UNKNOWN)); } TEST_F(MultiLogCTVerifierTest, CountsSingleEmbeddedSCTInOriginsHistogram) { int old_embedded_count = NumEmbeddedSCTsInHistogram(); ASSERT_TRUE(CheckPrecertificateVerification(embedded_sct_chain_)); EXPECT_EQ(old_embedded_count + 1, NumEmbeddedSCTsInHistogram()); } TEST_F(MultiLogCTVerifierTest, SetLogsRemovesOldLogs) { log_verifiers_.clear(); verifier_->SetLogs(log_verifiers_); // Log list is now empty so verification should fail. ASSERT_FALSE(CheckPrecertificateVerification(embedded_sct_chain_)); } TEST_F(MultiLogCTVerifierTest, SetLogsAddsNewLogs) { // Clear the log list. log_verifiers_.clear(); verifier_->SetLogs(log_verifiers_); // Add valid log again via SetLogs scoped_refptr log( CTLogVerifier::Create(ct::GetTestPublicKey(), kLogDescription)); ASSERT_TRUE(log); log_verifiers_.push_back(log); verifier_->SetLogs(log_verifiers_); // Verification should now succeed. ASSERT_TRUE(CheckPrecertificateVerification(embedded_sct_chain_)); } } // namespace } // namespace net