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_++;
   }