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