Check Expect-CT at connection setup
This CL adds an Expect-CT check to ShouldRequireCT(), with an option to send
reports if the host is configured with Expect-CT.
This CL is missing a test for ProofVerifierChromium, which I'm omitting because
all the tests for that file are mysteriously disabled and I'm not sure why.
BUG=679012
Review-Url: https://codereview.chromium.org/2850033002
Cr-Commit-Position: refs/heads/master@{#469686}
diff --git a/net/http/transport_security_state.cc b/net/http/transport_security_state.cc
index 44869549..bc45c63 100644
--- a/net/http/transport_security_state.cc
+++ b/net/http/transport_security_state.cc
@@ -50,7 +50,7 @@
// Points to the active transport security state source.
const TransportSecurityStateSource* g_hsts_source = &kHSTSSource;
-// Override for ShouldRequireCT() for unit tests. Possible values:
+// Override for CheckCTRequirements() for unit tests. Possible values:
// -1: Unless a delegate says otherwise, do not require CT.
// 0: Use the default implementation (e.g. production)
// 1: Unless a delegate says otherwise, require CT.
@@ -860,26 +860,61 @@
return false;
}
-bool TransportSecurityState::ShouldRequireCT(
- const std::string& hostname,
+TransportSecurityState::CTRequirementsStatus
+TransportSecurityState::CheckCTRequirements(
+ const net::HostPortPair& host_port_pair,
+ bool is_issued_by_known_root,
+ const HashValueVector& public_key_hashes,
const X509Certificate* validated_certificate_chain,
- const HashValueVector& public_key_hashes) {
+ const X509Certificate* served_certificate_chain,
+ const SignedCertificateTimestampAndStatusList&
+ signed_certificate_timestamps,
+ const ExpectCTReportStatus report_status,
+ ct::CertPolicyCompliance cert_policy_compliance) {
using CTRequirementLevel = RequireCTDelegate::CTRequirementLevel;
+ std::string hostname = host_port_pair.host();
+
+ // If the connection complies with CT policy, then no further checks are
+ // necessary.
+ if (cert_policy_compliance ==
+ ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS ||
+ cert_policy_compliance ==
+ ct::CertPolicyCompliance::CERT_POLICY_BUILD_NOT_TIMELY) {
+ return CT_REQUIREMENTS_MET;
+ }
+
+ // Check Expect-CT first so that other CT requirements do not prevent
+ // Expect-CT reports from being sent.
+ ExpectCTState state;
+ if (is_issued_by_known_root && IsDynamicExpectCTEnabled() &&
+ GetDynamicExpectCTState(hostname, &state)) {
+ if (expect_ct_reporter_ && !state.report_uri.is_empty() &&
+ report_status == ENABLE_EXPECT_CT_REPORTS) {
+ expect_ct_reporter_->OnExpectCTFailed(
+ host_port_pair, state.report_uri, validated_certificate_chain,
+ served_certificate_chain, signed_certificate_timestamps);
+ }
+ if (state.enforce)
+ return CT_REQUIREMENTS_NOT_MET;
+ }
CTRequirementLevel ct_required = CTRequirementLevel::DEFAULT;
if (require_ct_delegate_)
ct_required = require_ct_delegate_->IsCTRequiredForHost(hostname);
if (ct_required != CTRequirementLevel::DEFAULT)
- return ct_required == CTRequirementLevel::REQUIRED;
+ return (ct_required == CTRequirementLevel::REQUIRED
+ ? CT_REQUIREMENTS_NOT_MET
+ : CT_REQUIREMENTS_MET);
// Allow unittests to override the default result.
if (g_ct_required_for_testing)
- return g_ct_required_for_testing == 1;
+ return (g_ct_required_for_testing == 1 ? CT_REQUIREMENTS_NOT_MET
+ : CT_REQUIREMENTS_MET);
// Until CT is required for all secure hosts on the Internet, this should
- // remain false. It is provided to simplify the various short-circuit
- // returns below.
- bool default_response = false;
+ // remain CT_REQUIREMENTS_MET. It is provided to simplify the various
+ // short-circuit returns below.
+ const CTRequirementsStatus default_response = CT_REQUIREMENTS_MET;
// FieldTrials are not supported in Native Client apps.
#if !defined(OS_NACL)
@@ -930,7 +965,7 @@
}
// No exception found. This certificate must conform to the CT policy.
- return true;
+ return CT_REQUIREMENTS_NOT_MET;
}
}
@@ -1413,8 +1448,10 @@
return;
ExpectCTState state;
if (GetStaticExpectCTState(host_port_pair.host(), &state)) {
- expect_ct_reporter_->OnExpectCTFailed(host_port_pair, state.report_uri,
- ssl_info);
+ expect_ct_reporter_->OnExpectCTFailed(
+ host_port_pair, state.report_uri, ssl_info.cert.get(),
+ ssl_info.unverified_cert.get(),
+ ssl_info.signed_certificate_timestamps);
}
return;
}
@@ -1447,8 +1484,10 @@
// processing the header.
if (expect_ct_reporter_ && !report_uri.is_empty() &&
!GetDynamicExpectCTState(host_port_pair.host(), &state)) {
- expect_ct_reporter_->OnExpectCTFailed(host_port_pair, report_uri,
- ssl_info);
+ expect_ct_reporter_->OnExpectCTFailed(
+ host_port_pair, report_uri, ssl_info.cert.get(),
+ ssl_info.unverified_cert.get(),
+ ssl_info.signed_certificate_timestamps);
}
return;
}
diff --git a/net/http/transport_security_state.h b/net/http/transport_security_state.h
index f2d182c..7ce883a 100644
--- a/net/http/transport_security_state.h
+++ b/net/http/transport_security_state.h
@@ -20,11 +20,16 @@
#include "net/base/expiring_cache.h"
#include "net/base/hash_value.h"
#include "net/base/net_export.h"
+#include "net/cert/signed_certificate_timestamp_and_status.h"
#include "net/http/transport_security_state_source.h"
#include "url/gurl.h"
namespace net {
+namespace ct {
+enum class CertPolicyCompliance;
+};
+
class HostPortPair;
class SSLInfo;
class X509Certificate;
@@ -310,9 +315,13 @@
// Called when the host in |host_port_pair| has opted in to have
// reports about Expect CT policy violations sent to |report_uri|,
// and such a violation has occurred.
- virtual void OnExpectCTFailed(const net::HostPortPair& host_port_pair,
- const GURL& report_uri,
- const net::SSLInfo& ssl_info) = 0;
+ virtual void OnExpectCTFailed(
+ const net::HostPortPair& host_port_pair,
+ const GURL& report_uri,
+ const X509Certificate* validated_certificate_chain,
+ const X509Certificate* served_certificate_chain,
+ const SignedCertificateTimestampAndStatusList&
+ signed_certificate_timestamps) = 0;
protected:
virtual ~ExpectCTReporter() {}
@@ -322,6 +331,22 @@
// report if a violation is detected.
enum PublicKeyPinReportStatus { ENABLE_PIN_REPORTS, DISABLE_PIN_REPORTS };
+ // Indicates whether or not an Expect-CT check should send a report if a
+ // violation is detected.
+ enum ExpectCTReportStatus {
+ ENABLE_EXPECT_CT_REPORTS,
+ DISABLE_EXPECT_CT_REPORTS
+ };
+
+ // Indicates whether a connection met CT requirements.
+ enum CTRequirementsStatus {
+ // CT was not required for the connection, or CT was required for the
+ // connection and valid Certificate Transparency information was provided.
+ CT_REQUIREMENTS_MET,
+ // CT was required for the connection but valid CT info was not provided.
+ CT_REQUIREMENTS_NOT_MET,
+ };
+
// Feature that controls whether Expect-CT HTTP headers are parsed, processed,
// and stored.
static const base::Feature kDynamicExpectCTFeature;
@@ -361,16 +386,30 @@
const SSLInfo& ssl_info,
base::StringPiece ocsp_response);
- // Returns true if connections to |host|, using the validated certificate
- // |validated_certificate_chain|, are expected to be accompanied with
- // valid Certificate Transparency information that complies with the
- // connection's CTPolicyEnforcer.
+ // Returns CT_REQUIREMENTS_NOT_MET if a connection violates CT policy
+ // requirements: that is, if a connection to |host|, using the validated
+ // certificate |validated_certificate_chain|, is expected to be accompanied
+ // with valid Certificate Transparency information that complies with the
+ // connection's CTPolicyEnforcer and |cert_policy_compliance| indicates that
+ // the connection does not comply.
//
// The behavior may be further be altered by setting a RequireCTDelegate
// via |SetRequireCTDelegate()|.
- bool ShouldRequireCT(const std::string& host,
- const X509Certificate* validated_certificate_chain,
- const HashValueVector& hashes);
+ //
+ // This method checks Expect-CT state for |host| if |issued_by_known_root| is
+ // true. If Expect-CT is configured for |host| and the connection is not
+ // compliant and |report_status| is ENABLE_EXPECT_CT_REPORTS, then a report
+ // will be sent.
+ CTRequirementsStatus CheckCTRequirements(
+ const net::HostPortPair& host_port_pair,
+ bool is_issued_by_known_root,
+ const HashValueVector& public_key_hashes,
+ const X509Certificate* validated_certificate_chain,
+ const X509Certificate* served_certificate_chain,
+ const SignedCertificateTimestampAndStatusList&
+ signed_certificate_timestamps,
+ const ExpectCTReportStatus report_status,
+ ct::CertPolicyCompliance cert_policy_compliance);
// Assign a |Delegate| for persisting the transport security state. If
// |NULL|, state will not be persisted. The caller retains
@@ -527,9 +566,10 @@
const HostPortPair& host_port_pair,
const SSLInfo& ssl_info);
- // For unit tests only; causes ShouldRequireCT() to return |*required|
- // by default (that is, unless a RequireCTDelegate overrides). Set to
- // nullptr to reset.
+ // For unit tests only. Causes CheckCTRequirements() to return
+ // CT_REQUIREMENTS_NOT_MET (if |*required| is true) or CT_REQUIREMENTS_MET (if
+ // |*required| is false) for non-compliant connections by default (that is,
+ // unless a RequireCTDelegate overrides). Set to nullptr to reset.
static void SetShouldRequireCTForTesting(bool* required);
private:
diff --git a/net/http/transport_security_state_unittest.cc b/net/http/transport_security_state_unittest.cc
index 65e1361..e657f013 100644
--- a/net/http/transport_security_state_unittest.cc
+++ b/net/http/transport_security_state_unittest.cc
@@ -93,6 +93,26 @@
nullptr,
};
+// Constructs a SignedCertificateTimestampAndStatus with the given information
+// and appends it to |sct_list|.
+void MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::Origin origin,
+ const std::string& log_id,
+ const std::string& extensions,
+ const std::string& signature_data,
+ const base::Time& timestamp,
+ ct::SCTVerifyStatus status,
+ SignedCertificateTimestampAndStatusList* sct_list) {
+ scoped_refptr<net::ct::SignedCertificateTimestamp> sct(
+ new net::ct::SignedCertificateTimestamp());
+ sct->version = net::ct::SignedCertificateTimestamp::V1;
+ sct->log_id = log_id;
+ sct->extensions = extensions;
+ sct->timestamp = timestamp;
+ sct->signature.signature_data = signature_data;
+ sct->origin = origin;
+ sct_list->push_back(net::SignedCertificateTimestampAndStatus(sct, status));
+}
+
// A mock ReportSenderInterface that just remembers the latest report
// URI and report to be sent.
class MockCertificateReportSender
@@ -161,23 +181,39 @@
void OnExpectCTFailed(const HostPortPair& host_port_pair,
const GURL& report_uri,
- const net::SSLInfo& ssl_info) override {
+ const X509Certificate* validated_certificate_chain,
+ const X509Certificate* served_certificate_chain,
+ const SignedCertificateTimestampAndStatusList&
+ signed_certificate_timestamps) override {
num_failures_++;
host_port_pair_ = host_port_pair;
report_uri_ = report_uri;
- ssl_info_ = ssl_info;
+ served_certificate_chain_ = served_certificate_chain;
+ validated_certificate_chain_ = validated_certificate_chain;
+ signed_certificate_timestamps_ = signed_certificate_timestamps;
}
const HostPortPair& host_port_pair() { return host_port_pair_; }
const GURL& report_uri() { return report_uri_; }
- const SSLInfo& ssl_info() { return ssl_info_; }
uint32_t num_failures() { return num_failures_; }
+ const X509Certificate* served_certificate_chain() {
+ return served_certificate_chain_;
+ }
+ const X509Certificate* validated_certificate_chain() {
+ return validated_certificate_chain_;
+ }
+ const SignedCertificateTimestampAndStatusList&
+ signed_certificate_timestamps() {
+ return signed_certificate_timestamps_;
+ }
private:
HostPortPair host_port_pair_;
GURL report_uri_;
- SSLInfo ssl_info_;
uint32_t num_failures_;
+ const X509Certificate* served_certificate_chain_;
+ const X509Certificate* validated_certificate_chain_;
+ SignedCertificateTimestampAndStatusList signed_certificate_timestamps_;
};
class MockRequireCTDelegate : public TransportSecurityState::RequireCTDelegate {
@@ -2028,6 +2064,18 @@
ssl_info.ct_cert_policy_compliance =
ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS;
ssl_info.is_issued_by_known_root = true;
+ scoped_refptr<X509Certificate> cert1 =
+ ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
+ scoped_refptr<X509Certificate> cert2 =
+ ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
+ ASSERT_TRUE(cert1);
+ ASSERT_TRUE(cert2);
+ ssl_info.unverified_cert = cert1;
+ ssl_info.cert = cert2;
+ MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::SCT_EMBEDDED, "test_log",
+ std::string(), std::string(), base::Time::Now(),
+ ct::SCT_STATUS_INVALID_SIGNATURE,
+ &ssl_info.signed_certificate_timestamps);
TransportSecurityState state;
TransportSecurityStateTest::EnableStaticExpectCT(&state);
@@ -2035,12 +2083,17 @@
state.SetExpectCTReporter(&reporter);
state.ProcessExpectCTHeader("preload", host_port, ssl_info);
EXPECT_EQ(1u, reporter.num_failures());
- EXPECT_TRUE(reporter.ssl_info().ct_compliance_details_available);
- EXPECT_EQ(ssl_info.ct_cert_policy_compliance,
- reporter.ssl_info().ct_cert_policy_compliance);
EXPECT_EQ(host_port.host(), reporter.host_port_pair().host());
EXPECT_EQ(host_port.port(), reporter.host_port_pair().port());
EXPECT_EQ(GURL(kExpectCTStaticReportURI), reporter.report_uri());
+ EXPECT_EQ(cert1.get(), reporter.served_certificate_chain());
+ EXPECT_EQ(cert2.get(), reporter.validated_certificate_chain());
+ EXPECT_EQ(ssl_info.signed_certificate_timestamps.size(),
+ reporter.signed_certificate_timestamps().size());
+ EXPECT_EQ(ssl_info.signed_certificate_timestamps[0].status,
+ reporter.signed_certificate_timestamps()[0].status);
+ EXPECT_EQ(ssl_info.signed_certificate_timestamps[0].sct,
+ reporter.signed_certificate_timestamps()[0].sct);
}
// Simple test for the HSTS preload process. The trie (generated from
@@ -2510,51 +2563,123 @@
{
TransportSecurityState state;
- bool original_status =
- state.ShouldRequireCT("www.example.com", cert.get(), hashes);
+ const TransportSecurityState::CTRequirementsStatus original_status =
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS);
MockRequireCTDelegate always_require_delegate;
EXPECT_CALL(always_require_delegate, IsCTRequiredForHost(_))
.WillRepeatedly(Return(CTRequirementLevel::REQUIRED));
state.SetRequireCTDelegate(&always_require_delegate);
- EXPECT_TRUE(state.ShouldRequireCT("www.example.com", cert.get(), hashes));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_BUILD_NOT_TIMELY));
state.SetRequireCTDelegate(nullptr);
- EXPECT_EQ(original_status,
- state.ShouldRequireCT("www.example.com", cert.get(), hashes));
+ EXPECT_EQ(
+ original_status,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
}
{
TransportSecurityState state;
- bool original_status =
- state.ShouldRequireCT("www.example.com", cert.get(), hashes);
+ const TransportSecurityState::CTRequirementsStatus original_status =
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS);
MockRequireCTDelegate never_require_delegate;
EXPECT_CALL(never_require_delegate, IsCTRequiredForHost(_))
.WillRepeatedly(Return(CTRequirementLevel::NOT_REQUIRED));
state.SetRequireCTDelegate(&never_require_delegate);
- EXPECT_FALSE(state.ShouldRequireCT("www.example.com", cert.get(), hashes));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS));
state.SetRequireCTDelegate(nullptr);
- EXPECT_EQ(original_status,
- state.ShouldRequireCT("www.example.com", cert.get(), hashes));
+ EXPECT_EQ(
+ original_status,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
}
{
TransportSecurityState state;
- bool original_status =
- state.ShouldRequireCT("www.example.com", cert.get(), hashes);
+ const TransportSecurityState::CTRequirementsStatus original_status =
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS);
MockRequireCTDelegate default_require_ct_delegate;
EXPECT_CALL(default_require_ct_delegate, IsCTRequiredForHost(_))
.WillRepeatedly(Return(CTRequirementLevel::DEFAULT));
state.SetRequireCTDelegate(&default_require_ct_delegate);
- EXPECT_EQ(original_status,
- state.ShouldRequireCT("www.example.com", cert.get(), hashes));
+ EXPECT_EQ(
+ original_status,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
state.SetRequireCTDelegate(nullptr);
- EXPECT_EQ(original_status,
- state.ShouldRequireCT("www.example.com", cert.get(), hashes));
+ EXPECT_EQ(
+ original_status,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, cert.get(),
+ cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
}
}
@@ -2586,29 +2711,80 @@
// Certificates issued by Symantec prior to 1 June 2016 should not
// be required to be disclosed via CT.
- EXPECT_FALSE(
- state.ShouldRequireCT("www.example.com", before_cert.get(), hashes));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, before_cert.get(),
+ before_cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
// ... but certificates issued after 1 June 2016 are required to be...
- EXPECT_TRUE(
- state.ShouldRequireCT("www.example.com", after_cert.get(), hashes));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
+ after_cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
+ after_cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
+ after_cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_BUILD_NOT_TIMELY));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
+ after_cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS));
// ... unless they were issued by an excluded intermediate.
hashes.push_back(HashValue(google_hash_value));
- EXPECT_FALSE(
- state.ShouldRequireCT("www.example.com", before_cert.get(), hashes));
- EXPECT_FALSE(
- state.ShouldRequireCT("www.example.com", after_cert.get(), hashes));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, before_cert.get(),
+ before_cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
+ after_cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
// And other certificates should remain unaffected.
SHA256HashValue unrelated_hash_value = {{0x01, 0x02}};
HashValueVector unrelated_hashes;
unrelated_hashes.push_back(HashValue(unrelated_hash_value));
- EXPECT_FALSE(state.ShouldRequireCT("www.example.com", before_cert.get(),
- unrelated_hashes));
- EXPECT_FALSE(state.ShouldRequireCT("www.example.com", after_cert.get(),
- unrelated_hashes));
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, unrelated_hashes,
+ before_cert.get(), before_cert.get(),
+ SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, unrelated_hashes,
+ after_cert.get(), after_cert.get(),
+ SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
// And the emergency field trial should disable the requirement, if
// necessary.
@@ -2619,10 +2795,20 @@
base::FieldTrialList::CreateFieldTrial("EnforceCTForProblematicRoots",
"disabled");
- EXPECT_FALSE(
- state.ShouldRequireCT("www.example.com", before_cert.get(), hashes));
- EXPECT_FALSE(
- state.ShouldRequireCT("www.example.com", after_cert.get(), hashes));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, before_cert.get(),
+ before_cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(
+ TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("www.example.com", 443), true, hashes, after_cert.get(),
+ after_cert.get(), SignedCertificateTimestampAndStatusList(),
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
}
// Tests that dynamic Expect-CT state is cleared from ClearDynamicData().
@@ -2786,13 +2972,26 @@
// Tests that Expect-CT reports are sent when an Expect-CT header is received
// over a non-compliant connection.
-TEST_F(TransportSecurityStateTest, DynamicExpectCTNonCompliant) {
+TEST_F(TransportSecurityStateTest,
+ DynamicExpectCTHeaderProcessingNonCompliant) {
const char kHeader[] = "max-age=123,enforce,report-uri=\"http://foo.test\"";
SSLInfo ssl;
ssl.is_issued_by_known_root = true;
ssl.ct_compliance_details_available = true;
ssl.ct_cert_policy_compliance =
ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS;
+ scoped_refptr<X509Certificate> cert1 =
+ ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
+ scoped_refptr<X509Certificate> cert2 =
+ ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
+ ASSERT_TRUE(cert1);
+ ASSERT_TRUE(cert2);
+ ssl.unverified_cert = cert1;
+ ssl.cert = cert2;
+ MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::SCT_EMBEDDED, "test_log",
+ std::string(), std::string(), base::Time::Now(),
+ ct::SCT_STATUS_INVALID_SIGNATURE,
+ &ssl.signed_certificate_timestamps);
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
@@ -2805,6 +3004,192 @@
EXPECT_FALSE(state.GetDynamicExpectCTState("example.test", &expect_ct_state));
EXPECT_EQ(1u, reporter.num_failures());
EXPECT_EQ("example.test", reporter.host_port_pair().host());
+ EXPECT_EQ(cert1.get(), reporter.served_certificate_chain());
+ EXPECT_EQ(cert2.get(), reporter.validated_certificate_chain());
+ EXPECT_EQ(ssl.signed_certificate_timestamps.size(),
+ reporter.signed_certificate_timestamps().size());
+ EXPECT_EQ(ssl.signed_certificate_timestamps[0].status,
+ reporter.signed_certificate_timestamps()[0].status);
+ EXPECT_EQ(ssl.signed_certificate_timestamps[0].sct,
+ reporter.signed_certificate_timestamps()[0].sct);
+}
+
+// Tests that CheckCTRequirements() returns false if a connection to a host
+// violates an Expect-CT header, and that it reports violations.
+TEST_F(TransportSecurityStateTest, CheckCTRequirementsWithExpectCT) {
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ scoped_refptr<X509Certificate> cert1 =
+ ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
+ scoped_refptr<X509Certificate> cert2 =
+ ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
+ ASSERT_TRUE(cert1);
+ ASSERT_TRUE(cert2);
+ SignedCertificateTimestampAndStatusList sct_list;
+ MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::SCT_EMBEDDED, "test_log",
+ std::string(), std::string(), base::Time::Now(),
+ ct::SCT_STATUS_INVALID_SIGNATURE, &sct_list);
+
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(
+ TransportSecurityState::kDynamicExpectCTFeature);
+
+ TransportSecurityState state;
+ MockExpectCTReporter reporter;
+ state.SetExpectCTReporter(&reporter);
+ state.AddExpectCT("example.test", expiry, true /* enforce */,
+ GURL("https://example-report.test"));
+ state.AddExpectCT("example-report-only.test", expiry, false /* enforce */,
+ GURL("https://example-report.test"));
+ state.AddExpectCT("example-enforce-only.test", expiry, true /* enforce */,
+ GURL());
+
+ // Test that a connection to an unrelated host is not affected.
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("example2.test", 443), true, HashValueVector(),
+ cert1.get(), cert2.get(), sct_list,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("example2.test", 443), true, HashValueVector(),
+ cert1.get(), cert2.get(), sct_list,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS));
+ EXPECT_EQ(0u, reporter.num_failures());
+
+ // A connection to an Expect-CT host should be closed and reported.
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
+ state.CheckCTRequirements(
+ HostPortPair("example.test", 443), true, HashValueVector(),
+ cert1.get(), cert2.get(), sct_list,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(1u, reporter.num_failures());
+ EXPECT_EQ("example.test", reporter.host_port_pair().host());
+ EXPECT_EQ(443, reporter.host_port_pair().port());
+ EXPECT_EQ(cert1.get(), reporter.validated_certificate_chain());
+ EXPECT_EQ(cert2.get(), reporter.served_certificate_chain());
+ EXPECT_EQ(sct_list.size(), reporter.signed_certificate_timestamps().size());
+ EXPECT_EQ(sct_list[0].status,
+ reporter.signed_certificate_timestamps()[0].status);
+ EXPECT_EQ(sct_list[0].sct, reporter.signed_certificate_timestamps()[0].sct);
+
+ // A compliant connection to an Expect-CT host should not be closed or
+ // reported.
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("example.test", 443), true, HashValueVector(),
+ cert1.get(), cert2.get(), sct_list,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS));
+ EXPECT_EQ(1u, reporter.num_failures());
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("example.test", 443), true, HashValueVector(),
+ cert1.get(), cert2.get(), sct_list,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_BUILD_NOT_TIMELY));
+ EXPECT_EQ(1u, reporter.num_failures());
+
+ // A connection to a report-only host should be reported only.
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("example-report-only.test", 443), true,
+ HashValueVector(), cert1.get(), cert2.get(), sct_list,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS));
+ EXPECT_EQ(2u, reporter.num_failures());
+ EXPECT_EQ("example-report-only.test", reporter.host_port_pair().host());
+ EXPECT_EQ(443, reporter.host_port_pair().port());
+ EXPECT_EQ(cert1.get(), reporter.validated_certificate_chain());
+ EXPECT_EQ(cert2.get(), reporter.served_certificate_chain());
+ EXPECT_EQ(sct_list.size(), reporter.signed_certificate_timestamps().size());
+ EXPECT_EQ(sct_list[0].status,
+ reporter.signed_certificate_timestamps()[0].status);
+ EXPECT_EQ(sct_list[0].sct, reporter.signed_certificate_timestamps()[0].sct);
+
+ // A connection to an enforce-only host should be closed but not reported.
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
+ state.CheckCTRequirements(
+ HostPortPair("example-enforce-only.test", 443), true,
+ HashValueVector(), cert1.get(), cert2.get(), sct_list,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS));
+ EXPECT_EQ(2u, reporter.num_failures());
+
+ // A connection with a private root should be neither enforced nor reported.
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_MET,
+ state.CheckCTRequirements(
+ HostPortPair("example.test", 443), false, HashValueVector(),
+ cert1.get(), cert2.get(), sct_list,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(2u, reporter.num_failures());
+
+ // A connection with DISABLE_EXPECT_CT_REPORTS should not send a report.
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
+ state.CheckCTRequirements(
+ HostPortPair("example.test", 443), true, HashValueVector(),
+ cert1.get(), cert2.get(), sct_list,
+ TransportSecurityState::DISABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(2u, reporter.num_failures());
+}
+
+// Tests that for a host that requires CT by delegate and is also
+// Expect-CT-enabled, CheckCTRequirements() sends reports.
+TEST_F(TransportSecurityStateTest, CheckCTRequirementsWithExpectCTAndDelegate) {
+ using ::testing::_;
+ using ::testing::Return;
+ using CTRequirementLevel =
+ TransportSecurityState::RequireCTDelegate::CTRequirementLevel;
+
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ scoped_refptr<X509Certificate> cert1 =
+ ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
+ scoped_refptr<X509Certificate> cert2 =
+ ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
+ ASSERT_TRUE(cert1);
+ ASSERT_TRUE(cert2);
+ SignedCertificateTimestampAndStatusList sct_list;
+ MakeTestSCTAndStatus(ct::SignedCertificateTimestamp::SCT_EMBEDDED, "test_log",
+ std::string(), std::string(), base::Time::Now(),
+ ct::SCT_STATUS_INVALID_SIGNATURE, &sct_list);
+
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(
+ TransportSecurityState::kDynamicExpectCTFeature);
+
+ TransportSecurityState state;
+ MockExpectCTReporter reporter;
+ state.SetExpectCTReporter(&reporter);
+ state.AddExpectCT("example.test", expiry, false /* enforce */,
+ GURL("https://example-report.test"));
+
+ // A connection to an Expect-CT host, which also requires CT by the delegate,
+ // should be closed and reported.
+ MockRequireCTDelegate always_require_delegate;
+ EXPECT_CALL(always_require_delegate, IsCTRequiredForHost(_))
+ .WillRepeatedly(Return(CTRequirementLevel::REQUIRED));
+ state.SetRequireCTDelegate(&always_require_delegate);
+ EXPECT_EQ(TransportSecurityState::CT_REQUIREMENTS_NOT_MET,
+ state.CheckCTRequirements(
+ HostPortPair("example.test", 443), true, HashValueVector(),
+ cert1.get(), cert2.get(), sct_list,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+ EXPECT_EQ(1u, reporter.num_failures());
+ EXPECT_EQ("example.test", reporter.host_port_pair().host());
+ EXPECT_EQ(443, reporter.host_port_pair().port());
+ EXPECT_EQ(cert1.get(), reporter.validated_certificate_chain());
+ EXPECT_EQ(cert2.get(), reporter.served_certificate_chain());
+ EXPECT_EQ(sct_list.size(), reporter.signed_certificate_timestamps().size());
+ EXPECT_EQ(sct_list[0].status,
+ reporter.signed_certificate_timestamps()[0].status);
+ EXPECT_EQ(sct_list[0].sct, reporter.signed_certificate_timestamps()[0].sct);
}
} // namespace net
diff --git a/net/quic/chromium/crypto/proof_verifier_chromium.cc b/net/quic/chromium/crypto/proof_verifier_chromium.cc
index 95b1999..a36b5cf7 100644
--- a/net/quic/chromium/crypto/proof_verifier_chromium.cc
+++ b/net/quic/chromium/crypto/proof_verifier_chromium.cc
@@ -420,13 +420,15 @@
cert_verify_result.verified_cert.get(), verified_scts, net_log_);
int ct_result = OK;
- if (verify_details_->ct_verify_result.cert_policy_compliance !=
- ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS &&
- verify_details_->ct_verify_result.cert_policy_compliance !=
- ct::CertPolicyCompliance::CERT_POLICY_BUILD_NOT_TIMELY &&
- transport_security_state_->ShouldRequireCT(
- hostname_, cert_verify_result.verified_cert.get(),
- cert_verify_result.public_key_hashes)) {
+ if (transport_security_state_->CheckCTRequirements(
+ HostPortPair(hostname_, port_),
+ cert_verify_result.is_issued_by_known_root,
+ cert_verify_result.public_key_hashes,
+ cert_verify_result.verified_cert.get(), cert_.get(),
+ verify_details_->ct_verify_result.scts,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ verify_details_->ct_verify_result.cert_policy_compliance) !=
+ TransportSecurityState::CT_REQUIREMENTS_MET) {
verify_details_->cert_verify_result.cert_status |=
CERT_STATUS_CERTIFICATE_TRANSPARENCY_REQUIRED;
ct_result = ERR_CERTIFICATE_TRANSPARENCY_REQUIRED;
diff --git a/net/socket/ssl_client_socket_impl.cc b/net/socket/ssl_client_socket_impl.cc
index fe5aabd..433c8d4 100644
--- a/net/socket/ssl_client_socket_impl.cc
+++ b/net/socket/ssl_client_socket_impl.cc
@@ -1568,13 +1568,14 @@
server_cert_verify_result_.verified_cert.get(), verified_scts,
net_log_);
- if (ct_verify_result_.cert_policy_compliance !=
- ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS &&
- ct_verify_result_.cert_policy_compliance !=
- ct::CertPolicyCompliance::CERT_POLICY_BUILD_NOT_TIMELY &&
- transport_security_state_->ShouldRequireCT(
- host_and_port_.host(), server_cert_verify_result_.verified_cert.get(),
- server_cert_verify_result_.public_key_hashes)) {
+ if (transport_security_state_->CheckCTRequirements(
+ host_and_port_, server_cert_verify_result_.is_issued_by_known_root,
+ server_cert_verify_result_.public_key_hashes,
+ server_cert_verify_result_.verified_cert.get(), server_cert_.get(),
+ ct_verify_result_.scts,
+ TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
+ ct_verify_result_.cert_policy_compliance) !=
+ TransportSecurityState::CT_REQUIREMENTS_MET) {
server_cert_verify_result_.cert_status |=
CERT_STATUS_CERTIFICATE_TRANSPARENCY_REQUIRED;
return ERR_CERTIFICATE_TRANSPARENCY_REQUIRED;
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index 6067721..1b62a26 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -805,6 +805,50 @@
bool IsEphemeral() override { return true; }
};
+// A mock ExpectCTReporter that remembers the latest violation that was
+// reported and the number of violations reported.
+class MockExpectCTReporter : public TransportSecurityState::ExpectCTReporter {
+ public:
+ MockExpectCTReporter() : num_failures_(0) {}
+ ~MockExpectCTReporter() override {}
+
+ void OnExpectCTFailed(const HostPortPair& host_port_pair,
+ const GURL& report_uri,
+ const X509Certificate* validated_certificate_chain,
+ const X509Certificate* served_certificate_chain,
+ const SignedCertificateTimestampAndStatusList&
+ signed_certificate_timestamps) override {
+ num_failures_++;
+ host_port_pair_ = host_port_pair;
+ report_uri_ = report_uri;
+ served_certificate_chain_ = served_certificate_chain;
+ validated_certificate_chain_ = validated_certificate_chain;
+ signed_certificate_timestamps_ = signed_certificate_timestamps;
+ }
+
+ const HostPortPair& host_port_pair() { return host_port_pair_; }
+ const GURL& report_uri() { return report_uri_; }
+ uint32_t num_failures() { return num_failures_; }
+ const X509Certificate* served_certificate_chain() {
+ return served_certificate_chain_;
+ }
+ const X509Certificate* validated_certificate_chain() {
+ return validated_certificate_chain_;
+ }
+ const SignedCertificateTimestampAndStatusList&
+ signed_certificate_timestamps() {
+ return signed_certificate_timestamps_;
+ }
+
+ private:
+ HostPortPair host_port_pair_;
+ GURL report_uri_;
+ uint32_t num_failures_;
+ const X509Certificate* served_certificate_chain_;
+ const X509Certificate* validated_certificate_chain_;
+ SignedCertificateTimestampAndStatusList signed_certificate_timestamps_;
+};
+
// A mock CTVerifier that records every call to Verify but doesn't verify
// anything.
class MockCTVerifier : public CTVerifier {
@@ -3478,6 +3522,105 @@
EXPECT_TRUE(sock_->IsConnected());
}
+// Test that when CT is required (in this case, by an Expect-CT opt-in), the
+// absence of CT information is a socket error.
+TEST_F(SSLClientSocketTest, CTIsRequiredByExpectCT) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(
+ TransportSecurityState::kDynamicExpectCTFeature);
+
+ SpawnedTestServer::SSLOptions ssl_options;
+ ASSERT_TRUE(StartTestServer(ssl_options));
+ scoped_refptr<X509Certificate> server_cert =
+ spawned_test_server()->GetCertificate();
+
+ // Certificate is trusted and chains to a public root.
+ CertVerifyResult verify_result;
+ verify_result.is_issued_by_known_root = true;
+ verify_result.verified_cert = server_cert;
+ verify_result.public_key_hashes = MakeHashValueVector(0);
+ cert_verifier_->AddResultForCert(server_cert.get(), verify_result, OK);
+
+ // Set up the Expect-CT opt-in.
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ transport_security_state_->AddExpectCT(
+ spawned_test_server()->host_port_pair().host(), expiry,
+ true /* enforce */, GURL("https://example-report.test"));
+ MockExpectCTReporter reporter;
+ transport_security_state_->SetExpectCTReporter(&reporter);
+
+ EXPECT_CALL(*ct_policy_enforcer_,
+ DoesConformToCertPolicy(server_cert.get(), _, _))
+ .WillRepeatedly(
+ Return(ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS));
+
+ SSLConfig ssl_config;
+ int rv;
+ ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
+ SSLInfo ssl_info;
+ ASSERT_TRUE(sock_->GetSSLInfo(&ssl_info));
+
+ EXPECT_THAT(rv, IsError(ERR_CERTIFICATE_TRANSPARENCY_REQUIRED));
+ EXPECT_TRUE(ssl_info.cert_status &
+ CERT_STATUS_CERTIFICATE_TRANSPARENCY_REQUIRED);
+ EXPECT_TRUE(sock_->IsConnected());
+
+ EXPECT_EQ(1u, reporter.num_failures());
+ EXPECT_EQ(GURL("https://example-report.test"), reporter.report_uri());
+ EXPECT_EQ(ssl_info.unverified_cert.get(),
+ reporter.served_certificate_chain());
+ EXPECT_EQ(ssl_info.cert.get(), reporter.validated_certificate_chain());
+ EXPECT_EQ(0u, reporter.signed_certificate_timestamps().size());
+
+ EXPECT_CALL(*ct_policy_enforcer_,
+ DoesConformToCertPolicy(server_cert.get(), _, _))
+ .WillRepeatedly(
+ Return(ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS));
+ ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
+ ASSERT_TRUE(sock_->GetSSLInfo(&ssl_info));
+
+ EXPECT_THAT(rv, IsError(ERR_CERTIFICATE_TRANSPARENCY_REQUIRED));
+ EXPECT_TRUE(ssl_info.cert_status &
+ CERT_STATUS_CERTIFICATE_TRANSPARENCY_REQUIRED);
+ EXPECT_TRUE(sock_->IsConnected());
+
+ EXPECT_EQ(2u, reporter.num_failures());
+ EXPECT_EQ(GURL("https://example-report.test"), reporter.report_uri());
+ EXPECT_EQ(ssl_info.unverified_cert.get(),
+ reporter.served_certificate_chain());
+ EXPECT_EQ(ssl_info.cert.get(), reporter.validated_certificate_chain());
+ EXPECT_EQ(0u, reporter.signed_certificate_timestamps().size());
+
+ // If the connection is CT compliant, then there should be no socket error nor
+ // a report.
+ EXPECT_CALL(*ct_policy_enforcer_,
+ DoesConformToCertPolicy(server_cert.get(), _, _))
+ .WillRepeatedly(
+ Return(ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS));
+ ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
+ ASSERT_TRUE(sock_->GetSSLInfo(&ssl_info));
+
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_FALSE(ssl_info.cert_status &
+ CERT_STATUS_CERTIFICATE_TRANSPARENCY_REQUIRED);
+ EXPECT_TRUE(sock_->IsConnected());
+ EXPECT_EQ(2u, reporter.num_failures());
+
+ EXPECT_CALL(*ct_policy_enforcer_,
+ DoesConformToCertPolicy(server_cert.get(), _, _))
+ .WillRepeatedly(
+ Return(ct::CertPolicyCompliance::CERT_POLICY_BUILD_NOT_TIMELY));
+ ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
+ ASSERT_TRUE(sock_->GetSSLInfo(&ssl_info));
+
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_FALSE(ssl_info.cert_status &
+ CERT_STATUS_CERTIFICATE_TRANSPARENCY_REQUIRED);
+ EXPECT_TRUE(sock_->IsConnected());
+ EXPECT_EQ(2u, reporter.num_failures());
+}
+
// When both HPKP and CT are required for a host, and both fail, the more
// serious error is that the HPKP pin validation failed.
TEST_F(SSLClientSocketTest, PKPMoreImportantThanCT) {
diff --git a/net/spdy/chromium/spdy_session.cc b/net/spdy/chromium/spdy_session.cc
index 96768b6..5a5cb670 100644
--- a/net/spdy/chromium/spdy_session.cc
+++ b/net/spdy/chromium/spdy_session.cc
@@ -711,12 +711,15 @@
return false;
}
- if (ssl_info.ct_cert_policy_compliance !=
- ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS &&
- ssl_info.ct_cert_policy_compliance !=
- ct::CertPolicyCompliance::CERT_POLICY_BUILD_NOT_TIMELY &&
- transport_security_state->ShouldRequireCT(
- new_hostname, ssl_info.cert.get(), ssl_info.public_key_hashes)) {
+ // As with CheckPublicKeyPins above, disable Expect-CT reports.
+ if (transport_security_state->CheckCTRequirements(
+ HostPortPair(new_hostname, 0), ssl_info.is_issued_by_known_root,
+ ssl_info.public_key_hashes, ssl_info.cert.get(),
+ ssl_info.unverified_cert.get(),
+ ssl_info.signed_certificate_timestamps,
+ TransportSecurityState::DISABLE_EXPECT_CT_REPORTS,
+ ssl_info.ct_cert_policy_compliance) !=
+ TransportSecurityState::CT_REQUIREMENTS_MET) {
return false;
}
diff --git a/net/spdy/chromium/spdy_session_unittest.cc b/net/spdy/chromium/spdy_session_unittest.cc
index 57bfa214..dbd985d 100644
--- a/net/spdy/chromium/spdy_session_unittest.cc
+++ b/net/spdy/chromium/spdy_session_unittest.cc
@@ -5945,6 +5945,49 @@
&tss, ssl_info, "www.example.org", "mail.google.com"));
}
+TEST(CanPoolTest, CanPoolExpectCT) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(
+ TransportSecurityState::kDynamicExpectCTFeature);
+ // Load a cert that is valid for:
+ // www.example.org
+ // mail.example.org
+ // mail.example.com
+
+ TransportSecurityState tss;
+ SSLInfo ssl_info;
+ ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
+ ssl_info.unverified_cert = ssl_info.cert;
+ ssl_info.ct_cert_policy_compliance =
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS;
+ ssl_info.is_issued_by_known_root = true;
+
+ EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, "www.example.org",
+ "www.example.org"));
+
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ ssl_info.ct_cert_policy_compliance =
+ ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS;
+
+ // A different Expect-CT enabled host should not be allowed to pool.
+ tss.AddExpectCT("mail.example.org", expiry, true, GURL());
+ EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, "www.example.org",
+ "mail.example.org"));
+ // A report-only Expect-CT configuration should not prevent pooling.
+ tss.AddExpectCT("mail.example.org", expiry, false,
+ GURL("https://report.test"));
+ EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, "www.example.org",
+ "mail.example.org"));
+ // If Expect-CT becomes enabled for the same host for which the connection was
+ // already made, subsequent connections to that host should not be allowed to
+ // pool.
+ tss.AddExpectCT("www.example.org", expiry, true, GURL());
+ EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, "www.example.org",
+ "www.example.org"));
+}
+
TEST(CanPoolTest, CanNotPoolWithCertErrors) {
// Load a cert that is valid for:
// www.example.org
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 6e016bf..25b603b 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -6538,7 +6538,10 @@
void OnExpectCTFailed(const HostPortPair& host_port_pair,
const GURL& report_uri,
- const net::SSLInfo& ssl_info) override {
+ const X509Certificate* validated_certificate_chain,
+ const X509Certificate* served_certificate_chain,
+ const SignedCertificateTimestampAndStatusList&
+ signed_certificate_timestamps) override {
num_failures_++;
}