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