summaryrefslogtreecommitdiff
path: root/chromium/weblayer/browser/ssl_browsertest.cc
blob: 063beb9c31003eb6abd602f50e67fe66a6868bb8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "weblayer/test/weblayer_browser_test.h"

#include "base/files/file_path.h"
#include "base/scoped_observation.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "components/network_time/network_time_tracker.h"
#include "components/security_interstitials/content/insecure_form_blocking_page.h"
#include "components/security_interstitials/content/ssl_error_assistant.h"
#include "components/security_interstitials/content/ssl_error_handler.h"
#include "net/ssl/ssl_info.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "weblayer/browser/browser_process.h"
#include "weblayer/browser/weblayer_security_blocking_page_factory.h"
#include "weblayer/public/browser.h"
#include "weblayer/public/browser_observer.h"
#include "weblayer/public/error_page.h"
#include "weblayer/public/error_page_delegate.h"
#include "weblayer/public/tab.h"
#include "weblayer/shell/browser/shell.h"
#include "weblayer/test/interstitial_utils.h"
#include "weblayer/test/load_completion_observer.h"
#include "weblayer/test/test_navigation_observer.h"
#include "weblayer/test/weblayer_browser_test_utils.h"

namespace weblayer {
namespace {

#if defined(OS_ANDROID)
// Waits for a new tab to be created, and then load |url|.
class NewTabWaiter : public BrowserObserver {
 public:
  NewTabWaiter(Browser* browser, const GURL& url) : url_(url) {
    observation_.Observe(browser);
  }

  void OnTabAdded(Tab* tab) override {
    navigation_observer_ = std::make_unique<TestNavigationObserver>(
        url_, TestNavigationObserver::NavigationEvent::kStart, tab);
    run_loop_.Quit();
  }

  void Wait() {
    if (!navigation_observer_)
      run_loop_.Run();
    navigation_observer_->Wait();
  }

 private:
  GURL url_;
  std::unique_ptr<TestNavigationObserver> navigation_observer_;
  base::RunLoop run_loop_;
  base::ScopedObservation<Browser, BrowserObserver> observation_{this};
};
#endif

class TestErrorPageDelegate : public ErrorPageDelegate {
 public:
  bool was_get_error_page_content_called() const {
    return was_get_error_page_content_called_;
  }

  // ErrorPageDelegate:
  bool OnBackToSafety() override { return false; }
  std::unique_ptr<ErrorPage> GetErrorPageContent(
      Navigation* navigation) override {
    was_get_error_page_content_called_ = true;
    return std::make_unique<ErrorPage>();
  }

 private:
  bool was_get_error_page_content_called_ = false;
};

}  // namespace

class SSLBrowserTest : public WebLayerBrowserTest {
 public:
  SSLBrowserTest() = default;

  SSLBrowserTest(const SSLBrowserTest&) = delete;
  SSLBrowserTest& operator=(const SSLBrowserTest&) = delete;

  ~SSLBrowserTest() override = default;

  // WebLayerBrowserTest:
  void SetUpOnMainThread() override {
    https_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    https_server_->AddDefaultHandlers(
        base::FilePath(FILE_PATH_LITERAL("weblayer/test/data")));

    https_server_mismatched_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    https_server_mismatched_->SetSSLConfig(
        net::EmbeddedTestServer::CERT_MISMATCHED_NAME);
    https_server_mismatched_->AddDefaultHandlers(
        base::FilePath(FILE_PATH_LITERAL("weblayer/test/data")));

    https_server_expired_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    https_server_expired_->SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
    https_server_expired_->AddDefaultHandlers(
        base::FilePath(FILE_PATH_LITERAL("weblayer/test/data")));

    ASSERT_TRUE(https_server_->Start());
    ASSERT_TRUE(https_server_mismatched_->Start());
    ASSERT_TRUE(https_server_expired_->Start());
  }

  void PostRunTestOnMainThread() override {
    https_server_.reset();
    https_server_mismatched_.reset();
    WebLayerBrowserTest::PostRunTestOnMainThread();
  }

  void NavigateToOkPage() {
    ASSERT_EQ("127.0.0.1", ok_url().host());
    NavigateAndWaitForCompletion(ok_url(), shell());
    EXPECT_FALSE(IsShowingSecurityInterstitial(shell()->tab()));
  }

