| 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);
|
| + }
|
| +
|
| + 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.
|
|
|