diff options
author | eranm <eranm@chromium.org> | 2014-12-03 07:53:23 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-12-03 15:53:46 +0000 |
commit | 6571b2b68658337e301c074763383e0b5c231aea (patch) | |
tree | bf97e15557650b703fe7fc4be8146a2ce36c13d8 | |
parent | c4c9cc88986c37bebe988fecf3b64a53b7070874 (diff) | |
download | chromium_src-6571b2b68658337e301c074763383e0b5c231aea.zip chromium_src-6571b2b68658337e301c074763383e0b5c231aea.tar.gz chromium_src-6571b2b68658337e301c074763383e0b5c231aea.tar.bz2 |
Certificate Transparency: Require SCTs for EV certificates.
Add a flag to enforce the policy detailed here:
http://dev.chromium.org/Home/chromium-security/certificate-transparency
BUG=397458
Review URL: https://codereview.chromium.org/422063004
Cr-Commit-Position: refs/heads/master@{#306612}
24 files changed, 590 insertions, 113 deletions
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc index 7cbcf7b..4fa4eb5 100644 --- a/chrome/browser/io_thread.cc +++ b/chrome/browser/io_thread.cc @@ -53,9 +53,11 @@ #include "content/public/browser/cookie_store_factory.h" #include "net/base/host_mapping_rules.h" #include "net/base/net_util.h" +#include "net/cert/cert_policy_enforcer.h" #include "net/cert/cert_verifier.h" #include "net/cert/cert_verify_proc.h" #include "net/cert/ct_known_logs.h" +#include "net/cert/ct_known_logs_static.h" #include "net/cert/ct_log_verifier.h" #include "net/cert/ct_verifier.h" #include "net/cert/multi_log_ct_verifier.h" @@ -642,6 +644,15 @@ void IOThread::InitAsync() { } } + net::CertPolicyEnforcer* policy_enforcer = NULL; + // TODO(eranm): Control with Finch, crbug.com/437766 + if (command_line.HasSwitch(switches::kRequireCTForEV)) { + policy_enforcer = new net::CertPolicyEnforcer(kNumKnownCTLogs, true); + } else { + policy_enforcer = new net::CertPolicyEnforcer(kNumKnownCTLogs, false); + } + globals_->cert_policy_enforcer.reset(policy_enforcer); + globals_->ssl_config_service = GetSSLConfigService(); SetupDataReductionProxy(network_delegate); @@ -988,6 +999,7 @@ void IOThread::InitializeNetworkSessionParamsFromGlobals( net::HttpNetworkSession::Params* params) { params->host_resolver = globals.host_resolver.get(); params->cert_verifier = globals.cert_verifier.get(); + params->cert_policy_enforcer = globals.cert_policy_enforcer.get(); params->channel_id_service = globals.system_channel_id_service.get(); params->transport_security_state = globals.transport_security_state.get(); params->ssl_config_service = globals.ssl_config_service.get(); diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h index 897d2aa..690306f 100644 --- a/chrome/browser/io_thread.h +++ b/chrome/browser/io_thread.h @@ -49,6 +49,7 @@ class EventRouterForwarder; } namespace net { +class CertPolicyEnforcer; class CertVerifier; class ChannelIDService; class CookieStore; @@ -132,6 +133,7 @@ class IOThread : public content::BrowserThreadDelegate { // pins. scoped_ptr<net::TransportSecurityState> transport_security_state; scoped_ptr<net::CTVerifier> cert_transparency_verifier; + scoped_ptr<net::CertPolicyEnforcer> cert_policy_enforcer; scoped_refptr<net::SSLConfigService> ssl_config_service; scoped_ptr<net::HttpAuthHandlerFactory> http_auth_handler_factory; scoped_ptr<net::HttpServerProperties> http_server_properties; diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 31dcf37..967c85e 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -1019,6 +1019,11 @@ const char kRecordMode[] = "record-mode"; // time delta to remember certificates should be specified in seconds. const char kRememberCertErrorDecisions[] = "remember-cert-error-decisions"; +// Requires presence of Certificate Transparency for Extended Validation +// certificates. Enforce the policy detailed at: +// http://dev.chromium.org/Home/chromium-security/certificate-transparency +const char kRequireCTForEV[] = "require-ct-for-ev"; + // If set, the app list will forget it has been installed on startup. Note this // doesn't prevent the app list from running, it just makes Chrome think the app // list hasn't been enabled (as in kEnableAppList) yet. diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 814f8ca..876564c 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -283,6 +283,7 @@ extern const char kQuicMaxPacketLength[]; extern const char kQuicVersion[]; extern const char kRecordMode[]; extern const char kRememberCertErrorDecisions[]; +extern const char kRequireCTForEV[]; extern const char kResetAppListInstallState[]; extern const char kRestoreLastSession[]; extern const char kSavePageAsMHTML[]; diff --git a/net/cert/cert_policy_enforcer.cc b/net/cert/cert_policy_enforcer.cc new file mode 100644 index 0000000..c9ce7cc --- /dev/null +++ b/net/cert/cert_policy_enforcer.cc @@ -0,0 +1,165 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/cert_policy_enforcer.h" + +#include <algorithm> + +#include "base/build_time.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/histogram.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_number_conversions.h" +#include "net/cert/ct_ev_whitelist.h" +#include "net/cert/ct_verify_result.h" +#include "net/cert/signed_certificate_timestamp.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +namespace { + +bool IsEmbeddedSCT(const scoped_refptr<ct::SignedCertificateTimestamp>& sct) { + return sct->origin == ct::SignedCertificateTimestamp::SCT_EMBEDDED; +} + +// Returns true if the current build is recent enough to ensure that +// built-in security information (e.g. CT Logs) is fresh enough. +// TODO(eranm): Move to base or net/base +bool IsBuildTimely() { +#if defined(DONT_EMBED_BUILD_METADATA) && !defined(OFFICIAL_BUILD) + return true; +#else + const base::Time build_time = base::GetBuildTime(); + // We consider built-in information to be timely for 10 weeks. + return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */; +#endif +} + +uint32_t ApproximateMonthDifference(const base::Time& start, + const base::Time& end) { + base::Time::Exploded exploded_start; + base::Time::Exploded exploded_expiry; + start.UTCExplode(&exploded_start); + end.UTCExplode(&exploded_expiry); + uint32_t month_diff = (exploded_expiry.year - exploded_start.year) * 12 + + (exploded_expiry.month - exploded_start.month); + + // Add any remainder as a full month. + if (exploded_expiry.day_of_month > exploded_start.day_of_month) + ++month_diff; + + return month_diff; +} + +enum CTComplianceStatus { + CT_NOT_COMPLIANT = 0, + CT_IN_WHITELIST = 1, + CT_ENOUGH_SCTS = 2, + CT_COMPLIANCE_MAX, +}; + +void LogCTComplianceStatusToUMA(CTComplianceStatus status) { + UMA_HISTOGRAM_ENUMERATION("Net.SSL_EVCertificateCTCompliance", status, + CT_COMPLIANCE_MAX); +} + +} // namespace + +CertPolicyEnforcer::CertPolicyEnforcer(size_t num_ct_logs, + bool require_ct_for_ev) + : num_ct_logs_(num_ct_logs), require_ct_for_ev_(require_ct_for_ev) { +} + +CertPolicyEnforcer::~CertPolicyEnforcer() { +} + +bool CertPolicyEnforcer::DoesConformToCTEVPolicy( + X509Certificate* cert, + const ct::EVCertsWhitelist* ev_whitelist, + const ct::CTVerifyResult& ct_result) { + if (!require_ct_for_ev_) + return true; + + if (!IsBuildTimely()) + return false; + + if (IsCertificateInWhitelist(cert, ev_whitelist)) { + LogCTComplianceStatusToUMA(CT_IN_WHITELIST); + return true; + } + + if (HasRequiredNumberOfSCTs(cert, ct_result)) { + LogCTComplianceStatusToUMA(CT_ENOUGH_SCTS); + return true; + } + + LogCTComplianceStatusToUMA(CT_NOT_COMPLIANT); + return false; +} + +bool CertPolicyEnforcer::IsCertificateInWhitelist( + X509Certificate* cert, + const ct::EVCertsWhitelist* ev_whitelist) { + bool cert_in_ev_whitelist = false; + if (ev_whitelist && ev_whitelist->IsValid()) { + const SHA256HashValue fingerprint( + X509Certificate::CalculateFingerprint256(cert->os_cert_handle())); + + std::string truncated_fp = + std::string(reinterpret_cast<const char*>(fingerprint.data), 8); + cert_in_ev_whitelist = ev_whitelist->ContainsCertificateHash(truncated_fp); + + UMA_HISTOGRAM_BOOLEAN("Net.SSL_EVCertificateInWhitelist", + cert_in_ev_whitelist); + } + return cert_in_ev_whitelist; +} + +bool CertPolicyEnforcer::HasRequiredNumberOfSCTs( + X509Certificate* cert, + const ct::CTVerifyResult& ct_result) { + // TODO(eranm): Count the number of *independent* SCTs once the information + // about log operators is available, crbug.com/425174 + size_t num_valid_scts = ct_result.verified_scts.size(); + size_t num_embedded_scts = + std::count_if(ct_result.verified_scts.begin(), + ct_result.verified_scts.end(), IsEmbeddedSCT); + + size_t num_non_embedded_scts = num_valid_scts - num_embedded_scts; + // If at least two valid SCTs were delivered by means other than embedding + // (i.e. in a TLS extension or OCSP), then the certificate conforms to bullet + // number 3 of the "Qualifying Certificate" section of the CT/EV policy. + if (num_non_embedded_scts >= 2) + return true; + + if (cert->valid_start().is_null() || cert->valid_expiry().is_null() || + cert->valid_start().is_max() || cert->valid_expiry().is_max()) { + // Will not be able to calculate the certificate's validity period. + return false; + } + + uint32_t expiry_in_months_approx = + ApproximateMonthDifference(cert->valid_start(), cert->valid_expiry()); + + // For embedded SCTs, if the certificate has the number of SCTs specified in + // table 1 of the "Qualifying Certificate" section of the CT/EV policy, then + // it qualifies. + size_t num_required_embedded_scts; + if (expiry_in_months_approx > 39) { + num_required_embedded_scts = 5; + } else if (expiry_in_months_approx > 27) { + num_required_embedded_scts = 4; + } else if (expiry_in_months_approx >= 15) { + num_required_embedded_scts = 3; + } else { + num_required_embedded_scts = 2; + } + + size_t min_acceptable_logs = std::max(num_ct_logs_, static_cast<size_t>(2u)); + return num_embedded_scts >= + std::min(num_required_embedded_scts, min_acceptable_logs); +} + +} // namespace net diff --git a/net/cert/cert_policy_enforcer.h b/net/cert/cert_policy_enforcer.h new file mode 100644 index 0000000..68039b3 --- /dev/null +++ b/net/cert/cert_policy_enforcer.h @@ -0,0 +1,56 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef NET_CERT_CERT_POLICY_ENFORCER_H +#define NET_CERT_CERT_POLICY_ENFORCER_H + +#include <stddef.h> + +#include "net/base/net_export.h" + +namespace net { + +namespace ct { + +struct CTVerifyResult; +class EVCertsWhitelist; + +} // namespace ct + +class X509Certificate; + +// Class for checking that a given certificate conforms to security-related +// policies. +class NET_EXPORT CertPolicyEnforcer { + public: + // Set the parameters for this policy enforcer: + // |num_ct_logs| is the number of Certificate Transparency log currently + // known to Chrome. + // |require_ct_for_ev| indicates whether Certificate Transparency presence + // is required for EV certificates. + CertPolicyEnforcer(size_t num_ct_logs, bool require_ct_for_ev); + virtual ~CertPolicyEnforcer(); + + // Returns true if the collection of SCTs for the given certificate + // conforms with the CT/EV policy. + // |cert| is the certificate for which the SCTs apply. + // |ct_result| must contain the result of verifying any SCTs associated with + // |cert| prior to invoking this method. + bool DoesConformToCTEVPolicy(X509Certificate* cert, + const ct::EVCertsWhitelist* ev_whitelist, + const ct::CTVerifyResult& ct_result); + + private: + bool IsCertificateInWhitelist(X509Certificate* cert, + const ct::EVCertsWhitelist* ev_whitelist); + + bool HasRequiredNumberOfSCTs(X509Certificate* cert, + const ct::CTVerifyResult& ct_result); + + size_t num_ct_logs_; + bool require_ct_for_ev_; +}; + +} // namespace net + +#endif // NET_CERT_CERT_POLICY_ENFORCER_H diff --git a/net/cert/cert_policy_enforcer_unittest.cc b/net/cert/cert_policy_enforcer_unittest.cc new file mode 100644 index 0000000..45a2e6a --- /dev/null +++ b/net/cert/cert_policy_enforcer_unittest.cc @@ -0,0 +1,212 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cert/cert_policy_enforcer.h" + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "net/base/test_data_directory.h" +#include "net/cert/ct_ev_whitelist.h" +#include "net/cert/ct_verify_result.h" +#include "net/cert/x509_certificate.h" +#include "net/test/cert_test_util.h" +#include "net/test/ct_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class DummyEVCertsWhitelist : public ct::EVCertsWhitelist { + public: + DummyEVCertsWhitelist(bool is_valid_response, bool contains_hash_response) + : canned_is_valid_(is_valid_response), + canned_contains_response_(contains_hash_response) {} + + bool IsValid() const override { return canned_is_valid_; } + + bool ContainsCertificateHash( + const std::string& certificate_hash) const override { + return canned_contains_response_; + } + + protected: + ~DummyEVCertsWhitelist() override {} + + private: + bool canned_is_valid_; + bool canned_contains_response_; +}; + +class CertPolicyEnforcerTest : public ::testing::Test { + public: + virtual void SetUp() override { + policy_enforcer_.reset(new CertPolicyEnforcer(5, true)); + + std::string der_test_cert(ct::GetDerEncodedX509Cert()); + chain_ = X509Certificate::CreateFromBytes(der_test_cert.data(), + der_test_cert.size()); + ASSERT_TRUE(chain_.get()); + } + + void FillResultWithSCTsOfOrigin( + ct::SignedCertificateTimestamp::Origin desired_origin, + int num_scts, + ct::CTVerifyResult* result) { + for (int i = 0; i < num_scts; ++i) { + scoped_refptr<ct::SignedCertificateTimestamp> sct( + new ct::SignedCertificateTimestamp()); + sct->origin = desired_origin; + result->verified_scts.push_back(sct); + } + } + + protected: + scoped_ptr<CertPolicyEnforcer> policy_enforcer_; + scoped_refptr<X509Certificate> chain_; +}; + +TEST_F(CertPolicyEnforcerTest, ConformsToCTEVPolicyWithNonEmbeddedSCTs) { + ct::CTVerifyResult result; + FillResultWithSCTsOfOrigin( + ct::SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION, 2, &result); + + EXPECT_TRUE( + policy_enforcer_->DoesConformToCTEVPolicy(chain_.get(), nullptr, result)); +} + +TEST_F(CertPolicyEnforcerTest, ConformsToCTEVPolicyWithEmbeddedSCTs) { + // This chain_ is valid for 10 years - over 121 months - so requires 5 SCTs. + ct::CTVerifyResult result; + FillResultWithSCTsOfOrigin(ct::SignedCertificateTimestamp::SCT_EMBEDDED, 5, + &result); + + EXPECT_TRUE( + policy_enforcer_->DoesConformToCTEVPolicy(chain_.get(), nullptr, result)); +} + +TEST_F(CertPolicyEnforcerTest, DoesNotConformToCTEVPolicyNotEnoughSCTs) { + scoped_refptr<ct::EVCertsWhitelist> non_including_whitelist( + new DummyEVCertsWhitelist(true, false)); + // This chain_ is valid for 10 years - over 121 months - so requires 5 SCTs. + // However, as there are only two logs, two SCTs will be required - supply one + // to guarantee the test fails. + ct::CTVerifyResult result; + FillResultWithSCTsOfOrigin(ct::SignedCertificateTimestamp::SCT_EMBEDDED, 1, + &result); + + EXPECT_FALSE(policy_enforcer_->DoesConformToCTEVPolicy( + chain_.get(), non_including_whitelist.get(), result)); + + // ... but should be OK if whitelisted. + scoped_refptr<ct::EVCertsWhitelist> whitelist( + new DummyEVCertsWhitelist(true, true)); + EXPECT_TRUE(policy_enforcer_->DoesConformToCTEVPolicy( + chain_.get(), whitelist.get(), result)); +} + +TEST_F(CertPolicyEnforcerTest, DoesNotEnforceCTPolicyIfNotRequired) { + scoped_ptr<CertPolicyEnforcer> enforcer(new CertPolicyEnforcer(3, false)); + + ct::CTVerifyResult result; + FillResultWithSCTsOfOrigin(ct::SignedCertificateTimestamp::SCT_EMBEDDED, 1, + &result); + // Expect true despite the chain not having enough SCTs as the policy + // is not enforced. + EXPECT_TRUE(enforcer->DoesConformToCTEVPolicy(chain_.get(), nullptr, result)); +} + +TEST_F(CertPolicyEnforcerTest, DoesNotConformToPolicyInvalidDates) { + scoped_refptr<X509Certificate> no_valid_dates_cert(new X509Certificate( + "subject", "issuer", base::Time(), base::Time::Now())); + ct::CTVerifyResult result; + FillResultWithSCTsOfOrigin(ct::SignedCertificateTimestamp::SCT_EMBEDDED, 5, + &result); + EXPECT_FALSE(policy_enforcer_->DoesConformToCTEVPolicy( + no_valid_dates_cert.get(), nullptr, result)); + // ... but should be OK if whitelisted. + scoped_refptr<ct::EVCertsWhitelist> whitelist( + new DummyEVCertsWhitelist(true, true)); + EXPECT_TRUE(policy_enforcer_->DoesConformToCTEVPolicy( + chain_.get(), whitelist.get(), result)); +} + +TEST_F(CertPolicyEnforcerTest, + ConformsToPolicyExactNumberOfSCTsForValidityPeriod) { + // Test multiple validity periods: Over 27 months, Over 15 months (but less + // than 27 months), + // Less than 15 months. + const size_t validity_period[] = {12, 19, 30, 50}; + const size_t needed_scts[] = {2, 3, 4, 5}; + + for (int i = 0; i < 3; ++i) { + size_t curr_validity = validity_period[i]; + scoped_refptr<X509Certificate> cert(new X509Certificate( + "subject", "issuer", base::Time::Now(), + base::Time::Now() + base::TimeDelta::FromDays(31 * curr_validity))); + size_t curr_required_scts = needed_scts[i]; + ct::CTVerifyResult result; + for (size_t j = 0; j < curr_required_scts - 1; ++j) { + FillResultWithSCTsOfOrigin(ct::SignedCertificateTimestamp::SCT_EMBEDDED, + 1, &result); + EXPECT_FALSE(policy_enforcer_->DoesConformToCTEVPolicy(cert.get(), + nullptr, result)) + << " for: " << curr_validity << " and " << curr_required_scts + << " scts=" << result.verified_scts.size() << " j=" << j; + } + FillResultWithSCTsOfOrigin(ct::SignedCertificateTimestamp::SCT_EMBEDDED, 1, + &result); + EXPECT_TRUE( + policy_enforcer_->DoesConformToCTEVPolicy(cert.get(), nullptr, result)); + } +} + +TEST_F(CertPolicyEnforcerTest, + ConformsToPolicyButDoesNotRequireMoreThanNumLogs) { + scoped_ptr<CertPolicyEnforcer> enforcer(new CertPolicyEnforcer(2, true)); + + ct::CTVerifyResult result; + FillResultWithSCTsOfOrigin(ct::SignedCertificateTimestamp::SCT_EMBEDDED, 2, + &result); + // Expect true despite the chain not having enough SCTs according to the + // policy + // since we only have 2 logs. + EXPECT_TRUE(enforcer->DoesConformToCTEVPolicy(chain_.get(), nullptr, result)); +} + +TEST_F(CertPolicyEnforcerTest, ConformsToPolicyByEVWhitelistPresence) { + scoped_refptr<ct::EVCertsWhitelist> whitelist( + new DummyEVCertsWhitelist(true, true)); + + ct::CTVerifyResult result; + FillResultWithSCTsOfOrigin(ct::SignedCertificateTimestamp::SCT_EMBEDDED, 1, + &result); + EXPECT_TRUE(policy_enforcer_->DoesConformToCTEVPolicy( + chain_.get(), whitelist.get(), result)); +} + +TEST_F(CertPolicyEnforcerTest, IgnoresInvalidEVWhitelist) { + scoped_refptr<ct::EVCertsWhitelist> whitelist( + new DummyEVCertsWhitelist(false, true)); + + ct::CTVerifyResult result; + FillResultWithSCTsOfOrigin(ct::SignedCertificateTimestamp::SCT_EMBEDDED, 1, + &result); + EXPECT_FALSE(policy_enforcer_->DoesConformToCTEVPolicy( + chain_.get(), whitelist.get(), result)); +} + +TEST_F(CertPolicyEnforcerTest, IgnoresNullEVWhitelist) { + ct::CTVerifyResult result; + FillResultWithSCTsOfOrigin(ct::SignedCertificateTimestamp::SCT_EMBEDDED, 1, + &result); + EXPECT_FALSE( + policy_enforcer_->DoesConformToCTEVPolicy(chain_.get(), nullptr, result)); +} + +} // namespace + +} // namespace net diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc index 28b3b22..656db54 100644 --- a/net/http/http_network_session.cc +++ b/net/http/http_network_session.cc @@ -37,21 +37,14 @@ net::ClientSocketPoolManager* CreateSocketPoolManager( // TODO(yutak): Differentiate WebSocket pool manager and allow more // simultaneous connections for WebSockets. return new net::ClientSocketPoolManagerImpl( - params.net_log, - params.client_socket_factory - ? params.client_socket_factory - : net::ClientSocketFactory::GetDefaultFactory(), - params.host_resolver, - params.cert_verifier, - params.channel_id_service, - params.transport_security_state, - params.cert_transparency_verifier, - params.ssl_session_cache_shard, - params.proxy_service, - params.ssl_config_service, - params.enable_ssl_connect_job_waiting, - params.proxy_delegate, - pool_type); + params.net_log, params.client_socket_factory + ? params.client_socket_factory + : net::ClientSocketFactory::GetDefaultFactory(), + params.host_resolver, params.cert_verifier, params.channel_id_service, + params.transport_security_state, params.cert_transparency_verifier, + params.cert_policy_enforcer, params.ssl_session_cache_shard, + params.proxy_service, params.ssl_config_service, + params.enable_ssl_connect_job_waiting, params.proxy_delegate, pool_type); } } // unnamed namespace @@ -62,6 +55,7 @@ HttpNetworkSession::Params::Params() : client_socket_factory(NULL), host_resolver(NULL), cert_verifier(NULL), + cert_policy_enforcer(NULL), channel_id_service(NULL), transport_security_state(NULL), cert_transparency_verifier(NULL), diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h index 557b84e..bf93c83 100644 --- a/net/http/http_network_session.h +++ b/net/http/http_network_session.h @@ -29,6 +29,7 @@ class Value; namespace net { +class CertPolicyEnforcer; class CertVerifier; class ChannelIDService; class ClientSocketFactory; @@ -66,6 +67,7 @@ class NET_EXPORT HttpNetworkSession ClientSocketFactory* client_socket_factory; HostResolver* host_resolver; CertVerifier* cert_verifier; + CertPolicyEnforcer* cert_policy_enforcer; ChannelIDService* channel_id_service; TransportSecurityState* transport_security_state; CTVerifier* cert_transparency_verifier; diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 878406e..eacd06c 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -583,6 +583,7 @@ CaptureGroupNameSSLSocketPool::CaptureGroupNameSocketPool( NULL, NULL, NULL, + NULL, std::string(), NULL, NULL, diff --git a/net/http/http_proxy_client_socket_pool_unittest.cc b/net/http/http_proxy_client_socket_pool_unittest.cc index bdaf57f..f818432 100644 --- a/net/http/http_proxy_client_socket_pool_unittest.cc +++ b/net/http/http_proxy_client_socket_pool_unittest.cc @@ -171,6 +171,7 @@ class HttpProxyClientSocketPoolTest NULL /* channel_id_store */, NULL /* transport_security_state */, NULL /* cert_transparency_verifier */, + NULL /* cert_policy_enforcer */, std::string() /* ssl_session_cache_shard */, session_deps_.deterministic_socket_factory.get(), &transport_socket_pool_, diff --git a/net/http/http_stream_factory_impl_unittest.cc b/net/http/http_stream_factory_impl_unittest.cc index 4ac5467..beac733 100644 --- a/net/http/http_stream_factory_impl_unittest.cc +++ b/net/http/http_stream_factory_impl_unittest.cc @@ -403,16 +403,17 @@ CapturePreconnectsSSLSocketPool::CapturePreconnectsSocketPool( nullptr, // ssl_histograms host_resolver, cert_verifier, - nullptr, // channel_id_store - nullptr, // transport_security_state - nullptr, // cert_transparency_verifier + nullptr, // channel_id_store + nullptr, // transport_security_state + nullptr, // cert_transparency_verifier + nullptr, // cert_policy_enforcer std::string(), // ssl_session_cache_shard - nullptr, // deterministic_socket_factory - nullptr, // transport_socket_pool + nullptr, // deterministic_socket_factory + nullptr, // transport_socket_pool nullptr, nullptr, nullptr, // ssl_config_service - false, // enable_ssl_connect_job_waiting + false, // enable_ssl_connect_job_waiting nullptr), // net_log last_num_streams_(-1) { } diff --git a/net/net.gypi b/net/net.gypi index cc9ff357..89a0503 100644 --- a/net/net.gypi +++ b/net/net.gypi @@ -64,6 +64,8 @@ 'cert/cert_database.cc', 'cert/cert_database.h', 'cert/cert_database_openssl.cc', + 'cert/cert_policy_enforcer.cc', + 'cert/cert_policy_enforcer.h', 'cert/cert_status_flags.cc', 'cert/cert_status_flags.h', 'cert/cert_verifier.cc', @@ -1297,6 +1299,7 @@ 'base/upload_bytes_element_reader_unittest.cc', 'base/upload_file_element_reader_unittest.cc', 'base/url_util_unittest.cc', + 'cert/cert_policy_enforcer_unittest.cc', 'cert/cert_verify_proc_unittest.cc', 'cert/crl_set_unittest.cc', 'cert/ct_log_response_parser_unittest.cc', diff --git a/net/socket/client_socket_pool_manager_impl.cc b/net/socket/client_socket_pool_manager_impl.cc index 5ed31fc..190023c 100644 --- a/net/socket/client_socket_pool_manager_impl.cc +++ b/net/socket/client_socket_pool_manager_impl.cc @@ -42,6 +42,7 @@ ClientSocketPoolManagerImpl::ClientSocketPoolManagerImpl( ChannelIDService* channel_id_service, TransportSecurityState* transport_security_state, CTVerifier* cert_transparency_verifier, + CertPolicyEnforcer* cert_policy_enforcer, const std::string& ssl_session_cache_shard, ProxyService* proxy_service, SSLConfigService* ssl_config_service, @@ -55,6 +56,7 @@ ClientSocketPoolManagerImpl::ClientSocketPoolManagerImpl( channel_id_service_(channel_id_service), transport_security_state_(transport_security_state), cert_transparency_verifier_(cert_transparency_verifier), + cert_policy_enforcer_(cert_policy_enforcer), ssl_session_cache_shard_(ssl_session_cache_shard), proxy_service_(proxy_service), ssl_config_service_(ssl_config_service), @@ -85,6 +87,7 @@ ClientSocketPoolManagerImpl::ClientSocketPoolManagerImpl( channel_id_service, transport_security_state, cert_transparency_verifier, + cert_policy_enforcer, ssl_session_cache_shard, socket_factory, transport_socket_pool_.get(), @@ -297,22 +300,17 @@ ClientSocketPoolManagerImpl::GetSocketPoolForHTTPProxy( std::pair<SSLSocketPoolMap::iterator, bool> ssl_https_ret = ssl_socket_pools_for_https_proxies_.insert(std::make_pair( http_proxy, - new SSLClientSocketPool(max_sockets_per_proxy_server(pool_type_), - max_sockets_per_group(pool_type_), - &ssl_for_https_proxy_pool_histograms_, - host_resolver_, - cert_verifier_, - channel_id_service_, - transport_security_state_, - cert_transparency_verifier_, - ssl_session_cache_shard_, - socket_factory_, - tcp_https_ret.first->second /* https proxy */, - NULL /* no socks proxy */, - NULL /* no http proxy */, - ssl_config_service_.get(), - enable_ssl_connect_job_waiting_, - net_log_))); + new SSLClientSocketPool( + max_sockets_per_proxy_server(pool_type_), + max_sockets_per_group(pool_type_), + &ssl_for_https_proxy_pool_histograms_, host_resolver_, + cert_verifier_, channel_id_service_, transport_security_state_, + cert_transparency_verifier_, cert_policy_enforcer_, + ssl_session_cache_shard_, socket_factory_, + tcp_https_ret.first->second /* https proxy */, + NULL /* no socks proxy */, NULL /* no http proxy */, + ssl_config_service_.get(), enable_ssl_connect_job_waiting_, + net_log_))); DCHECK(tcp_https_ret.second); std::pair<HTTPProxySocketPoolMap::iterator, bool> ret = @@ -341,21 +339,14 @@ SSLClientSocketPool* ClientSocketPoolManagerImpl::GetSocketPoolForSSLWithProxy( SSLClientSocketPool* new_pool = new SSLClientSocketPool( max_sockets_per_proxy_server(pool_type_), - max_sockets_per_group(pool_type_), - &ssl_pool_histograms_, - host_resolver_, - cert_verifier_, - channel_id_service_, - transport_security_state_, - cert_transparency_verifier_, - ssl_session_cache_shard_, - socket_factory_, + max_sockets_per_group(pool_type_), &ssl_pool_histograms_, host_resolver_, + cert_verifier_, channel_id_service_, transport_security_state_, + cert_transparency_verifier_, cert_policy_enforcer_, + ssl_session_cache_shard_, socket_factory_, NULL, /* no tcp pool, we always go through a proxy */ GetSocketPoolForSOCKSProxy(proxy_server), - GetSocketPoolForHTTPProxy(proxy_server), - ssl_config_service_.get(), - enable_ssl_connect_job_waiting_, - net_log_); + GetSocketPoolForHTTPProxy(proxy_server), ssl_config_service_.get(), + enable_ssl_connect_job_waiting_, net_log_); std::pair<SSLSocketPoolMap::iterator, bool> ret = ssl_socket_pools_for_proxies_.insert(std::make_pair(proxy_server, diff --git a/net/socket/client_socket_pool_manager_impl.h b/net/socket/client_socket_pool_manager_impl.h index f9f8d3b..ca609d5 100644 --- a/net/socket/client_socket_pool_manager_impl.h +++ b/net/socket/client_socket_pool_manager_impl.h @@ -65,6 +65,7 @@ class ClientSocketPoolManagerImpl : public base::NonThreadSafe, ChannelIDService* channel_id_service, TransportSecurityState* transport_security_state, CTVerifier* cert_transparency_verifier, + CertPolicyEnforcer* cert_policy_enforcer, const std::string& ssl_session_cache_shard, ProxyService* proxy_service, SSLConfigService* ssl_config_service, @@ -114,6 +115,7 @@ class ClientSocketPoolManagerImpl : public base::NonThreadSafe, ChannelIDService* const channel_id_service_; TransportSecurityState* const transport_security_state_; CTVerifier* const cert_transparency_verifier_; + CertPolicyEnforcer* const cert_policy_enforcer_; const std::string ssl_session_cache_shard_; ProxyService* const proxy_service_; const scoped_refptr<SSLConfigService> ssl_config_service_; diff --git a/net/socket/ssl_client_socket.h b/net/socket/ssl_client_socket.h index af4f3ba..11b19a1 100644 --- a/net/socket/ssl_client_socket.h +++ b/net/socket/ssl_client_socket.h @@ -16,6 +16,7 @@ namespace net { +class CertPolicyEnforcer; class CertVerifier; class ChannelIDService; class CTVerifier; @@ -34,23 +35,27 @@ struct SSLClientSocketContext { : cert_verifier(NULL), channel_id_service(NULL), transport_security_state(NULL), - cert_transparency_verifier(NULL) {} + cert_transparency_verifier(NULL), + cert_policy_enforcer(NULL) {} SSLClientSocketContext(CertVerifier* cert_verifier_arg, ChannelIDService* channel_id_service_arg, TransportSecurityState* transport_security_state_arg, CTVerifier* cert_transparency_verifier_arg, + CertPolicyEnforcer* cert_policy_enforcer_arg, const std::string& ssl_session_cache_shard_arg) : cert_verifier(cert_verifier_arg), channel_id_service(channel_id_service_arg), transport_security_state(transport_security_state_arg), cert_transparency_verifier(cert_transparency_verifier_arg), + cert_policy_enforcer(cert_policy_enforcer_arg), ssl_session_cache_shard(ssl_session_cache_shard_arg) {} CertVerifier* cert_verifier; ChannelIDService* channel_id_service; TransportSecurityState* transport_security_state; CTVerifier* cert_transparency_verifier; + CertPolicyEnforcer* cert_policy_enforcer; // ssl_session_cache_shard is an opaque string that identifies a shard of the // SSL session cache. SSL sockets with the same ssl_session_cache_shard may // resume each other's SSL sessions but we'll never sessions between shards. diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc index 1319e4b..3651e8d 100644 --- a/net/socket/ssl_client_socket_nss.cc +++ b/net/socket/ssl_client_socket_nss.cc @@ -91,6 +91,7 @@ #include "net/base/net_errors.h" #include "net/base/net_log.h" #include "net/cert/asn1_util.h" +#include "net/cert/cert_policy_enforcer.h" #include "net/cert/cert_status_flags.h" #include "net/cert/cert_verifier.h" #include "net/cert/ct_ev_whitelist.h" @@ -2831,6 +2832,7 @@ SSLClientSocketNSS::SSLClientSocketNSS( nss_fd_(NULL), net_log_(transport_->socket()->NetLog()), transport_security_state_(context.transport_security_state), + policy_enforcer_(context.cert_policy_enforcer), valid_thread_id_(base::kInvalidThreadId) { EnterFunction(""); InitCore(); @@ -3528,21 +3530,6 @@ int SSLClientSocketNSS::DoVerifyCertComplete(int result) { result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN; } - scoped_refptr<ct::EVCertsWhitelist> ev_whitelist = - SSLConfigService::GetEVCertsWhitelist(); - if (server_cert_verify_result_.cert_status & CERT_STATUS_IS_EV) { - if (ev_whitelist.get() && ev_whitelist->IsValid()) { - const SHA256HashValue fingerprint( - X509Certificate::CalculateFingerprint256( - server_cert_verify_result_.verified_cert->os_cert_handle())); - - UMA_HISTOGRAM_BOOLEAN( - "Net.SSL_EVCertificateInWhitelist", - ev_whitelist->ContainsCertificateHash( - std::string(reinterpret_cast<const char*>(fingerprint.data), 8))); - } - } - if (result == OK) { // Only check Certificate Transparency if there were no other errors with // the connection. @@ -3566,20 +3553,31 @@ void SSLClientSocketNSS::VerifyCT() { // Note that this is a completely synchronous operation: The CT Log Verifier // gets all the data it needs for SCT verification and does not do any // external communication. - int result = cert_transparency_verifier_->Verify( + cert_transparency_verifier_->Verify( server_cert_verify_result_.verified_cert.get(), core_->state().stapled_ocsp_response, - core_->state().sct_list_from_tls_extension, - &ct_verify_result_, - net_log_); + core_->state().sct_list_from_tls_extension, &ct_verify_result_, net_log_); // TODO(ekasper): wipe stapled_ocsp_response and sct_list_from_tls_extension // from the state after verification is complete, to conserve memory. - VLOG(1) << "CT Verification complete: result " << result - << " Invalid scts: " << ct_verify_result_.invalid_scts.size() - << " Verified scts: " << ct_verify_result_.verified_scts.size() - << " scts from unknown logs: " - << ct_verify_result_.unknown_logs_scts.size(); + if (!policy_enforcer_) { + server_cert_verify_result_.cert_status &= ~CERT_STATUS_IS_EV; + } else { + if (server_cert_verify_result_.cert_status & CERT_STATUS_IS_EV) { + scoped_refptr<ct::EVCertsWhitelist> ev_whitelist = + SSLConfigService::GetEVCertsWhitelist(); + if (!policy_enforcer_->DoesConformToCTEVPolicy( + server_cert_verify_result_.verified_cert.get(), + ev_whitelist.get(), ct_verify_result_)) { + // TODO(eranm): Log via the BoundNetLog, see crbug.com/437766 + VLOG(1) << "EV certificate for " + << server_cert_verify_result_.verified_cert->subject() + .GetDisplayName() + << " does not conform to CT policy, removing EV status."; + server_cert_verify_result_.cert_status &= ~CERT_STATUS_IS_EV; + } + } + } } void SSLClientSocketNSS::EnsureThreadIdAssigned() const { diff --git a/net/socket/ssl_client_socket_nss.h b/net/socket/ssl_client_socket_nss.h index 71f09c0..10bb57f 100644 --- a/net/socket/ssl_client_socket_nss.h +++ b/net/socket/ssl_client_socket_nss.h @@ -36,6 +36,7 @@ class SequencedTaskRunner; namespace net { class BoundNetLog; +class CertPolicyEnforcer; class CertVerifier; class ChannelIDService; class CTVerifier; @@ -199,6 +200,8 @@ class SSLClientSocketNSS : public SSLClientSocket { TransportSecurityState* transport_security_state_; + CertPolicyEnforcer* const policy_enforcer_; + // pinning_failure_log contains a message produced by // TransportSecurityState::CheckPublicKeyPins in the event of a // pinning failure. It is a (somewhat) human-readable string. diff --git a/net/socket/ssl_client_socket_openssl.cc b/net/socket/ssl_client_socket_openssl.cc index b417e13..4e86c05 100644 --- a/net/socket/ssl_client_socket_openssl.cc +++ b/net/socket/ssl_client_socket_openssl.cc @@ -24,6 +24,7 @@ #include "crypto/openssl_util.h" #include "crypto/scoped_openssl_types.h" #include "net/base/net_errors.h" +#include "net/cert/cert_policy_enforcer.h" #include "net/cert/cert_verifier.h" #include "net/cert/ct_ev_whitelist.h" #include "net/cert/ct_verifier.h" @@ -380,6 +381,7 @@ SSLClientSocketOpenSSL::SSLClientSocketOpenSSL( handshake_succeeded_(false), marked_session_as_good_(false), transport_security_state_(context.transport_security_state), + policy_enforcer_(context.cert_policy_enforcer), net_log_(transport_->socket()->NetLog()), weak_factory_(this) { } @@ -1144,21 +1146,6 @@ int SSLClientSocketOpenSSL::DoVerifyCertComplete(int result) { result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN; } - scoped_refptr<ct::EVCertsWhitelist> ev_whitelist = - SSLConfigService::GetEVCertsWhitelist(); - if (server_cert_verify_result_.cert_status & CERT_STATUS_IS_EV) { - if (ev_whitelist.get() && ev_whitelist->IsValid()) { - const SHA256HashValue fingerprint( - X509Certificate::CalculateFingerprint256( - server_cert_verify_result_.verified_cert->os_cert_handle())); - - UMA_HISTOGRAM_BOOLEAN( - "Net.SSL_EVCertificateInWhitelist", - ev_whitelist->ContainsCertificateHash( - std::string(reinterpret_cast<const char*>(fingerprint.data), 8))); - } - } - if (result == OK) { // Only check Certificate Transparency if there were no other errors with // the connection. @@ -1251,15 +1238,28 @@ void SSLClientSocketOpenSSL::VerifyCT() { // Note that this is a completely synchronous operation: The CT Log Verifier // gets all the data it needs for SCT verification and does not do any // external communication. - int result = cert_transparency_verifier_->Verify( - server_cert_verify_result_.verified_cert.get(), - ocsp_response, sct_list, &ct_verify_result_, net_log_); - - VLOG(1) << "CT Verification complete: result " << result - << " Invalid scts: " << ct_verify_result_.invalid_scts.size() - << " Verified scts: " << ct_verify_result_.verified_scts.size() - << " scts from unknown logs: " - << ct_verify_result_.unknown_logs_scts.size(); + cert_transparency_verifier_->Verify( + server_cert_verify_result_.verified_cert.get(), ocsp_response, sct_list, + &ct_verify_result_, net_log_); + + if (!policy_enforcer_) { + server_cert_verify_result_.cert_status &= ~CERT_STATUS_IS_EV; + } else { + if (server_cert_verify_result_.cert_status & CERT_STATUS_IS_EV) { + scoped_refptr<ct::EVCertsWhitelist> ev_whitelist = + SSLConfigService::GetEVCertsWhitelist(); + if (!policy_enforcer_->DoesConformToCTEVPolicy( + server_cert_verify_result_.verified_cert.get(), + ev_whitelist.get(), ct_verify_result_)) { + // TODO(eranm): Log via the BoundNetLog, see crbug.com/437766 + VLOG(1) << "EV certificate for " + << server_cert_verify_result_.verified_cert->subject() + .GetDisplayName() + << " does not conform to CT policy, removing EV status."; + server_cert_verify_result_.cert_status &= ~CERT_STATUS_IS_EV; + } + } + } } void SSLClientSocketOpenSSL::OnHandshakeIOComplete(int result) { diff --git a/net/socket/ssl_client_socket_openssl.h b/net/socket/ssl_client_socket_openssl.h index 53d33c4..6343cb7 100644 --- a/net/socket/ssl_client_socket_openssl.h +++ b/net/socket/ssl_client_socket_openssl.h @@ -304,6 +304,8 @@ class SSLClientSocketOpenSSL : public SSLClientSocket { TransportSecurityState* transport_security_state_; + CertPolicyEnforcer* const policy_enforcer_; + // pinning_failure_log contains a message produced by // TransportSecurityState::CheckPublicKeyPins in the event of a // pinning failure. It is a (somewhat) human-readable string. diff --git a/net/socket/ssl_client_socket_pool.cc b/net/socket/ssl_client_socket_pool.cc index 56df1d8..c3b98b8 100644 --- a/net/socket/ssl_client_socket_pool.cc +++ b/net/socket/ssl_client_socket_pool.cc @@ -197,6 +197,7 @@ SSLConnectJob::SSLConnectJob(const std::string& group_name, context.channel_id_service, context.transport_security_state, context.cert_transparency_verifier, + context.cert_policy_enforcer, (params->privacy_mode() == PRIVACY_MODE_ENABLED ? "pm/" + context.ssl_session_cache_shard : context.ssl_session_cache_shard)), @@ -634,6 +635,7 @@ SSLClientSocketPool::SSLClientSocketPool( ChannelIDService* channel_id_service, TransportSecurityState* transport_security_state, CTVerifier* cert_transparency_verifier, + CertPolicyEnforcer* cert_policy_enforcer, const std::string& ssl_session_cache_shard, ClientSocketFactory* client_socket_factory, TransportClientSocketPool* transport_pool, @@ -661,6 +663,7 @@ SSLClientSocketPool::SSLClientSocketPool( channel_id_service, transport_security_state, cert_transparency_verifier, + cert_policy_enforcer, ssl_session_cache_shard), base::Bind( &SSLClientSocketPool::GetOrCreateSSLConnectJobMessenger, diff --git a/net/socket/ssl_client_socket_pool.h b/net/socket/ssl_client_socket_pool.h index c7f613e..59e754a 100644 --- a/net/socket/ssl_client_socket_pool.h +++ b/net/socket/ssl_client_socket_pool.h @@ -23,6 +23,7 @@ namespace net { +class CertPolicyEnforcer; class CertVerifier; class ClientSocketFactory; class ConnectJobFactory; @@ -285,6 +286,7 @@ class NET_EXPORT_PRIVATE SSLClientSocketPool ChannelIDService* channel_id_service, TransportSecurityState* transport_security_state, CTVerifier* cert_transparency_verifier, + CertPolicyEnforcer* cert_policy_enforcer, const std::string& ssl_session_cache_shard, ClientSocketFactory* client_socket_factory, TransportClientSocketPool* transport_pool, diff --git a/net/socket/ssl_client_socket_pool_unittest.cc b/net/socket/ssl_client_socket_pool_unittest.cc index 23bb111..1e4a14f 100644 --- a/net/socket/ssl_client_socket_pool_unittest.cc +++ b/net/socket/ssl_client_socket_pool_unittest.cc @@ -143,22 +143,15 @@ class SSLClientSocketPoolTest void CreatePool(bool transport_pool, bool http_proxy_pool, bool socks_pool) { ssl_histograms_.reset(new ClientSocketPoolHistograms("SSLUnitTest")); pool_.reset(new SSLClientSocketPool( - kMaxSockets, - kMaxSocketsPerGroup, - ssl_histograms_.get(), - NULL /* host_resolver */, - NULL /* cert_verifier */, - NULL /* channel_id_service */, - NULL /* transport_security_state */, - NULL /* cert_transparency_verifier */, - std::string() /* ssl_session_cache_shard */, - &socket_factory_, + kMaxSockets, kMaxSocketsPerGroup, ssl_histograms_.get(), + NULL /* host_resolver */, NULL /* cert_verifier */, + NULL /* channel_id_service */, NULL /* transport_security_state */, + NULL /* cert_transparency_verifier */, NULL /* cert_policy_enforcer */, + std::string() /* ssl_session_cache_shard */, &socket_factory_, transport_pool ? &transport_socket_pool_ : NULL, socks_pool ? &socks_socket_pool_ : NULL, - http_proxy_pool ? &http_proxy_socket_pool_ : NULL, - NULL, - enable_ssl_connect_job_waiting_, - NULL)); + http_proxy_pool ? &http_proxy_socket_pool_ : NULL, NULL, + enable_ssl_connect_job_waiting_, NULL)); } scoped_refptr<SSLSocketParams> SSLParams(ProxyServer::Scheme proxy, diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index c1a6e07..1241bc9 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -18801,6 +18801,23 @@ Therefore, the affected-histogram name has to have at least one dot in it. </summary> </histogram> +<histogram name="Net.SSL_EVCertificateCTCompliance" + enum="CTRequirementCompliance"> + <owner>eranm@chromium.org</owner> + <owner>rsleevi@chromium.org</owner> + <summary> + The state of compliance with Certificate Transparency presence requirements + for each EV certificate. An EV certificate could be non-compliant (in which + case it loses the EV status), comply through inclusion in the EV whitelist + or have the required number of Signed Certificate Timestamps. This metric + will gauge adoption rate of Certificate Transparency and will help identify + when the EV whitelist is no longer needed. Emitted during every SSL + connection establishment, but only if the client is checking compliance with + Certificate Transparency requirements (currently guarded by a Finch + experiment). + </summary> +</histogram> + <histogram name="Net.SSL_EVCertificateInWhitelist" enum="Boolean"> <owner>eranm@chromium.org</owner> <owner>rsleevi@chromium.org</owner> @@ -42809,6 +42826,12 @@ Therefore, the affected-histogram name has to have at least one dot in it. <int value="8" label="PlayMusic"/> </enum> +<enum name="CTRequirementCompliance" type="int"> + <int value="0" label="Not Compliant"/> + <int value="1" label="Whitelisted"/> + <int value="2" label="Has enough SCTs"/> +</enum> + <enum name="DailyEventIntervalType" type="int"> <int value="0" label="First Run"/> <int value="1" label="Day Elapsed"/> |