Home | History | Annotate | Download | only in net
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/net/chrome_fraudulent_certificate_reporter.h"
      6 
      7 #include <string>
      8 
      9 #include "base/bind.h"
     10 #include "base/files/file_path.h"
     11 #include "base/memory/scoped_ptr.h"
     12 #include "base/message_loop/message_loop.h"
     13 #include "base/synchronization/waitable_event.h"
     14 #include "base/threading/thread.h"
     15 #include "content/public/test/test_browser_thread.h"
     16 #include "net/base/request_priority.h"
     17 #include "net/base/test_data_directory.h"
     18 #include "net/cert/x509_certificate.h"
     19 #include "net/http/transport_security_state.h"
     20 #include "net/ssl/ssl_info.h"
     21 #include "net/test/cert_test_util.h"
     22 #include "net/url_request/fraudulent_certificate_reporter.h"
     23 #include "net/url_request/url_request.h"
     24 #include "net/url_request/url_request_test_util.h"
     25 #include "testing/gtest/include/gtest/gtest.h"
     26 
     27 using content::BrowserThread;
     28 using net::SSLInfo;
     29 
     30 namespace chrome_browser_net {
     31 
     32 // Builds an SSLInfo from an invalid cert chain. In this case, the cert is
     33 // expired; what matters is that the cert would not pass even a normal
     34 // sanity check. We test that we DO NOT send a fraudulent certificate report
     35 // in this case.
     36 static SSLInfo GetBadSSLInfo() {
     37   SSLInfo info;
     38 
     39   info.cert = net::ImportCertFromFile(net::GetTestCertsDirectory(),
     40                                       "expired_cert.pem");
     41   info.cert_status = net::CERT_STATUS_DATE_INVALID;
     42   info.is_issued_by_known_root = false;
     43 
     44   return info;
     45 }
     46 
     47 // Builds an SSLInfo from a "good" cert chain, as defined by IsGoodSSLInfo,
     48 // but which does not pass DomainState::IsChainOfPublicKeysPermitted. In this
     49 // case, the certificate is for mail.google.com, signed by our Chrome test
     50 // CA. During testing, Chrome believes this CA is part of the root system
     51 // store. But, this CA is not in the pin list; we test that we DO send a
     52 // fraudulent certicate report in this case.
     53 static SSLInfo GetGoodSSLInfo() {
     54   SSLInfo info;
     55 
     56   info.cert = net::ImportCertFromFile(net::GetTestCertsDirectory(),
     57                                       "test_mail_google_com.pem");
     58   info.is_issued_by_known_root = true;
     59 
     60   return info;
     61 }
     62 
     63 // Checks that |info| is good as required by the SSL checks performed in
     64 // URLRequestHttpJob::OnStartCompleted, which are enough to trigger pin
     65 // checking but not sufficient to pass
     66 // DomainState::IsChainOfPublicKeysPermitted.
     67 static bool IsGoodSSLInfo(const SSLInfo& info) {
     68   return info.is_valid() && info.is_issued_by_known_root;
     69 }
     70 
     71 class TestReporter : public ChromeFraudulentCertificateReporter {
     72  public:
     73   explicit TestReporter(net::URLRequestContext* request_context)
     74       : ChromeFraudulentCertificateReporter(request_context) {}
     75 };
     76 
     77 class SendingTestReporter : public TestReporter {
     78  public:
     79   explicit SendingTestReporter(net::URLRequestContext* request_context)
     80       : TestReporter(request_context), passed_(false) {}
     81 
     82   // Passes if invoked with a good SSLInfo and for a hostname that is a Google
     83   // pinned property.
     84   virtual void SendReport(const std::string& hostname,
     85                           const SSLInfo& ssl_info,
     86                           bool sni_available) OVERRIDE {
     87     EXPECT_TRUE(IsGoodSSLInfo(ssl_info));
     88     EXPECT_TRUE(net::TransportSecurityState::IsGooglePinnedProperty(
     89         hostname, sni_available));
     90     passed_ = true;
     91   }
     92 
     93   virtual ~SendingTestReporter() {
     94     // If the object is destroyed without having its SendReport method invoked,
     95     // we failed.
     96     EXPECT_TRUE(passed_);
     97   }
     98 
     99   bool passed_;
    100 };
    101 
    102 class NotSendingTestReporter : public TestReporter {
    103  public:
    104   explicit NotSendingTestReporter(net::URLRequestContext* request_context)
    105       : TestReporter(request_context) {}
    106 
    107   // Passes if invoked with a bad SSLInfo and for a hostname that is not a
    108   // Google pinned property.
    109   virtual void SendReport(const std::string& hostname,
    110                           const SSLInfo& ssl_info,
    111                           bool sni_available) OVERRIDE {
    112     EXPECT_FALSE(IsGoodSSLInfo(ssl_info));
    113     EXPECT_FALSE(net::TransportSecurityState::IsGooglePinnedProperty(
    114         hostname, sni_available));
    115   }
    116 };
    117 
    118 // For the first version of the feature, sending reports is "fire and forget".
    119 // Therefore, we test only that the Reporter tried to send a request at all.
    120 // In the future, when we have more sophisticated (i.e., any) error handling
    121 // and re-tries, we will need more sopisticated tests as well.
    122 //
    123 // This class doesn't do anything now, but in near future versions it will.
    124 class MockURLRequest : public net::URLRequest {
    125  public:
    126   explicit MockURLRequest(net::URLRequestContext* context)
    127       : net::URLRequest(GURL(std::string()),
    128                         net::DEFAULT_PRIORITY,
    129                         NULL,
    130                         context) {}
    131 
    132  private:
    133 };
    134 
    135 // A ChromeFraudulentCertificateReporter that uses a MockURLRequest, but is
    136 // otherwise normal: reports are constructed and sent in the usual way.
    137 class MockReporter : public ChromeFraudulentCertificateReporter {
    138  public:
    139   explicit MockReporter(net::URLRequestContext* request_context)
    140     : ChromeFraudulentCertificateReporter(request_context) {}
    141 
    142   virtual scoped_ptr<net::URLRequest> CreateURLRequest(
    143       net::URLRequestContext* context) OVERRIDE {
    144     return scoped_ptr<net::URLRequest>(new MockURLRequest(context));
    145   }
    146 
    147   virtual void SendReport(
    148       const std::string& hostname,
    149       const net::SSLInfo& ssl_info,
    150       bool sni_available) OVERRIDE {
    151     DCHECK(!hostname.empty());
    152     DCHECK(ssl_info.is_valid());
    153     ChromeFraudulentCertificateReporter::SendReport(hostname, ssl_info,
    154                                                     sni_available);
    155   }
    156 };
    157 
    158 static void DoReportIsSent() {
    159   net::TestURLRequestContext context;
    160   SendingTestReporter reporter(&context);
    161   SSLInfo info = GetGoodSSLInfo();
    162   reporter.SendReport("mail.google.com", info, true);
    163 }
    164 
    165 static void DoReportIsNotSent() {
    166   net::TestURLRequestContext context;
    167   NotSendingTestReporter reporter(&context);
    168   SSLInfo info = GetBadSSLInfo();
    169   reporter.SendReport("www.example.com", info, true);
    170 }
    171 
    172 static void DoMockReportIsSent() {
    173   net::TestURLRequestContext context;
    174   MockReporter reporter(&context);
    175   SSLInfo info = GetGoodSSLInfo();
    176   reporter.SendReport("mail.google.com", info, true);
    177 }
    178 
    179 TEST(ChromeFraudulentCertificateReporterTest, GoodBadInfo) {
    180   SSLInfo good = GetGoodSSLInfo();
    181   EXPECT_TRUE(IsGoodSSLInfo(good));
    182 
    183   SSLInfo bad = GetBadSSLInfo();
    184   EXPECT_FALSE(IsGoodSSLInfo(bad));
    185 }
    186 
    187 TEST(ChromeFraudulentCertificateReporterTest, ReportIsSent) {
    188   base::MessageLoopForIO loop;
    189   content::TestBrowserThread io_thread(BrowserThread::IO, &loop);
    190   loop.PostTask(FROM_HERE, base::Bind(&DoReportIsSent));
    191   loop.RunUntilIdle();
    192 }
    193 
    194 TEST(ChromeFraudulentCertificateReporterTest, MockReportIsSent) {
    195   base::MessageLoopForIO loop;
    196   content::TestBrowserThread io_thread(BrowserThread::IO, &loop);
    197   loop.PostTask(FROM_HERE, base::Bind(&DoMockReportIsSent));
    198   loop.RunUntilIdle();
    199 }
    200 
    201 TEST(ChromeFraudulentCertificateReporterTest, ReportIsNotSent) {
    202   base::MessageLoopForIO loop;
    203   content::TestBrowserThread io_thread(BrowserThread::IO, &loop);
    204   loop.PostTask(FROM_HERE, base::Bind(&DoReportIsNotSent));
    205   loop.RunUntilIdle();
    206 }
    207 
    208 }  // namespace chrome_browser_net
    209