summaryrefslogtreecommitdiffstats
path: root/chrome/browser/net
diff options
context:
space:
mode:
authorpalmer@chromium.org <palmer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-15 00:54:05 +0000
committerpalmer@chromium.org <palmer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-15 00:54:05 +0000
commitce2b2ee29a7d34577d9369b42bb0063e3baf3617 (patch)
tree13d62a8bb0b1edcb8a982678f92267ee84a79850 /chrome/browser/net
parent9c8ae81d134ffa1b5364fb992a27386c9ef96937 (diff)
downloadchromium_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')
-rw-r--r--chrome/browser/net/cert_logger.proto49
-rw-r--r--chrome/browser/net/chrome_fraudulent_certificate_reporter.cc153
-rw-r--r--chrome/browser/net/chrome_fraudulent_certificate_reporter.h61
-rw-r--r--chrome/browser/net/chrome_fraudulent_certificate_reporter_unittest.cc201
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
+