1 // Copyright 2017 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 "components/security_interstitials/content/ssl_error_navigation_throttle.h"
6 
7 #include "base/bind.h"
8 #include "base/run_loop.h"
9 #include "base/threading/thread_task_runner_handle.h"
10 #include "components/security_interstitials/content/security_interstitial_controller_client.h"
11 #include "components/security_interstitials/content/security_interstitial_page.h"
12 #include "components/security_interstitials/core/metrics_helper.h"
13 #include "components/security_interstitials/core/ssl_error_ui.h"
14 #include "content/public/browser/navigation_throttle.h"
15 #include "content/public/test/mock_navigation_handle.h"
16 #include "content/public/test/test_renderer_host.h"
17 #include "net/cert/cert_status_flags.h"
18 #include "net/test/cert_test_util.h"
19 #include "net/test/test_data_directory.h"
20 
21 namespace {
22 
23 // Constructs a MetricsHelper instance for usage in this context.
24 std::unique_ptr<security_interstitials::MetricsHelper>
CreateMetricsHelperForTest(const GURL & request_url)25 CreateMetricsHelperForTest(const GURL& request_url) {
26   security_interstitials::MetricsHelper::ReportDetails report_details;
27   report_details.metric_prefix = "test";
28   return std::make_unique<security_interstitials::MetricsHelper>(
29       request_url, report_details, /*history_service=*/nullptr);
30 }
31 
32 // A minimal SSLCertReporter implementation.
33 class FakeSSLCertReporter : public SSLCertReporter {
34  public:
ReportInvalidCertificateChain(const std::string & serialized_report)35   void ReportInvalidCertificateChain(
36       const std::string& serialized_report) override {
37     // Reports are not expected to be sent in this context.
38     NOTREACHED();
39   }
40 };
41 
42 // A SecurityInterstitialPage implementation that does the minimum necessary
43 // to satisfy SSLErrorNavigationThrottle's expectations of the instance passed
44 // to its ShowInterstitial() method, in particular populates the data
45 // needed to instantiate the template HTML.
46 class FakeSSLBlockingPage
47     : public security_interstitials::SecurityInterstitialPage {
48  public:
FakeSSLBlockingPage(content::WebContents * web_contents,int cert_error,const net::SSLInfo & ssl_info,const GURL & request_url)49   FakeSSLBlockingPage(content::WebContents* web_contents,
50                       int cert_error,
51                       const net::SSLInfo& ssl_info,
52                       const GURL& request_url)
53       : security_interstitials::SecurityInterstitialPage(
54             web_contents,
55             request_url,
56             std::make_unique<
57                 security_interstitials::SecurityInterstitialControllerClient>(
58                 web_contents,
59                 CreateMetricsHelperForTest(request_url),
60                 /*prefs=*/nullptr,
61                 "en_US",
62                 GURL("about:blank"))),
63         ssl_error_ui_(request_url,
64                       cert_error,
65                       ssl_info,
66                       /*options_mask=*/0,
67                       base::Time::NowFromSystemTime(),
68                       /*support_url=*/GURL(),
69                       controller()) {}
70 
~FakeSSLBlockingPage()71   ~FakeSSLBlockingPage() override {}
72 
73   // SecurityInterstitialPage:
OnInterstitialClosing()74   void OnInterstitialClosing() override {}
ShouldCreateNewNavigation() const75   bool ShouldCreateNewNavigation() const override { return false; }
PopulateInterstitialStrings(base::DictionaryValue * load_time_data)76   void PopulateInterstitialStrings(
77       base::DictionaryValue* load_time_data) override {
78     ssl_error_ui_.PopulateStringsForHTML(load_time_data);
79   }
80 
81  private:
82   security_interstitials::SSLErrorUI ssl_error_ui_;
83 };
84 
85 // Replacement for SSLErrorHandler::HandleSSLError that calls
86 // |blocking_page_ready_callback|. |async| specifies whether this call should be
87 // done synchronously or using PostTask().
MockHandleSSLError(bool async,content::WebContents * web_contents,int cert_error,const net::SSLInfo & ssl_info,const GURL & request_url,std::unique_ptr<SSLCertReporter> ssl_cert_reporter,base::OnceCallback<void (std::unique_ptr<security_interstitials::SecurityInterstitialPage>)> blocking_page_ready_callback)88 void MockHandleSSLError(
89     bool async,
90     content::WebContents* web_contents,
91     int cert_error,
92     const net::SSLInfo& ssl_info,
93     const GURL& request_url,
94     std::unique_ptr<SSLCertReporter> ssl_cert_reporter,
95     base::OnceCallback<
96         void(std::unique_ptr<security_interstitials::SecurityInterstitialPage>)>
97         blocking_page_ready_callback) {
98   auto blocking_page = std::make_unique<FakeSSLBlockingPage>(
99       web_contents, cert_error, ssl_info, request_url);
100   if (async) {
101     base::ThreadTaskRunnerHandle::Get()->PostTask(
102         FROM_HERE, base::BindOnce(std::move(blocking_page_ready_callback),
103                                   std::move(blocking_page)));
104   } else {
105     std::move(blocking_page_ready_callback).Run(std::move(blocking_page));
106   }
107 }
108 
IsInHostedApp(content::WebContents * web_contents)109 bool IsInHostedApp(content::WebContents* web_contents) {
110   return false;
111 }
112 
113 class TestSSLErrorNavigationThrottle : public SSLErrorNavigationThrottle {
114  public:
TestSSLErrorNavigationThrottle(content::NavigationHandle * handle,bool async_handle_ssl_error,base::OnceCallback<void (content::NavigationThrottle::ThrottleCheckResult)> on_cancel_deferred_navigation)115   TestSSLErrorNavigationThrottle(
116       content::NavigationHandle* handle,
117       bool async_handle_ssl_error,
118       base::OnceCallback<void(content::NavigationThrottle::ThrottleCheckResult)>
119           on_cancel_deferred_navigation)
120       : SSLErrorNavigationThrottle(
121             handle,
122             std::make_unique<FakeSSLCertReporter>(),
123             base::BindOnce(&MockHandleSSLError, async_handle_ssl_error),
124             base::BindOnce(&IsInHostedApp)),
125         on_cancel_deferred_navigation_(
126             std::move(on_cancel_deferred_navigation)) {}
127 
128   // NavigationThrottle:
CancelDeferredNavigation(content::NavigationThrottle::ThrottleCheckResult result)129   void CancelDeferredNavigation(
130       content::NavigationThrottle::ThrottleCheckResult result) override {
131     std::move(on_cancel_deferred_navigation_).Run(result);
132   }
133 
134  private:
135   base::OnceCallback<void(content::NavigationThrottle::ThrottleCheckResult)>
136       on_cancel_deferred_navigation_;
137 
138   DISALLOW_COPY_AND_ASSIGN(TestSSLErrorNavigationThrottle);
139 };
140 
141 class SSLErrorNavigationThrottleTest
142     : public content::RenderViewHostTestHarness,
143       public testing::WithParamInterface<bool> {
144  public:
SSLErrorNavigationThrottleTest()145   SSLErrorNavigationThrottleTest() {}
SetUp()146   void SetUp() override {
147     content::RenderViewHostTestHarness::SetUp();
148     handle_ = std::make_unique<content::MockNavigationHandle>(web_contents());
149     handle_->set_has_committed(true);
150     async_ = GetParam();
151     throttle_ = std::make_unique<TestSSLErrorNavigationThrottle>(
152         handle_.get(), async_,
153         base::BindOnce(&SSLErrorNavigationThrottleTest::RecordDeferredResult,
154                        base::Unretained(this)));
155   }
156 
RecordDeferredResult(content::NavigationThrottle::ThrottleCheckResult result)157   void RecordDeferredResult(
158       content::NavigationThrottle::ThrottleCheckResult result) {
159     deferred_result_ = result;
160   }
161 
162  protected:
163   bool async_ = false;
164   std::unique_ptr<content::MockNavigationHandle> handle_;
165   std::unique_ptr<TestSSLErrorNavigationThrottle> throttle_;
166   content::NavigationThrottle::ThrottleCheckResult deferred_result_ =
167       content::NavigationThrottle::DEFER;
168 
169  private:
170   DISALLOW_COPY_AND_ASSIGN(SSLErrorNavigationThrottleTest);
171 };
172 
173 // Tests that the throttle ignores a request with a non SSL related network
174 // error code.
TEST_P(SSLErrorNavigationThrottleTest,NoSSLError)175 TEST_P(SSLErrorNavigationThrottleTest, NoSSLError) {
176   SCOPED_TRACE(::testing::Message()
177                << "Asynchronous MockHandleSSLError: " << async_);
178 
179   handle_->set_net_error_code(net::ERR_BLOCKED_BY_CLIENT);
180   content::NavigationThrottle::ThrottleCheckResult result =
181       throttle_->WillFailRequest();
182   EXPECT_EQ(content::NavigationThrottle::PROCEED, result);
183 }
184 
185 // Tests that the throttle defers and cancels a request with a net error that
186 // is a cert error.
TEST_P(SSLErrorNavigationThrottleTest,SSLInfoWithCertError)187 TEST_P(SSLErrorNavigationThrottleTest, SSLInfoWithCertError) {
188   SCOPED_TRACE(::testing::Message()
189                << "Asynchronous MockHandleSSLError: " << async_);
190 
191   net::SSLInfo ssl_info;
192   ssl_info.cert =
193       net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
194   ssl_info.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
195   handle_->set_net_error_code(net::ERR_CERT_COMMON_NAME_INVALID);
196   handle_->set_ssl_info(ssl_info);
197   content::NavigationThrottle::ThrottleCheckResult synchronous_result =
198       throttle_->WillFailRequest();
199 
200   EXPECT_EQ(content::NavigationThrottle::DEFER, synchronous_result.action());
201   base::RunLoop().RunUntilIdle();
202   EXPECT_EQ(content::NavigationThrottle::CANCEL, deferred_result_.action());
203   EXPECT_EQ(net::ERR_CERT_COMMON_NAME_INVALID,
204             deferred_result_.net_error_code());
205   EXPECT_TRUE(deferred_result_.error_page_content().has_value());
206 }
207 
208 INSTANTIATE_TEST_SUITE_P(All,
209                          SSLErrorNavigationThrottleTest,
210                          ::testing::Values(false, true));
211 
212 }  // namespace
213