Chromium Code Reviews| Index: chrome/browser/ssl/ssl_browser_tests.cc |
| diff --git a/chrome/browser/ssl/ssl_browser_tests.cc b/chrome/browser/ssl/ssl_browser_tests.cc |
| index 9bbf09a3a2d08454c0f0df78ea52cd223036300d..4af5ff9526f68c9d4791530db31259777831a6a1 100644 |
| --- a/chrome/browser/ssl/ssl_browser_tests.cc |
| +++ b/chrome/browser/ssl/ssl_browser_tests.cc |
| @@ -18,6 +18,7 @@ |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/histogram_tester.h" |
| +#include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/default_clock.h" |
| @@ -38,6 +39,7 @@ |
| #include "chrome/browser/ssl/common_name_mismatch_handler.h" |
| #include "chrome/browser/ssl/security_state_tab_helper.h" |
| #include "chrome/browser/ssl/ssl_blocking_page.h" |
| +#include "chrome/browser/ssl/ssl_error_assistant.pb.h" |
| #include "chrome/browser/ssl/ssl_error_handler.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| @@ -84,9 +86,11 @@ |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "content/public/test/test_utils.h" |
| +#include "crypto/sha2.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| +#include "net/cert/asn1_util.h" |
| #include "net/cert/cert_status_flags.h" |
| #include "net/cert/mock_cert_verifier.h" |
| #include "net/cert/x509_certificate.h" |
| @@ -104,6 +108,10 @@ |
| #include "net/url_request/url_request_job.h" |
| #include "net/url_request/url_request_test_util.h" |
| +#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
| +#include "chrome/browser/ssl/captive_portal_blocking_page.h" |
| +#endif |
| + |
| #if defined(USE_NSS_CERTS) |
| #include "chrome/browser/net/nss_context.h" |
| #include "net/base/crypto_module.h" |
| @@ -226,12 +234,17 @@ class SSLInterstitialTimerObserver { |
| // Waits until the interstitial delay timer in SSLErrorHandler is started. |
| void WaitForTimerStarted() { message_loop_runner_->Run(); } |
| + // Returns true if the interstitial delay timer has been started. |
| + bool timer_started() const { return timer_started_; } |
| + |
| private: |
| void OnTimerStarted(content::WebContents* web_contents) { |
| + timer_started_ = true; |
| if (web_contents_ == web_contents) |
| message_loop_runner_->Quit(); |
| } |
| + bool timer_started_ = false; |
| const content::WebContents* web_contents_; |
| SSLErrorHandler::TimerStartedCallback callback_; |
| @@ -273,6 +286,19 @@ std::string EncodeQuery(const std::string& query) { |
| return std::string(buffer.data(), buffer.length()); |
| } |
| +// Returns the Sha256 hash of the SPKI of |cert|. |
| +net::HashValue GetSPKIHash(net::X509Certificate* cert) { |
| + std::string der_data; |
| + EXPECT_TRUE( |
| + net::X509Certificate::GetDEREncoded(cert->os_cert_handle(), &der_data)); |
| + base::StringPiece der_bytes(der_data); |
| + base::StringPiece spki_bytes; |
| + EXPECT_TRUE(net::asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)); |
| + net::HashValue sha256(net::HASH_VALUE_SHA256); |
| + crypto::SHA256HashString(spki_bytes, sha256.data(), crypto::kSHA256Length); |
| + return sha256; |
| +} |
| + |
| } // namespace |
| class SSLUITest : public InProcessBrowserTest { |
| @@ -308,6 +334,16 @@ class SSLUITest : public InProcessBrowserTest { |
| "https", "localhost", std::move(interceptor)); |
| } |
| + void SetUp() override { |
| + InProcessBrowserTest::SetUp(); |
| + SSLErrorHandler::ResetConfigForTesting(); |
| + } |
| + |
| + void TearDown() override { |
| + SSLErrorHandler::ResetConfigForTesting(); |
| + InProcessBrowserTest::TearDown(); |
| + } |
| + |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Browser will both run and display insecure content. |
| command_line->AppendSwitch(switches::kAllowRunningInsecureContent); |
| @@ -3885,6 +3921,290 @@ IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreLocalhostCertErrors, |
| ASSERT_TRUE(content::ExecuteScript(tab, "window.open()")); |
| } |
| +// Put captive portal related tests under a different namespace for nicer |
| +// pattern matching. |
| +using SSLUICaptivePortalListTest = SSLUITest; |
| + |
| +#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
| + |
| +// Tests that the captive portal certificate list is not used when the feature |
| +// is disabled via Finch. The list is passed to SSLErrorHandler via a proto. |
| +IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListTest, |
| + CaptivePortalCertificateList_Disabled) { |
| + base::test::ScopedFeatureList scoped_feature_list; |
| + // Use InitFromCommandLine instead of InitAndDisableFeature to avoid making |
| + // the feature public in SSLErrorHandler header. |
| + scoped_feature_list.InitFromCommandLine( |
| + std::string() /* enabled */, |
| + "CaptivePortalCertificateList" /* disabled */); |
| + |
| + ASSERT_TRUE(https_server_mismatched_.Start()); |
| + base::HistogramTester histograms; |
| + |
| + // Mark the server's cert as a captive portal cert. |
| + const net::HashValue server_spki_hash = |
| + GetSPKIHash(https_server_mismatched_.GetCertificate().get()); |
| + chrome_browser_ssl::SSLErrorAssistantConfig config_proto; |
| + config_proto.add_captive_portal_cert()->set_sha256_hash( |
| + server_spki_hash.ToString()); |
| + SSLErrorHandler::SetErrorAssistantProtoForTesting(config_proto); |
| + |
| + // Navigate to an unsafe page on the server. A normal SSL interstitial should |
| + // be displayed since CaptivePortalCertificateList feature is disabled. |
| + WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| + SSLInterstitialTimerObserver interstitial_timer_observer(tab); |
| + ui_test_utils::NavigateToURL( |
| + browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html")); |
| + content::WaitForInterstitialAttach(tab); |
| + |
| + InterstitialPage* interstitial_page = tab->GetInterstitialPage(); |
| + ASSERT_EQ(SSLBlockingPage::kTypeForTesting, |
| + interstitial_page->GetDelegateForTesting()->GetTypeForTesting()); |
| + EXPECT_TRUE(interstitial_timer_observer.timer_started()); |
| + |
| + // Check that the histogram for the SSL interstitial was recorded. |
| + histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2); |
| + histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::HANDLE_ALL, 1); |
| + histograms.ExpectBucketCount( |
| + SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1); |
| + histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::CAPTIVE_PORTAL_CERT_FOUND, 0); |
| +} |
| + |
| +// Tests that the captive portal certificate list is used when the feature |
| +// is enabled via Finch. The list is passed to SSLErrorHandler via a proto. |
| +IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListTest, |
| + CaptivePortalCertificateList_Enabled_FromProto) { |
| + base::test::ScopedFeatureList scoped_feature_list; |
| + scoped_feature_list.InitFromCommandLine( |
| + "CaptivePortalCertificateList" /* enabled */, |
| + std::string() /* disabled */); |
| + |
| + ASSERT_TRUE(https_server_mismatched_.Start()); |
| + base::HistogramTester histograms; |
| + |
| + // Mark the server's cert as a captive portal cert. |
| + const net::HashValue server_spki_hash = |
| + GetSPKIHash(https_server_mismatched_.GetCertificate().get()); |
| + chrome_browser_ssl::SSLErrorAssistantConfig config_proto; |
| + config_proto.add_captive_portal_cert()->set_sha256_hash( |
| + server_spki_hash.ToString()); |
| + SSLErrorHandler::SetErrorAssistantProtoForTesting(config_proto); |
| + |
| + // Navigate to an unsafe page on the server. The captive portal interstitial |
| + // should be displayed since CaptivePortalCertificateList feature is enabled. |
| + WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| + SSLInterstitialTimerObserver interstitial_timer_observer(tab); |
| + ui_test_utils::NavigateToURL( |
| + browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html")); |
| + content::WaitForInterstitialAttach(tab); |
| + |
| + InterstitialPage* interstitial_page = tab->GetInterstitialPage(); |
| + ASSERT_EQ(CaptivePortalBlockingPage::kTypeForTesting, |
| + interstitial_page->GetDelegateForTesting()->GetTypeForTesting()); |
| + EXPECT_FALSE(interstitial_timer_observer.timer_started()); |
| + |
| + // Check that the histogram for the captive portal cert was recorded. |
| + histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 3); |
| + histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::HANDLE_ALL, 1); |
| + histograms.ExpectBucketCount( |
| + SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE, 1); |
| + histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::CAPTIVE_PORTAL_CERT_FOUND, 1); |
| +} |
| + |
| +namespace { |
| + |
| +// Test class that mimics a URL request with a certificate whose SPKI hash is in |
| +// ssl_error_assistant.asciipb resource. A better way of testing the SPKI hashes |
| +// inside the resource bundle would be to serve the actual certificate from the |
| +// embedded test server, but the test server can only serve a limited number of |
| +// predefined certificates. |
| +class SSLUICaptivePortalListResourceBundleTest |
| + : public CertVerifierBrowserTest { |
| + public: |
| + SSLUICaptivePortalListResourceBundleTest() |
| + : CertVerifierBrowserTest(), |
| + https_server_(net::EmbeddedTestServer::TYPE_HTTPS), |
| + https_server_mismatched_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| + https_server_.ServeFilesFromSourceDirectory(base::FilePath(kDocRoot)); |
| + |
| + https_server_mismatched_.SetSSLConfig( |
| + net::EmbeddedTestServer::CERT_MISMATCHED_NAME); |
| + https_server_mismatched_.AddDefaultHandlers(base::FilePath(kDocRoot)); |
| + } |
| + |
| + void SetUp() override { |
| + CertVerifierBrowserTest::SetUp(); |
| + SSLErrorHandler::ResetConfigForTesting(); |
| + SetUpCertVerifier(0, net::OK); |
| + } |
| + |
| + void TearDown() override { |
| + SSLErrorHandler::ResetConfigForTesting(); |
| + CertVerifierBrowserTest::TearDown(); |
| + } |
| + |
| + protected: |
| + void SetUpCertVerifier(net::CertStatus cert_status, int net_result) { |
| + scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate()); |
| + net::CertVerifyResult verify_result; |
| + verify_result.is_issued_by_known_root = |
| + (net_result != net::ERR_CERT_AUTHORITY_INVALID); |
| + verify_result.verified_cert = cert; |
| + verify_result.cert_status = cert_status; |
| + |
| + // Set the SPKI hash to captive-portal.badssl.com leaf certificate. This |
| + // doesn't match the actual cert (ok_cert.pem) but is good enough for |
| + // testing. |
| + net::HashValue hash; |
| + ASSERT_TRUE( |
| + hash.FromString("sha256/fjZPHewEHTrMDX3I1ecEIeoy3WFxHyGplOLv28kIbtI=")); |
| + verify_result.public_key_hashes.push_back(hash); |
| + mock_cert_verifier()->AddResultForCert(cert, verify_result, net_result); |
|
estark
2017/02/07 21:07:58
Ohh, I was confused about this, I thought you need
meacer
2017/02/07 21:20:17
I see what you meant. Yeah, serving the same cert
|
| + } |
| + |
| + net::EmbeddedTestServer* https_server() { return &https_server_; } |
| + net::EmbeddedTestServer* https_server_mismatched() { |
| + return &https_server_mismatched_; |
| + } |
| + |
| + private: |
| + net::EmbeddedTestServer https_server_; |
| + net::EmbeddedTestServer https_server_mismatched_; |
| +}; |
| + |
| +} // namespace |
| + |
| +// Same as CaptivePortalCertificateList_Enabled_FromProto, but this time the |
| +// cert's SPKI hash is listed in ssl_error_assistant.asciipb. |
| +IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListResourceBundleTest, |
| + Enabled_FromResource) { |
| + base::test::ScopedFeatureList scoped_feature_list; |
| + scoped_feature_list.InitFromCommandLine( |
| + "CaptivePortalCertificateList" /* enabled */, |
| + std::string() /* disabled */); |
| + ASSERT_TRUE(https_server()->Start()); |
| + base::HistogramTester histograms; |
| + |
| + // Mark the server's cert as a captive portal cert. |
| + SetUpCertVerifier(net::CERT_STATUS_COMMON_NAME_INVALID, |
| + net::ERR_CERT_COMMON_NAME_INVALID); |
| + |
| + // Navigate to an unsafe page on the server. The captive portal interstitial |
| + // should be displayed since CaptivePortalCertificateList feature is enabled. |
| + WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| + SSLInterstitialTimerObserver interstitial_timer_observer(tab); |
| + ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")); |
| + content::WaitForInterstitialAttach(tab); |
| + |
| + InterstitialPage* interstitial_page = tab->GetInterstitialPage(); |
| + ASSERT_EQ(CaptivePortalBlockingPage::kTypeForTesting, |
| + interstitial_page->GetDelegateForTesting()->GetTypeForTesting()); |
| + EXPECT_FALSE(interstitial_timer_observer.timer_started()); |
| + |
| + // Check that the histogram for the captive portal cert was recorded. |
| + histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 3); |
| + histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::HANDLE_ALL, 1); |
| + histograms.ExpectBucketCount( |
| + SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE, 1); |
| + histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::CAPTIVE_PORTAL_CERT_FOUND, 1); |
| +} |
| + |
| +// Same as SSLUICaptivePortalNameMismatchTest, but this time the error is |
| +// authority-invalid. Captive portal interstitial should not be shown. |
| +IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListResourceBundleTest, |
| + Enabled_FromResource_AuthorityInvalid) { |
| + base::test::ScopedFeatureList scoped_feature_list; |
| + scoped_feature_list.InitFromCommandLine( |
| + "CaptivePortalCertificateList" /* enabled */, |
| + std::string() /* disabled */); |
| + ASSERT_TRUE(https_server()->Start()); |
| + base::HistogramTester histograms; |
| + |
| + // Set interstitial delay to zero. |
| + SSLErrorHandler::SetInterstitialDelayForTesting(base::TimeDelta()); |
| + // Mark the server's cert as a captive portal cert, but with an |
| + // authority-invalid error. |
| + SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID, |
| + net::ERR_CERT_AUTHORITY_INVALID); |
| + |
| + // Navigate to an unsafe page on the server. CaptivePortalCertificateList |
| + // feature is enabled but the error is not a name mismatch, so a generic SSL |
| + // interstitial should be displayed. |
| + WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| + SSLInterstitialTimerObserver interstitial_timer_observer(tab); |
| + ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")); |
| + content::WaitForInterstitialAttach(tab); |
| + |
| + InterstitialPage* interstitial_page = tab->GetInterstitialPage(); |
| + ASSERT_EQ(SSLBlockingPage::kTypeForTesting, |
| + interstitial_page->GetDelegateForTesting()->GetTypeForTesting()); |
| + EXPECT_TRUE(interstitial_timer_observer.timer_started()); |
| + |
| + // Check that the histogram for the captive portal cert was recorded. |
| + histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2); |
| + histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::HANDLE_ALL, 1); |
| + histograms.ExpectBucketCount( |
| + SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1); |
| +} |
| + |
| +#else |
| + |
| +// Tests that the captive portal certificate list is not used when captive |
| +// portal checks are disabled by build, even if the captive portal certificate |
| +// list feature is enabled via Finch. The list is passed to SSLErrorHandler via |
| +// a proto. |
| +IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListTest, PortalChecksDisabled) { |
| + base::test::ScopedFeatureList scoped_feature_list; |
| + scoped_feature_list.InitFromCommandLine( |
| + "CaptivePortalCertificateList" /* enabled */, |
| + std::string() /* disabled */); |
| + |
| + ASSERT_TRUE(https_server_mismatched_.Start()); |
| + base::HistogramTester histograms; |
| + |
| + // Mark the server's cert as a captive portal cert. |
| + const net::HashValue server_spki_hash = |
| + GetSPKIHash(https_server_mismatched_.GetCertificate().get()); |
| + chrome_browser_ssl::SSLErrorAssistantConfig config_proto; |
| + config_proto.add_captive_portal_cert()->set_sha256_hash( |
| + server_spki_hash.ToString()); |
| + SSLErrorHandler::SetErrorAssistantProtoForTesting(config_proto); |
| + |
| + // Navigate to an unsafe page on the server. The captive portal interstitial |
| + // should be displayed since CaptivePortalCertificateList feature is enabled. |
| + WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| + SSLInterstitialTimerObserver interstitial_timer_observer(tab); |
| + ui_test_utils::NavigateToURL( |
| + browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html")); |
| + content::WaitForInterstitialAttach(tab); |
| + |
| + InterstitialPage* interstitial_page = tab->GetInterstitialPage(); |
| + ASSERT_EQ(SSLBlockingPage::kTypeForTesting, |
| + interstitial_page->GetDelegateForTesting()->GetTypeForTesting()); |
| + EXPECT_FALSE(interstitial_timer_observer.timer_started()); |
| + |
| + // Check that the histogram for the captive portal cert was recorded. |
| + histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2); |
| + histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::HANDLE_ALL, 1); |
| + histograms.ExpectBucketCount( |
| + SSLErrorHandler::GetHistogramNameForTesting(), |
| + SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1); |
| +} |
| + |
| +#endif // BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) |
| + |
| // TODO(jcampan): more tests to do below. |
| // Visit a page over https that contains a frame with a redirect. |