  void NavigateToPageWithMismatchedCertExpectSSLInterstitial() {
    // Do a navigation that should result in an SSL error.
    NavigateAndWaitForFailure(mismatched_cert_url(), shell());
    // First check that there *is* an interstitial.
    ASSERT_TRUE(IsShowingSecurityInterstitial(shell()->tab()));

    // Now verify that the interstitial is in fact an SSL interstitial.
    EXPECT_TRUE(IsShowingSSLInterstitial(shell()->tab()));

    // TODO(blundell): Check the security state once security state is available
    // via the public WebLayer API, following the example of //chrome's
    // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
  }

  void NavigateToPageWithMismatchedCertExpectCaptivePortalInterstitial() {
    // Do a navigation that should result in an SSL error.
    NavigateAndWaitForFailure(mismatched_cert_url(), shell());
    // First check that there *is* an interstitial.
    ASSERT_TRUE(IsShowingSecurityInterstitial(shell()->tab()));

    // Now verify that the interstitial is in fact a captive portal
    // interstitial.
    EXPECT_TRUE(IsShowingCaptivePortalInterstitial(shell()->tab()));

    // TODO(blundell): Check the security state once security state is available
    // via the public WebLayer API, following the example of //chrome's
    // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
  }

  void NavigateToPageWithExpiredCertExpectSSLInterstitial() {
    // Do a navigation that should result in an SSL error.
    NavigateAndWaitForFailure(expired_cert_url(), shell());
    // First check that there *is* an interstitial.
    ASSERT_TRUE(IsShowingSecurityInterstitial(shell()->tab()));

    // Now verify that the interstitial is in fact an SSL interstitial.
    EXPECT_TRUE(IsShowingSSLInterstitial(shell()->tab()));

    // TODO(blundell): Check the security state once security state is available
    // via the public WebLayer API, following the example of //chrome's
    // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
  }

  void NavigateToPageWithExpiredCertExpectBadClockInterstitial() {
    // Do a navigation that should result in an SSL error.
    NavigateAndWaitForFailure(expired_cert_url(), shell());
    // First check that there *is* an interstitial.
    ASSERT_TRUE(IsShowingSecurityInterstitial(shell()->tab()));

    // Now verify that the interstitial is in fact a bad clock interstitial.
    EXPECT_TRUE(IsShowingBadClockInterstitial(shell()->tab()));

    // TODO(blundell): Check the security state once security state is available
    // via the public WebLayer API, following the example of //chrome's
    // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
  }

  void NavigateToPageWithMismatchedCertExpectNotBlocked() {
    NavigateAndWaitForCompletion(mismatched_cert_url(), shell());
    EXPECT_FALSE(IsShowingSecurityInterstitial(shell()->tab()));

    // TODO(blundell): Check the security state once security state is available
    // via the public WebLayer API, following the example of //chrome's
    // ssl_browsertest.cc's CheckAuthenticationBrokenState() function.
  }

  void SendInterstitialNavigationCommandAndWait(
      bool proceed,
      absl::optional<GURL> previous_url = absl::nullopt) {
    GURL expected_url =
        proceed ? mismatched_cert_url() : previous_url.value_or(ok_url());
    ASSERT_TRUE(IsShowingSSLInterstitial(shell()->tab()));

    TestNavigationObserver navigation_observer(
        expected_url, TestNavigationObserver::NavigationEvent::kCompletion,
        shell());
    ExecuteScript(shell(),
                  "window.certificateErrorPageController." +
                      std::string(proceed ? "proceed" : "dontProceed") + "();",
                  false /*use_separate_isolate*/);
    navigation_observer.Wait();
    EXPECT_FALSE(IsShowingSSLInterstitial(shell()->tab()));
  }

  void SendInterstitialReloadCommandAndWait() {
    ASSERT_TRUE(IsShowingSSLInterstitial(shell()->tab()));

    LoadCompletionObserver load_observer(shell());
    ExecuteScript(shell(), "window.certificateErrorPageController.reload();",
                  false /*use_separate_isolate*/);
    load_observer.Wait();

    // Should still be showing the SSL interstitial after the reload command is
    // processed.
    EXPECT_TRUE(IsShowingSSLInterstitial(shell()->tab()));
  }

#if defined(OS_ANDROID)
  void SendInterstitialOpenLoginCommandAndWait() {
    ASSERT_TRUE(IsShowingCaptivePortalInterstitial(shell()->tab()));

    // Note: The embedded test server cannot actually load the captive portal
    // login URL, so simply detect the start of the navigation to the page.
    NewTabWaiter waiter(shell()->browser(),
                        WebLayerSecurityBlockingPageFactory::
                            GetCaptivePortalLoginPageUrlForTesting());
    ExecuteScript(shell(), "window.certificateErrorPageController.openLogin();",
                  false /*use_separate_isolate*/);
    waiter.Wait();
  }
#endif

