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