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 "base/containers/span.h"
6 #include "base/files/file_path.h"
7 #include "base/files/file_util.h"
8 #include "base/path_service.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/threading/scoped_blocking_call.h"
12 #include "build/build_config.h"
13 #include "chrome/browser/interstitials/security_interstitial_page_test_utils.h"
14 #include "chrome/browser/ssl/cert_verifier_platform_browser_test.h"
15 #include "chrome/browser/ssl/ssl_browsertest_util.h"
16 #include "chrome/common/chrome_paths.h"
17 #include "chrome/test/base/chrome_test_utils.h"
18 #include "components/security_interstitials/core/controller_client.h"
19 #include "content/public/browser/navigation_controller.h"
20 #include "content/public/browser/navigation_entry.h"
21 #include "content/public/browser/network_service_instance.h"
22 #include "content/public/browser/ssl_status.h"
23 #include "content/public/browser/web_contents.h"
24 #include "content/public/test/browser_test.h"
25 #include "content/public/test/content_browser_test_utils.h"
26 #include "net/base/net_errors.h"
27 #include "net/cert/cert_status_flags.h"
28 #include "net/cert/cert_verify_result.h"
29 #include "net/cert/mock_cert_verifier.h"
30 #include "net/cert/x509_certificate.h"
31 #include "net/dns/mock_host_resolver.h"
32 #include "net/test/embedded_test_server/embedded_test_server.h"
33 #include "net/test/test_data_directory.h"
34 
35 #if defined(OS_ANDROID)
36 #include "chrome/test/base/android/android_browser_test.h"
37 #else
38 #include "chrome/test/base/in_process_browser_test.h"
39 #endif
40 
41 namespace AuthState = ssl_test_util::AuthState;
42 namespace CertError = ssl_test_util::CertError;
43 
44 namespace {
45 
46 class CRLSetBrowserTest : public PlatformBrowserTest {
47  public:
CRLSetBrowserTest()48   CRLSetBrowserTest() : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
49   ~CRLSetBrowserTest() override = default;
50 
SetUpOnMainThread()51   void SetUpOnMainThread() override {
52     PlatformBrowserTest::SetUpOnMainThread();
53 
54     host_resolver()->AddRule("*", "127.0.0.1");
55 
56     https_server_.ServeFilesFromSourceDirectory("chrome/test/data/");
57   }
58 
59  protected:
GetActiveWebContents()60   content::WebContents* GetActiveWebContents() {
61     return chrome_test_utils::GetActiveWebContents(this);
62   }
63 
64   net::EmbeddedTestServer https_server_;
65 };
66 
67 const char kHstsTestHostName[] = "hsts-example.test";
68 
69 }  // namespace
70 
IN_PROC_BROWSER_TEST_F(CRLSetBrowserTest,TestCRLSetRevoked)71 IN_PROC_BROWSER_TEST_F(CRLSetBrowserTest, TestCRLSetRevoked) {
72   ASSERT_TRUE(https_server_.Start());
73 
74   std::string crl_set_bytes;
75   {
76     base::ScopedAllowBlockingForTesting allow_blocking;
77     base::ReadFileToString(
78         net::GetTestCertsDirectory().AppendASCII("crlset_by_leaf_spki.raw"),
79         &crl_set_bytes);
80   }
81   base::RunLoop run_loop;
82   content::GetNetworkService()->UpdateCRLSet(
83       base::as_bytes(base::make_span(crl_set_bytes)), run_loop.QuitClosure());
84   run_loop.Run();
85 
86   bool interstitial_expected =
87       ssl_test_util::CertVerifierSupportsCRLSetBlocking();
88 
89   // If an interstitial is expected, NavigateToURL should fail, because the
90   // interstitial will be the committed URL, rather than the navigated URL.
91   EXPECT_EQ(!interstitial_expected,
92             content::NavigateToURL(GetActiveWebContents(),
93                                    https_server_.GetURL("/ssl/google.html")));
94 
95   ASSERT_EQ(interstitial_expected,
96             chrome_browser_interstitials::IsShowingInterstitial(
97                 GetActiveWebContents()));
98   ASSERT_TRUE(WaitForRenderFrameReady(GetActiveWebContents()->GetMainFrame()));
99 
100   if (interstitial_expected) {
101     ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(
102         GetActiveWebContents()));
103 
104     ssl_test_util::CheckAuthenticationBrokenState(
105         GetActiveWebContents(), net::CERT_STATUS_REVOKED,
106         AuthState::SHOWING_INTERSTITIAL);
107   } else {
108     ssl_test_util::CheckAuthenticatedState(GetActiveWebContents(),
109                                            AuthState::NONE);
110   }
111 }
112 
113 // Test that CRLSets configured to block MITM certificates cause the
114 // known interception interstitial.
IN_PROC_BROWSER_TEST_F(CRLSetBrowserTest,TestCRLSetBlockedInterception)115 IN_PROC_BROWSER_TEST_F(CRLSetBrowserTest, TestCRLSetBlockedInterception) {
116   ASSERT_TRUE(https_server_.Start());
117 
118   // Load a CRLSet that marks the root as a blocked MITM.
119   std::string crl_set_bytes;
120   {
121     base::ScopedAllowBlockingForTesting allow_blocking;
122     base::ReadFileToString(net::GetTestCertsDirectory().AppendASCII(
123                                "crlset_blocked_interception_by_root.raw"),
124                            &crl_set_bytes);
125   }
126   base::RunLoop run_loop;
127   content::GetNetworkService()->UpdateCRLSet(
128       base::as_bytes(base::make_span(crl_set_bytes)), run_loop.QuitClosure());
129   run_loop.Run();
130 
131   bool interstitial_expected =
132       ssl_test_util::CertVerifierSupportsCRLSetBlocking();
133 
134   // If an interstitial is expected, NavigateToURL should fail, because the
135   // interstitial will be the committed URL, rather than the navigated URL.
136   EXPECT_EQ(!interstitial_expected,
137             content::NavigateToURL(GetActiveWebContents(),
138                                    https_server_.GetURL("/ssl/google.html")));
139 
140   ASSERT_EQ(interstitial_expected,
141             chrome_browser_interstitials::IsShowingInterstitial(
142                 GetActiveWebContents()));
143   ASSERT_TRUE(WaitForRenderFrameReady(GetActiveWebContents()->GetMainFrame()));
144 
145   if (interstitial_expected) {
146     ASSERT_TRUE(
147         chrome_browser_interstitials::IsShowingBlockedInterceptionInterstitial(
148             GetActiveWebContents()));
149 
150     ssl_test_util::CheckAuthenticationBrokenState(
151         GetActiveWebContents(),
152         net::CERT_STATUS_KNOWN_INTERCEPTION_BLOCKED | net::CERT_STATUS_REVOKED,
153         AuthState::SHOWING_INTERSTITIAL);
154   } else {
155     ssl_test_util::CheckAuthenticatedState(GetActiveWebContents(),
156                                            AuthState::NONE);
157   }
158 }
159 
160 // Test that CRLSets configured to identify known MITM certificates do not
161 // cause an interstitial unless the MITM certificate is blocked.
IN_PROC_BROWSER_TEST_F(CRLSetBrowserTest,TestCRLSetKnownInterception)162 IN_PROC_BROWSER_TEST_F(CRLSetBrowserTest, TestCRLSetKnownInterception) {
163   ASSERT_TRUE(https_server_.Start());
164 
165   // Load a CRLSet that marks the root as a known MITM.
166   std::string crl_set_bytes;
167   {
168     base::ScopedAllowBlockingForTesting allow_blocking;
169     base::ReadFileToString(net::GetTestCertsDirectory().AppendASCII(
170                                "crlset_known_interception_by_root.raw"),
171                            &crl_set_bytes);
172   }
173   base::RunLoop run_loop;
174   content::GetNetworkService()->UpdateCRLSet(
175       base::as_bytes(base::make_span(crl_set_bytes)), run_loop.QuitClosure());
176   run_loop.Run();
177 
178   // Navigate to the page. It should not cause an interstitial, but should
179   // allow for the display of additional information that interception is
180   // happening.
181   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(),
182                                      https_server_.GetURL("/ssl/google.html")));
183   ssl_test_util::CheckAuthenticatedState(GetActiveWebContents(),
184                                          AuthState::NONE);
185 
186   content::NavigationEntry* entry =
187       GetActiveWebContents()->GetController().GetVisibleEntry();
188   ASSERT_TRUE(entry);
189   EXPECT_TRUE(entry->GetSSL().cert_status &
190               net::CERT_STATUS_KNOWN_INTERCEPTION_DETECTED);
191 }
192 
193 // While TestCRLSetBlockedInterception and TestCRLSetKnownInterception use
194 // a real CertVerifier in order to test that a real CRLSet is delivered and
195 // processed, testing HSTS requires with a MockCertVerifier so that the
196 // cert will match the intended hostname, and thus only fail because it's a
197 // blocked MITM certificate. This requires using a CertVerifierBrowserTest,
198 // which is not suitable for the previous tests because it does not test
199 // CRLSets.
200 class CRLSetInterceptionBrowserTest : public CertVerifierPlatformBrowserTest {
201  public:
CRLSetInterceptionBrowserTest()202   CRLSetInterceptionBrowserTest()
203       : CertVerifierPlatformBrowserTest(),
204         https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
205 
SetUpOnMainThread()206   void SetUpOnMainThread() override {
207     CertVerifierPlatformBrowserTest::SetUpOnMainThread();
208     ssl_test_util::SetHSTSForHostName(
209         GetActiveWebContents()->GetBrowserContext(), kHstsTestHostName);
210 
211     host_resolver()->AddRule("*", "127.0.0.1");
212     https_server_.ServeFilesFromSourceDirectory("chrome/test/data/");
213   }
214 
215  protected:
GetActiveWebContents()216   content::WebContents* GetActiveWebContents() {
217     return chrome_test_utils::GetActiveWebContents(this);
218   }
219 
220   net::EmbeddedTestServer https_server_;
221 };
222 
IN_PROC_BROWSER_TEST_F(CRLSetInterceptionBrowserTest,KnownInterceptionWorksOnHSTS)223 IN_PROC_BROWSER_TEST_F(CRLSetInterceptionBrowserTest,
224                        KnownInterceptionWorksOnHSTS) {
225   ASSERT_TRUE(https_server_.Start());
226 
227   net::CertVerifyResult verify_result;
228   verify_result.verified_cert = https_server_.GetCertificate();
229   verify_result.cert_status =
230       net::CERT_STATUS_REVOKED | net::CERT_STATUS_KNOWN_INTERCEPTION_BLOCKED;
231 
232   // Simulate verification returning that the certificate is a blocked
233   // interception certificate.
234   mock_cert_verifier()->AddResultForCertAndHost(
235       https_server_.GetCertificate(), kHstsTestHostName, verify_result,
236       net::ERR_CERT_KNOWN_INTERCEPTION_BLOCKED);
237 
238   GURL::Replacements replacements;
239   replacements.SetHostStr(kHstsTestHostName);
240   GURL url =
241       https_server_.GetURL("/ssl/google.html").ReplaceComponents(replacements);
242 
243   // Expect navigation to fail, because this should always result in an
244   // interstitial.
245   ASSERT_FALSE(content::NavigateToURL(GetActiveWebContents(), url));
246 
247   ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
248       GetActiveWebContents()));
249   ASSERT_TRUE(WaitForRenderFrameReady(GetActiveWebContents()->GetMainFrame()));
250 
251   ssl_test_util::CheckSecurityState(
252       GetActiveWebContents(),
253       net::CERT_STATUS_KNOWN_INTERCEPTION_BLOCKED | net::CERT_STATUS_REVOKED,
254       security_state::DANGEROUS, AuthState::SHOWING_INTERSTITIAL);
255 
256   ASSERT_TRUE(
257       chrome_browser_interstitials::IsShowingBlockedInterceptionInterstitial(
258           GetActiveWebContents()));
259 
260   // Expect there to be a proceed link, even for HSTS.
261   EXPECT_TRUE(chrome_browser_interstitials::InterstitialHasProceedLink(
262       GetActiveWebContents()->GetMainFrame()));
263 }
264