  void NavigateToOtherOkPage() {
    NavigateAndWaitForCompletion(https_server_->GetURL("/simple_page2.html"),
                                 shell());
    EXPECT_FALSE(IsShowingSecurityInterstitial(shell()->tab()));
  }

  GURL ok_url() { return https_server_->GetURL("/simple_page.html"); }
  GURL mismatched_cert_url() {
    return https_server_mismatched_->GetURL("/simple_page.html");
  }

  GURL expired_cert_url() {
    return https_server_expired_->GetURL("/simple_page.html");
  }

 protected:
  std::unique_ptr<net::EmbeddedTestServer> https_server_;
  std::unique_ptr<net::EmbeddedTestServer> https_server_mismatched_;
  std::unique_ptr<net::EmbeddedTestServer> https_server_expired_;
};

// Tests clicking "take me back" on the interstitial page.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest, TakeMeBack) {
  NavigateToOkPage();
  NavigateToPageWithMismatchedCertExpectSSLInterstitial();

  // Click "Take me back".
  SendInterstitialNavigationCommandAndWait(false /*proceed*/);

  // Check that it's possible to navigate to a new page.
  NavigateToOtherOkPage();

  // Navigate to the bad SSL page again, an interstitial shows again (in
  // contrast to what would happen had the user chosen to proceed).
  NavigateToPageWithMismatchedCertExpectSSLInterstitial();
}

// Tests clicking "take me back" on the interstitial page when there's no
// navigation history. The user should be taken to a safe page (about:blank).
IN_PROC_BROWSER_TEST_F(SSLBrowserTest, TakeMeBackEmptyNavigationHistory) {
  NavigateToPageWithMismatchedCertExpectSSLInterstitial();

  // Click "Take me back".
  SendInterstitialNavigationCommandAndWait(false /*proceed*/,
                                           GURL("about:blank"));
}

IN_PROC_BROWSER_TEST_F(SSLBrowserTest, Reload) {
  NavigateToOkPage();
  NavigateToPageWithMismatchedCertExpectSSLInterstitial();

  SendInterstitialReloadCommandAndWait();

  // TODO(blundell): Ideally we would fix the SSL error, reload, and verify
  // that the SSL interstitial isn't showing. However, currently this doesn't
  // work: Calling ResetSSLConfig() on |http_server_mismatched_| passing
  // CERT_OK does not cause future reloads or navigations to
  // mismatched_cert_url() to succeed; they still fail and pop an interstitial.
  // I verified that the LoadCompletionObserver is in fact waiting for a new
  // load, i.e., there is actually a *new* SSL interstitial popped up. From
  // looking at the ResetSSLConfig() impl there shouldn't be any waiting or
  // anything needed within the client.
}

// Tests clicking proceed link on the interstitial page. This is a PRE_ test
// because it also acts as setup for the test below which verifies the behavior
// across restarts.
// TODO(crbug.com/654704): Android does not support PRE_ tests. For Android just
// run only the PRE_ version of this test.
#if defined(OS_ANDROID)
#define PRE_Proceed Proceed
#endif
IN_PROC_BROWSER_TEST_F(SSLBrowserTest, PRE_Proceed) {
  NavigateToOkPage();
  NavigateToPageWithMismatchedCertExpectSSLInterstitial();
  SendInterstitialNavigationCommandAndWait(true /*proceed*/);

  // Go back to an OK page, then try to navigate again. The "Proceed" decision
  // should be saved, so no interstitial is shown this time.
  NavigateToOkPage();
  NavigateToPageWithMismatchedCertExpectNotBlocked();
}

#if !defined(OS_ANDROID)
// The proceed decision is perpetuated across WebLayer sessions, i.e.  WebLayer
// will not block again when navigating to the same bad page that was previously
// proceeded through.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest, Proceed) {
  NavigateToPageWithMismatchedCertExpectNotBlocked();
}
#endif

// Tests navigating away from the interstitial page.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest, NavigateAway) {
  NavigateToOkPage();
  NavigateToPageWithMismatchedCertExpectSSLInterstitial();
  NavigateToOtherOkPage();
}

