1 // Copyright 2019 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 "weblayer/test/weblayer_browser_test.h"
6
7 #include "base/files/file_path.h"
8 #include "base/macros.h"
9 #include "base/optional.h"
10 #include "build/build_config.h"
11 #include "components/network_time/network_time_tracker.h"
12 #include "components/security_interstitials/content/ssl_error_handler.h"
13 #include "net/test/embedded_test_server/embedded_test_server.h"
14 #include "weblayer/browser/browser_process.h"
15 #include "weblayer/browser/weblayer_security_blocking_page_factory.h"
16 #include "weblayer/shell/browser/shell.h"
17 #include "weblayer/test/interstitial_utils.h"
18 #include "weblayer/test/load_completion_observer.h"
19 #include "weblayer/test/test_navigation_observer.h"
20 #include "weblayer/test/weblayer_browser_test_utils.h"
21
22 namespace weblayer {
23
24 class SSLBrowserTest : public WebLayerBrowserTest {
25 public:
26 SSLBrowserTest() = default;
27 ~SSLBrowserTest() override = default;
28
29 // WebLayerBrowserTest:
SetUpOnMainThread()30 void SetUpOnMainThread() override {
31 https_server_ = std::make_unique<net::EmbeddedTestServer>(
32 net::EmbeddedTestServer::TYPE_HTTPS);
33 https_server_->AddDefaultHandlers(
34 base::FilePath(FILE_PATH_LITERAL("weblayer/test/data")));
35
36 https_server_mismatched_ = std::make_unique<net::EmbeddedTestServer>(
37 net::EmbeddedTestServer::TYPE_HTTPS);
38 https_server_mismatched_->SetSSLConfig(
39 net::EmbeddedTestServer::CERT_MISMATCHED_NAME);
40 https_server_mismatched_->AddDefaultHandlers(
41 base::FilePath(FILE_PATH_LITERAL("weblayer/test/data")));
42
43 https_server_expired_ = std::make_unique<net::EmbeddedTestServer>(
44 net::EmbeddedTestServer::TYPE_HTTPS);
45 https_server_expired_->SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
46 https_server_expired_->AddDefaultHandlers(
47 base::FilePath(FILE_PATH_LITERAL("weblayer/test/data")));
48
49 ASSERT_TRUE(https_server_->Start());
50 ASSERT_TRUE(https_server_mismatched_->Start());
51 ASSERT_TRUE(https_server_expired_->Start());
52 }
53
PostRunTestOnMainThread()54 void PostRunTestOnMainThread() override {
55 https_server_.reset();
56 https_server_mismatched_.reset();
57 WebLayerBrowserTest::PostRunTestOnMainThread();
58 }
59
NavigateToOkPage()60 void NavigateToOkPage() {
61 ASSERT_EQ("127.0.0.1", ok_url().host());
62 NavigateAndWaitForCompletion(ok_url(), shell());
63 EXPECT_FALSE(IsShowingSecurityInterstitial(shell()->tab()));
64 }
65
NavigateToPageWithMismatchedCertExpectSSLInterstitial()66 void NavigateToPageWithMismatchedCertExpectSSLInterstitial() {
67 // Do a navigation that should result in an SSL error.
68 NavigateAndWaitForFailure(mismatched_cert_url(), shell());
69 // First check that there *is* an interstitial.
70 ASSERT_TRUE(IsShowingSecurityInterstitial(shell()->tab()));
71
72 // Now verify that the interstitial is in fact an SSL interstitial.
73 EXPECT_TRUE(IsShowingSSLInterstitial(shell()->tab()));
74
75 // TODO(blundell): Check the security state once security state is available
76 // via the public WebLayer API, following the example of //chrome's
77 // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
78 }
79
NavigateToPageWithMismatchedCertExpectCaptivePortalInterstitial()80 void NavigateToPageWithMismatchedCertExpectCaptivePortalInterstitial() {
81 // Do a navigation that should result in an SSL error.
82 NavigateAndWaitForFailure(mismatched_cert_url(), shell());
83 // First check that there *is* an interstitial.
84 ASSERT_TRUE(IsShowingSecurityInterstitial(shell()->tab()));
85
86 // Now verify that the interstitial is in fact a captive portal
87 // interstitial.
88 EXPECT_TRUE(IsShowingCaptivePortalInterstitial(shell()->tab()));
89
90 // TODO(blundell): Check the security state once security state is available
91 // via the public WebLayer API, following the example of //chrome's
92 // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
93 }
94
NavigateToPageWithExpiredCertExpectSSLInterstitial()95 void NavigateToPageWithExpiredCertExpectSSLInterstitial() {
96 // Do a navigation that should result in an SSL error.
97 NavigateAndWaitForFailure(expired_cert_url(), shell());
98 // First check that there *is* an interstitial.
99 ASSERT_TRUE(IsShowingSecurityInterstitial(shell()->tab()));
100
101 // Now verify that the interstitial is in fact an SSL interstitial.
102 EXPECT_TRUE(IsShowingSSLInterstitial(shell()->tab()));
103
104 // TODO(blundell): Check the security state once security state is available
105 // via the public WebLayer API, following the example of //chrome's
106 // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
107 }
108
NavigateToPageWithExpiredCertExpectBadClockInterstitial()109 void NavigateToPageWithExpiredCertExpectBadClockInterstitial() {
110 // Do a navigation that should result in an SSL error.
111 NavigateAndWaitForFailure(expired_cert_url(), shell());
112 // First check that there *is* an interstitial.
113 ASSERT_TRUE(IsShowingSecurityInterstitial(shell()->tab()));
114
115 // Now verify that the interstitial is in fact a bad clock interstitial.
116 EXPECT_TRUE(IsShowingBadClockInterstitial(shell()->tab()));
117
118 // TODO(blundell): Check the security state once security state is available
119 // via the public WebLayer API, following the example of //chrome's
120 // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
121 }
122
NavigateToPageWithMismatchedCertExpectNotBlocked()123 void NavigateToPageWithMismatchedCertExpectNotBlocked() {
124 NavigateAndWaitForCompletion(mismatched_cert_url(), shell());
125 EXPECT_FALSE(IsShowingSecurityInterstitial(shell()->tab()));
126
127 // TODO(blundell): Check the security state once security state is available
128 // via the public WebLayer API, following the example of //chrome's
129 // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
130 }
131
SendInterstitialNavigationCommandAndWait(bool proceed,base::Optional<GURL> previous_url=base::nullopt)132 void SendInterstitialNavigationCommandAndWait(
133 bool proceed,
134 base::Optional<GURL> previous_url = base::nullopt) {
135 GURL expected_url =
136 proceed ? mismatched_cert_url() : previous_url.value_or(ok_url());
137 ASSERT_TRUE(IsShowingSSLInterstitial(shell()->tab()));
138
139 TestNavigationObserver navigation_observer(
140 expected_url, TestNavigationObserver::NavigationEvent::kCompletion,
141 shell());
142 ExecuteScript(shell(),
143 "window.certificateErrorPageController." +
144 std::string(proceed ? "proceed" : "dontProceed") + "();",
145 false /*use_separate_isolate*/);
146 navigation_observer.Wait();
147 EXPECT_FALSE(IsShowingSSLInterstitial(shell()->tab()));
148 }
149
SendInterstitialReloadCommandAndWait()150 void SendInterstitialReloadCommandAndWait() {
151 ASSERT_TRUE(IsShowingSSLInterstitial(shell()->tab()));
152
153 LoadCompletionObserver load_observer(shell());
154 ExecuteScript(shell(), "window.certificateErrorPageController.reload();",
155 false /*use_separate_isolate*/);
156 load_observer.Wait();
157
158 // Should still be showing the SSL interstitial after the reload command is
159 // processed.
160 EXPECT_TRUE(IsShowingSSLInterstitial(shell()->tab()));
161 }
162
163 #if defined(OS_ANDROID)
SendInterstitialOpenLoginCommandAndWait()164 void SendInterstitialOpenLoginCommandAndWait() {
165 ASSERT_TRUE(IsShowingCaptivePortalInterstitial(shell()->tab()));
166
167 // Note: The embedded test server cannot actually load the captive portal
168 // login URL, so simply detect the start of the navigation to the page.
169 TestNavigationObserver navigation_observer(
170 WebLayerSecurityBlockingPageFactory::
171 GetCaptivePortalLoginPageUrlForTesting(),
172 TestNavigationObserver::NavigationEvent::kStart, shell());
173 ExecuteScript(shell(), "window.certificateErrorPageController.openLogin();",
174 false /*use_separate_isolate*/);
175 navigation_observer.Wait();
176 }
177 #endif
178
NavigateToOtherOkPage()179 void NavigateToOtherOkPage() {
180 NavigateAndWaitForCompletion(https_server_->GetURL("/simple_page2.html"),
181 shell());
182 EXPECT_FALSE(IsShowingSecurityInterstitial(shell()->tab()));
183 }
184
ok_url()185 GURL ok_url() { return https_server_->GetURL("/simple_page.html"); }
mismatched_cert_url()186 GURL mismatched_cert_url() {
187 return https_server_mismatched_->GetURL("/simple_page.html");
188 }
189
expired_cert_url()190 GURL expired_cert_url() {
191 return https_server_expired_->GetURL("/simple_page.html");
192 }
193
194 protected:
195 std::unique_ptr<net::EmbeddedTestServer> https_server_;
196 std::unique_ptr<net::EmbeddedTestServer> https_server_mismatched_;
197 std::unique_ptr<net::EmbeddedTestServer> https_server_expired_;
198
199 private:
200 DISALLOW_COPY_AND_ASSIGN(SSLBrowserTest);
201 };
202
203 // Tests clicking "take me back" on the interstitial page.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,TakeMeBack)204 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, TakeMeBack) {
205 NavigateToOkPage();
206 NavigateToPageWithMismatchedCertExpectSSLInterstitial();
207
208 // Click "Take me back".
209 SendInterstitialNavigationCommandAndWait(false /*proceed*/);
210
211 // Check that it's possible to navigate to a new page.
212 NavigateToOtherOkPage();
213
214 // Navigate to the bad SSL page again, an interstitial shows again (in
215 // contrast to what would happen had the user chosen to proceed).
216 NavigateToPageWithMismatchedCertExpectSSLInterstitial();
217 }
218
219 // Tests clicking "take me back" on the interstitial page when there's no
220 // navigation history. The user should be taken to a safe page (about:blank).
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,TakeMeBackEmptyNavigationHistory)221 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, TakeMeBackEmptyNavigationHistory) {
222 NavigateToPageWithMismatchedCertExpectSSLInterstitial();
223
224 // Click "Take me back".
225 SendInterstitialNavigationCommandAndWait(false /*proceed*/,
226 GURL("about:blank"));
227 }
228
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,Reload)229 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, Reload) {
230 NavigateToOkPage();
231 NavigateToPageWithMismatchedCertExpectSSLInterstitial();
232
233 SendInterstitialReloadCommandAndWait();
234
235 // TODO(blundell): Ideally we would fix the SSL error, reload, and verify
236 // that the SSL interstitial isn't showing. However, currently this doesn't
237 // work: Calling ResetSSLConfig() on |http_server_mismatched_| passing
238 // CERT_OK does not cause future reloads or navigations to
239 // mismatched_cert_url() to succeed; they still fail and pop an interstitial.
240 // I verified that the LoadCompletionObserver is in fact waiting for a new
241 // load, i.e., there is actually a *new* SSL interstitial popped up. From
242 // looking at the ResetSSLConfig() impl there shouldn't be any waiting or
243 // anything needed within the client.
244 }
245
246 // Tests clicking proceed link on the interstitial page. This is a PRE_ test
247 // because it also acts as setup for the test below which verifies the behavior
248 // across restarts.
249 // TODO(crbug.com/654704): Android does not support PRE_ tests. For Android just
250 // run only the PRE_ version of this test.
251 #if defined(OS_ANDROID)
252 #define PRE_Proceed Proceed
253 #endif
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,PRE_Proceed)254 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, PRE_Proceed) {
255 NavigateToOkPage();
256 NavigateToPageWithMismatchedCertExpectSSLInterstitial();
257 SendInterstitialNavigationCommandAndWait(true /*proceed*/);
258
259 // Go back to an OK page, then try to navigate again. The "Proceed" decision
260 // should be saved, so no interstitial is shown this time.
261 NavigateToOkPage();
262 NavigateToPageWithMismatchedCertExpectNotBlocked();
263 }
264
265 #if !defined(OS_ANDROID)
266 // The proceed decision is perpetuated across WebLayer sessions, i.e. WebLayer
267 // will not block again when navigating to the same bad page that was previously
268 // proceeded through.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,Proceed)269 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, Proceed) {
270 NavigateToPageWithMismatchedCertExpectNotBlocked();
271 }
272 #endif
273
274 // Tests navigating away from the interstitial page.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,NavigateAway)275 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, NavigateAway) {
276 NavigateToOkPage();
277 NavigateToPageWithMismatchedCertExpectSSLInterstitial();
278 NavigateToOtherOkPage();
279 }
280
281 // Tests the scenario where the OS reports that an SSL error is due to a
282 // captive portal. A captive portal interstitial should be displayed. The test
283 // then switches OS captive portal status to false and reloads the page. This
284 // time, a normal SSL interstitial should be displayed.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,OSReportsCaptivePortal)285 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, OSReportsCaptivePortal) {
286 SSLErrorHandler::SetOSReportsCaptivePortalForTesting(true);
287
288 NavigateToPageWithMismatchedCertExpectCaptivePortalInterstitial();
289
290 // Check that clearing the test setting causes behavior to revert to normal.
291 SSLErrorHandler::SetOSReportsCaptivePortalForTesting(false);
292 NavigateToPageWithMismatchedCertExpectSSLInterstitial();
293 }
294
295 #if defined(OS_ANDROID)
296 // Tests that after reaching a captive portal interstitial, clicking on the
297 // connect link will cause a navigation to the login page.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,CaptivePortalConnectToLoginPage)298 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, CaptivePortalConnectToLoginPage) {
299 SSLErrorHandler::SetOSReportsCaptivePortalForTesting(true);
300
301 NavigateToPageWithMismatchedCertExpectCaptivePortalInterstitial();
302
303 SendInterstitialOpenLoginCommandAndWait();
304 }
305 #endif
306
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,BadClockInterstitial)307 IN_PROC_BROWSER_TEST_F(SSLBrowserTest, BadClockInterstitial) {
308 // Without the NetworkTimeTracker reporting that the clock is ahead or
309 // behind, navigating to a page with an expired cert should result in the
310 // default SSL interstitial appearing.
311 NavigateToPageWithExpiredCertExpectSSLInterstitial();
312
313 // Set network time back ten minutes.
314 BrowserProcess::GetInstance()->GetNetworkTimeTracker()->UpdateNetworkTime(
315 base::Time::Now() - base::TimeDelta::FromMinutes(10),
316 base::TimeDelta::FromMilliseconds(1), /* resolution */
317 base::TimeDelta::FromMilliseconds(500), /* latency */
318 base::TimeTicks::Now() /* posting time of this update */);
319
320 // Now navigating to a page with an expired cert should cause the bad clock
321 // interstitial to appear.
322 NavigateToPageWithExpiredCertExpectBadClockInterstitial();
323 }
324
325 } // namespace weblayer
326