summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/captive_portal/captive_portal_browsertest.cc39
-rw-r--r--chrome/browser/ssl/common_name_mismatch_handler.cc104
-rw-r--r--chrome/browser/ssl/common_name_mismatch_handler.h90
-rw-r--r--chrome/browser/ssl/ssl_browser_tests.cc362
-rw-r--r--chrome/browser/ssl/ssl_error_classification.cc43
-rw-r--r--chrome/browser/ssl/ssl_error_classification.h22
-rw-r--r--chrome/browser/ssl/ssl_error_handler.cc123
-rw-r--r--chrome/browser/ssl/ssl_error_handler.h18
-rw-r--r--chrome/browser/ssl/ssl_error_handler_unittest.cc142
-rw-r--r--chrome/chrome_browser.gypi2
10 files changed, 869 insertions, 76 deletions
diff --git a/chrome/browser/captive_portal/captive_portal_browsertest.cc b/chrome/browser/captive_portal/captive_portal_browsertest.cc
index dd999a2..bbe6452 100644
--- a/chrome/browser/captive_portal/captive_portal_browsertest.cc
+++ b/chrome/browser/captive_portal/captive_portal_browsertest.cc
@@ -54,7 +54,10 @@
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test_utils.h"
#include "net/base/net_errors.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/x509_certificate.h"
#include "net/http/transport_security_state.h"
+#include "net/test/cert_test_util.h"
#include "net/test/url_request/url_request_failed_job.h"
#include "net/test/url_request/url_request_mock_http_job.h"
#include "net/url_request/url_request.h"
@@ -144,7 +147,8 @@ class URLRequestTimeoutOnDemandJob : public net::URLRequestJob,
// Fails all active URLRequestTimeoutOnDemandJobs with SSL cert errors.
// |expected_num_jobs| behaves just as in FailJobs.
- static void FailJobsWithCertError(int expected_num_jobs);
+ static void FailJobsWithCertError(int expected_num_jobs,
+ const net::SSLInfo& ssl_info);
// Abandon all active URLRequestTimeoutOnDemandJobs. |expected_num_jobs|
// behaves just as in FailJobs.
@@ -169,9 +173,9 @@ class URLRequestTimeoutOnDemandJob : public net::URLRequestJob,
bool RemoveFromList();
static void WaitForJobsOnIOThread(int num_jobs);
- static void FailOrAbandonJobsOnIOThread(
- int expected_num_jobs,
- EndJobOperation end_job_operation);
+ static void FailOrAbandonJobsOnIOThread(int expected_num_jobs,
+ EndJobOperation end_job_operation,
+ const net::SSLInfo& ssl_info);
// Checks if there are at least |num_jobs_to_wait_for_| jobs in
// |job_list_|. If so, exits the message loop on the UI thread, which
@@ -239,18 +243,17 @@ void URLRequestTimeoutOnDemandJob::FailJobs(int expected_num_jobs) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread,
- expected_num_jobs,
- FAIL_JOBS));
+ expected_num_jobs, FAIL_JOBS, net::SSLInfo()));
}
// static
void URLRequestTimeoutOnDemandJob::FailJobsWithCertError(
- int expected_num_jobs) {
+ int expected_num_jobs,
+ const net::SSLInfo& ssl_info) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread,
- expected_num_jobs,
- FAIL_JOBS_WITH_CERT_ERROR));
+ expected_num_jobs, FAIL_JOBS_WITH_CERT_ERROR, ssl_info));
}
// static
@@ -258,8 +261,7 @@ void URLRequestTimeoutOnDemandJob::AbandonJobs(int expected_num_jobs) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread,
- expected_num_jobs,
- ABANDON_JOBS));
+ expected_num_jobs, ABANDON_JOBS, net::SSLInfo()));
}
URLRequestTimeoutOnDemandJob::URLRequestTimeoutOnDemandJob(
@@ -324,7 +326,8 @@ void URLRequestTimeoutOnDemandJob::MaybeStopWaitingForJobsOnIOThread() {
// static
void URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread(
int expected_num_jobs,
- EndJobOperation end_job_operation) {
+ EndJobOperation end_job_operation,
+ const net::SSLInfo& ssl_info) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
ASSERT_LT(0, expected_num_jobs);
EXPECT_EQ(last_num_jobs_to_wait_for_, expected_num_jobs);
@@ -343,11 +346,7 @@ void URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread(
net::ERR_CONNECTION_TIMED_OUT));
} else if (end_job_operation == FAIL_JOBS_WITH_CERT_ERROR) {
DCHECK(job->request()->url().SchemeIsCryptographic());
- net::SSLInfo info;
- info.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
- info.cert = new net::X509Certificate(
- "bad.host", "CA", base::Time::Max(), base::Time::Max());
- job->NotifySSLCertificateError(info, true);
+ job->NotifySSLCertificateError(ssl_info, true);
}
}
@@ -2863,7 +2862,11 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest,
CaptivePortalObserver portal_observer(browser()->profile());
MultiNavigationObserver navigation_observer;
- URLRequestTimeoutOnDemandJob::FailJobsWithCertError(1);
+ net::SSLInfo info;
+ info.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
+ info.cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
+ URLRequestTimeoutOnDemandJob::FailJobsWithCertError(1, info);
navigation_observer.WaitForNavigations(1);
EXPECT_EQ(CaptivePortalTabReloader::STATE_NEEDS_RELOAD,
diff --git a/chrome/browser/ssl/common_name_mismatch_handler.cc b/chrome/browser/ssl/common_name_mismatch_handler.cc
new file mode 100644
index 0000000..941127b
--- /dev/null
+++ b/chrome/browser/ssl/common_name_mismatch_handler.cc
@@ -0,0 +1,104 @@
+// Copyright 2015 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 "chrome/browser/ssl/common_name_mismatch_handler.h"
+
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/browser/ssl/ssl_error_classification.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request_status.h"
+
+CommonNameMismatchHandler::CommonNameMismatchHandler(
+ const GURL& request_url,
+ const scoped_refptr<net::URLRequestContextGetter>& request_context)
+ : request_url_(request_url), request_context_(request_context) {}
+
+CommonNameMismatchHandler::~CommonNameMismatchHandler() {}
+
+// static
+CommonNameMismatchHandler::TestingState
+ CommonNameMismatchHandler::testing_state_ = NOT_TESTING;
+
+void CommonNameMismatchHandler::CheckSuggestedUrl(
+ const GURL& url,
+ const CheckUrlCallback& callback) {
+ // Should be used only in tests.
+ if (testing_state_ == IGNORE_REQUESTS_FOR_TESTING)
+ return;
+
+ DCHECK(CalledOnValidThread());
+ DCHECK(!IsCheckingSuggestedUrl());
+ DCHECK(check_url_callback_.is_null());
+
+ check_url_callback_ = callback;
+
+ url_fetcher_ = net::URLFetcher::Create(url, net::URLFetcher::HEAD, this);
+ url_fetcher_->SetAutomaticallyRetryOn5xx(false);
+ url_fetcher_->SetRequestContext(request_context_.get());
+
+ // Can't safely use net::LOAD_DISABLE_CERT_REVOCATION_CHECKING here,
+ // since then the connection may be reused without checking the cert.
+ url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SEND_AUTH_DATA);
+ url_fetcher_->Start();
+}
+
+// static
+bool CommonNameMismatchHandler::GetSuggestedUrl(
+ const GURL& request_url,
+ const std::vector<std::string>& dns_names,
+ GURL* suggested_url) {
+ std::string host_name = request_url.host();
+ std::string www_mismatch_hostname;
+ if (!SSLErrorClassification::GetWWWSubDomainMatch(host_name, dns_names,
+ &www_mismatch_hostname)) {
+ return false;
+ }
+ // The full URL should be pinged, not just the new hostname. So, get the
+ // |suggested_url| with the |request_url|'s hostname replaced with
+ // new hostname. Keep resource path, query params the same.
+ GURL::Replacements replacements;
+ replacements.SetHostStr(www_mismatch_hostname);
+ *suggested_url = request_url.ReplaceComponents(replacements);
+ return true;
+}
+
+void CommonNameMismatchHandler::Cancel() {
+ url_fetcher_.reset();
+ check_url_callback_.Reset();
+}
+
+void CommonNameMismatchHandler::OnURLFetchComplete(
+ const net::URLFetcher* source) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(IsCheckingSuggestedUrl());
+ DCHECK_EQ(url_fetcher_.get(), source);
+ DCHECK(!check_url_callback_.is_null());
+ DCHECK(!url_fetcher_.get()->GetStatus().is_io_pending());
+
+ SuggestedUrlCheckResult result = SUGGESTED_URL_NOT_AVAILABLE;
+ // Save a copy of |suggested_url| so it can be used after |url_fetcher_|
+ // is destroyed.
+ const GURL suggested_url = url_fetcher_->GetOriginalURL();
+ const GURL& landing_url = url_fetcher_->GetURL();
+
+ // Make sure the |landing_url| is a HTTPS page and returns a proper response
+ // code.
+ if (url_fetcher_.get()->GetResponseCode() == 200 &&
+ landing_url.SchemeIsCryptographic() &&
+ landing_url.host() != request_url_.host()) {
+ result = SUGGESTED_URL_AVAILABLE;
+ }
+ url_fetcher_.reset();
+ base::ResetAndReturn(&check_url_callback_).Run(result, suggested_url);
+}
+
+bool CommonNameMismatchHandler::IsCheckingSuggestedUrl() const {
+ return url_fetcher_;
+}
diff --git a/chrome/browser/ssl/common_name_mismatch_handler.h b/chrome/browser/ssl/common_name_mismatch_handler.h
new file mode 100644
index 0000000..5b8067f
--- /dev/null
+++ b/chrome/browser/ssl/common_name_mismatch_handler.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2015 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.
+
+#ifndef CHROME_BROWSER_SSL_COMMON_NAME_MISMATCH_HANDLER_H_
+#define CHROME_BROWSER_SSL_COMMON_NAME_MISMATCH_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
+
+namespace net {
+class URLFetcher;
+}
+
+// This class handles errors due to common name mismatches
+// (|ERR_CERT_COMMON_NAME_INVALID|) and helps remediate them by suggesting
+// alternative URLs that may be what the user intended to load.
+class CommonNameMismatchHandler : public net::URLFetcherDelegate,
+ public base::NonThreadSafe {
+ public:
+ enum SuggestedUrlCheckResult {
+ // The request succeeds with good response code i.e. URL exists and its
+ // certificate is valid.
+ SUGGESTED_URL_AVAILABLE,
+ // Suggested URL is either not available or has a bad certificate.
+ SUGGESTED_URL_NOT_AVAILABLE
+ };
+
+ enum TestingState {
+ NOT_TESTING,
+ // Disables the actual request to the |suggested_url|.
+ IGNORE_REQUESTS_FOR_TESTING
+ };
+
+ typedef base::Callback<void(const SuggestedUrlCheckResult& result,
+ const GURL& suggested_url)> CheckUrlCallback;
+
+ CommonNameMismatchHandler(
+ const GURL& request_url,
+ const scoped_refptr<net::URLRequestContextGetter>& request_context);
+ ~CommonNameMismatchHandler() override;
+
+ // Performs a network request to suggested URL. After completion, runs the
+ // |callback|.
+ void CheckSuggestedUrl(const GURL& url, const CheckUrlCallback& callback);
+
+ // Determines if, for |request_url| serving a certificate that is valid for
+ // the domain names |dns_names|, there is a name that the certificate is
+ // valid for that closely matches the original name in |request_url|. If
+ // so, returns true, and sets |*suggested_url| to a URL that is unlikely
+ // to cause an ERR_CERT_COMMON_NAME_INVALID error.
+ static bool GetSuggestedUrl(const GURL& request_url,
+ const std::vector<std::string>& dns_names,
+ GURL* suggested_url);
+
+ // Used in tests, to disable the request to |suggested_url|.
+ // If |testing_state| is IGNORE_REQUESTS_FOR_TESTING, then the
+ // callback won't get called.
+ static void set_state_for_testing(TestingState testing_state) {
+ testing_state_ = testing_state;
+ }
+
+ // Cancels the request to |suggested_url|
+ void Cancel();
+
+ private:
+ // net::URLFetcherDelegate:
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ // Returns true if the check is currently running.
+ bool IsCheckingSuggestedUrl() const;
+
+ static TestingState testing_state_;
+ const GURL request_url_;
+ scoped_refptr<net::URLRequestContextGetter> request_context_;
+ CheckUrlCallback check_url_callback_;
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(CommonNameMismatchHandler);
+};
+
+#endif // CHROME_BROWSER_SSL_COMMON_NAME_MISMATCH_HANDLER_H_
diff --git a/chrome/browser/ssl/ssl_browser_tests.cc b/chrome/browser/ssl/ssl_browser_tests.cc
index 66f94a7..1320c49 100644
--- a/chrome/browser/ssl/ssl_browser_tests.cc
+++ b/chrome/browser/ssl/ssl_browser_tests.cc
@@ -28,7 +28,9 @@
#include "chrome/browser/ssl/certificate_error_report.h"
#include "chrome/browser/ssl/certificate_reporting_test_utils.h"
#include "chrome/browser/ssl/chrome_ssl_host_state_delegate.h"
+#include "chrome/browser/ssl/common_name_mismatch_handler.h"
#include "chrome/browser/ssl/ssl_blocking_page.h"
+#include "chrome/browser/ssl/ssl_error_handler.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_navigator.h"
@@ -57,6 +59,7 @@
#include "content/public/common/ssl_status.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
+#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_renderer_host.h"
#include "net/base/host_port_pair.h"
#include "net/base/net_errors.h"
@@ -64,8 +67,11 @@
#include "net/cert/cert_status_flags.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/cert/x509_certificate.h"
+#include "net/dns/mock_host_resolver.h"
#include "net/ssl/ssl_info.h"
+#include "net/test/cert_test_util.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/test/test_certificate_data.h"
#include "net/url_request/url_request_context.h"
#if defined(USE_NSS_CERTS)
@@ -199,6 +205,38 @@ void CheckSecurityState(WebContents* tab,
AuthState::Check(*entry, expected_authentication_state);
}
+// This observer waits for the SSLErrorHandler to start an interstitial timer
+// for the given web contents.
+class SSLInterstitialTimerObserver {
+ public:
+ explicit SSLInterstitialTimerObserver(content::WebContents* web_contents)
+ : web_contents_(web_contents), message_loop_runner_(new base::RunLoop) {
+ callback_ = base::Bind(&SSLInterstitialTimerObserver::OnTimerStarted,
+ base::Unretained(this));
+ SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(&callback_);
+ }
+
+ ~SSLInterstitialTimerObserver() {
+ SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(nullptr);
+ }
+
+ // Waits until the interstitial delay timer in SSLErrorHandler is started.
+ void WaitForTimerStarted() { message_loop_runner_->Run(); }
+
+ private:
+ void OnTimerStarted(content::WebContents* web_contents) {
+ if (web_contents_ == web_contents)
+ message_loop_runner_->Quit();
+ }
+
+ const content::WebContents* web_contents_;
+ SSLErrorHandler::TimerStartedCallback callback_;
+
+ scoped_ptr<base::RunLoop> message_loop_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLInterstitialTimerObserver);
+};
+
} // namespace
class SSLUITest
@@ -2267,6 +2305,330 @@ IN_PROC_BROWSER_TEST_F(SSLUITest, BadCertFollowedByGoodCert) {
EXPECT_FALSE(state->HasAllowException(https_server_host));
}
+using CommonNameMismatchBrowserTest = CertVerifierBrowserTest;
+
+// Visit the URL www.mail.example.com on a server that presents a valid
+// certificate for mail.example.com. Verify that the page navigates to
+// mail.example.com.
+IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
+ ShouldShowWWWSubdomainMismatchInterstitial) {
+ net::SpawnedTestServer https_server_example_domain_(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::SSLOptions(
+ net::SpawnedTestServer::SSLOptions::CERT_OK),
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(https_server_example_domain_.Start());
+
+ host_resolver()->AddRule(
+ "mail.example.com", https_server_example_domain_.host_port_pair().host());
+ host_resolver()->AddRule(
+ "www.mail.example.com",
+ https_server_example_domain_.host_port_pair().host());
+
+ scoped_refptr<net::X509Certificate> cert =
+ https_server_example_domain_.GetCertificate();
+
+ // Use the "spdy_pooling.pem" cert which has "mail.example.com"
+ // as one of its SANs.
+ net::CertVerifyResult verify_result;
+ verify_result.verified_cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
+ verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
+
+ // Request to "www.mail.example.com" should result in
+ // |net::ERR_CERT_COMMON_NAME_INVALID| error.
+ mock_cert_verifier()->AddResultForCertAndHost(
+ cert.get(), "www.mail.example.com", verify_result,
+ net::ERR_CERT_COMMON_NAME_INVALID);
+
+ net::CertVerifyResult verify_result_valid;
+ verify_result_valid.verified_cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
+ // Request to "www.mail.example.com" should not result in any error.
+ mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "mail.example.com",
+ verify_result_valid, net::OK);
+
+ // Use a complex URL to ensure the path, etc., are preserved. The path itself
+ // does not matter.
+ GURL https_server_url =
+ https_server_example_domain_.GetURL("files/ssl/google.html?a=b#anchor");
+ GURL::Replacements replacements;
+ replacements.SetHostStr("www.mail.example.com");
+ GURL https_server_mismatched_url =
+ https_server_url.ReplaceComponents(replacements);
+
+ WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
+ content::TestNavigationObserver observer(contents, 2);
+ ui_test_utils::NavigateToURL(browser(), https_server_mismatched_url);
+ observer.Wait();
+
+ CheckSecurityState(contents, CertError::NONE,
+ content::SECURITY_STYLE_AUTHENTICATED, AuthState::NONE);
+ replacements.SetHostStr("mail.example.com");
+ GURL https_server_new_url = https_server_url.ReplaceComponents(replacements);
+ // Verify that the current URL is the suggested URL.
+ EXPECT_EQ(https_server_new_url.spec(),
+ contents->GetLastCommittedURL().spec());
+}
+
+// Visit the URL example.org on a server that presents a valid certificate
+// for www.example.org. Verify that the page redirects to www.example.org.
+IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
+ CheckWWWSubdomainMismatchInverse) {
+ net::SpawnedTestServer https_server_example_domain_(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::SSLOptions(
+ net::SpawnedTestServer::SSLOptions::CERT_OK),
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(https_server_example_domain_.Start());
+
+ host_resolver()->AddRule(
+ "www.example.org", https_server_example_domain_.host_port_pair().host());
+ host_resolver()->AddRule(
+ "example.org", https_server_example_domain_.host_port_pair().host());
+
+ scoped_refptr<net::X509Certificate> cert =
+ https_server_example_domain_.GetCertificate();
+
+ net::CertVerifyResult verify_result;
+ verify_result.verified_cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
+ verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
+
+ mock_cert_verifier()->AddResultForCertAndHost(
+ cert.get(), "example.org", verify_result,
+ net::ERR_CERT_COMMON_NAME_INVALID);
+
+ net::CertVerifyResult verify_result_valid;
+ verify_result_valid.verified_cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
+ mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "www.example.org",
+ verify_result_valid, net::OK);
+
+ GURL https_server_url =
+ https_server_example_domain_.GetURL("files/ssl/google.html?a=b");
+ GURL::Replacements replacements;
+ replacements.SetHostStr("example.org");
+ GURL https_server_mismatched_url =
+ https_server_url.ReplaceComponents(replacements);
+
+ WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
+ content::TestNavigationObserver observer(contents, 2);
+ ui_test_utils::NavigateToURL(browser(), https_server_mismatched_url);
+ observer.Wait();
+
+ CheckSecurityState(contents, CertError::NONE,
+ content::SECURITY_STYLE_AUTHENTICATED, AuthState::NONE);
+}
+
+// Tests this scenario:
+// - |CommonNameMismatchHandler| does not give a callback as it's set into the
+// state |IGNORE_REQUESTS_FOR_TESTING|. So no suggested URL check result can
+// arrive.
+// - A cert error triggers an interstitial timer with a very long timeout.
+// - No suggested URL check results arrive, causing the tab to appear as loading
+// indefinitely (also because the timer has a long timeout).
+// - Stopping the page load shouldn't result in any interstitials.
+IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
+ InterstitialStopNavigationWhileLoading) {
+ net::SpawnedTestServer https_server_example_domain_(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::SSLOptions(
+ net::SpawnedTestServer::SSLOptions::CERT_OK),
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(https_server_example_domain_.Start());
+
+ host_resolver()->AddRule(
+ "mail.example.com", https_server_example_domain_.host_port_pair().host());
+ host_resolver()->AddRule(
+ "www.mail.example.com",
+ https_server_example_domain_.host_port_pair().host());
+
+ scoped_refptr<net::X509Certificate> cert =
+ https_server_example_domain_.GetCertificate();
+
+ net::CertVerifyResult verify_result;
+ verify_result.verified_cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
+ verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
+
+ mock_cert_verifier()->AddResultForCertAndHost(
+ cert.get(), "www.mail.example.com", verify_result,
+ net::ERR_CERT_COMMON_NAME_INVALID);
+
+ net::CertVerifyResult verify_result_valid;
+ verify_result_valid.verified_cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
+ mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "mail.example.com",
+ verify_result_valid, net::OK);
+
+ GURL https_server_url =
+ https_server_example_domain_.GetURL("files/ssl/google.html?a=b");
+ GURL::Replacements replacements;
+ replacements.SetHostStr("www.mail.example.com");
+ GURL https_server_mismatched_url =
+ https_server_url.ReplaceComponents(replacements);
+
+ WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
+ CommonNameMismatchHandler::set_state_for_testing(
+ CommonNameMismatchHandler::IGNORE_REQUESTS_FOR_TESTING);
+ SSLErrorHandler::SetInterstitialDelayTypeForTest(SSLErrorHandler::LONG);
+ SSLInterstitialTimerObserver interstitial_timer_observer(contents);
+
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(), https_server_mismatched_url, CURRENT_TAB,
+ ui_test_utils::BROWSER_TEST_NONE);
+ interstitial_timer_observer.WaitForTimerStarted();
+
+ EXPECT_TRUE(contents->IsLoading());
+ content::WindowedNotificationObserver observer(
+ content::NOTIFICATION_LOAD_STOP,
+ content::NotificationService::AllSources());
+ contents->Stop();
+ observer.Wait();
+
+ SSLErrorHandler* ssl_error_handler =
+ SSLErrorHandler::FromWebContents(contents);
+ // Make sure that the |SSLErrorHandler| is deleted.
+ EXPECT_FALSE(ssl_error_handler);
+ EXPECT_FALSE(contents->ShowingInterstitialPage());
+ EXPECT_FALSE(contents->IsLoading());
+}
+
+// Same as above, but instead of stopping, the loading page is reloaded. The end
+// result is the same. (i.e. page load stops, no interstitials shown)
+IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
+ InterstitialReloadNavigationWhileLoading) {
+ net::SpawnedTestServer https_server_example_domain_(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::SSLOptions(
+ net::SpawnedTestServer::SSLOptions::CERT_OK),
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(https_server_example_domain_.Start());
+
+ host_resolver()->AddRule(
+ "mail.example.com", https_server_example_domain_.host_port_pair().host());
+ host_resolver()->AddRule(
+ "www.mail.example.com",
+ https_server_example_domain_.host_port_pair().host());
+
+ scoped_refptr<net::X509Certificate> cert =
+ https_server_example_domain_.GetCertificate();
+
+ net::CertVerifyResult verify_result;
+ verify_result.verified_cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
+ verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
+
+ mock_cert_verifier()->AddResultForCertAndHost(
+ cert.get(), "www.mail.example.com", verify_result,
+ net::ERR_CERT_COMMON_NAME_INVALID);
+
+ net::CertVerifyResult verify_result_valid;
+ verify_result_valid.verified_cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
+ mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "mail.example.com",
+ verify_result_valid, net::OK);
+
+ GURL https_server_url =
+ https_server_example_domain_.GetURL("files/ssl/google.html?a=b");
+ GURL::Replacements replacements;
+ replacements.SetHostStr("www.mail.example.com");
+ GURL https_server_mismatched_url =
+ https_server_url.ReplaceComponents(replacements);
+
+ WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
+ CommonNameMismatchHandler::set_state_for_testing(
+ CommonNameMismatchHandler::IGNORE_REQUESTS_FOR_TESTING);
+ SSLErrorHandler::SetInterstitialDelayTypeForTest(SSLErrorHandler::LONG);
+ SSLInterstitialTimerObserver interstitial_timer_observer(contents);
+
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(), https_server_mismatched_url, CURRENT_TAB,
+ ui_test_utils::BROWSER_TEST_NONE);
+ interstitial_timer_observer.WaitForTimerStarted();
+
+ EXPECT_TRUE(contents->IsLoading());
+ content::TestNavigationObserver observer(contents, 1);
+ chrome::Reload(browser(), CURRENT_TAB);
+ observer.Wait();
+
+ SSLErrorHandler* ssl_error_handler =
+ SSLErrorHandler::FromWebContents(contents);
+ // Make sure that the |SSLErrorHandler| is deleted.
+ EXPECT_FALSE(ssl_error_handler);
+ EXPECT_FALSE(contents->ShowingInterstitialPage());
+ EXPECT_FALSE(contents->IsLoading());
+}
+
+// Same as above, but instead of reloading, the page is navigated away. The
+// new page should load, and no interstitials should be shown.
+IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
+ InterstitialNavigateAwayWhileLoading) {
+ net::SpawnedTestServer https_server_example_domain_(
+ net::SpawnedTestServer::TYPE_HTTPS,
+ net::SpawnedTestServer::SSLOptions(
+ net::SpawnedTestServer::SSLOptions::CERT_OK),
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(https_server_example_domain_.Start());
+
+ host_resolver()->AddRule(
+ "mail.example.com", https_server_example_domain_.host_port_pair().host());
+ host_resolver()->AddRule(
+ "www.mail.example.com",
+ https_server_example_domain_.host_port_pair().host());
+
+ scoped_refptr<net::X509Certificate> cert =
+ https_server_example_domain_.GetCertificate();
+
+ net::CertVerifyResult verify_result;
+ verify_result.verified_cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
+ verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
+
+ mock_cert_verifier()->AddResultForCertAndHost(
+ cert.get(), "www.mail.example.com", verify_result,
+ net::ERR_CERT_COMMON_NAME_INVALID);
+
+ net::CertVerifyResult verify_result_valid;
+ verify_result_valid.verified_cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
+ mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "mail.example.com",
+ verify_result_valid, net::OK);
+
+ GURL https_server_url =
+ https_server_example_domain_.GetURL("files/ssl/google.html?a=b");
+ GURL::Replacements replacements;
+ replacements.SetHostStr("www.mail.example.com");
+ GURL https_server_mismatched_url =
+ https_server_url.ReplaceComponents(replacements);
+
+ WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
+ CommonNameMismatchHandler::set_state_for_testing(
+ CommonNameMismatchHandler::IGNORE_REQUESTS_FOR_TESTING);
+ SSLErrorHandler::SetInterstitialDelayTypeForTest(SSLErrorHandler::LONG);
+ SSLInterstitialTimerObserver interstitial_timer_observer(contents);
+
+ ui_test_utils::NavigateToURLWithDisposition(
+ browser(), https_server_mismatched_url, CURRENT_TAB,
+ ui_test_utils::BROWSER_TEST_NONE);
+ interstitial_timer_observer.WaitForTimerStarted();
+
+ EXPECT_TRUE(contents->IsLoading());
+ content::TestNavigationObserver observer(contents, 1);
+ browser()->OpenURL(content::OpenURLParams(GURL("https://google.com"),
+ content::Referrer(), CURRENT_TAB,
+ ui::PAGE_TRANSITION_TYPED, false));
+ observer.Wait();
+
+ SSLErrorHandler* ssl_error_handler =
+ SSLErrorHandler::FromWebContents(contents);
+ // Make sure that the |SSLErrorHandler| is deleted.
+ EXPECT_FALSE(ssl_error_handler);
+ EXPECT_FALSE(contents->ShowingInterstitialPage());
+ EXPECT_FALSE(contents->IsLoading());
+}
+
class SSLBlockingPageIDNTest : public SecurityInterstitialIDNTest {
protected:
// SecurityInterstitialIDNTest implementation
diff --git a/chrome/browser/ssl/ssl_error_classification.cc b/chrome/browser/ssl/ssl_error_classification.cc
index 305b2bb..dc953fa 100644
--- a/chrome/browser/ssl/ssl_error_classification.cc
+++ b/chrome/browser/ssl/ssl_error_classification.cc
@@ -324,33 +324,44 @@ Tokenize(const std::string& name) {
}
// We accept the inverse case for www for historical reasons.
-bool SSLErrorClassification::IsWWWSubDomainMatch() const {
- std::string host_name = request_url_.host();
+bool SSLErrorClassification::GetWWWSubDomainMatch(
+ const std::string& host_name,
+ const std::vector<std::string>& dns_names,
+ std::string* www_match_host_name) {
if (IsHostNameKnownTLD(host_name)) {
- std::vector<std::string> dns_names;
- cert_.GetDNSNames(&dns_names);
- bool result = false;
// Need to account for all possible domains given in the SSL certificate.
for (size_t i = 0; i < dns_names.size(); ++i) {
- if (dns_names[i].empty() || dns_names[i].find('\0') != std::string::npos
- || dns_names[i].length() == host_name.length()
- || !(IsHostNameKnownTLD(dns_names[i]))) {
- result = result || false;
+ if (dns_names[i].empty() ||
+ dns_names[i].find('\0') != std::string::npos ||
+ dns_names[i].length() == host_name.length() ||
+ !IsHostNameKnownTLD(dns_names[i])) {
+ continue;
} else if (dns_names[i].length() > host_name.length()) {
- result = result ||
- net::StripWWW(base::ASCIIToUTF16(dns_names[i])) ==
- base::ASCIIToUTF16(host_name);
+ if (net::StripWWW(base::ASCIIToUTF16(dns_names[i])) ==
+ base::ASCIIToUTF16(host_name)) {
+ *www_match_host_name = dns_names[i];
+ return true;
+ }
} else {
- result = result ||
- net::StripWWW(base::ASCIIToUTF16(host_name)) ==
- base::ASCIIToUTF16(dns_names[i]);
+ if (net::StripWWW(base::ASCIIToUTF16(host_name)) ==
+ base::ASCIIToUTF16(dns_names[i])) {
+ *www_match_host_name = dns_names[i];
+ return true;
+ }
}
}
- return result;
}
return false;
}
+bool SSLErrorClassification::IsWWWSubDomainMatch() const {
+ const std::string& host_name = request_url_.host();
+ std::vector<std::string> dns_names;
+ cert_.GetDNSNames(&dns_names);
+ std::string www_host;
+ return GetWWWSubDomainMatch(host_name, dns_names, &www_host);
+}
+
bool SSLErrorClassification::NameUnderAnyNames(
const Tokens& child,
const std::vector<Tokens>& potential_parents) const {
diff --git a/chrome/browser/ssl/ssl_error_classification.h b/chrome/browser/ssl/ssl_error_classification.h
index 89869b9..7d377dc 100644
--- a/chrome/browser/ssl/ssl_error_classification.h
+++ b/chrome/browser/ssl/ssl_error_classification.h
@@ -50,6 +50,19 @@ class SSLErrorClassification : public content::NotificationObserver {
// 3.|hostname| is a dotless domain.
static bool IsHostnameNonUniqueOrDotless(const std::string& hostname);
+ // Returns true if the site's hostname differs from one of the DNS
+ // names in the certificate (CN or SANs) only by the presence or
+ // absence of the single-label prefix "www". E.g.: (The first domain
+ // is hostname and the second domain is a DNS name in the certificate)
+ //
+ // www.example.com ~ example.com -> true
+ // example.com ~ www.example.com -> true
+ // www.food.example.com ~ example.com -> false
+ // mail.example.com ~ example.com -> false
+ static bool GetWWWSubDomainMatch(const std::string& host_name,
+ const std::vector<std::string>& dns_names,
+ std::string* www_match_host_name);
+
// A function which calculates the severity score when the ssl error is
// |CERT_DATE_INVALID|. The calculated score is between 0.0 and 1.0, higher
// being more severe, indicating how severe the certificate's
@@ -83,14 +96,7 @@ class SSLErrorClassification : public content::NotificationObserver {
// Returns true if the hostname has a known Top Level Domain.
static bool IsHostNameKnownTLD(const std::string& host_name);
- // Returns true if the site's hostname differs from one of the DNS
- // names in the certificate (CN or SANs) only by the presence or
- // absence of the single-label prefix "www". E.g.:
- //
- // www.example.com ~ example.com -> true
- // example.com ~ www.example.com -> true
- // www.food.example.com ~ example.com -> false
- // mail.example.com ~ example.com -> false
+ // Returns true if GetWWWSubDomainMatch finds a www mismatch.
bool IsWWWSubDomainMatch() const;
// Returns true if |child| is a subdomain of any of the |potential_parents|.
diff --git a/chrome/browser/ssl/ssl_error_handler.cc b/chrome/browser/ssl/ssl_error_handler.cc
index 594a683..60cd2ae 100644
--- a/chrome/browser/ssl/ssl_error_handler.cc
+++ b/chrome/browser/ssl/ssl_error_handler.cc
@@ -11,6 +11,7 @@
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ssl/ssl_blocking_page.h"
#include "chrome/browser/ssl/ssl_cert_reporter.h"
+#include "chrome/browser/ssl/ssl_error_classification.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/web_contents.h"
@@ -39,6 +40,8 @@ enum SSLErrorHandlerEvent {
SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE,
SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE,
SHOW_SSL_INTERSTITIAL_OVERRIDABLE,
+ SHOW_COMMON_NAME_MISMATCH_INTERSTITIAL_NONOVERRIDABLE,
+ SHOW_COMMON_NAME_MISMATCH_INTERSTITIAL_OVERRIDABLE,
SSL_ERROR_HANDLER_EVENT_COUNT
};
@@ -48,10 +51,10 @@ void RecordUMA(SSLErrorHandlerEvent event) {
SSL_ERROR_HANDLER_EVENT_COUNT);
}
-#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
// The delay before displaying the SSL interstitial for cert errors.
-// - If a "captive portal detected" result arrives in this many seconds,
-// a captive portal interstitial is displayed.
+// - If a "captive portal detected" or "suggested URL valid" result
+// arrives in this many seconds, then a captive portal interstitial
+// or a common name mismatch interstitial is displayed.
// - Otherwise, an SSL interstitial is displayed.
const int kDefaultInterstitialDisplayDelayInSeconds = 2;
@@ -74,6 +77,7 @@ base::TimeDelta GetInterstitialDisplayDelay(
return base::TimeDelta();
}
+#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
bool IsCaptivePortalInterstitialEnabled() {
return base::FieldTrialList::FindFullName("CaptivePortalInterstitial") ==
"Enabled";
@@ -92,13 +96,6 @@ void SSLErrorHandler::HandleSSLError(
int options_mask,
scoped_ptr<SSLCertReporter> ssl_cert_reporter,
const base::Callback<void(bool)>& callback) {
-#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
- CaptivePortalTabHelper* captive_portal_tab_helper =
- CaptivePortalTabHelper::FromWebContents(web_contents);
- if (captive_portal_tab_helper) {
- captive_portal_tab_helper->OnSSLCertError(ssl_info);
- }
-#endif
DCHECK(!FromWebContents(web_contents));
web_contents->SetUserData(
UserDataKey(),
@@ -137,15 +134,8 @@ SSLErrorHandler::SSLErrorHandler(content::WebContents* web_contents,
request_url_(request_url),
options_mask_(options_mask),
callback_(callback),
- ssl_cert_reporter_(ssl_cert_reporter.Pass()) {
-#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
- Profile* profile = Profile::FromBrowserContext(
- web_contents->GetBrowserContext());
- registrar_.Add(this,
- chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
- content::Source<Profile>(profile));
-#endif
-}
+ profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
+ ssl_cert_reporter_(ssl_cert_reporter.Pass()) {}
SSLErrorHandler::~SSLErrorHandler() {
}
@@ -153,7 +143,46 @@ SSLErrorHandler::~SSLErrorHandler() {
void SSLErrorHandler::StartHandlingError() {
RecordUMA(HANDLE_ALL);
+ std::vector<std::string> dns_names;
+ ssl_info_.cert->GetDNSNames(&dns_names);
+ DCHECK(!dns_names.empty());
+ GURL suggested_url;
+ if (ssl_info_.cert_status == net::CERT_STATUS_COMMON_NAME_INVALID &&
+ GetSuggestedUrl(dns_names, &suggested_url)) {
+ net::CertStatus extra_cert_errors =
+ ssl_info_.cert_status ^ net::CERT_STATUS_COMMON_NAME_INVALID;
+
+ // Show the SSL intersitial if |CERT_STATUS_COMMON_NAME_INVALID| is not
+ // the only error. Need not check for captive portal in this case.
+ // (See the comment below).
+ if (net::IsCertStatusError(extra_cert_errors) &&
+ !net::IsCertStatusMinorError(ssl_info_.cert_status)) {
+ ShowSSLInterstitial();
+ return;
+ }
+ CheckSuggestedUrl(suggested_url);
+ timer_.Start(FROM_HERE,
+ GetInterstitialDisplayDelay(g_interstitial_delay_type), this,
+ &SSLErrorHandler::OnTimerExpired);
+ if (g_timer_started_callback)
+ g_timer_started_callback->Run(web_contents_);
+
+ // Do not check for a captive portal in this case, because a captive
+ // portal most likely cannot serve a valid certificate which passes the
+ // similarity check.
+ return;
+ }
+
#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
+ CaptivePortalTabHelper* captive_portal_tab_helper =
+ CaptivePortalTabHelper::FromWebContents(web_contents_);
+ if (captive_portal_tab_helper) {
+ captive_portal_tab_helper->OnSSLCertError(ssl_info_);
+ }
+
+ registrar_.Add(this, chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
+ content::Source<Profile>(profile_));
+
if (IsCaptivePortalInterstitialEnabled()) {
CheckForCaptivePortal();
timer_.Start(FROM_HERE,
@@ -172,12 +201,34 @@ void SSLErrorHandler::OnTimerExpired() {
ShowSSLInterstitial();
}
+bool SSLErrorHandler::GetSuggestedUrl(const std::vector<std::string>& dns_names,
+ GURL* suggested_url) const {
+ return CommonNameMismatchHandler::GetSuggestedUrl(request_url_, dns_names,
+ suggested_url);
+}
+
+void SSLErrorHandler::CheckSuggestedUrl(const GURL& suggested_url) {
+ scoped_refptr<net::URLRequestContextGetter> request_context(
+ profile_->GetRequestContext());
+ common_name_mismatch_handler_.reset(
+ new CommonNameMismatchHandler(request_url_, request_context));
+
+ common_name_mismatch_handler_->CheckSuggestedUrl(
+ suggested_url,
+ base::Bind(&SSLErrorHandler::CommonNameMismatchHandlerCallback,
+ base::Unretained(this)));
+}
+
+void SSLErrorHandler::NavigateToSuggestedURL(const GURL& suggested_url) {
+ content::NavigationController::LoadURLParams load_params(suggested_url);
+ load_params.transition_type = ui::PAGE_TRANSITION_TYPED;
+ web_contents()->GetController().LoadURLWithParams(load_params);
+}
+
void SSLErrorHandler::CheckForCaptivePortal() {
#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
- Profile* profile = Profile::FromBrowserContext(
- web_contents_->GetBrowserContext());
CaptivePortalService* captive_portal_service =
- CaptivePortalServiceFactory::GetForProfile(profile);
+ CaptivePortalServiceFactory::GetForProfile(profile_);
captive_portal_service->DetectCaptivePortal();
#else
NOTREACHED();
@@ -187,9 +238,7 @@ void SSLErrorHandler::CheckForCaptivePortal() {
void SSLErrorHandler::ShowCaptivePortalInterstitial(const GURL& landing_url) {
#if defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
// Show captive portal blocking page. The interstitial owns the blocking page.
- const Profile* const profile =
- Profile::FromBrowserContext(web_contents_->GetBrowserContext());
- RecordUMA(SSLBlockingPage::IsOverridable(options_mask_, profile)
+ RecordUMA(SSLBlockingPage::IsOverridable(options_mask_, profile_)
? SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE
: SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE);
(new CaptivePortalBlockingPage(web_contents_, request_url_, landing_url,
@@ -205,14 +254,14 @@ void SSLErrorHandler::ShowCaptivePortalInterstitial(const GURL& landing_url) {
void SSLErrorHandler::ShowSSLInterstitial() {
// Show SSL blocking page. The interstitial owns the blocking page.
- const Profile* const profile =
- Profile::FromBrowserContext(web_contents_->GetBrowserContext());
- RecordUMA(SSLBlockingPage::IsOverridable(options_mask_, profile)
+ RecordUMA(SSLBlockingPage::IsOverridable(options_mask_, profile_)
? SHOW_SSL_INTERSTITIAL_OVERRIDABLE
: SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE);
+
(new SSLBlockingPage(web_contents_, cert_error_, ssl_info_, request_url_,
options_mask_, base::Time::NowFromSystemTime(),
- ssl_cert_reporter_.Pass(), callback_))->Show();
+ ssl_cert_reporter_.Pass(), callback_))
+ ->Show();
// Once an interstitial is displayed, no need to keep the handler around.
// This is the equivalent of "delete this".
web_contents_->RemoveUserData(UserDataKey());
@@ -255,5 +304,21 @@ void SSLErrorHandler::DeleteSSLErrorHandler() {
if (!callback_.is_null()) {
base::ResetAndReturn(&callback_).Run(false);
}
+ if (common_name_mismatch_handler_) {
+ common_name_mismatch_handler_->Cancel();
+ common_name_mismatch_handler_.reset();
+ }
web_contents_->RemoveUserData(UserDataKey());
}
+
+void SSLErrorHandler::CommonNameMismatchHandlerCallback(
+ const CommonNameMismatchHandler::SuggestedUrlCheckResult& result,
+ const GURL& suggested_url) {
+ timer_.Stop();
+ if (result == CommonNameMismatchHandler::SuggestedUrlCheckResult::
+ SUGGESTED_URL_AVAILABLE) {
+ NavigateToSuggestedURL(suggested_url);
+ } else {
+ ShowSSLInterstitial();
+ }
+}
diff --git a/chrome/browser/ssl/ssl_error_handler.h b/chrome/browser/ssl/ssl_error_handler.h
index 601baf0..025eb1b 100644
--- a/chrome/browser/ssl/ssl_error_handler.h
+++ b/chrome/browser/ssl/ssl_error_handler.h
@@ -11,6 +11,8 @@
#include "base/macros.h"
#include "base/timer/timer.h"
#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ssl/common_name_mismatch_handler.h"
#include "chrome/browser/ssl/ssl_cert_reporter.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
@@ -19,6 +21,9 @@
#include "net/ssl/ssl_info.h"
#include "url/gurl.h"
+class Profile;
+class CommonNameMismatchHandler;
+
namespace content {
class RenderViewHost;
class WebContents;
@@ -61,6 +66,12 @@ class SSLErrorHandler : public content::WebContentsUserData<SSLErrorHandler>,
static void SetInterstitialTimerStartedCallbackForTest(
TimerStartedCallback* callback);
+ // Gets the result of whether the suggested URL is valid. Displays
+ // common name mismatch interstitial or ssl interstitial accordingly.
+ void CommonNameMismatchHandlerCallback(
+ const CommonNameMismatchHandler::SuggestedUrlCheckResult& result,
+ const GURL& suggested_url);
+
protected:
SSLErrorHandler(content::WebContents* web_contents,
int cert_error,
@@ -89,6 +100,10 @@ class SSLErrorHandler : public content::WebContentsUserData<SSLErrorHandler>,
virtual void CheckForCaptivePortal();
virtual void ShowCaptivePortalInterstitial(const GURL& landing_url);
virtual void ShowSSLInterstitial();
+ virtual bool GetSuggestedUrl(const std::vector<std::string>& dns_names,
+ GURL* suggested_url) const;
+ virtual void CheckSuggestedUrl(const GURL& suggested_url);
+ virtual void NavigateToSuggestedURL(const GURL& suggested_url);
// content::NotificationObserver:
void Observe(
@@ -114,10 +129,13 @@ class SSLErrorHandler : public content::WebContentsUserData<SSLErrorHandler>,
const GURL request_url_;
const int options_mask_;
base::Callback<void(bool)> callback_;
+ Profile* const profile_;
content::NotificationRegistrar registrar_;
base::OneShotTimer<SSLErrorHandler> timer_;
+ scoped_ptr<CommonNameMismatchHandler> common_name_mismatch_handler_;
+
scoped_ptr<SSLCertReporter> ssl_cert_reporter_;
DISALLOW_COPY_AND_ASSIGN(SSLErrorHandler);
diff --git a/chrome/browser/ssl/ssl_error_handler_unittest.cc b/chrome/browser/ssl/ssl_error_handler_unittest.cc
index e93908e..10d21e4 100644
--- a/chrome/browser/ssl/ssl_error_handler_unittest.cc
+++ b/chrome/browser/ssl/ssl_error_handler_unittest.cc
@@ -11,12 +11,17 @@
#include "base/time/time.h"
#include "chrome/browser/captive_portal/captive_portal_service.h"
#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ssl/common_name_mismatch_handler.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/captive_portal/captive_portal_testing_utils.h"
#include "content/public/browser/notification_service.h"
#include "net/base/net_errors.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/x509_certificate.h"
#include "net/ssl/ssl_info.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/test_certificate_data.h"
#include "testing/gtest/include/gtest/gtest.h"
class TestSSLErrorHandler : public SSLErrorHandler {
@@ -33,8 +38,11 @@ class TestSSLErrorHandler : public SSLErrorHandler {
base::Callback<void(bool)>()),
profile_(profile),
captive_portal_checked_(false),
+ suggested_url_exists_(false),
+ suggested_url_checked_(false),
ssl_interstitial_shown_(false),
- captive_portal_interstitial_shown_(false) {}
+ captive_portal_interstitial_shown_(false),
+ common_name_mismatch_redirect_(false) {}
~TestSSLErrorHandler() override {
}
@@ -52,6 +60,12 @@ class TestSSLErrorHandler : public SSLErrorHandler {
content::Details<CaptivePortalService::Results>(&results));
}
+ void SendSuggestedUrlCheckResult(
+ const CommonNameMismatchHandler::SuggestedUrlCheckResult& result,
+ const GURL& suggested_url) {
+ CommonNameMismatchHandlerCallback(result, suggested_url);
+ }
+
bool IsTimerRunning() const {
return get_timer().IsRunning();
}
@@ -68,10 +82,31 @@ class TestSSLErrorHandler : public SSLErrorHandler {
return captive_portal_interstitial_shown_;
}
+ void SetSuggestedUrlExists(bool suggested_url_exists) {
+ suggested_url_exists_ = suggested_url_exists;
+ }
+
+ bool GetSuggestedUrl(const std::vector<std::string>& dns_names,
+ GURL* suggested_url) const override {
+ if (!suggested_url_exists_)
+ return false;
+ *suggested_url = GURL("www.example.com");
+ return true;
+ }
+
+ bool suggested_url_checked() const { return suggested_url_checked_; }
+
+ bool common_name_mismatch_redirect() const {
+ return common_name_mismatch_redirect_;
+ }
+
void Reset() {
captive_portal_checked_ = false;
+ suggested_url_exists_ = false;
+ suggested_url_checked_ = false;
ssl_interstitial_shown_ = false;
captive_portal_interstitial_shown_ = false;
+ common_name_mismatch_redirect_ = false;
}
private:
@@ -79,18 +114,27 @@ class TestSSLErrorHandler : public SSLErrorHandler {
captive_portal_checked_ = true;
}
- void ShowSSLInterstitial() override {
- ssl_interstitial_shown_ = true;
- }
+ void ShowSSLInterstitial() override { ssl_interstitial_shown_ = true; }
void ShowCaptivePortalInterstitial(const GURL& landing_url) override {
captive_portal_interstitial_shown_ = true;
}
+ void CheckSuggestedUrl(const GURL& suggested_url) override {
+ suggested_url_checked_ = true;
+ }
+
+ void NavigateToSuggestedURL(const GURL& suggested_url) override {
+ common_name_mismatch_redirect_ = true;
+ }
+
Profile* profile_;
bool captive_portal_checked_;
+ bool suggested_url_exists_;
+ bool suggested_url_checked_;
bool ssl_interstitial_shown_;
bool captive_portal_interstitial_shown_;
+ bool common_name_mismatch_redirect_;
DISALLOW_COPY_AND_ASSIGN(TestSSLErrorHandler);
};
@@ -104,13 +148,16 @@ class SSLErrorHandlerTest : public ChromeRenderViewHostTestHarness {
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
SSLErrorHandler::SetInterstitialDelayTypeForTest(SSLErrorHandler::NONE);
+ ssl_info_.cert =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
+ ssl_info_.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
error_handler_.reset(new TestSSLErrorHandler(profile(),
web_contents(),
ssl_info_));
// Enable finch experiment for captive portal interstitials.
ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
"CaptivePortalInterstitial", "Enabled"));
-}
+ }
void TearDown() override {
EXPECT_FALSE(error_handler()->IsTimerRunning());
@@ -191,11 +238,39 @@ TEST_F(SSLErrorHandlerTest,
EXPECT_FALSE(error_handler()->captive_portal_interstitial_shown());
}
+TEST_F(SSLErrorHandlerTest, ShouldNotCheckSuggestedUrlIfNoSuggestedUrl) {
+ error_handler()->SetSuggestedUrlExists(false);
+ error_handler()->StartHandlingError();
+
+ EXPECT_TRUE(error_handler()->captive_portal_checked());
+ EXPECT_TRUE(error_handler()->IsTimerRunning());
+ EXPECT_FALSE(error_handler()->suggested_url_checked());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(error_handler()->IsTimerRunning());
+ EXPECT_TRUE(error_handler()->ssl_interstitial_shown());
+}
+
+TEST_F(SSLErrorHandlerTest, ShouldNotCheckCaptivePortalIfSuggestedUrlExists) {
+ EXPECT_FALSE(error_handler()->IsTimerRunning());
+ error_handler()->SetSuggestedUrlExists(true);
+ error_handler()->StartHandlingError();
+
+ EXPECT_TRUE(error_handler()->IsTimerRunning());
+ EXPECT_TRUE(error_handler()->suggested_url_checked());
+ EXPECT_FALSE(error_handler()->captive_portal_checked());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(error_handler()->IsTimerRunning());
+ EXPECT_TRUE(error_handler()->ssl_interstitial_shown());
+}
+
#else // #if !defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
TEST_F(SSLErrorHandlerTest,
ShouldShowSSLInterstitialOnCaptivePortalDetectionDisabled) {
EXPECT_FALSE(error_handler()->IsTimerRunning());
+ error_handler()->SetSuggestedUrlExists(false);
error_handler()->StartHandlingError();
EXPECT_FALSE(error_handler()->IsTimerRunning());
EXPECT_FALSE(error_handler()->captive_portal_checked());
@@ -204,3 +279,60 @@ TEST_F(SSLErrorHandlerTest,
}
#endif // defined(ENABLE_CAPTIVE_PORTAL_DETECTION)
+
+TEST_F(SSLErrorHandlerTest,
+ ShouldShowSSLInterstitialOnTimerExpiredWhenSuggestedUrlExists) {
+ error_handler()->SetSuggestedUrlExists(true);
+ error_handler()->StartHandlingError();
+
+ EXPECT_TRUE(error_handler()->IsTimerRunning());
+ EXPECT_TRUE(error_handler()->suggested_url_checked());
+ EXPECT_FALSE(error_handler()->ssl_interstitial_shown());
+ EXPECT_FALSE(error_handler()->common_name_mismatch_redirect());
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(error_handler()->IsTimerRunning());
+ EXPECT_TRUE(error_handler()->ssl_interstitial_shown());
+ EXPECT_FALSE(error_handler()->common_name_mismatch_redirect());
+}
+
+TEST_F(SSLErrorHandlerTest, ShouldRedirectOnSuggestedUrlCheckResult) {
+ error_handler()->SetSuggestedUrlExists(true);
+ error_handler()->StartHandlingError();
+
+ EXPECT_TRUE(error_handler()->IsTimerRunning());
+ EXPECT_TRUE(error_handler()->suggested_url_checked());
+ EXPECT_FALSE(error_handler()->ssl_interstitial_shown());
+ EXPECT_FALSE(error_handler()->common_name_mismatch_redirect());
+ // Fake a valid suggested URL check result.
+ // The URL returned by |SuggestedUrlCheckResult| can be different from
+ // |suggested_url|, if there is a redirect.
+ error_handler()->SendSuggestedUrlCheckResult(
+ CommonNameMismatchHandler::SuggestedUrlCheckResult::
+ SUGGESTED_URL_AVAILABLE,
+ GURL("https://random.example.com"));
+
+ EXPECT_FALSE(error_handler()->IsTimerRunning());
+ EXPECT_FALSE(error_handler()->ssl_interstitial_shown());
+ EXPECT_TRUE(error_handler()->common_name_mismatch_redirect());
+}
+
+TEST_F(SSLErrorHandlerTest, ShouldShowSSLInterstitialOnInvalidUrlCheckResult) {
+ error_handler()->SetSuggestedUrlExists(true);
+ error_handler()->StartHandlingError();
+
+ EXPECT_TRUE(error_handler()->IsTimerRunning());
+ EXPECT_TRUE(error_handler()->suggested_url_checked());
+ EXPECT_FALSE(error_handler()->ssl_interstitial_shown());
+ EXPECT_FALSE(error_handler()->common_name_mismatch_redirect());
+ // Fake an Invalid Suggested URL Check result.
+ error_handler()->SendSuggestedUrlCheckResult(
+ CommonNameMismatchHandler::SuggestedUrlCheckResult::
+ SUGGESTED_URL_NOT_AVAILABLE,
+ GURL());
+
+ EXPECT_FALSE(error_handler()->IsTimerRunning());
+ EXPECT_TRUE(error_handler()->ssl_interstitial_shown());
+ EXPECT_FALSE(error_handler()->common_name_mismatch_redirect());
+}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index f27b353..9e6e9b0 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -2735,6 +2735,8 @@
'browser/ssl/chrome_ssl_host_state_delegate.h',
'browser/ssl/chrome_ssl_host_state_delegate_factory.cc',
'browser/ssl/chrome_ssl_host_state_delegate_factory.h',
+ 'browser/ssl/common_name_mismatch_handler.cc',
+ 'browser/ssl/common_name_mismatch_handler.h',
'browser/ssl/connection_security.cc',
'browser/ssl/connection_security.h',
'browser/ssl/connection_security_android.cc',