diff options
author | palmer@chromium.org <palmer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-15 00:54:05 +0000 |
---|---|---|
committer | palmer@chromium.org <palmer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-15 00:54:05 +0000 |
commit | ce2b2ee29a7d34577d9369b42bb0063e3baf3617 (patch) | |
tree | 13d62a8bb0b1edcb8a982678f92267ee84a79850 /chrome/browser/net | |
parent | 9c8ae81d134ffa1b5364fb992a27386c9ef96937 (diff) | |
download | chromium_src-ce2b2ee29a7d34577d9369b42bb0063e3baf3617.zip chromium_src-ce2b2ee29a7d34577d9369b42bb0063e3baf3617.tar.gz chromium_src-ce2b2ee29a7d34577d9369b42bb0063e3baf3617.tar.bz2 |
Revert 105614.
BUG=99185
Review URL: http://codereview.chromium.org/8289023
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@105624 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/net')
4 files changed, 464 insertions, 0 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 + |