diff options
Diffstat (limited to 'chrome/browser/ssl')
-rw-r--r-- | chrome/browser/ssl/common_name_mismatch_handler.cc | 104 | ||||
-rw-r--r-- | chrome/browser/ssl/common_name_mismatch_handler.h | 90 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_browser_tests.cc | 362 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_error_classification.cc | 43 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_error_classification.h | 22 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_error_handler.cc | 123 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_error_handler.h | 18 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_error_handler_unittest.cc | 142 |
8 files changed, 846 insertions, 58 deletions
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()); +} |