// Tests the scenario where the OS reports that an SSL error is due to a
// captive portal. A captive portal interstitial should be displayed. The test
// then switches OS captive portal status to false and reloads the page. This
// time, a normal SSL interstitial should be displayed.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest, OSReportsCaptivePortal) {
  SSLErrorHandler::SetOSReportsCaptivePortalForTesting(true);

  NavigateToPageWithMismatchedCertExpectCaptivePortalInterstitial();

  // Check that clearing the test setting causes behavior to revert to normal.
  SSLErrorHandler::SetOSReportsCaptivePortalForTesting(false);
  NavigateToPageWithMismatchedCertExpectSSLInterstitial();
}

#if defined(OS_ANDROID)
// Tests that after reaching a captive portal interstitial, clicking on the
// connect link will cause a navigation to the login page.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest, CaptivePortalConnectToLoginPage) {
  SSLErrorHandler::SetOSReportsCaptivePortalForTesting(true);

  NavigateToPageWithMismatchedCertExpectCaptivePortalInterstitial();

  SendInterstitialOpenLoginCommandAndWait();
}
#endif

IN_PROC_BROWSER_TEST_F(SSLBrowserTest, BadClockInterstitial) {
  // Without the NetworkTimeTracker reporting that the clock is ahead or
  // behind, navigating to a page with an expired cert should result in the
  // default SSL interstitial appearing.
  NavigateToPageWithExpiredCertExpectSSLInterstitial();

  // Set network time back ten minutes.
  BrowserProcess::GetInstance()->GetNetworkTimeTracker()->UpdateNetworkTime(
      base::Time::Now() - base::Minutes(10),
      base::Milliseconds(1),   /* resolution */
      base::Milliseconds(500), /* latency */
      base::TimeTicks::Now() /* posting time of this update */);

  // Now navigating to a page with an expired cert should cause the bad clock
  // interstitial to appear.
  NavigateToPageWithExpiredCertExpectBadClockInterstitial();
}

// This test verifies that a certificate in the list of known captive portal
// certificates in ssl_error_assistant.asciipb is detected as such. This serves
// to verify that the ssl_error_assistant proto was correctly loaded.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,
                       CertificateInKnownCaptivePortalsListDetected) {
  net::SSLInfo ssl_info_with_known_captive_portal_cert;
  net::HashValue captive_portal_public_key;

  // Set up the SSSLInfo with the certificate of captive-portal.badssl.com
  // (taken from ssl_error_assistant.asciipb).
  ASSERT_TRUE(captive_portal_public_key.FromString(
      "sha256/fjZPHewEHTrMDX3I1ecEIeoy3WFxHyGplOLv28kIbtI="));
  net::HashValueVector public_keys;
  public_keys.push_back(captive_portal_public_key);
  ssl_info_with_known_captive_portal_cert.public_key_hashes = public_keys;

  EXPECT_TRUE(SSLErrorAssistant().IsKnownCaptivePortalCertificate(
      ssl_info_with_known_captive_portal_cert));
}

// Verifies an error page is not requested for an ssl error.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest, ErrorPageNotCalledForMismatch) {
  TestErrorPageDelegate error_page_delegate;
  shell()->tab()->SetErrorPageDelegate(&error_page_delegate);
  NavigateToOkPage();
  EXPECT_FALSE(error_page_delegate.was_get_error_page_content_called());
  NavigateToPageWithMismatchedCertExpectSSLInterstitial();
  EXPECT_FALSE(error_page_delegate.was_get_error_page_content_called());
}

// Visits a page that displays an insecure form, submits the form, and checks an
// interstitial is shown.
IN_PROC_BROWSER_TEST_F(SSLBrowserTest,
                       TestDisplaysInsecureFormSubmissionWarning) {
  GURL insecure_form_url = https_server_->GetURL("/insecure_form.html");
  GURL form_target_url = GURL("http://does-not-exist.test/form_target.html?");
  NavigateAndWaitForCompletion(insecure_form_url, shell());

  // Submit the form and wait for the interstitial to load.
  TestNavigationObserver navigation_observer(
      form_target_url, TestNavigationObserver::NavigationEvent::kFailure,
      shell());
  ExecuteScript(shell(), "submitForm();", false /*use_separate_isolate*/);
  navigation_observer.Wait();

  // Check the correct interstitial loaded.
  EXPECT_TRUE(IsShowingInsecureFormInterstitial(shell()->tab()));
}

}  // namespace weblayer