Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6686)

Unified Diff: chrome/browser/ssl/ssl_browser_tests.cc

Issue 2620203003: Add initial version of captive portal list checking. (Closed)
Patch Set: Fix Android tests Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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.

Powered by Google App Engine
This is Rietveld 408576698