diff options
-rw-r--r-- | chrome/browser/net/cert_logger.proto | 49 | ||||
-rw-r--r-- | chrome/browser/net/chrome_fraudulent_certificate_reporter.cc | 153 | ||||
-rw-r--r-- | chrome/browser/net/chrome_fraudulent_certificate_reporter.h | 61 | ||||
-rw-r--r-- | chrome/browser/net/chrome_fraudulent_certificate_reporter_unittest.cc | 201 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 15 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | net/base/transport_security_state.cc | 488 | ||||
-rw-r--r-- | net/base/transport_security_state.h | 15 | ||||
-rw-r--r-- | net/base/transport_security_state_unittest.cc | 63 | ||||
-rw-r--r-- | net/data/ssl/certificates/README | 5 | ||||
-rw-r--r-- | net/data/ssl/certificates/test_mail_google_com.pem | 26 | ||||
-rw-r--r-- | net/net.gyp | 1 | ||||
-rw-r--r-- | net/url_request/fraudulent_certificate_reporter.h | 35 |
13 files changed, 894 insertions, 219 deletions
diff --git a/chrome/browser/net/cert_logger.proto b/chrome/browser/net/cert_logger.proto new file mode 100644 index 0000000..e09ed2a --- /dev/null +++ b/chrome/browser/net/cert_logger.proto @@ -0,0 +1,49 @@ +// Copyright (c) 2011 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. +// +// This protobuffer is intended to store reports from Chrome users of +// certificate pinning errors. A report will be sent from Chrome when it gets +// e.g. a certificate for google.com that chains up to a root CA not expected by +// Chrome for that origin, such as DigiNotar (compromised in July 2011), or +// other pinning errors such as a blacklisted cert in the chain. The +// report from the user will include the hostname being accessed, +// the full certificate chain (in PEM format), and the +// timestamp of when the client tried to access the site. A response is +// generated by the frontend and logged, including validation and error checking +// done on the client's input data. + + +syntax = "proto2"; + +package chrome_browser_net; + +// Chrome requires this. +option optimize_for = LITE_RUNTIME; + +// Protocol types +message CertLoggerRequest { + // The hostname being accessed (required as the cert could be valid for + // multiple hosts, e.g. a wildcard or a SubjectAltName. + required string hostname = 1; + // The certificate chain as a series of PEM-encoded certificates, including + // intermediates but not necessarily the root. + required string cert_chain = 2; + // The time (in usec since the epoch) when the client attempted to access the + // site generating the pinning error. + required int64 time_usec = 3; +}; + +// The response sent back to the user. +message CertLoggerResponse { + enum ResponseCode { + OK = 1; + MALFORMED_CERT_DATA = 2; + HOST_CERT_DONT_MATCH = 3; + ROOT_NOT_RECOGNIZED = 4; + ROOT_NOT_UNEXPECTED = 5; + OTHER_ERROR = 6; + }; + required ResponseCode response = 1; +}; + diff --git a/chrome/browser/net/chrome_fraudulent_certificate_reporter.cc b/chrome/browser/net/chrome_fraudulent_certificate_reporter.cc new file mode 100644 index 0000000..bb1b028 --- /dev/null +++ b/chrome/browser/net/chrome_fraudulent_certificate_reporter.cc @@ -0,0 +1,153 @@ +// Copyright (c) 2011 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 "chrome/browser/net/chrome_fraudulent_certificate_reporter.h" + +#include <set> + +#include "base/base64.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/time.h" +#include "chrome/browser/net/cert_logger.pb.h" +#include "net/base/ssl_info.h" +#include "net/base/x509_certificate.h" +#include "net/url_request/url_request_context.h" + +namespace chrome_browser_net { + +ChromeFraudulentCertificateReporter::ChromeFraudulentCertificateReporter( + net::URLRequestContext* request_context) + : request_context_(request_context), + upload_url_(FRAUDULENT_CERTIFICATE_UPLOAD_ENDPOINT) { +} + +ChromeFraudulentCertificateReporter::~ChromeFraudulentCertificateReporter() { + STLDeleteElements(&inflight_requests_); +} + +// TODO(palmer): Move this to some globally-visible utility module. +static bool DerToPem(const std::string& der_certificate, std::string* output) { + std::string b64_encoded; + if (!base::Base64Encode(der_certificate, &b64_encoded)) + return false; + + *output = "-----BEGIN CERTIFICATE-----\r\n"; + + size_t size = b64_encoded.size(); + for (size_t i = 0; i < size; ) { + size_t todo = size - i; + if (todo > 64) + todo = 64; + *output += b64_encoded.substr(i, todo); + *output += "\r\n"; + i += todo; + } + + *output += "-----END CERTIFICATE-----\r\n"; + return true; +} + +static std::string BuildReport( + const std::string& hostname, + const net::SSLInfo& ssl_info) { + CertLoggerRequest request; + base::Time now = base::Time::Now(); + request.set_time_usec(now.ToInternalValue()); + request.set_hostname(hostname); + + std::string der_encoded, pem_encoded; + + net::X509Certificate* certificate = ssl_info.cert; + if (!certificate->GetDEREncoded(&der_encoded) || + !DerToPem(der_encoded, &pem_encoded)) { + LOG(ERROR) << "Could not PEM encode DER certificate"; + } + + std::string* cert_chain = request.mutable_cert_chain(); + *cert_chain += pem_encoded; + + const net::X509Certificate::OSCertHandles& intermediates = + certificate->GetIntermediateCertificates(); + + for (net::X509Certificate::OSCertHandles::const_iterator + i = intermediates.begin(); i != intermediates.end(); ++i) { + scoped_refptr<net::X509Certificate> cert = + net::X509Certificate::CreateFromHandle(*i, intermediates); + + if (!cert->GetDEREncoded(&der_encoded) || + !DerToPem(der_encoded, &pem_encoded)) { + LOG(ERROR) << "Could not PEM encode DER certificate"; + continue; + } + + *cert_chain += pem_encoded; + } + + std::string out; + request.SerializeToString(&out); + return out; +} + +net::URLRequest* ChromeFraudulentCertificateReporter::CreateURLRequest() { + return new net::URLRequest(upload_url_, this); +} + +void ChromeFraudulentCertificateReporter::SendReport( + const std::string& hostname, + const net::SSLInfo& ssl_info, + bool sni_available) { + // We do silent/automatic reporting ONLY for Google properties. For other + // domains (when we start supporting that), we will ask for user permission. + if (!net::TransportSecurityState::IsGooglePinnedProperty(hostname, + sni_available)) { + return; + } + + std::string report = BuildReport(hostname, ssl_info); + + net::URLRequest* url_request = CreateURLRequest(); + url_request->set_context(request_context_); + url_request->set_method("POST"); + url_request->AppendBytesToUpload(report.data(), report.size()); + + net::HttpRequestHeaders headers; + headers.SetHeader(net::HttpRequestHeaders::kContentType, + "x-application/chrome-fraudulent-cert-report"); + url_request->SetExtraRequestHeaders(headers); + + inflight_requests_.insert(url_request); + url_request->Start(); +} + +void ChromeFraudulentCertificateReporter::RequestComplete( + net::URLRequest* request) { + std::set<net::URLRequest*>::iterator i = inflight_requests_.find(request); + DCHECK(i != inflight_requests_.end()); + delete *i; + inflight_requests_.erase(i); +} + +// TODO(palmer): Currently, the upload is fire-and-forget but soon we will +// try to recover by retrying, and trying different endpoints, and +// appealing to the user. +void ChromeFraudulentCertificateReporter::OnResponseStarted( + net::URLRequest* request) { + const net::URLRequestStatus& status(request->status()); + if (!status.is_success()) { + LOG(WARNING) << "Certificate upload failed" + << " status:" << status.status() + << " error:" << status.error(); + } else if (request->GetResponseCode() != 200) { + LOG(WARNING) << "Certificate upload HTTP status: " + << request->GetResponseCode(); + } + RequestComplete(request); +} + +void ChromeFraudulentCertificateReporter::OnReadCompleted( + net::URLRequest* request, int bytes_read) {} + +} // namespace chrome_browser_net + diff --git a/chrome/browser/net/chrome_fraudulent_certificate_reporter.h b/chrome/browser/net/chrome_fraudulent_certificate_reporter.h new file mode 100644 index 0000000..1a78ec2 --- /dev/null +++ b/chrome/browser/net/chrome_fraudulent_certificate_reporter.h @@ -0,0 +1,61 @@ +// Copyright (c) 2011 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 CHROME_BROWSER_NET_CHROME_FRAUDULENT_CERTIFICATE_REPORTER_H_ +#define CHROME_BROWSER_NET_CHROME_FRAUDULENT_CERTIFICATE_REPORTER_H_ +#pragma once + +#include <set> +#include <string> + +#include "net/url_request/fraudulent_certificate_reporter.h" +#include "net/url_request/url_request.h" + +namespace chrome_browser_net { + +// TODO(palmer): Switch to HTTPS when the error handling delegate is more +// sophisticated. Ultimately we plan to attempt the report on many transports. +const char FRAUDULENT_CERTIFICATE_UPLOAD_ENDPOINT[] = + "http://clients3.google.com/log_cert_error"; + +class ChromeFraudulentCertificateReporter + : public net::FraudulentCertificateReporter, + public net::URLRequest::Delegate { + public: + explicit ChromeFraudulentCertificateReporter( + net::URLRequestContext* request_context); + + virtual ~ChromeFraudulentCertificateReporter(); + + // Allows users of this class to override this and set their own URLRequest + // type. Used by SendReport. + virtual net::URLRequest* CreateURLRequest(); + + // net::FraudulentCertificateReporter + virtual void SendReport(const std::string& hostname, + const net::SSLInfo& ssl_info, + bool sni_available) OVERRIDE; + + // net::URLRequest::Delegate + virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; + virtual void OnReadCompleted(net::URLRequest* request, + int bytes_read) OVERRIDE; + + protected: + net::URLRequestContext* const request_context_; + + private: + // Performs post-report cleanup. + void RequestComplete(net::URLRequest* request); + + const GURL upload_url_; + std::set<net::URLRequest*> inflight_requests_; + + DISALLOW_COPY_AND_ASSIGN(ChromeFraudulentCertificateReporter); +}; + +} // namespace chrome_browser_net + +#endif // CHROME_BROWSER_NET_CHROME_FRAUDULENT_CERTIFICATE_REPORTER_H_ + diff --git a/chrome/browser/net/chrome_fraudulent_certificate_reporter_unittest.cc b/chrome/browser/net/chrome_fraudulent_certificate_reporter_unittest.cc new file mode 100644 index 0000000..06da291 --- /dev/null +++ b/chrome/browser/net/chrome_fraudulent_certificate_reporter_unittest.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2011 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 "chrome/browser/net/chrome_fraudulent_certificate_reporter.h" + +#include <string> + +#include "base/bind.h" +#include "base/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "chrome/browser/net/chrome_url_request_context.h" +#include "content/browser/browser_thread.h" +#include "net/base/cert_test_util.h" +#include "net/base/ssl_info.h" +#include "net/base/transport_security_state.h" +#include "net/base/x509_certificate.h" +#include "net/url_request/fraudulent_certificate_reporter.h" +#include "net/url_request/url_request.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::SSLInfo; + +namespace chrome_browser_net { + +// Builds an SSLInfo from an invalid cert chain. In this case, the cert is +// expired; what matters is that the cert would not pass even a normal +// sanity check. We test that we DO NOT send a fraudulent certificate report +// in this case. +static SSLInfo GetBadSSLInfo() { + SSLInfo info; + + info.cert = net::ImportCertFromFile(net::GetTestCertsDirectory(), + "expired_cert.pem"); + info.is_issued_by_known_root = false; + + return info; +} + +// Builds an SSLInfo from a "good" cert chain, as defined by IsGoodSSLInfo, +// but which does not pass DomainState::IsChainOfPublicKeysPermitted. In this +// case, the certificate is for mail.google.com, signed by our Chrome test +// CA. During testing, Chrome believes this CA is part of the root system +// store. But, this CA is not in the pin list; we test that we DO send a +// fraudulent certicate report in this case. +static SSLInfo GetGoodSSLInfo() { + SSLInfo info; + + info.cert = net::ImportCertFromFile(net::GetTestCertsDirectory(), + "test_mail_google_com.pem"); + info.is_issued_by_known_root = true; + + return info; +} + +// Checks that |info| is good as required by the SSL checks performed in +// URLRequestHttpJob::OnStartCompleted, which are enough to trigger pin +// checking but not sufficient to pass +// DomainState::IsChainOfPublicKeysPermitted. +static bool IsGoodSSLInfo(const SSLInfo& info) { + return info.is_valid() && info.is_issued_by_known_root; +} + +class TestReporter : public ChromeFraudulentCertificateReporter { + public: + explicit TestReporter(net::URLRequestContext* request_context) + : ChromeFraudulentCertificateReporter(request_context) {} +}; + +class SendingTestReporter : public TestReporter { + public: + explicit SendingTestReporter(net::URLRequestContext* request_context) + : TestReporter(request_context), passed_(false) {} + + // Passes if invoked with a good SSLInfo and for a hostname that is a Google + // pinned property. + virtual void SendReport(const std::string& hostname, + const SSLInfo& ssl_info, + bool sni_available) OVERRIDE { + EXPECT_TRUE(IsGoodSSLInfo(ssl_info)); + EXPECT_TRUE(net::TransportSecurityState::IsGooglePinnedProperty( + hostname, sni_available)); + passed_ = true; + } + + virtual ~SendingTestReporter() { + // If the object is destroyed without having its SendReport method invoked, + // we failed. + EXPECT_TRUE(passed_); + } + + bool passed_; +}; + +class NotSendingTestReporter : public TestReporter { + public: + explicit NotSendingTestReporter(net::URLRequestContext* request_context) + : TestReporter(request_context) {} + + // Passes if invoked with a bad SSLInfo and for a hostname that is not a + // Google pinned property. + virtual void SendReport(const std::string& hostname, + const SSLInfo& ssl_info, + bool sni_available) OVERRIDE { + EXPECT_FALSE(IsGoodSSLInfo(ssl_info)); + EXPECT_FALSE(net::TransportSecurityState::IsGooglePinnedProperty( + hostname, sni_available)); + } +}; + +// For the first version of the feature, sending reports is "fire and forget". +// Therefore, we test only that the Reporter tried to send a request at all. +// In the future, when we have more sophisticated (i.e., any) error handling +// and re-tries, we will need more sopisticated tests as well. +// +// This class doesn't do anything now, but in near future versions it will. +class MockURLRequest : public net::URLRequest { + public: + MockURLRequest() : net::URLRequest(GURL(""), NULL), passed_(false) { + } + + private: + bool passed_; +}; + +// A ChromeFraudulentCertificateReporter that uses a MockURLRequest, but is +// otherwise normal: reports are constructed and sent in the usual way. +class MockReporter : public ChromeFraudulentCertificateReporter { + public: + explicit MockReporter(net::URLRequestContext* request_context) + : ChromeFraudulentCertificateReporter(request_context) {} + + virtual net::URLRequest* CreateURLRequest() OVERRIDE { + return new MockURLRequest(); + } + + virtual void SendReport( + const std::string& hostname, + const net::SSLInfo& ssl_info, + bool sni_available) { + DCHECK(!hostname.empty()); + DCHECK(ssl_info.is_valid()); + ChromeFraudulentCertificateReporter::SendReport(hostname, ssl_info, sni_available); + } +}; + +static void DoReportIsSent() { + scoped_refptr<ChromeURLRequestContext> context = new ChromeURLRequestContext; + SendingTestReporter reporter(context.get()); + SSLInfo info = GetGoodSSLInfo(); + reporter.SendReport("mail.google.com", info, true); +} + +static void DoReportIsNotSent() { + scoped_refptr<ChromeURLRequestContext> context = new ChromeURLRequestContext; + NotSendingTestReporter reporter(context.get()); + SSLInfo info = GetBadSSLInfo(); + reporter.SendReport("127.0.0.1", info, true); +} + +static void DoMockReportIsSent() { + scoped_refptr<ChromeURLRequestContext> context = new ChromeURLRequestContext; + MockReporter reporter(context.get()); + SSLInfo info = GetGoodSSLInfo(); + reporter.SendReport("mail.google.com", info, true); +} + +TEST(ChromeFraudulentCertificateReporterTest, GoodBadInfo) { + SSLInfo good = GetGoodSSLInfo(); + EXPECT_TRUE(IsGoodSSLInfo(good)); + + SSLInfo bad = GetBadSSLInfo(); + EXPECT_FALSE(IsGoodSSLInfo(bad)); +} + +TEST(ChromeFraudulentCertificateReporterTest, ReportIsSent) { + MessageLoop loop(MessageLoop::TYPE_IO); + BrowserThread io_thread(BrowserThread::IO, &loop); + loop.PostTask(FROM_HERE, base::Bind(&DoReportIsSent)); + loop.RunAllPending(); +} + +TEST(ChromeFraudulentCertificateReporterTest, MockReportIsSent) { + MessageLoop loop(MessageLoop::TYPE_IO); + BrowserThread io_thread(BrowserThread::IO, &loop); + loop.PostTask(FROM_HERE, base::Bind(&DoMockReportIsSent)); + loop.RunAllPending(); +} + +TEST(ChromeFraudulentCertificateReporterTest, ReportIsNotSent) { + MessageLoop loop(MessageLoop::TYPE_IO); + BrowserThread io_thread(BrowserThread::IO, &loop); + loop.PostTask(FROM_HERE, base::Bind(&DoReportIsNotSent)); + loop.RunAllPending(); +} + +} // namespace chrome_browser_net + diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 8a5f5f4..d5b190e 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -10,6 +10,7 @@ 'dependencies': [ 'app/policy/cloud_policy_codegen.gyp:policy', 'browser/sync/protocol/sync_proto.gyp:sync_proto', + 'cert_logger_proto', 'chrome_extra_resources', 'chrome_resources', 'chrome_strings', @@ -1552,6 +1553,8 @@ 'browser/net/chrome_dns_cert_provenance_checker.h', 'browser/net/chrome_dns_cert_provenance_checker_factory.cc', 'browser/net/chrome_dns_cert_provenance_checker_factory.h', + 'browser/net/chrome_fraudulent_certificate_reporter.cc', + 'browser/net/chrome_fraudulent_certificate_reporter.h', 'browser/net/chrome_net_log.cc', 'browser/net/chrome_net_log.h', 'browser/net/chrome_network_delegate.cc', @@ -5121,6 +5124,18 @@ ], }, { + # Protobuf compiler / generator for the fraudulent certificate reporting + # protocol buffer. + 'target_name': 'cert_logger_proto', + 'type': 'static_library', + 'sources': [ 'browser/net/cert_logger.proto', ], + 'variables': { + 'proto_in_dir': 'browser/net', + 'proto_out_dir': 'chrome/browser/net', + }, + 'includes': [ '../build/protoc.gypi', ], + }, + { # Protobuf compiler / generate rule for feedback 'target_name': 'userfeedback_proto', 'type': 'static_library', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 6b6f6f7..f85fdb5 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1374,6 +1374,7 @@ 'browser/metrics/thread_watcher_unittest.cc', 'browser/mock_keychain_mac.cc', 'browser/mock_keychain_mac.h', + 'browser/net/chrome_fraudulent_certificate_reporter_unittest.cc', 'browser/net/chrome_net_log_unittest.cc', 'browser/net/connection_tester_unittest.cc', 'browser/net/gaia/gaia_oauth_fetcher_unittest.cc', diff --git a/net/base/transport_security_state.cc b/net/base/transport_security_state.cc index 0926fc0..b634cfc 100644 --- a/net/base/transport_security_state.cc +++ b/net/base/transport_security_state.cc @@ -844,126 +844,114 @@ static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, return false; } -// IsPreloadedSTS returns true if the canonicalized hostname should always be -// considered to have STS enabled. -bool TransportSecurityState::IsPreloadedSTS( - const std::string& canonicalized_host, - bool sni_available, - DomainState* out) { - DCHECK(CalledOnValidThread()); - - out->preloaded = true; - out->mode = DomainState::MODE_STRICT; - out->include_subdomains = false; - - // These hashes are base64 encodings of SHA1 hashes for cert public keys. - static const char kCertPKHashVerisignClass3[] = +// These hashes are base64 encodings of SHA1 hashes for cert public keys. +static const char kCertPKHashVerisignClass3[] = "sha1/4n972HfV354KP560yw4uqe/baXc="; - static const char kCertPKHashVerisignClass3G3[] = +static const char kCertPKHashVerisignClass3G3[] = "sha1/IvGeLsbqzPxdI0b0wuj2xVTdXgc="; - static const char kCertPKHashGoogle1024[] = +static const char kCertPKHashGoogle1024[] = "sha1/QMVAHW+MuvCLAO3vse6H0AWzuc0="; - static const char kCertPKHashGoogle2048[] = +static const char kCertPKHashGoogle2048[] = "sha1/AbkhxY0L343gKf+cki7NVWp+ozk="; - static const char kCertPKHashEquifaxSecureCA[] = +static const char kCertPKHashEquifaxSecureCA[] = "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q="; - static const char* const kGoogleAcceptableCerts[] = { - kCertPKHashVerisignClass3, - kCertPKHashVerisignClass3G3, - kCertPKHashGoogle1024, - kCertPKHashGoogle2048, - kCertPKHashEquifaxSecureCA, - 0, - }; - - static const char kCertRapidSSL[] = +static const char* const kGoogleAcceptableCerts[] = { + kCertPKHashVerisignClass3, + kCertPKHashVerisignClass3G3, + kCertPKHashGoogle1024, + kCertPKHashGoogle2048, + kCertPKHashEquifaxSecureCA, + NULL, +}; + +static const char kCertRapidSSL[] = "sha1/m9lHYJYke9k0GtVZ+bXSQYE8nDI="; - static const char kCertDigiCertEVRoot[] = +static const char kCertDigiCertEVRoot[] = "sha1/gzF+YoVCU9bXeDGQ7JGQVumRueM="; - static const char kCertTor1[] = +static const char kCertTor1[] = "sha1/juNxSTv9UANmpC9kF5GKpmWNx3Y="; - static const char kCertTor2[] = +static const char kCertTor2[] = "sha1/lia43lPolzSPVIq34Dw57uYcLD8="; - static const char kCertTor3[] = +static const char kCertTor3[] = "sha1/rzEyQIKOh77j87n5bjWUNguXF8Y="; - static const char* const kTorAcceptableCerts[] = { - kCertRapidSSL, - kCertDigiCertEVRoot, - kCertTor1, - kCertTor2, - kCertTor3, - 0, - }; - - static const char kCertVerisignClass1[] = +static const char* const kTorAcceptableCerts[] = { + kCertRapidSSL, + kCertDigiCertEVRoot, + kCertTor1, + kCertTor2, + kCertTor3, + NULL, +}; + +static const char kCertVerisignClass1[] = "sha1/I0PRSKJViZuUfUYaeX7ATP7RcLc="; - static const char kCertVerisignClass3[] = +static const char kCertVerisignClass3[] = "sha1/4n972HfV354KP560yw4uqe/baXc="; - static const char kCertVerisignClass3_G4[] = +static const char kCertVerisignClass3_G4[] = "sha1/7WYxNdMb1OymFMQp4xkGn5TBJlA="; - static const char kCertVerisignClass4_G3[] = +static const char kCertVerisignClass4_G3[] = "sha1/PANDaGiVHPNpKri0Jtq6j+ki5b0="; - static const char kCertVerisignClass3_G3[] = +static const char kCertVerisignClass3_G3[] = "sha1/IvGeLsbqzPxdI0b0wuj2xVTdXgc="; - static const char kCertVerisignClass1_G3[] = +static const char kCertVerisignClass1_G3[] = "sha1/VRmyeKyygdftp6vBg5nDu2kEJLU="; - static const char kCertVerisignClass2_G3[] = +static const char kCertVerisignClass2_G3[] = "sha1/Wr7Fddyu87COJxlD/H8lDD32YeM="; - static const char kCertVerisignClass3_G2[] = +static const char kCertVerisignClass3_G2[] = "sha1/GiG0lStik84Ys2XsnA6TTLOB5tQ="; - static const char kCertVerisignClass2_G2[] = +static const char kCertVerisignClass2_G2[] = "sha1/Eje6RRfurSkm/cHN/r7t8t7ZFFw="; - static const char kCertVerisignClass3_G5[] = +static const char kCertVerisignClass3_G5[] = "sha1/sYEIGhmkwJQf+uiVKMEkyZs0rMc="; - static const char kCertVerisignUniversal[] = +static const char kCertVerisignUniversal[] = "sha1/u8I+KQuzKHcdrT6iTb30I70GsD0="; - static const char kCertTwitter1[] = +static const char kCertTwitter1[] = "sha1/Vv7zwhR9TtOIN/29MFI4cgHld40="; - static const char kCertGeoTrustGlobal[] = +static const char kCertGeoTrustGlobal[] = "sha1/wHqYaI2J+6sFZAwRfap9ZbjKzE4="; - static const char kCertGeoTrustGlobal2[] = +static const char kCertGeoTrustGlobal2[] = "sha1/cTg28gIxU0crbrplRqkQFVggBQk="; - static const char kCertGeoTrustUniversal[] = +static const char kCertGeoTrustUniversal[] = "sha1/h+hbY1PGI6MSjLD/u/VR/lmADiI="; - static const char kCertGeoTrustUniversal2[] = +static const char kCertGeoTrustUniversal2[] = "sha1/Xk9ThoXdT57KX9wNRW99UbHcm3s="; - static const char kCertGeoTrustPrimary[] = +static const char kCertGeoTrustPrimary[] = "sha1/sBmJ5+/7Sq/LFI9YRjl2IkFQ4bo="; - static const char kCertGeoTrustPrimaryG2[] = +static const char kCertGeoTrustPrimaryG2[] = "sha1/vb6nG6txV/nkddlU0rcngBqCJoI="; - static const char kCertGeoTrustPrimaryG3[] = +static const char kCertGeoTrustPrimaryG3[] = "sha1/nKmNAK90Dd2BgNITRaWLjy6UONY="; - static const char* const kTwitterComAcceptableCerts[] = { - kCertVerisignClass1, - kCertVerisignClass3, - kCertVerisignClass3_G4, - kCertVerisignClass4_G3, - kCertVerisignClass3_G3, - kCertVerisignClass1_G3, - kCertVerisignClass2_G3, - kCertVerisignClass3_G2, - kCertVerisignClass2_G2, - kCertVerisignClass3_G5, - kCertVerisignUniversal, - kCertGeoTrustGlobal, - kCertGeoTrustGlobal2, - kCertGeoTrustUniversal, - kCertGeoTrustUniversal2, - kCertGeoTrustPrimary, - kCertGeoTrustPrimaryG2, - kCertGeoTrustPrimaryG3, - kCertTwitter1, - 0, - }; - - // kTestAcceptableCerts doesn't actually match any public keys and is used - // with "pinningtest.appspot.com", below, to test if pinning is active. - static const char* const kTestAcceptableCerts[] = { - "sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=", - }; +static const char* const kTwitterComAcceptableCerts[] = { + kCertVerisignClass1, + kCertVerisignClass3, + kCertVerisignClass3_G4, + kCertVerisignClass4_G3, + kCertVerisignClass3_G3, + kCertVerisignClass1_G3, + kCertVerisignClass2_G3, + kCertVerisignClass3_G2, + kCertVerisignClass2_G2, + kCertVerisignClass3_G5, + kCertVerisignUniversal, + kCertGeoTrustGlobal, + kCertGeoTrustGlobal2, + kCertGeoTrustUniversal, + kCertGeoTrustUniversal2, + kCertGeoTrustPrimary, + kCertGeoTrustPrimaryG2, + kCertGeoTrustPrimaryG3, + kCertTwitter1, + NULL, +}; + +// kTestAcceptableCerts doesn't actually match any public keys and is used +// with "pinningtest.appspot.com", below, to test if pinning is active. +static const char* const kTestAcceptableCerts[] = { + "sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=", +}; #if defined(OS_CHROMEOS) static const bool kTwitterHSTS = true; @@ -971,140 +959,202 @@ bool TransportSecurityState::IsPreloadedSTS( static const bool kTwitterHSTS = false; #endif - // In the medium term this list is likely to just be hardcoded here. This, - // slightly odd, form removes the need for additional relocations records. - static const struct HSTSPreload kPreloadedSTS[] = { - // (*.)google.com, iff using SSL must use an acceptable certificate. - {12, true, "\006google\003com", false, kGoogleAcceptableCerts }, - {25, true, "\013pinningtest\007appspot\003com", false, - kTestAcceptableCerts }, - // Now we force HTTPS for subtrees of google.com. - {19, true, "\006health\006google\003com", true, kGoogleAcceptableCerts }, - {21, true, "\010checkout\006google\003com", true, kGoogleAcceptableCerts }, - {19, true, "\006chrome\006google\003com", true, kGoogleAcceptableCerts }, - {17, true, "\004docs\006google\003com", true, kGoogleAcceptableCerts }, - {18, true, "\005sites\006google\003com", true, kGoogleAcceptableCerts }, - {25, true, "\014spreadsheets\006google\003com", true, - kGoogleAcceptableCerts }, - {22, false, "\011appengine\006google\003com", true, - kGoogleAcceptableCerts }, - {22, true, "\011encrypted\006google\003com", true, kGoogleAcceptableCerts }, - {21, true, "\010accounts\006google\003com", true, kGoogleAcceptableCerts }, - {21, true, "\010profiles\006google\003com", true, kGoogleAcceptableCerts }, - {17, true, "\004mail\006google\003com", true, kGoogleAcceptableCerts }, - {23, true, "\012talkgadget\006google\003com", true, - kGoogleAcceptableCerts }, - {17, true, "\004talk\006google\003com", true, kGoogleAcceptableCerts }, - {29, true, "\020hostedtalkgadget\006google\003com", true, - kGoogleAcceptableCerts }, - {17, true, "\004plus\006google\003com", true, kGoogleAcceptableCerts }, - // Other Google-related domains that must use HTTPS. - {20, true, "\006market\007android\003com", true, kGoogleAcceptableCerts }, - {26, true, "\003ssl\020google-analytics\003com", true, - kGoogleAcceptableCerts }, - {18, true, "\005drive\006google\003com", true, kGoogleAcceptableCerts }, - {16, true, "\012googleplex\003com", true, kGoogleAcceptableCerts }, - // Other Google-related domains that must use an acceptable certificate - // iff using SSL. - {11, true, "\005ytimg\003com", false, kGoogleAcceptableCerts }, - {23, true, "\021googleusercontent\003com", false, kGoogleAcceptableCerts }, - {13, true, "\007youtube\003com", false, kGoogleAcceptableCerts }, - {16, true, "\012googleapis\003com", false, kGoogleAcceptableCerts }, - {22, true, "\020googleadservices\003com", false, kGoogleAcceptableCerts }, - {16, true, "\012googlecode\003com", false, kGoogleAcceptableCerts }, - {13, true, "\007appspot\003com", false, kGoogleAcceptableCerts }, - {23, true, "\021googlesyndication\003com", false, kGoogleAcceptableCerts }, - {17, true, "\013doubleclick\003net", false, kGoogleAcceptableCerts }, - {17, true, "\003ssl\007gstatic\003com", false, kGoogleAcceptableCerts }, - // Exclude the learn.doubleclick.net subdomain because it uses a different - // CA. - {23, true, "\005learn\013doubleclick\003net", false, 0 }, - // Now we force HTTPS for other sites that have requested it. - {16, false, "\003www\006paypal\003com", true, 0 }, - {16, false, "\003www\006elanex\003biz", true, 0 }, - {12, true, "\006jottit\003com", true, 0 }, - {19, true, "\015sunshinepress\003org", true, 0 }, - {21, false, "\003www\013noisebridge\003net", true, 0 }, - {10, false, "\004neg9\003org", true, 0 }, - {12, true, "\006riseup\003net", true, 0 }, - {11, false, "\006factor\002cc", true, 0 }, - {22, false, "\007members\010mayfirst\003org", true, 0 }, - {22, false, "\007support\010mayfirst\003org", true, 0 }, - {17, false, "\002id\010mayfirst\003org", true, 0 }, - {20, false, "\005lists\010mayfirst\003org", true, 0 }, - {19, true, "\015splendidbacon\003com", true, 0 }, - {28, false, "\016aladdinschools\007appspot\003com", true, 0 }, - {14, true, "\011ottospora\002nl", true, 0 }, - {25, false, "\003www\017paycheckrecords\003com", true, 0 }, - {14, false, "\010lastpass\003com", true, 0 }, - {18, false, "\003www\010lastpass\003com", true, 0 }, - {14, true, "\010keyerror\003com", true, 0 }, - {13, false, "\010entropia\002de", true, 0 }, - {17, false, "\003www\010entropia\002de", true, 0 }, - {11, true, "\005romab\003com", true, 0 }, - {16, false, "\012logentries\003com", true, 0 }, - {20, false, "\003www\012logentries\003com", true, 0 }, - {12, true, "\006stripe\003com", true, 0 }, - {27, true, "\025cloudsecurityalliance\003org", true, 0 }, - {15, true, "\005login\004sapo\002pt", true, 0 }, - {19, true, "\015mattmccutchen\003net", true, 0 }, - {11, true, "\006betnet\002fr", true, 0 }, - {13, true, "\010uprotect\002it", true, 0 }, - {14, false, "\010squareup\003com", true, 0 }, - {9, true, "\004cert\002se", true, 0 }, - {11, true, "\006crypto\002is", true, 0 }, - {20, true, "\005simon\007butcher\004name", true, 0 }, - {10, true, "\004linx\003net", true, 0 }, - {13, false, "\007dropcam\003com", true, 0 }, - {17, false, "\003www\007dropcam\003com", true, 0 }, - {30, true, "\010ebanking\014indovinabank\003com\002vn", true, 0 }, - {13, false, "\007epoxate\003com", true, 0 }, - {16, false, "\012torproject\003org", true, kTorAcceptableCerts }, - {21, true, "\004blog\012torproject\003org", true, kTorAcceptableCerts }, - {22, true, "\005check\012torproject\003org", true, kTorAcceptableCerts }, - {20, true, "\003www\012torproject\003org", true, kTorAcceptableCerts }, - {22, true, "\003www\014moneybookers\003com", true, 0 }, - {17, false, "\013ledgerscope\003net", true, 0 }, - {21, false, "\003www\013ledgerscope\003net", true, 0 }, - {10, false, "\004kyps\003net", true, 0 }, - {14, false, "\003www\004kyps\003net", true, 0 }, - {17, true, "\003app\007recurly\003com", true, 0 }, - {17, true, "\003api\007recurly\003com", true, 0 }, - {13, false, "\007greplin\003com", true, 0 }, - {17, false, "\003www\007greplin\003com", true, 0 }, - {27, true, "\006luneta\016nearbuysystems\003com", true, 0 }, - {12, true, "\006ubertt\003org", true, 0 }, - - {13, false, "\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {17, true, "\003www\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {17, true, "\003api\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {19, true, "\005oauth\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {20, true, "\006mobile\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {17, true, "\003dev\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {22, true, "\010business\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, +// In the medium term this list is likely to just be hardcoded here. This +// slightly odd form removes the need for additional relocations records. +static const struct HSTSPreload kPreloadedSTS[] = { + // (*.)google.com, iff using SSL must use an acceptable certificate. + {12, true, "\006google\003com", false, kGoogleAcceptableCerts }, + {25, true, "\013pinningtest\007appspot\003com", false, + kTestAcceptableCerts }, + // Now we force HTTPS for subtrees of google.com. + {19, true, "\006health\006google\003com", true, kGoogleAcceptableCerts }, + {21, true, "\010checkout\006google\003com", true, kGoogleAcceptableCerts }, + {19, true, "\006chrome\006google\003com", true, kGoogleAcceptableCerts }, + {17, true, "\004docs\006google\003com", true, kGoogleAcceptableCerts }, + {18, true, "\005sites\006google\003com", true, kGoogleAcceptableCerts }, + {25, true, "\014spreadsheets\006google\003com", true, + kGoogleAcceptableCerts }, + {22, false, "\011appengine\006google\003com", true, + kGoogleAcceptableCerts }, + {22, true, "\011encrypted\006google\003com", true, kGoogleAcceptableCerts }, + {21, true, "\010accounts\006google\003com", true, kGoogleAcceptableCerts }, + {21, true, "\010profiles\006google\003com", true, kGoogleAcceptableCerts }, + {17, true, "\004mail\006google\003com", true, kGoogleAcceptableCerts }, + {23, true, "\012talkgadget\006google\003com", true, + kGoogleAcceptableCerts }, + {17, true, "\004talk\006google\003com", true, kGoogleAcceptableCerts }, + {29, true, "\020hostedtalkgadget\006google\003com", true, + kGoogleAcceptableCerts }, + {17, true, "\004plus\006google\003com", true, kGoogleAcceptableCerts }, + // Other Google-related domains that must use HTTPS. + {20, true, "\006market\007android\003com", true, kGoogleAcceptableCerts }, + {26, true, "\003ssl\020google-analytics\003com", true, + kGoogleAcceptableCerts }, + {18, true, "\005drive\006google\003com", true, kGoogleAcceptableCerts }, + {16, true, "\012googleplex\003com", true, kGoogleAcceptableCerts }, + // Other Google-related domains that must use an acceptable certificate + // iff using SSL. + {11, true, "\005ytimg\003com", false, kGoogleAcceptableCerts }, + {23, true, "\021googleusercontent\003com", false, kGoogleAcceptableCerts }, + {13, true, "\007youtube\003com", false, kGoogleAcceptableCerts }, + {16, true, "\012googleapis\003com", false, kGoogleAcceptableCerts }, + {22, true, "\020googleadservices\003com", false, kGoogleAcceptableCerts }, + {16, true, "\012googlecode\003com", false, kGoogleAcceptableCerts }, + {13, true, "\007appspot\003com", false, kGoogleAcceptableCerts }, + {23, true, "\021googlesyndication\003com", false, kGoogleAcceptableCerts }, + {17, true, "\013doubleclick\003net", false, kGoogleAcceptableCerts }, + {17, true, "\003ssl\007gstatic\003com", false, kGoogleAcceptableCerts }, + // Exclude the learn.doubleclick.net subdomain because it uses a different + // CA. + {23, true, "\005learn\013doubleclick\003net", false, 0 }, + // Now we force HTTPS for other sites that have requested it. + {16, false, "\003www\006paypal\003com", true, 0 }, + {16, false, "\003www\006elanex\003biz", true, 0 }, + {12, true, "\006jottit\003com", true, 0 }, + {19, true, "\015sunshinepress\003org", true, 0 }, + {21, false, "\003www\013noisebridge\003net", true, 0 }, + {10, false, "\004neg9\003org", true, 0 }, + {12, true, "\006riseup\003net", true, 0 }, + {11, false, "\006factor\002cc", true, 0 }, + {22, false, "\007members\010mayfirst\003org", true, 0 }, + {22, false, "\007support\010mayfirst\003org", true, 0 }, + {17, false, "\002id\010mayfirst\003org", true, 0 }, + {20, false, "\005lists\010mayfirst\003org", true, 0 }, + {19, true, "\015splendidbacon\003com", true, 0 }, + {28, false, "\016aladdinschools\007appspot\003com", true, 0 }, + {14, true, "\011ottospora\002nl", true, 0 }, + {25, false, "\003www\017paycheckrecords\003com", true, 0 }, + {14, false, "\010lastpass\003com", true, 0 }, + {18, false, "\003www\010lastpass\003com", true, 0 }, + {14, true, "\010keyerror\003com", true, 0 }, + {13, false, "\010entropia\002de", true, 0 }, + {17, false, "\003www\010entropia\002de", true, 0 }, + {11, true, "\005romab\003com", true, 0 }, + {16, false, "\012logentries\003com", true, 0 }, + {20, false, "\003www\012logentries\003com", true, 0 }, + {12, true, "\006stripe\003com", true, 0 }, + {27, true, "\025cloudsecurityalliance\003org", true, 0 }, + {15, true, "\005login\004sapo\002pt", true, 0 }, + {19, true, "\015mattmccutchen\003net", true, 0 }, + {11, true, "\006betnet\002fr", true, 0 }, + {13, true, "\010uprotect\002it", true, 0 }, + {14, false, "\010squareup\003com", true, 0 }, + {9, true, "\004cert\002se", true, 0 }, + {11, true, "\006crypto\002is", true, 0 }, + {20, true, "\005simon\007butcher\004name", true, 0 }, + {10, true, "\004linx\003net", true, 0 }, + {13, false, "\007dropcam\003com", true, 0 }, + {17, false, "\003www\007dropcam\003com", true, 0 }, + {30, true, "\010ebanking\014indovinabank\003com\002vn", true, 0 }, + {13, false, "\007epoxate\003com", true, 0 }, + {16, false, "\012torproject\003org", true, kTorAcceptableCerts }, + {21, true, "\004blog\012torproject\003org", true, kTorAcceptableCerts }, + {22, true, "\005check\012torproject\003org", true, kTorAcceptableCerts }, + {20, true, "\003www\012torproject\003org", true, kTorAcceptableCerts }, + {22, true, "\003www\014moneybookers\003com", true, 0 }, + {17, false, "\013ledgerscope\003net", true, 0 }, + {21, false, "\003www\013ledgerscope\003net", true, 0 }, + {10, false, "\004kyps\003net", true, 0 }, + {14, false, "\003www\004kyps\003net", true, 0 }, + {17, true, "\003app\007recurly\003com", true, 0 }, + {17, true, "\003api\007recurly\003com", true, 0 }, + {13, false, "\007greplin\003com", true, 0 }, + {17, false, "\003www\007greplin\003com", true, 0 }, + {27, true, "\006luneta\016nearbuysystems\003com", true, 0 }, + {12, true, "\006ubertt\003org", true, 0 }, + + {13, false, "\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, + {17, true, "\003www\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, + {17, true, "\003api\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, + {19, true, "\005oauth\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, + {20, true, "\006mobile\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, + {17, true, "\003dev\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, + {22, true, "\010business\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, #if 0 - // Twitter CDN pins disabled in order to track down pinning failures --agl - {22, true, "\010platform\007twitter\003com", false, kTwitterCDNAcceptableCerts }, - {15, true, "\003si0\005twimg\003com", false, kTwitterCDNAcceptableCerts }, - {23, true, "\010twimg0-a\010akamaihd\003net", false, kTwitterCDNAcceptableCerts }, + // Twitter CDN pins disabled in order to track down pinning failures --agl + {22, true, "\010platform\007twitter\003com", false, kTwitterCDNAcceptableCerts }, + {15, true, "\003si0\005twimg\003com", false, kTwitterCDNAcceptableCerts }, + {23, true, "\010twimg0-a\010akamaihd\003net", false, kTwitterCDNAcceptableCerts }, #endif - }; - static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS); - - static const struct HSTSPreload kPreloadedSNISTS[] = { - // These SNI-only domains must always use HTTPS. - {11, false, "\005gmail\003com", true, kGoogleAcceptableCerts }, - {16, false, "\012googlemail\003com", true, kGoogleAcceptableCerts }, - {15, false, "\003www\005gmail\003com", true, kGoogleAcceptableCerts }, - {20, false, "\003www\012googlemail\003com", true, kGoogleAcceptableCerts }, - // These SNI-only domains must use an acceptable certificate iff using - // HTTPS. - {22, true, "\020google-analytics\003com", false, kGoogleAcceptableCerts }, - // www. requires SNI. - {18, true, "\014googlegroups\003com", false, kGoogleAcceptableCerts }, - }; - static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); +}; +static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS); + +static const struct HSTSPreload kPreloadedSNISTS[] = { + // These SNI-only domains must always use HTTPS. + {11, false, "\005gmail\003com", true, kGoogleAcceptableCerts }, + {16, false, "\012googlemail\003com", true, kGoogleAcceptableCerts }, + {15, false, "\003www\005gmail\003com", true, kGoogleAcceptableCerts }, + {20, false, "\003www\012googlemail\003com", true, kGoogleAcceptableCerts }, + // These SNI-only domains must use an acceptable certificate iff using + // HTTPS. + {22, true, "\020google-analytics\003com", false, kGoogleAcceptableCerts }, + // www. requires SNI. + {18, true, "\014googlegroups\003com", false, kGoogleAcceptableCerts }, +}; +static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); + +// Returns true if there is an HSTSPreload entry for the host in |entries|, and +// if its |required_hashes| member is identical (by address) to |certs|. +static bool ScanForHostAndCerts( + const std::string& canonicalized_host, + const struct HSTSPreload* entries, + size_t num_entries, + const char* const certs[]) { + bool hit = false; + + for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { + for (size_t j = 0; j < num_entries; j++) { + const struct HSTSPreload& entry = entries[j]; + + if (i != 0 && !entry.include_subdomains) + continue; + + if (entry.length == canonicalized_host.size() - i && + memcmp(entry.dns_name, &canonicalized_host[i], entry.length) == 0) { + hit = entry.required_hashes == certs; + // Return immediately upon exact match: + if (i == 0) + return hit; + } + } + } + + return hit; +} + +// static +bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host, + bool sni_available) { + std::string canonicalized_host = CanonicalizeHost(host); + + if (ScanForHostAndCerts(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS, + kGoogleAcceptableCerts)) { + return true; + } + + if (sni_available) { + if (ScanForHostAndCerts(canonicalized_host, kPreloadedSNISTS, kNumPreloadedSNISTS, + kGoogleAcceptableCerts)) { + return true; + } + } + + return false; +} + + +// IsPreloadedSTS returns true if the canonicalized hostname should always be +// considered to have STS enabled. +bool TransportSecurityState::IsPreloadedSTS( + const std::string& canonicalized_host, + bool sni_available, + DomainState* out) { + DCHECK(CalledOnValidThread()); + + out->preloaded = true; + out->mode = DomainState::MODE_STRICT; + out->include_subdomains = false; for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { std::string host_sub_chunk(&canonicalized_host[i], diff --git a/net/base/transport_security_state.h b/net/base/transport_security_state.h index f65da62..6832daf 100644 --- a/net/base/transport_security_state.h +++ b/net/base/transport_security_state.h @@ -113,6 +113,21 @@ class NET_EXPORT TransportSecurityState const std::string& host, bool sni_available); + // Returns true if we have a preloaded certificate pin for the |host| and if + // its set of required certificates is the set we expect for Google + // properties. If |sni_available| is true, searches the preloads defined for + // SNI-using hosts as well as the usual preload list. + // + // Note that like HasMetadata, if |host| matches both an exact entry and is a + // subdomain of another entry, the exact match determines the return value. + // + // This function is used by ChromeFraudulentCertificateReporter to determine + // whether or not we can automatically post fraudulent certificate reports to + // Google; we only do so automatically in cases when the user was trying to + // connect to Google in the first place. + static bool IsGooglePinnedProperty(const std::string& host, + bool sni_available); + // Deletes all records created since a given time. void DeleteSince(const base::Time& time); diff --git a/net/base/transport_security_state_unittest.cc b/net/base/transport_security_state_unittest.cc index 8bbf641..d9337a9 100644 --- a/net/base/transport_security_state_unittest.cc +++ b/net/base/transport_security_state_unittest.cc @@ -1032,4 +1032,67 @@ TEST_F(TransportSecurityStateTest, DISABLED_ParseSidePinsFuzz) { } } +TEST_F(TransportSecurityStateTest, GooglePinnedProperties) { + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "www.example.com", true)); + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "www.paypal.com", true)); + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "mail.twitter.com", true)); + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "www.google.com.int", true)); + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "jottit.com", true)); + // learn.doubleclick.net has a more specific match than + // *.doubleclick.com, and has 0 or NULL for its required certs. + // This test ensures that the exact-match-preferred behavior + // works. + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "learn.doubleclick.net", true)); + + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "encrypted.google.com", true)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "mail.google.com", true)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "accounts.google.com", true)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "doubleclick.net", true)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "ad.doubleclick.net", true)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "youtube.com", true)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "www.profiles.google.com", true)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "checkout.google.com", true)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "googleadservices.com", true)); + + // Test with sni_enabled false: + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "www.example.com", false)); + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "www.paypal.com", false)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "checkout.google.com", false)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "googleadservices.com", false)); + + // Test some SNI hosts: + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "gmail.com", true)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "googlegroups.com", true)); + EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty( + "www.googlegroups.com", true)); + // Expect to fail for SNI hosts when not searching the SNI list: + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "gmail.com", false)); + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "googlegroups.com", false)); + EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty( + "www.googlegroups.com", false)); +} + } // namespace net diff --git a/net/data/ssl/certificates/README b/net/data/ssl/certificates/README index 8ca5c9c..d782cdb4 100644 --- a/net/data/ssl/certificates/README +++ b/net/data/ssl/certificates/README @@ -51,3 +51,8 @@ unit tests. - google_diginotar.pem - diginotar_public_ca_2025.pem : A certificate chain for the regression test of http://crbug.com/94673 + +- test_mail_google_com.pem : A certificate signed by the test CA for + "mail.google.com". Because it is signed by that CA instead of the true CA + for that host, it will fail the + TransportSecurityState::IsChainOfPublicKeysPermitted test. diff --git a/net/data/ssl/certificates/test_mail_google_com.pem b/net/data/ssl/certificates/test_mail_google_com.pem new file mode 100644 index 0000000..d72d562 --- /dev/null +++ b/net/data/ssl/certificates/test_mail_google_com.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEXDCCAkSgAwIBAgIBBjANBgkqhkiG9w0BAQUFADBgMRAwDgYDVQQDEwdUZXN0 +IENBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN +TW91bnRhaW4gVmlldzESMBAGA1UEChMJQ2VydCBUZXN0MB4XDTExMTAxMTE5MTYy +MVoXDTEzMDcyNzAwMDAwMFowbTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlm +b3JuaWExEjAQBgNVBAoTCUNlcnQgVGVzdDEbMBkGA1UECxMSR29hdCBUZWxlcG9y +dGF0aW9uMRgwFgYDVQQDEw9tYWlsLmdvb2dsZS5jb20wXDANBgkqhkiG9w0BAQEF +AANLADBIAkEAvy9N7zZ2yuMamRGUDc7KiLHq+OwVkfmvDRsrj77+MMR1DkUx1Qez +s+tKtm6dyi5mariRL5ChbgIBqNYhb/cecQIDAQABo4HbMIHYMBIGA1UdEwEB/wQI +MAYBAf8CAQEwDgYDVR0PAQH/BAQDAgIEMB0GA1UdDgQWBBQQFF3/oZUdPuM69r0i +Gl5tEK5e7TCBkgYDVR0jBIGKMIGHgBRdzn+Z49QZQTFPxs+xJfVar+OXMaFkpGIw +YDEQMA4GA1UEAxMHVGVzdCBDQTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlm +b3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxEjAQBgNVBAoTCUNlcnQgVGVz +dIIJANRRk9Q/3tlOMA0GCSqGSIb3DQEBBQUAA4ICAQCPO6wgG6cFmu5ZgAN9q+dS +BVrMiJhHj62Tlw7qNjD+VAfidTTtQPM8T0y2LtNe2epO6jDOyIpRwsKkFi5mozcs +Dd3CfXAs7fkdY4ZnAxjXhhk1fvMkomR6CfTHEwcGkfwVm2MDozZmYbS83OP+E82B ++yKA41ppbw75/meJzH4nSECBd/Whzi8AuuX6e3bSae6XEAdhBQoLHyNAvZ0IEeCb +sI3DvXdpIP0LyYJH6+F/KG5Jugby44HuAK1MBn9/f5tYplucOj5cyw/fYWd8REGD +Ob71lh9/eZVcYjvbF6LxlizZQ+DNHV2QkHvSQqAACDbpFCUcU9KO5xvN8RaVtFmJ +sDuHtxDDXFcXHhLh6bcC2KFrsmwEV68jmek0++eMa/W99ADzNWUWCmGoyZQafP2e +eqQ6Ry8wgH+ZVkhQaaGk4fCKZATpX7//qdj7IzO52Kpx0dwsW7mHxPjdRKQzThkn +lwFSiKByJDMOm9JbjpGf52JsCX4OSFuHCRcc2TB867xKRfBoAXE06fMS2lTwAcQh +3vdzO0gEv9WOvdvehvngcrWzGwIdGaP6BBXi+9b5wPBR8ravMPAgQBXg01vME+/+ +TkpEaCFACOttO0YkVqG6lFFT1wigsh3k4/+Eyh/RsLTsFObZBJsMLetbY/XzwhTf +LyeXa2sT1sk6l+EfrzWS1Q== +-----END CERTIFICATE----- diff --git a/net/net.gyp b/net/net.gyp index 6bc9340..370a585 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -635,6 +635,7 @@ 'udp/udp_socket_libevent.h', 'udp/udp_socket_win.cc', 'udp/udp_socket_win.h', + 'url_request/fraudulent_certificate_reporter.h', 'url_request/url_request.cc', 'url_request/url_request.h', 'url_request/url_request_about_job.cc', diff --git a/net/url_request/fraudulent_certificate_reporter.h b/net/url_request/fraudulent_certificate_reporter.h new file mode 100644 index 0000000..7522c13 --- /dev/null +++ b/net/url_request/fraudulent_certificate_reporter.h @@ -0,0 +1,35 @@ +// Copyright (c) 2011 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_URL_REQUEST_FRAUDULENT_CERTIFICATE_REPORTER_H_ +#define NET_URL_REQUEST_FRAUDULENT_CERTIFICATE_REPORTER_H_ + +#include <string> + +#include "net/base/net_export.h" + +namespace net { + +class SSLInfo; + +// FraudulentCertificateReporter is an interface for asynchronously +// reporting certificate chains that fail the certificate pinning +// check. +class NET_EXPORT FraudulentCertificateReporter { + public: + virtual ~FraudulentCertificateReporter() {} + + // Sends a report to the report collection server containing the |ssl_info| + // associated with a connection to |hostname|. If |sni_available| is true, + // searches the SNI transport security metadata as well as the usual + // transport security metadata when determining policy for sending the report. + virtual void SendReport(const std::string& hostname, + const SSLInfo& ssl_info, + bool sni_available) = 0; +}; + +} // namespace net + +#endif // NET_URL_REQUEST_FRAUDULENT_CERTIFICATE_REPORTER_H_ + |