blob: baa384db059c703f269f6f1c52614e933a0fab42 [file] [log] [blame]
// Copyright (c) 2012 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 <memory>
#include <utility>
#include "base/base64.h"
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/metrics/field_trial.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/pattern.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/current_thread.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/interstitials/security_interstitial_idn_test.h"
#include "chrome/browser/interstitials/security_interstitial_page_test_utils.h"
#include "chrome/browser/net/profile_network_context_service.h"
#include "chrome/browser/net/profile_network_context_service_factory.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ssl/cert_verifier_browser_test.h"
#include "chrome/browser/ssl/certificate_reporting_test_utils.h"
#include "chrome/browser/ssl/chrome_security_blocking_page_factory.h"
#include "chrome/browser/ssl/security_state_tab_helper.h"
#include "chrome/browser/ssl/ssl_browsertest_util.h"
#include "chrome/browser/ssl/ssl_error_controller_client.h"
#include "chrome/browser/ssl/tls_deprecation_test_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/web_applications/components/web_application_info.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/safe_browsing_private.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/test_launcher_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/certificate_transparency/pref_names.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/common/content_settings_agent.mojom.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/error_page/content/browser/net_error_auto_reloader.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/network_time/network_time_test_utils.h"
#include "components/network_time/network_time_tracker.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/testing_pref_service.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/safe_browsing/core/features.h"
#include "components/security_interstitials/content/bad_clock_blocking_page.h"
#include "components/security_interstitials/content/captive_portal_blocking_page.h"
#include "components/security_interstitials/content/cert_report_helper.h"
#include "components/security_interstitials/content/common_name_mismatch_handler.h"
#include "components/security_interstitials/content/insecure_form_blocking_page.h"
#include "components/security_interstitials/content/insecure_form_navigation_throttle.h"
#include "components/security_interstitials/content/mitm_software_blocking_page.h"
#include "components/security_interstitials/content/security_interstitial_controller_client.h"
#include "components/security_interstitials/content/security_interstitial_page.h"
#include "components/security_interstitials/content/security_interstitial_tab_helper.h"
#include "components/security_interstitials/content/ssl_blocking_page.h"
#include "components/security_interstitials/content/ssl_error_assistant.h"
#include "components/security_interstitials/content/ssl_error_assistant.pb.h"
#include "components/security_interstitials/content/ssl_error_handler.h"
#include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
#include "components/security_interstitials/core/controller_client.h"
#include "components/security_interstitials/core/features.h"
#include "components/security_interstitials/core/metrics_helper.h"
#include "components/security_interstitials/core/pref_names.h"
#include "components/security_state/core/features.h"
#include "components/security_state/core/security_state.h"
#include "components/ssl_errors/error_classification.h"
#include "components/strings/grit/components_strings.h"
#include "components/variations/variations_associated_data.h"
#include "components/variations/variations_params_manager.h"
#include "components/variations/variations_switches.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/restore_type.h"
#include "content/public/browser/ssl_status.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/network_service_util.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.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 "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "crypto/sha2.h"
#include "extensions/browser/event_router.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "net/base/escape.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/cert/asn1_util.h"
#include "net/cert/cert_database.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/cert/test_root_certs.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/transport_security_state_test_util.h"
#include "net/ssl/client_cert_identity_test_util.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/ssl_config.h"
#include "net/ssl/ssl_info.h"
#include "net/ssl/ssl_server_config.h"
#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
#include "net/test/test_certificate_data.h"
#include "net/test/test_data_directory.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/page_state/page_state.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "ui/base/l10n/l10n_util.h"
#if defined(USE_NSS_CERTS)
#include "chrome/browser/certificate_manager_model.h"
#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/net/nss_context.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user_manager.h"
#include "crypto/scoped_test_nss_db.h"
#include "net/cert/nss_cert_database.h"
#include "net/cert/x509_util_nss.h"
#endif // defined(USE_NSS_CERTS)
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "base/path_service.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_builder.h"
#include "chromeos/constants/chromeos_switches.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/policy_service.h"
#include "components/session_manager/core/session_manager.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if defined(OS_MAC)
#include "base/mac/mac_util.h"
#endif
using content::WebContents;
namespace AuthState = ssl_test_util::AuthState;
namespace CertError = ssl_test_util::CertError;
namespace {
const int kLargeVersionId = 0xFFFFFF;
const char kHstsTestHostName[] = "hsts-example.test";
constexpr char kPreloadedPKPHost[] = "with-report-uri-pkp.preloaded.test";
constexpr char kPreloadedReportHost[] = "report-uri.preloaded.test";
enum ProceedDecision {
SSL_INTERSTITIAL_PROCEED,
SSL_INTERSTITIAL_DO_NOT_PROCEED
};
// This observer waits for the SSLErrorHandler to start an interstitial timer
// for the given web contents.
class SSLInterstitialTimerObserver {
public:
explicit SSLInterstitialTimerObserver(WebContents* web_contents)
: web_contents_(web_contents), message_loop_runner_(new base::RunLoop) {
callback_ = base::BindRepeating(
&SSLInterstitialTimerObserver::OnTimerStarted, base::Unretained(this));
SSLErrorHandler::SetInterstitialTimerStartedCallbackForTesting(&callback_);
}
~SSLInterstitialTimerObserver() {
SSLErrorHandler::SetInterstitialTimerStartedCallbackForTesting(nullptr);
}
// Waits until the interstitial delay timer in SSLErrorHandler is started.
void WaitForTimerStarted() { message_loop_runner_->Run(); }
// Returns true if the interstitial delay timer has been started.
bool timer_started() const { return timer_started_; }
private:
void OnTimerStarted(WebContents* web_contents) {
timer_started_ = true;
if (web_contents_ == web_contents)
message_loop_runner_->Quit();
}
bool timer_started_ = false;
const WebContents* web_contents_;
SSLErrorHandler::TimerStartedCallback callback_;
std::unique_ptr<base::RunLoop> message_loop_runner_;
DISALLOW_COPY_AND_ASSIGN(SSLInterstitialTimerObserver);
};
class ChromeContentBrowserClientForMixedContentTest
: public ChromeContentBrowserClient {
public:
ChromeContentBrowserClientForMixedContentTest() {}
void OverrideWebkitPrefs(
content::RenderViewHost* rvh,
blink::web_pref::WebPreferences* web_prefs) override {
web_prefs->allow_running_insecure_content = allow_running_insecure_content_;
web_prefs->strict_mixed_content_checking = strict_mixed_content_checking_;
web_prefs->strictly_block_blockable_mixed_content =
strictly_block_blockable_mixed_content_;
}
void SetMixedContentSettings(bool allow_running_insecure_content,
bool strict_mixed_content_checking,
bool strictly_block_blockable_mixed_content) {
allow_running_insecure_content_ = allow_running_insecure_content;
strict_mixed_content_checking_ = strict_mixed_content_checking;
strictly_block_blockable_mixed_content_ =
strictly_block_blockable_mixed_content;
}
private:
bool allow_running_insecure_content_ = false;
bool strict_mixed_content_checking_ = false;
bool strictly_block_blockable_mixed_content_ = false;
DISALLOW_COPY_AND_ASSIGN(ChromeContentBrowserClientForMixedContentTest);
};
std::string EncodeQuery(const std::string& query) {
url::RawCanonOutputT<char> buffer;
url::EncodeURIComponent(query.data(), query.size(), &buffer);
return std::string(buffer.data(), buffer.length());
}
// Returns the Sha256 hash of the SPKI of |cert|.
net::HashValue GetSPKIHash(const CRYPTO_BUFFER* cert) {
base::StringPiece spki_bytes;
EXPECT_TRUE(net::asn1::ExtractSPKIFromDERCert(
net::x509_util::CryptoBufferAsStringPiece(cert), &spki_bytes));
net::HashValue sha256(net::HASH_VALUE_SHA256);
crypto::SHA256HashString(spki_bytes, sha256.data(), crypto::kSHA256Length);
return sha256;
}
// Compares two SSLStatuses to check if they match up before and after an
// interstitial. To match up, they should have the same connection information
// properties, such as certificate, connection status, connection security,
// etc. Content status and user data are not compared. Returns true if the
// statuses match and false otherwise.
bool ComparePreAndPostInterstitialSSLStatuses(const content::SSLStatus& one,
const content::SSLStatus& two) {
// TODO(mattm): It feels like this should use
// certificate->EqualsIncludingChain, but that fails on some platforms. Find
// out why and document or fix.
return one.initialized == two.initialized &&
!!one.certificate == !!two.certificate &&
(one.certificate
? one.certificate->EqualsExcludingChain(two.certificate.get())
: true) &&
one.cert_status == two.cert_status &&
one.key_exchange_group == two.key_exchange_group &&
// Skip comparing the peer_signature_algorithm, because it is not
// filled in by the time of an interstitial.
one.connection_status == two.connection_status &&
one.pkp_bypassed == two.pkp_bypassed;
}
// Waits until an interstitial is showing.
//
// TODO(crbug.com/752372): This should not be needed for committed
// interstitials. Replace all call sites directly with the assert.
void WaitForInterstitial(WebContents* tab) {
ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetMainFrame()));
}
void ExpectInterstitialElementHidden(WebContents* tab,
const std::string& element_id,
bool expect_hidden) {
content::RenderFrameHost* frame = tab->GetMainFrame();
// Send CMD_TEXT_FOUND to indicate that the 'hidden' class is found, and
// CMD_TEXT_NOT_FOUND if not.
std::string command = base::StringPrintf(
"window.domAutomationController.send($('%s').classList.contains('hidden')"
" ? %d : %d);",
element_id.c_str(), security_interstitials::CMD_TEXT_FOUND,
security_interstitials::CMD_TEXT_NOT_FOUND);
int result = 0;
EXPECT_TRUE(content::ExecuteScriptAndExtractInt(frame, command, &result));
EXPECT_EQ(expect_hidden ? security_interstitials::CMD_TEXT_FOUND
: security_interstitials::CMD_TEXT_NOT_FOUND,
result);
}
// Runs |quit_callback| on the UI thread once a URL request has been seen.
// If |hung_response| is true, returns a request that hangs.
std::unique_ptr<net::test_server::HttpResponse> WaitForJsonRequest(
const base::RepeatingClosure& quit_closure,
bool hung_response,
const net::test_server::HttpRequest& request) {
// Basic sanity checks on the request.
EXPECT_EQ("/pkp", request.relative_url);
EXPECT_EQ("POST", request.method_string);
base::Optional<base::Value> value = base::JSONReader::Read(request.content);
EXPECT_TRUE(value);
content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, quit_closure);
if (hung_response)
return std::make_unique<net::test_server::HungResponse>();
return nullptr;
}
} // namespace
class SSLUITestBase : public InProcessBrowserTest,
public network::mojom::SSLConfigClient {
public:
SSLUITestBase()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
https_server_expired_(net::EmbeddedTestServer::TYPE_HTTPS),
https_server_mismatched_(net::EmbeddedTestServer::TYPE_HTTPS),
https_server_sha1_(net::EmbeddedTestServer::TYPE_HTTPS),
https_server_common_name_only_(net::EmbeddedTestServer::TYPE_HTTPS),
wss_server_expired_(net::SpawnedTestServer::TYPE_WSS,
SSLOptions(SSLOptions::CERT_EXPIRED),
net::GetWebSocketTestDataDirectory()),
wss_server_mismatched_(net::SpawnedTestServer::TYPE_WSS,
SSLOptions(SSLOptions::CERT_MISMATCHED_NAME),
net::GetWebSocketTestDataDirectory()) {
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
https_server_expired_.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
https_server_expired_.AddDefaultHandlers(GetChromeTestDataDir());
https_server_mismatched_.SetSSLConfig(
net::EmbeddedTestServer::CERT_MISMATCHED_NAME);
https_server_mismatched_.AddDefaultHandlers(GetChromeTestDataDir());
https_server_sha1_.SetSSLConfig(net::EmbeddedTestServer::CERT_SHA1_LEAF);
https_server_sha1_.AddDefaultHandlers(GetChromeTestDataDir());
https_server_common_name_only_.SetSSLConfig(
net::EmbeddedTestServer::CERT_COMMON_NAME_ONLY);
https_server_common_name_only_.AddDefaultHandlers(GetChromeTestDataDir());
}
void SetUp() override {
ON_CALL(policy_provider_, IsInitializationComplete(testing::_))
.WillByDefault(testing::Return(true));
ON_CALL(policy_provider_, IsFirstPolicyLoadComplete(testing::_))
.WillByDefault(testing::Return(true));
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
&policy_provider_);
InProcessBrowserTest::SetUp();
SSLErrorHandler::ResetConfigForTesting();
}
void TearDown() override {
SSLErrorHandler::ResetConfigForTesting();
InProcessBrowserTest::TearDown();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// Browser will both run and display insecure content.
command_line->AppendSwitch(switches::kAllowRunningInsecureContent);
// Use process-per-site so that navigating to a same-site page in a
// new tab will use the same process.
command_line->AppendSwitch(switches::kProcessPerSite);
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
network::mojom::NetworkContextParamsPtr context_params =
CreateDefaultNetworkContextParams();
last_ssl_config_ = *context_params->initial_ssl_config;
receiver_.Bind(std::move(context_params->ssl_config_client_receiver));
}
void TearDownOnMainThread() override { receiver_.reset(); }
void ProceedThroughInterstitial(WebContents* tab) {
content::TestNavigationObserver nav_observer(tab, 1);
SendInterstitialCommand(tab, security_interstitials::CMD_PROCEED);
nav_observer.Wait();
}
virtual void DontProceedThroughInterstitial(WebContents* tab) {
SendInterstitialCommand(tab, security_interstitials::CMD_DONT_PROCEED);
}
void SendInterstitialCommand(
WebContents* tab,
security_interstitials::SecurityInterstitialCommand command) {
std::string javascript;
switch (command) {
case security_interstitials::CMD_DONT_PROCEED: {
javascript = "window.certificateErrorPageController.dontProceed();";
break;
}
case security_interstitials::CMD_PROCEED: {
javascript = "window.certificateErrorPageController.proceed();";
break;
}
case security_interstitials::CMD_SHOW_MORE_SECTION: {
javascript = "window.certificateErrorPageController.showMoreSection();";
break;
}
case security_interstitials::CMD_OPEN_HELP_CENTER: {
javascript = "window.certificateErrorPageController.openHelpCenter();";
break;
}
case security_interstitials::CMD_OPEN_DIAGNOSTIC: {
javascript = "window.certificateErrorPageController.openDiagnostic();";
break;
}
case security_interstitials::CMD_RELOAD: {
javascript = "window.certificateErrorPageController.reload();";
break;
}
case security_interstitials::CMD_OPEN_DATE_SETTINGS: {
javascript =
"window.certificateErrorPageController.openDateSettings();";
break;
}
case security_interstitials::CMD_OPEN_LOGIN: {
javascript = "window.certificateErrorPageController.openLogin();";
break;
}
case security_interstitials::CMD_DO_REPORT: {
javascript = "window.certificateErrorPageController.doReport();";
break;
}
case security_interstitials::CMD_DONT_REPORT: {
javascript = "window.certificateErrorPageController.dontReport();";
break;
}
case security_interstitials::CMD_OPEN_REPORTING_PRIVACY: {
javascript =
"window.certificateErrorPageController.openReportingPrivacy();";
break;
}
case security_interstitials::CMD_OPEN_WHITEPAPER: {
javascript = "window.certificateErrorPageController.openWhitepaper();";
break;
}
case security_interstitials::CMD_REPORT_PHISHING_ERROR: {
javascript =
"window.certificateErrorPageController.reportPhishingError();";
break;
}
default: {
// Other values in the enum are not used by these tests, and don't
// have a Javascript equivalent that can be called here.
NOTREACHED();
}
}
ASSERT_TRUE(content::ExecuteScript(tab, javascript));
return;
}
network::mojom::NetworkContextParamsPtr CreateDefaultNetworkContextParams() {
return g_browser_process->system_network_context_manager()
->CreateDefaultNetworkContextParams();
}
static std::string GetFilePathWithHostAndPortReplacement(
const std::string& original_file_path,
const net::HostPortPair& host_port_pair) {
base::StringPairs replacement_text;
replacement_text.push_back(
make_pair("REPLACE_WITH_HOST_AND_PORT", host_port_pair.ToString()));
return net::test_server::GetFilePathWithReplacements(original_file_path,
replacement_text);
}
static std::string GetTopFramePath(
const net::EmbeddedTestServer& http_server,
const net::EmbeddedTestServer& good_https_server,
const net::EmbeddedTestServer& bad_https_server) {
// The "frame_left.html" page contained in the top_frame.html page contains
// <a href>'s to three different servers. This sets up all of the
// replacement text to work with test servers which listen on ephemeral
// ports.
GURL http_url = http_server.GetURL("/ssl/google.html");
GURL good_https_url = good_https_server.GetURL("/ssl/google.html");
GURL bad_https_url = bad_https_server.GetURL("/ssl/bad_iframe.html");
base::StringPairs replacement_text_frame_left;
replacement_text_frame_left.push_back(
make_pair("REPLACE_WITH_HTTP_PORT", http_url.port()));
replacement_text_frame_left.push_back(
make_pair("REPLACE_WITH_GOOD_HTTPS_PAGE", good_https_url.spec()));
replacement_text_frame_left.push_back(
make_pair("REPLACE_WITH_BAD_HTTPS_PAGE", bad_https_url.spec()));
std::string frame_left_path = net::test_server::GetFilePathWithReplacements(
"frame_left.html", replacement_text_frame_left);
// Substitute the generated frame_left URL into the top_frame page.
base::StringPairs replacement_text_top_frame;
replacement_text_top_frame.push_back(
make_pair("REPLACE_WITH_FRAME_LEFT_PATH", frame_left_path));
return net::test_server::GetFilePathWithReplacements(
"/ssl/top_frame.html", replacement_text_top_frame);
}
security_interstitials::SecurityInterstitialPage* GetInterstitialPage(
WebContents* tab) {
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
if (!helper)
return nullptr;
return helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting();
}
// Helper function for testing invalid certificate chain reporting.
void TestBrokenHTTPSReporting(
certificate_reporting_test_utils::OptIn opt_in,
ProceedDecision proceed,
certificate_reporting_test_utils::ExpectReport expect_report,
Browser* browser) {
ASSERT_TRUE(https_server_expired_.Start());
base::RunLoop run_loop;
certificate_reporting_test_utils::SSLCertReporterCallback reporter_callback(
&run_loop);
// Opt in to sending reports for invalid certificate chains.
certificate_reporting_test_utils::SetCertReportingOptIn(browser, opt_in);
ui_test_utils::NavigateToURL(browser,
https_server_expired_.GetURL("/title1.html"));
WebContents* tab = browser->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab != nullptr);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
std::unique_ptr<SSLCertReporter> ssl_cert_reporter =
certificate_reporting_test_utils::CreateMockSSLCertReporter(
base::BindRepeating(&certificate_reporting_test_utils::
SSLCertReporterCallback::ReportSent,
base::Unretained(&reporter_callback)),
expect_report);
SSLBlockingPage* interstitial_page =
static_cast<SSLBlockingPage*>(GetInterstitialPage(tab));
ASSERT_TRUE(interstitial_page);
interstitial_page->SetSSLCertReporterForTesting(
std::move(ssl_cert_reporter));
EXPECT_EQ(std::string(), reporter_callback.GetLatestHostnameReported());
EXPECT_EQ(chrome_browser_ssl::CertLoggerRequest::CHROME_CHANNEL_NONE,
reporter_callback.GetLatestChromeChannelReported());
// Leave the interstitial (either by proceeding or going back)
if (proceed == SSL_INTERSTITIAL_PROCEED) {
ProceedThroughInterstitial(tab);
} else {
DontProceedThroughInterstitial(tab);
}
if (expect_report ==
certificate_reporting_test_utils::CERT_REPORT_EXPECTED) {
// Check that the mock reporter received a request to send a report.
run_loop.Run();
EXPECT_EQ(https_server_expired_.GetURL("/title1.html").host(),
reporter_callback.GetLatestHostnameReported());
EXPECT_NE(chrome_browser_ssl::CertLoggerRequest::CHROME_CHANNEL_NONE,
reporter_callback.GetLatestChromeChannelReported());
} else {
base::RunLoop().RunUntilIdle();
EXPECT_EQ(std::string(), reporter_callback.GetLatestHostnameReported());
EXPECT_EQ(chrome_browser_ssl::CertLoggerRequest::CHROME_CHANNEL_NONE,
reporter_callback.GetLatestChromeChannelReported());
}
}
// Helper function for testing invalid certificate chain reporting with the
// bad clock interstitial.
void TestBadClockReporting(
certificate_reporting_test_utils::OptIn opt_in,
certificate_reporting_test_utils::ExpectReport expect_report,
Browser* browser) {
ASSERT_TRUE(https_server_expired_.Start());
base::RunLoop run_loop;
certificate_reporting_test_utils::SSLCertReporterCallback reporter_callback(
&run_loop);
// Set network time back ten minutes, which is sufficient to
// trigger the reporting.
g_browser_process->network_time_tracker()->UpdateNetworkTime(
base::Time::Now() - base::TimeDelta::FromMinutes(10),
base::TimeDelta::FromMilliseconds(1), /* resolution */
base::TimeDelta::FromMilliseconds(500), /* latency */
base::TimeTicks::Now() /* posting time of this update */);
// Opt in to sending reports for invalid certificate chains.
certificate_reporting_test_utils::SetCertReportingOptIn(browser, opt_in);
ui_test_utils::NavigateToURL(browser,
https_server_expired_.GetURL("/title1.html"));
WebContents* tab = browser->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
std::unique_ptr<SSLCertReporter> ssl_cert_reporter =
certificate_reporting_test_utils::CreateMockSSLCertReporter(
base::BindRepeating(&certificate_reporting_test_utils::
SSLCertReporterCallback::ReportSent,
base::Unretained(&reporter_callback)),
expect_report);
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingBadClockInterstitial(tab));
BadClockBlockingPage* clock_page =
static_cast<BadClockBlockingPage*>(GetInterstitialPage(tab));
clock_page->SetSSLCertReporterForTesting(std::move(ssl_cert_reporter));
EXPECT_EQ(std::string(), reporter_callback.GetLatestHostnameReported());
DontProceedThroughInterstitial(tab);
if (expect_report ==
certificate_reporting_test_utils::CERT_REPORT_EXPECTED) {
// Check that the mock reporter received a request to send a report.
run_loop.Run();
EXPECT_EQ(https_server_expired_.GetURL("/title1.html").host(),
reporter_callback.GetLatestHostnameReported());
} else {
base::RunLoop().RunUntilIdle();
EXPECT_EQ(std::string(), reporter_callback.GetLatestHostnameReported());
}
}
// Sets the policy identified by |policy_name| to be true, ensuring
// that the corresponding boolean pref |pref_name| is updated to match.
void EnablePolicy(PrefService* pref_service,
const char* policy_name,
const char* pref_name) {
policy::PolicyMap policy_map;
policy_map.Set(policy_name, policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
base::Value(true), nullptr);
EXPECT_NO_FATAL_FAILURE(UpdateChromePolicy(policy_map));
EXPECT_TRUE(pref_service->GetBoolean(pref_name));
EXPECT_TRUE(pref_service->IsManagedPreference(pref_name));
// Wait for the updated SSL configuration to be sent to the network service,
// to avoid a race.
g_browser_process->system_network_context_manager()
->FlushSSLConfigManagerForTesting();
}
// Sets the policy identified by |policy_name| to |policy_value|.
void SetPolicy(const char* policy_name, base::Value policy_value) {
policy::PolicyMap policy_map;
policy_map.Set(policy_name, policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
std::move(policy_value), nullptr);
EXPECT_NO_FATAL_FAILURE(UpdateChromePolicy(policy_map));
// Wait for the updated SSL configuration to be sent to the network service,
// to avoid a race.
g_browser_process->system_network_context_manager()
->FlushSSLConfigManagerForTesting();
}
// Helper function for TestInterstitialLinksOpenInNewTab. Implemented as a
// test fixture method because the whole test fixture class is friended by
// SSLBlockingPage.
security_interstitials::SecurityInterstitialControllerClient*
GetControllerClientFromSSLBlockingPage(SSLBlockingPage* ssl_interstitial) {
return ssl_interstitial->controller();
}
// Helper function that checks that after proceeding through an interstitial,
// the app window is closed, a new tab with the app URL is opened, and there
// is no interstitial.
void ProceedThroughInterstitialInAppAndCheckNewTabOpened(
Browser* app_browser,
const GURL& app_url) {
Profile* profile = browser()->profile();
size_t num_browsers = chrome::GetBrowserCount(profile);
EXPECT_EQ(app_browser, chrome::FindLastActive());
int num_tabs = browser()->tab_strip_model()->count();
ProceedThroughInterstitial(
app_browser->tab_strip_model()->GetActiveWebContents());
EXPECT_EQ(--num_browsers, chrome::GetBrowserCount(profile));
EXPECT_EQ(browser(), chrome::FindLastActive());
EXPECT_EQ(++num_tabs, browser()->tab_strip_model()->count());
WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(new_tab));
ssl_test_util::CheckAuthenticationBrokenState(
new_tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
EXPECT_EQ(app_url, new_tab->GetVisibleURL());
}
// network::mojom::SSLConfigClient implementation.
void OnSSLConfigUpdated(network::mojom::SSLConfigPtr ssl_config) override {
last_ssl_config_ = *ssl_config;
}
protected:
typedef net::SpawnedTestServer::SSLOptions SSLOptions;
// Navigates to an interstitial and clicks through the certificate
// error; then navigates to a page at |path| that loads unsafe content.
void SetUpUnsafeContentsWithUserException(const std::string& path) {
ASSERT_TRUE(https_server_.Start());
// Note that it is necessary to user https_server_mismatched_ here over the
// other invalid cert servers. This is because the test relies on the two
// servers having different hosts since SSL exceptions are per-host, not per
// origin, and https_server_mismatched_ uses 'localhost' rather than
// '127.0.0.1'.
ASSERT_TRUE(https_server_mismatched_.Start());
// Navigate to an unsafe site. Proceed with interstitial page to indicate
// the user approves the bad certificate.
ui_test_utils::NavigateToURL(
browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_COMMON_NAME_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
path, https_server_mismatched_.host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
}
Browser* InstallAndOpenTestWebApp(const GURL& start_url) {
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = start_url;
web_app_info->scope = start_url.GetWithoutFilename();
web_app_info->title = base::UTF8ToUTF16("Test app");
web_app_info->description = base::UTF8ToUTF16("Test description");
Profile* profile = browser()->profile();
web_app::AppId app_id =
web_app::InstallWebApp(profile, std::move(web_app_info));
Browser* app_browser = web_app::LaunchWebAppBrowserAndWait(profile, app_id);
return app_browser;
}
void UpdateChromePolicy(const policy::PolicyMap& policies) {
policy_provider_.UpdateChromePolicy(policies);
ASSERT_TRUE(base::CurrentThread::Get());
base::RunLoop().RunUntilIdle();
content::FlushNetworkServiceInstanceForTesting();
}
void RunOnIOThreadBlocking(base::OnceClosure task) {
base::RunLoop run_loop;
content::GetIOThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE, std::move(task), run_loop.QuitClosure());
run_loop.Run();
}
net::EmbeddedTestServer https_server_;
net::EmbeddedTestServer https_server_expired_;
net::EmbeddedTestServer https_server_mismatched_;
net::EmbeddedTestServer https_server_sha1_;
net::EmbeddedTestServer https_server_common_name_only_;
net::SpawnedTestServer wss_server_expired_;
net::SpawnedTestServer wss_server_mismatched_;
testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
network::mojom::SSLConfig last_ssl_config_;
mojo::Receiver<network::mojom::SSLConfigClient> receiver_{this};
private:
DISALLOW_COPY_AND_ASSIGN(SSLUITestBase);
};
class SSLUITest : public SSLUITestBase {
public:
SSLUITest() : SSLUITestBase() {
scoped_feature_list_.InitWithFeatures(
/* enabled_features */ {},
/* disabled_features */ {
blink::features::kMixedContentAutoupgrade,
safe_browsing::kEnhancedProtectionMessageInInterstitials});
}
protected:
void DontProceedThroughInterstitial(WebContents* tab) override {
content::TestNavigationObserver nav_observer(tab, 1);
SendInterstitialCommand(tab, security_interstitials::CMD_DONT_PROCEED);
nav_observer.Wait();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(SSLUITest);
};
class SSLUITestBlock : public SSLUITest {
public:
SSLUITestBlock() : SSLUITest() {}
// Browser will not run insecure content.
void SetUpCommandLine(base::CommandLine* command_line) override {
// By overriding SSLUITest, we won't apply the flag that allows running
// insecure content.
}
};
class SSLUITestIgnoreCertErrors : public SSLUITest {
public:
SSLUITestIgnoreCertErrors() : SSLUITest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
SSLUITest::SetUpCommandLine(command_line);
// Browser will ignore certificate errors.
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
};
static std::string MakeCertSPKIFingerprint(net::X509Certificate* cert) {
net::HashValue hash = GetSPKIHash(cert->cert_buffer());
std::string hash_base64;
base::Base64Encode(
base::StringPiece(reinterpret_cast<const char*>(hash.data()),
hash.size()),
&hash_base64);
return hash_base64;
}
class SSLUITestIgnoreCertErrorsBySPKIHTTPS : public SSLUITest {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SSLUITest::SetUpCommandLine(command_line);
std::string whitelist_flag = MakeCertSPKIFingerprint(
https_server_mismatched_.GetCertificate().get());
// Browser will ignore certificate errors for chains matching one of the
// public keys from the list.
command_line->AppendSwitchASCII(
network::switches::kIgnoreCertificateErrorsSPKIList, whitelist_flag);
}
};
class SSLUITestIgnoreCertErrorsBySPKIWSS : public SSLUITest {
public:
SSLUITestIgnoreCertErrorsBySPKIWSS() : SSLUITest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
SSLUITest::SetUpCommandLine(command_line);
std::string whitelist_flag =
MakeCertSPKIFingerprint(wss_server_expired_.GetCertificate().get());
// Browser will ignore certificate errors for chains matching one of the
// public keys from the list.
command_line->AppendSwitchASCII(
network::switches::kIgnoreCertificateErrorsSPKIList, whitelist_flag);
}
};
class SSLUITestIgnoreLocalhostCertErrors : public SSLUITest {
public:
SSLUITestIgnoreLocalhostCertErrors() : SSLUITest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
SSLUITest::SetUpCommandLine(command_line);
// Browser will ignore certificate errors on localhost.
command_line->AppendSwitch(switches::kAllowInsecureLocalhost);
}
};
class SSLUITestWithExtendedReporting : public SSLUITest {
public:
SSLUITestWithExtendedReporting() : SSLUITest() {
// Certificate reports are only sent from official builds, unless this has
// been called.
CertReportHelper::SetFakeOfficialBuildForTesting();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
SSLUITest::SetUpCommandLine(command_line);
// CertReportHelper::ShouldReportCertificateError checks the value of this
// variation. Ensure reporting is enabled.
variations::testing::VariationParamsManager::AppendVariationParams(
"ReportCertificateErrors", "ShowAndPossiblySend",
{{"sendingThreshold", "1.0"}}, command_line);
}
};
class SSLUITestHSTS : public SSLUITest {
public:
void SetUpOnMainThread() override {
SSLUITest::SetUpOnMainThread();
ssl_test_util::SetHSTSForHostName(browser()->profile(), kHstsTestHostName);
}
};
// Visits a regular page over http.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTP) {
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/ssl/google.html"));
ssl_test_util::CheckUnauthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}
// Visits a page over http which includes broken https resources (status should
// be OK).
// TODO(jcampan): test that bad HTTPS content is blocked (otherwise we'll give
// the secure cookies away!).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPWithBrokenHTTPSResource) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_expired_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_with_unsafe_contents.html",
https_server_expired_.host_port_pair());
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(replacement_path));
ssl_test_util::CheckUnauthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}
// Tests that after loading mixed content and then making a same-document
// navigation, the mixed content security indicator remains. See
// https://crbug.com/959571.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestMixedContentWithSamePageNavigation) {
ASSERT_TRUE(https_server_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Navigate to a secure page (no mixed content).
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// Add a mixed form after a same-document navigation.
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html#foo"));
ssl_test_util::SecurityStateWebContentsObserver observer(tab);
ASSERT_NE(false, content::EvalJs(tab,
"var f = document.createElement('form');"
"f.action = 'http://foo.test';"
"document.body.appendChild(f)"));
observer.WaitForDidChangeVisibleSecurityState();
// If mixed form warnings are enabled, we display the lock icon on otherwise
// secure sites with an insecure form.
security_state::SecurityLevel expected_level =
base::FeatureList::IsEnabled(
security_interstitials::kInsecureFormSubmissionInterstitial) &&
base::FeatureList::IsEnabled(
autofill::features::kAutofillPreventMixedFormsFilling)
? security_state::SECURE
: security_state::NONE;
ssl_test_util::CheckSecurityState(
tab, CertError::NONE, expected_level,
AuthState::DISPLAYED_FORM_WITH_INSECURE_ACTION);
// Go back (which should also be a same-document navigation) and test that the
// security indicator is still downgraded because of the mixed form.
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(content::WaitForLoadStop(tab));
ssl_test_util::CheckSecurityState(
tab, CertError::NONE, expected_level,
AuthState::DISPLAYED_FORM_WITH_INSECURE_ACTION);
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrokenHTTPSWithInsecureContent) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_expired_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_content.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID,
AuthState::DISPLAYED_INSECURE_CONTENT);
}
// Tests that the NavigationEntry gets marked as active mixed content,
// even if there is a certificate error. Regression test for
// https://crbug.com/593950.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrokenHTTPSWithActiveInsecureContent) {
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
// Navigate to a page with a certificate error and click through the
// interstitial.
ui_test_utils::NavigateToURL(
browser(),
https_server_expired_.GetURL("/ssl/page_runs_insecure_content.html"));
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
// Now check that the page is marked as having run insecure content.
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::RAN_INSECURE_CONTENT);
}
// Tests that when a subframe commits a main resource with a certificate error,
// the navigation entry is marked as insecure.
// Flaky. See https://crbug.com/1106370.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrors, DISABLED_SubframeHasCertError) {
ASSERT_TRUE(https_server_mismatched_.Start());
// Load a page with a data: favicon URL to suppress a favicon request. A
// favicon request can cause the navigation entry to get marked as having run
// insecure content (favicons are treated as active content), which would
// interfere with the test expectation below.
GURL main_frame_url =
https_server_mismatched_.GetURL("a.test", "/data_favicon.html");
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
EXPECT_FALSE(
tab->GetController().GetLastCommittedEntry()->GetSSL().content_status &
content::SSLStatus::RAN_CONTENT_WITH_CERT_ERRORS);
GURL subframe_url =
https_server_mismatched_.GetURL("b.test", "/data_favicon.html");
content::TestNavigationObserver iframe_observer(tab);
EXPECT_TRUE(content::ExecJs(
tab, content::JsReplace("var i = document.createElement('iframe');"
"i.src = $1;"
"document.body.appendChild(i);",
subframe_url.spec())));
iframe_observer.Wait();
EXPECT_TRUE(
tab->GetController().GetLastCommittedEntry()->GetSSL().content_status &
content::SSLStatus::RAN_CONTENT_WITH_CERT_ERRORS);
}
namespace {
// A WebContentsObserver that allows the user to wait for a same-document
// navigation. Tests using this observer will fail if a non-same-document
// navigation completes after calling WaitForSameDocumentNavigation.
class SameDocumentNavigationObserver : public content::WebContentsObserver {
public:
explicit SameDocumentNavigationObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
~SameDocumentNavigationObserver() override {}
void WaitForSameDocumentNavigation() { run_loop_.Run(); }
// WebContentsObserver:
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
ASSERT_TRUE(navigation_handle->IsSameDocument());
run_loop_.Quit();
}
private:
base::RunLoop run_loop_;
};
} // namespace
// Tests that the mixed content flags are reset when going back to an existing
// navigation entry that had mixed content. Regression test for
// https://crbug.com/750649.
IN_PROC_BROWSER_TEST_F(SSLUITest, GoBackToMixedContent) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
// Navigate to a URL and dynamically load mixed content.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
ssl_test_util::SecurityStateWebContentsObserver observer(tab);
ASSERT_TRUE(content::ExecuteScript(tab,
"var i = document.createElement('img');"
"i.src = 'http://example.test';"
"document.body.appendChild(i);"));
observer.WaitForDidChangeVisibleSecurityState();
ssl_test_util::CheckSecurityState(tab, CertError::NONE,
security_state::WARNING,
AuthState::DISPLAYED_INSECURE_CONTENT);
// Now navigate somewhere else, and then back to the page that dynamically
// loaded mixed content.
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/ssl/google.html"));
ssl_test_util::CheckUnauthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(content::WaitForLoadStop(tab));
// After going back, the mixed content indicator should no longer be present.
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Tests that the mixed content flags are not reset for an in-page navigation.
IN_PROC_BROWSER_TEST_F(SSLUITest, MixedContentWithSameDocumentNavigation) {
ASSERT_TRUE(https_server_.Start());
// Navigate to a URL and dynamically load mixed content.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
ssl_test_util::SecurityStateWebContentsObserver security_state_observer(tab);
ASSERT_TRUE(content::ExecuteScript(tab,
"var i = document.createElement('img');"
"i.src = 'http://example.test';"
"document.body.appendChild(i);"));
security_state_observer.WaitForDidChangeVisibleSecurityState();
ssl_test_util::CheckSecurityState(tab, CertError::NONE,
security_state::WARNING,
AuthState::DISPLAYED_INSECURE_CONTENT);
// Initiate a same-document navigation and check that the page is still
// marked as having displayed mixed content.
SameDocumentNavigationObserver navigation_observer(tab);
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html#foo"));
navigation_observer.WaitForSameDocumentNavigation();
ssl_test_util::CheckSecurityState(tab, CertError::NONE,
security_state::WARNING,
AuthState::DISPLAYED_INSECURE_CONTENT);
}
// Tests that the WebContents's flag for displaying content with cert
// errors get cleared upon navigation.
IN_PROC_BROWSER_TEST_F(SSLUITest,
DisplayedContentWithCertErrorsClearedOnNavigation) {
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
// Navigate to a page with a certificate error and click through the
// interstitial.
ui_test_utils::NavigateToURL(
browser(),
https_server_expired_.GetURL("/ssl/page_with_subresource.html"));
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
ASSERT_TRUE(entry);
EXPECT_TRUE(entry->GetSSL().content_status &
content::SSLStatus::DISPLAYED_CONTENT_WITH_CERT_ERRORS);
// Navigate away to a different page, and check that the flag gets cleared.
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
entry = tab->GetController().GetVisibleEntry();
ASSERT_TRUE(entry);
EXPECT_FALSE(entry->GetSSL().content_status &
content::SSLStatus::DISPLAYED_CONTENT_WITH_CERT_ERRORS);
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrokenHTTPSMetricsReporting_Proceed) {
ASSERT_TRUE(https_server_expired_.Start());
base::HistogramTester histograms;
const std::string decision_histogram =
"interstitial.ssl_overridable.decision";
const std::string interaction_histogram =
"interstitial.ssl_overridable.interaction";
// Histograms should start off empty.
histograms.ExpectTotalCount(decision_histogram, 0);
histograms.ExpectTotalCount(interaction_histogram, 0);
// After navigating to the page, the totals should be set.
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL("/title1.html"));
WaitForInterstitial(browser()->tab_strip_model()->GetActiveWebContents());
histograms.ExpectTotalCount(decision_histogram, 1);
histograms.ExpectBucketCount(decision_histogram,
security_interstitials::MetricsHelper::SHOW, 1);
histograms.ExpectTotalCount(interaction_histogram, 1);
histograms.ExpectBucketCount(
interaction_histogram,
security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
// Decision should be recorded.
SendInterstitialCommand(browser()->tab_strip_model()->GetActiveWebContents(),
security_interstitials::CMD_PROCEED);
histograms.ExpectTotalCount(decision_histogram, 2);
histograms.ExpectBucketCount(
decision_histogram, security_interstitials::MetricsHelper::PROCEED, 1);
histograms.ExpectTotalCount(interaction_histogram, 1);
histograms.ExpectBucketCount(
interaction_histogram,
security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrokenHTTPSMetricsReporting_DontProceed) {
ASSERT_TRUE(https_server_expired_.Start());
base::HistogramTester histograms;
const std::string decision_histogram =
"interstitial.ssl_overridable.decision";
const std::string interaction_histogram =
"interstitial.ssl_overridable.interaction";
// Histograms should start off empty.
histograms.ExpectTotalCount(decision_histogram, 0);
histograms.ExpectTotalCount(interaction_histogram, 0);
// After navigating to the page, the totals should be set.
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL("/title1.html"));
WaitForInterstitial(browser()->tab_strip_model()->GetActiveWebContents());
histograms.ExpectTotalCount(decision_histogram, 1);
histograms.ExpectBucketCount(decision_histogram,
security_interstitials::MetricsHelper::SHOW, 1);
histograms.ExpectTotalCount(interaction_histogram, 1);
histograms.ExpectBucketCount(
interaction_histogram,
security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
// Decision should be recorded.
SendInterstitialCommand(browser()->tab_strip_model()->GetActiveWebContents(),
security_interstitials::CMD_DONT_PROCEED);
histograms.ExpectTotalCount(decision_histogram, 2);
histograms.ExpectBucketCount(
decision_histogram, security_interstitials::MetricsHelper::DONT_PROCEED,
1);
histograms.ExpectTotalCount(interaction_histogram, 1);
histograms.ExpectBucketCount(
interaction_histogram,
security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
}
// Visits a page over OK https:
IN_PROC_BROWSER_TEST_F(SSLUITest, TestOKHTTPS) {
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
ssl_test_util::CheckAuthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}
// Visits a page with https error and proceed:
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSExpiredCertAndProceed) {
ASSERT_TRUE(https_server_expired_.Start());
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
EXPECT_EQ(https_server_expired_.GetURL("/ssl/google.html"),
tab->GetVisibleURL());
}
// Visits a page in an app window with https error and proceed:
// Disabled due to flaky failures; see https://crbug.com/1156046.
IN_PROC_BROWSER_TEST_F(SSLUITest,
DISABLED_InAppTestHTTPSExpiredCertAndProceed) {
ASSERT_TRUE(https_server_expired_.Start());
const GURL app_url = https_server_expired_.GetURL("/ssl/google.html");
Browser* app_browser = InstallAndOpenTestWebApp(app_url);
WebContents* app_tab = app_browser->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(app_tab);
ssl_test_util::CheckAuthenticationBrokenState(
app_tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitialInAppAndCheckNewTabOpened(app_browser, app_url);
}
// Visits a page with https error and proceed. Then open the app and proceed.
IN_PROC_BROWSER_TEST_F(SSLUITest,
InAppTestHTTPSExpiredCertAndPreviouslyProceeded) {
ASSERT_TRUE(https_server_expired_.Start());
const GURL app_url = https_server_expired_.GetURL("/ssl/google.html");
// Go through the interstitial in a regular browser tab.
ui_test_utils::NavigateToURL(browser(), app_url);
WebContents* initial_tab =
browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(initial_tab);
ssl_test_util::CheckAuthenticationBrokenState(
initial_tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(initial_tab);
ssl_test_util::CheckAuthenticationBrokenState(
initial_tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
Browser* app_browser = InstallAndOpenTestWebApp(app_url);
// Apps are not allowed to have SSL errors, so the interstitial should be
// showing even though the user proceeded through it in a regular tab.
WebContents* app_tab = app_browser->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(app_tab);
// TODO(crbug.com/1154877): Apps are not setting the right security state in
// this case, so we only check the presence of the interstitial (inside Wait
// ForInterstitial) and the behavior after clicking through.
// After the bug is fixed, add a call to CheckAuthenticationBrokenState
// with net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL
// parameters.
ProceedThroughInterstitialInAppAndCheckNewTabOpened(app_browser, app_url);
}
// Visits a page with https error and don't proceed (and ensure we can still
// navigate at that point):
IN_PROC_BROWSER_TEST_F(SSLUITest, TestInterstitialCrossSiteNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_mismatched_.Start());
// First navigate to an OK page.
GURL initial_url = https_server_.GetURL("/ssl/google.html");
ASSERT_EQ("127.0.0.1", initial_url.host());
ui_test_utils::NavigateToURL(browser(), initial_url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Navigate from 127.0.0.1 to localhost so it triggers a
// cross-site navigation to make sure http://crbug.com/5800 is gone.
GURL cross_site_url = https_server_mismatched_.GetURL("/ssl/google.html");
ASSERT_EQ("localhost", cross_site_url.host());
ui_test_utils::NavigateToURL(browser(), cross_site_url);
// An interstitial should be showing.
WaitForInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_COMMON_NAME_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
// Simulate user clicking "Take me back".
DontProceedThroughInterstitial(tab);
// We should be back to the original good page.
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// Navigate to a new page to make sure bug 5800 is fixed.
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/ssl/google.html"));
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
}
// Test that localhost pages don't show an interstitial.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreLocalhostCertErrors,
TestNoInterstitialOnLocalhost) {
ASSERT_TRUE(https_server_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Navigate to a localhost page.
GURL url = https_server_.GetURL("/ssl/page_with_subresource.html");
GURL::Replacements replacements;
std::string new_host("localhost");
replacements.SetHostStr(new_host);
url = url.ReplaceComponents(replacements);
ui_test_utils::NavigateToURL(browser(), url);
// We should see no interstitial, but we should have an error
// (red-crossed-out-https) in the URL bar.
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);
// We should see that the script tag in the page loaded and ran (and
// wasn't blocked by the certificate error).
base::string16 title;
base::string16 expected_title = base::ASCIIToUTF16("This script has loaded");
ui_test_utils::GetCurrentTabTitle(browser(), &title);
EXPECT_EQ(title, expected_title);
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSErrorCausedByClockUsingBuildTime) {
ASSERT_TRUE(https_server_expired_.Start());
// Set up the build and current clock times to be more than a year apart.
std::unique_ptr<base::SimpleTestClock> mock_clock(
new base::SimpleTestClock());
mock_clock->SetNow(base::Time::NowFromSystemTime());
mock_clock->Advance(base::TimeDelta::FromDays(367));
SSLErrorHandler::SetClockForTesting(mock_clock.get());
ssl_errors::SetBuildTimeForTesting(base::Time::NowFromSystemTime());
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL("/title1.html"));
WebContents* clock_tab = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(clock_tab);
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingBadClockInterstitial(clock_tab));
ssl_test_util::CheckSecurityState(clock_tab, net::CERT_STATUS_DATE_INVALID,
security_state::DANGEROUS,
AuthState::SHOWING_INTERSTITIAL);
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSErrorCausedByClockUsingNetwork) {
ASSERT_TRUE(https_server_expired_.Start());
// Set network forward ten minutes, which is sufficient to trigger
// the interstitial.
g_browser_process->network_time_tracker()->UpdateNetworkTime(
base::Time::Now() + base::TimeDelta::FromMinutes(10),
base::TimeDelta::FromMilliseconds(1), /* resolution */
base::TimeDelta::FromMilliseconds(500), /* latency */
base::TimeTicks::Now() /* posting time of this update */);
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL("/title1.html"));
WebContents* clock_tab = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(clock_tab);
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingBadClockInterstitial(clock_tab));
ssl_test_util::CheckSecurityState(clock_tab, net::CERT_STATUS_DATE_INVALID,
security_state::DANGEROUS,
AuthState::SHOWING_INTERSTITIAL);
}
// Visits a page with https error and then goes back using Browser::GoBack.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSExpiredCertAndGoBackViaButton) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_expired_.Start());
// First navigate to an HTTP page.
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/ssl/google.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Now go to a bad HTTPS page that shows an interstitial.
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
// Simulate user clicking on back button (crbug.com/39248).
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(content::WaitForLoadStop(tab));
// We should be back at the original good page.
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
}
// Visits a page with https error and then goes back using GoToOffset.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSExpiredCertAndGoBackViaMenu) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_expired_.Start());
// First navigate to an HTTP page.
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/ssl/google.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Now go to a bad HTTPS page that shows an interstitial.
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
// Simulate user clicking and holding on back button (crbug.com/37215). With
// committed interstitials enabled, this triggers a navigation.
content::TestNavigationObserver nav_observer(tab);
tab->GetController().GoToOffset(-1);
nav_observer.Wait();
// We should be back at the original good page.
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
}
// Visits a page with https error and then goes back using the DONT_PROCEED
// interstitial command.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSExpiredCertGoBackUsingCommand) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_expired_.Start());
// First navigate to an HTTP page.
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/ssl/google.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Now go to a bad HTTPS page that shows an interstitial.
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
SendInterstitialCommand(tab, security_interstitials::CMD_DONT_PROCEED);
observer.Wait();
// We should be back at the original good page.
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
}
// Visits a page that uses a SHA-1 leaf certificate, which should be rejected
// by default.
IN_PROC_BROWSER_TEST_F(SSLUITest, SHA1IsDefaultDisabled) {
EXPECT_FALSE(last_ssl_config_.sha1_local_anchors_enabled);
EXPECT_FALSE(CreateDefaultNetworkContextParams()
->initial_ssl_config->sha1_local_anchors_enabled);
ASSERT_TRUE(https_server_sha1_.Start());
ui_test_utils::NavigateToURL(browser(),
https_server_sha1_.GetURL("/ssl/google.html"));
int expected_error = net::CERT_STATUS_WEAK_SIGNATURE_ALGORITHM;
#if defined(OS_MAC)
// On macOS 10.15 (and presumably later) SHA1 certs are considered prima
// facie invalid by the system verifier.
// TODO(https://crbug.com/977767): Remove this when CertVerifyProcMac is
// removed.
if (base::mac::IsAtLeastOS10_15() &&
!base::FeatureList::IsEnabled(
net::features::kCertVerifierBuiltinFeature)) {
expected_error |= net::CERT_STATUS_INVALID;
}
#endif
ssl_test_util::CheckAuthenticationBrokenState(
browser()->tab_strip_model()->GetActiveWebContents(), expected_error,
AuthState::SHOWING_INTERSTITIAL);
}
// By default, trust in Symantec's Legacy PKI should be disabled. Unfortunately,
// there is currently no way to simulate navigation to a page that will
// meaningfully test that Symantec enforcement is actually applied to the
// request.
IN_PROC_BROWSER_TEST_F(SSLUITest, SymantecEnforcementIsNotDisabled) {
EXPECT_FALSE(last_ssl_config_.symantec_enforcement_disabled);
EXPECT_FALSE(CreateDefaultNetworkContextParams()
->initial_ssl_config->symantec_enforcement_disabled);
}
class CertificateTransparencySSLUITest : public CertVerifierBrowserTest {
public:
CertificateTransparencySSLUITest()
: CertVerifierBrowserTest(),
https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
true);
}
~CertificateTransparencySSLUITest() override {
SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
base::nullopt);
}
void SetUpOnMainThread() override {
CertVerifierBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
}
void SetUp() override {
ON_CALL(policy_provider_, IsInitializationComplete(testing::_))
.WillByDefault(testing::Return(true));
ON_CALL(policy_provider_, IsFirstPolicyLoadComplete(testing::_))
.WillByDefault(testing::Return(true));
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
&policy_provider_);
CertVerifierBrowserTest::SetUp();
}
// Sets the policy identified by |policy_name| to the value specified by
// |list_values|, ensuring that the corresponding list pref |pref_name| is
// updated to match. |policy_name| must specify a policy that is a list of
// string values.
void ConfigureStringListPolicy(PrefService* pref_service,
const char* policy_name,
const char* pref_name,
const std::vector<std::string>& list_values) {
base::Value policy_value(base::Value::Type::LIST);
for (const auto& value : list_values) {
policy_value.Append(value);
}
policy::PolicyMap policy_map;
policy_map.Set(policy_name, policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
std::move(policy_value), nullptr);
EXPECT_NO_FATAL_FAILURE(UpdateChromePolicy(policy_map));
const base::ListValue* pref_value = pref_service->GetList(pref_name);
ASSERT_TRUE(pref_value);
std::vector<std::string> pref_values;
for (const auto& value : pref_value->GetList()) {
ASSERT_TRUE(value.is_string());
pref_values.push_back(value.GetString());
}
EXPECT_THAT(pref_values, testing::UnorderedElementsAreArray(list_values));
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
private:
void UpdateChromePolicy(const policy::PolicyMap& policies) {
policy_provider_.UpdateChromePolicy(policies);
ASSERT_TRUE(base::CurrentThread::Get());
base::RunLoop().RunUntilIdle();
content::FlushNetworkServiceInstanceForTesting();
}
net::EmbeddedTestServer https_server_;
testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
DISALLOW_COPY_AND_ASSIGN(CertificateTransparencySSLUITest);
};
// Visit an HTTPS page that has a publicly trusted certificate issued after
// the Certificate Transparency requirement date of April 2018. The connection
// should be blocked, as the server will not be providing CT details, and the
// Chrome CT Policy should be being enforced.
IN_PROC_BROWSER_TEST_F(CertificateTransparencySSLUITest,
EnforcedAfterApril2018) {
ASSERT_TRUE(https_server()->Start());
net::CertVerifyResult verify_result;
verify_result.verified_cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "may_2018.pem");
ASSERT_TRUE(verify_result.verified_cert);
verify_result.is_issued_by_known_root = true;
verify_result.public_key_hashes.push_back(
GetSPKIHash(verify_result.verified_cert->cert_buffer()));
mock_cert_verifier()->AddResultForCert(https_server()->GetCertificate().get(),
verify_result, net::OK);
ui_test_utils::NavigateToURL(browser(),
https_server()->GetURL("/ssl/google.html"));
ssl_test_util::CheckSecurityState(
browser()->tab_strip_model()->GetActiveWebContents(),
net::CERT_STATUS_CERTIFICATE_TRANSPARENCY_REQUIRED,
security_state::DANGEROUS, AuthState::SHOWING_INTERSTITIAL);
}
// Visit an HTTPS page that has a publicly trusted certificate issued after
// the Certificate Transparency requirement date of April 2018. The connection
// would normally be blocked, as the server will not be providing CT details,
// and the Chrome CT Policy should be being enforced; however, because a policy
// configuration exists that disables CT enforcement for that cert, the
// connection should succeed.
IN_PROC_BROWSER_TEST_F(CertificateTransparencySSLUITest,
EnforcedAfterApril2018UnlessPoliciesSet) {
ASSERT_TRUE(https_server()->Start());
net::CertVerifyResult verify_result;
verify_result.verified_cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "may_2018.pem");
ASSERT_TRUE(verify_result.verified_cert);
verify_result.is_issued_by_known_root = true;
verify_result.public_key_hashes.push_back(
GetSPKIHash(verify_result.verified_cert->cert_buffer()));
mock_cert_verifier()->AddResultForCert(https_server()->GetCertificate().get(),
verify_result, net::OK);
ASSERT_NO_FATAL_FAILURE(ConfigureStringListPolicy(
browser()->profile()->GetPrefs(),
policy::key::kCertificateTransparencyEnforcementDisabledForCas,
certificate_transparency::prefs::kCTExcludedSPKIs,
{verify_result.public_key_hashes.back().ToString()}));
ui_test_utils::NavigateToURL(browser(),
https_server()->GetURL("/ssl/google.html"));
ssl_test_util::CheckSecurityState(
browser()->tab_strip_model()->GetActiveWebContents(), CertError::NONE,
security_state::SECURE, AuthState::NONE);
}
// Visit an HTTPS page that has a certificate issued by a certificate authority
// that is trusted in a root store that Chrome does not consider consistently
// secure. In the case where the certificate was issued after the Certificate
// Transparency requirement date of April 2018 the connection would normally be
// blocked, as the server will not be providing CT details, and the Chrome CT
// Policy should be being enforced; however, because a policy configuration
// exists that disables CT enforcement for that Legacy cert, the connection
// should succeed. For more detail, see /net/docs/certificate-transparency.md
IN_PROC_BROWSER_TEST_F(CertificateTransparencySSLUITest,
LegacyEnforcedAfterApril2018UnlessPoliciesSet) {
ASSERT_TRUE(https_server()->Start());
net::CertVerifyResult verify_result;
verify_result.verified_cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "may_2018.pem");
ASSERT_TRUE(verify_result.verified_cert);
verify_result.is_issued_by_known_root = true;
// We'll use a SPKI hash corresponding to the Federal Common Policy CA as
// captured at https://fpki.idmanagement.gov/announcements/mspkichanges/
const net::SHA256HashValue legacy_spki_hash = {
0x8e, 0x8b, 0x56, 0xf5, 0x91, 0x8a, 0x25, 0xbd, 0x85, 0xdc, 0xe7,
0x66, 0x63, 0xfd, 0x94, 0xcc, 0x23, 0x69, 0x0f, 0x10, 0xea, 0x95,
0x86, 0x61, 0x31, 0x71, 0xc6, 0xf8, 0x37, 0x88, 0x90, 0xd5};
verify_result.public_key_hashes.push_back(net::HashValue(legacy_spki_hash));
mock_cert_verifier()->AddResultForCert(https_server()->GetCertificate().get(),
verify_result, net::OK);
ASSERT_NO_FATAL_FAILURE(ConfigureStringListPolicy(
browser()->profile()->GetPrefs(),
policy::key::kCertificateTransparencyEnforcementDisabledForLegacyCas,
certificate_transparency::prefs::kCTExcludedLegacySPKIs,
{verify_result.public_key_hashes.back().ToString()}));
ui_test_utils::NavigateToURL(browser(),
https_server()->GetURL("/ssl/google.html"));
ssl_test_util::CheckSecurityState(
browser()->tab_strip_model()->GetActiveWebContents(), CertError::NONE,
security_state::SECURE, AuthState::NONE);
}
// Visit a HTTP page which request WSS connection to a server providing invalid
// certificate. Close the page while WSS connection waits for SSLManager's
// response from UI thread.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestWSSInvalidCertAndClose) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(wss_server_expired_.Start());
// Setup page title observer.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TitleWatcher watcher(tab, base::ASCIIToUTF16("PASS"));
watcher.AlsoWaitForTitle(base::ASCIIToUTF16("FAIL"));
// Create GURLs to test pages.
std::string master_url_path = base::StringPrintf(
"%s?%d",
embedded_test_server()->GetURL("/ssl/wss_close.html").spec().c_str(),
wss_server_expired_.host_port_pair().port());
GURL master_url(master_url_path);
std::string slave_url_path =
base::StringPrintf("%s?%d",
embedded_test_server()
->GetURL("/ssl/wss_close_slave.html")
.spec()
.c_str(),
wss_server_expired_.host_port_pair().port());
GURL slave_url(slave_url_path);
// Create tabs and visit pages which keep on creating wss connections.
WebContents* tabs[16];
for (int i = 0; i < 16; ++i) {
tabs[i] = chrome::AddSelectedTabWithURL(browser(), slave_url,
ui::PAGE_TRANSITION_LINK);
}
chrome::SelectNextTab(browser());
// Visit a page which waits for one TLS handshake failure.
// The title will be changed to 'PASS'.
ui_test_utils::NavigateToURL(browser(), master_url);
const base::string16 result = watcher.WaitAndGetTitle();
EXPECT_TRUE(base::LowerCaseEqualsASCII(result, "pass"));
// Close tabs which contains the test page.
for (int i = 0; i < 16; ++i)
chrome::CloseWebContents(browser(), tabs[i], false);
chrome::CloseWebContents(browser(), tab, false);
}
// Visit a HTTPS page and proceeds despite an invalid certificate. The page
// requests WSS connection to the same origin host to check if WSS connection
// share certificates policy with HTTPS correcly.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestWSSInvalidCert) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(wss_server_expired_.Start());
// Setup page title observer.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TitleWatcher watcher(tab, base::ASCIIToUTF16("PASS"));
watcher.AlsoWaitForTitle(base::ASCIIToUTF16("FAIL"));
// Visit bad HTTPS page.
GURL::Replacements replacements;
replacements.SetSchemeStr("https");
ui_test_utils::NavigateToURL(browser(),
wss_server_expired_.GetURL("connect_check.html")
.ReplaceComponents(replacements));
WaitForInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
// Proceed anyway.
ProceedThroughInterstitial(tab);
// Test page run a WebSocket wss connection test. The result will be shown
// as page title.
const base::string16 result = watcher.WaitAndGetTitle();
EXPECT_TRUE(base::LowerCaseEqualsASCII(result, "pass"));
}
// Data URLs should always be marked as non-secure.
IN_PROC_BROWSER_TEST_F(SSLUITest, MarkDataAsNonSecure) {
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
SecurityStateTabHelper* helper =
SecurityStateTabHelper::FromWebContents(contents);
ASSERT_TRUE(helper);
ui_test_utils::NavigateToURL(browser(), GURL("data:text/plain,hello"));
EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel());
}
// TODO(crbug.com/1148302): This class directly calls
// `GetNSSCertDatabaseForProfile()` that causes crash at the moment and is never
// called from Lacros-Chrome. This should be revisited when there is a solution
// for the client certificates settings page on Lacros-Chrome.
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
#if defined(USE_NSS_CERTS)
class SSLUITestWithClientCert : public SSLUITestBase {
public:
SSLUITestWithClientCert() : cert_db_(nullptr) {}
void SetUpOnMainThread() override {
SSLUITestBase::SetUpOnMainThread();
base::RunLoop loop;
GetNSSCertDatabaseForProfile(
browser()->profile(),
base::BindOnce(&SSLUITestWithClientCert::DidGetCertDatabase,
base::Unretained(this), &loop));
loop.Run();
}
protected:
void DidGetCertDatabase(base::RunLoop* loop, net::NSSCertDatabase* cert_db) {
cert_db_ = cert_db;
loop->Quit();
}
net::NSSCertDatabase* cert_db_;
};
// SSL client certificate tests are only enabled when using NSS for private key
// storage, as only NSS can avoid modifying global machine state when testing.
// See http://crbug.com/51132
// Visit a HTTPS page which requires client cert authentication. The client
// cert will be selected automatically, then a test which uses WebSocket runs.
IN_PROC_BROWSER_TEST_F(SSLUITestWithClientCert, TestWSSClientCert) {
// Import a client cert for test.
crypto::ScopedPK11Slot public_slot = cert_db_->GetPublicSlot();
std::string pkcs12_data;
base::FilePath cert_path = net::GetTestCertsDirectory().Append(
FILE_PATH_LITERAL("websocket_client_cert.p12"));
{
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::ReadFileToString(cert_path, &pkcs12_data));
}
EXPECT_EQ(net::OK,
cert_db_->ImportFromPKCS12(public_slot.get(), pkcs12_data,
base::string16(), true, nullptr));
// Start WebSocket test server with TLS and client cert authentication.
net::SpawnedTestServer::SSLOptions options(
net::SpawnedTestServer::SSLOptions::CERT_OK);
options.request_client_certificate = true;
base::FilePath ca_path = net::GetTestCertsDirectory().Append(
FILE_PATH_LITERAL("websocket_cacert.pem"));
options.client_authorities.push_back(ca_path);
net::SpawnedTestServer wss_server(net::SpawnedTestServer::TYPE_WSS, options,
net::GetWebSocketTestDataDirectory());
ASSERT_TRUE(wss_server.Start());
GURL::Replacements replacements;
replacements.SetSchemeStr("https");
GURL url =
wss_server.GetURL("connect_check.html").ReplaceComponents(replacements);
// Setup page title observer.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TitleWatcher watcher(tab, base::ASCIIToUTF16("PASS"));
watcher.AlsoWaitForTitle(base::ASCIIToUTF16("FAIL"));
// Add an entry into AutoSelectCertificateForUrls policy for automatic client
// cert selection.
Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
DCHECK(profile);
std::unique_ptr<base::DictionaryValue> setting =
std::make_unique<base::DictionaryValue>();
base::Value* filters = setting->SetKey("filters", base::ListValue());
base::DictionaryValue filter = base::DictionaryValue();
filter.SetString("ISSUER.CN", "pywebsocket");
filters->Append(std::move(filter));
HostContentSettingsMapFactory::GetForProfile(profile)
->SetWebsiteSettingDefaultScope(
url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
std::move(setting));
// Visit a HTTPS page which requires client certs.
ui_test_utils::NavigateToURL(browser(), url);
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// Test page runs a WebSocket wss connection test. The result will be shown
// as page title.
const base::string16 result = watcher.WaitAndGetTitle();
EXPECT_TRUE(base::LowerCaseEqualsASCII(result, "pass"));
}
#endif // defined(USE_NSS_CERTS)
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)
// A stub ClientCertStore that returns a FakeClientCertIdentity.
class ClientCertStoreStub : public net::ClientCertStore {
public:
explicit ClientCertStoreStub(net::ClientCertIdentityList list)
: list_(std::move(list)) {}
~ClientCertStoreStub() override {}
// net::ClientCertStore:
void GetClientCerts(const net::SSLCertRequestInfo& cert_request_info,
ClientCertListCallback callback) override {
std::move(callback).Run(std::move(list_));
}
private:
net::ClientCertIdentityList list_;
};
std::unique_ptr<net::ClientCertStore> CreateCertStore() {
base::FilePath certs_dir = net::GetTestCertsDirectory();
net::ClientCertIdentityList cert_identity_list;
{
base::ScopedAllowBlockingForTesting allow_blocking;
std::unique_ptr<net::FakeClientCertIdentity> cert_identity =
net::FakeClientCertIdentity::CreateFromCertAndKeyFiles(
certs_dir, "client_1.pem", "client_1.pk8");
EXPECT_TRUE(cert_identity.get());
if (cert_identity)
cert_identity_list.push_back(std::move(cert_identity));
}
return std::unique_ptr<net::ClientCertStore>(
new ClientCertStoreStub(std::move(cert_identity_list)));
}
std::unique_ptr<net::ClientCertStore> CreateFailSigningCertStore() {
base::FilePath certs_dir = net::GetTestCertsDirectory();
net::ClientCertIdentityList cert_identity_list;
{
base::ScopedAllowBlockingForTesting allow_blocking;
std::unique_ptr<net::FakeClientCertIdentity> cert_identity =
net::FakeClientCertIdentity::CreateFromCertAndFailSigning(
certs_dir, "client_1.pem");
EXPECT_TRUE(cert_identity.get());
if (cert_identity)
cert_identity_list.push_back(std::move(cert_identity));
}
return std::unique_ptr<net::ClientCertStore>(
new ClientCertStoreStub(std::move(cert_identity_list)));
}
std::unique_ptr<net::ClientCertStore> CreateEmptyCertStore() {
return std::unique_ptr<net::ClientCertStore>(new ClientCertStoreStub({}));
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrowserUseClientCertStore) {
// Make the browser use the ClientCertStoreStub instead of the regular one.
ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
->set_client_cert_store_factory_for_testing(
base::BindRepeating(&CreateCertStore));
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
net::SSLServerConfig ssl_config;
ssl_config.client_cert_type =
net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
https_server.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_server.Start());
GURL https_url =
https_server.GetURL("/ssl/browser_use_client_cert_store.html");
// Add an entry into AutoSelectCertificateForUrls policy for automatic client
// cert selection.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
DCHECK(profile);
std::unique_ptr<base::DictionaryValue> setting =
std::make_unique<base::DictionaryValue>();
base::Value* filters = setting->SetKey("filters", base::ListValue());
filters->Append(base::DictionaryValue());
HostContentSettingsMapFactory::GetForProfile(profile)
->SetWebsiteSettingDefaultScope(
https_url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
std::move(setting));
// Visit a HTTPS page which requires client certs.
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
https_url, 1);
EXPECT_EQ("pass", tab->GetLastCommittedURL().ref());
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestClientAuthSigningFails) {
// Make the browser use the ClientCertStoreStub instead of the regular one.
ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
->set_client_cert_store_factory_for_testing(
base::BindRepeating(&CreateFailSigningCertStore));
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
net::SSLServerConfig ssl_config;
ssl_config.client_cert_type =
net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
https_server.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_server.Start());
GURL https_url =
https_server.GetURL("/ssl/browser_use_client_cert_store.html");
// Add an entry into AutoSelectCertificateForUrls policy for automatic client
// cert selection.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
DCHECK(profile);
std::unique_ptr<base::DictionaryValue> setting =
std::make_unique<base::DictionaryValue>();
base::Value* filters = setting->SetKey("filters", base::ListValue());
filters->Append(base::DictionaryValue());
HostContentSettingsMapFactory::GetForProfile(profile)
->SetWebsiteSettingDefaultScope(
https_url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
std::move(setting));
// Visit a HTTPS page which requires client certs.
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
https_url, 1);
// Page should not load successfully.
EXPECT_EQ("", tab->GetLastCommittedURL().ref());
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestClientAuthContinueWithoutCert) {
// Make the browser use a ClientCertStoreStub that returns no certs.
ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
->set_client_cert_store_factory_for_testing(
base::BindRepeating(&CreateEmptyCertStore));
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
net::SSLServerConfig ssl_config;
ssl_config.client_cert_type =
net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
https_server.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_server.Start());
GURL https_url =
https_server.GetURL("/ssl/browser_use_client_cert_store.html");
// Visit a HTTPS page which requires client certs.
// The browser should automatically continue to the site without a client
// cert, since the ClientCertStore returns no certs.
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
https_url, 1);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Page should not load successfully.
EXPECT_EQ("", tab->GetLastCommittedURL().ref());
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestCertDBChangedFlushesClientAuthCache) {
// Make the browser use the ClientCertStoreStub instead of the regular one.
ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
->set_client_cert_store_factory_for_testing(
base::BindRepeating(&CreateCertStore));
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
net::SSLServerConfig ssl_config;
ssl_config.client_cert_type =
net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
https_server.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_server.Start());
GURL https_url =
https_server.GetURL("/ssl/browser_use_client_cert_store.html");
// Add an entry into AutoSelectCertificateForUrls policy for automatic client
// cert selection.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
DCHECK(profile);
std::unique_ptr<base::DictionaryValue> setting =
std::make_unique<base::DictionaryValue>();
base::Value* filters = setting->SetKey("filters", base::ListValue());
filters->Append(base::DictionaryValue());
HostContentSettingsMapFactory::GetForProfile(profile)
->SetWebsiteSettingDefaultScope(
https_url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
std::move(setting));
// Visit a HTTPS page which requires client certs.
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
https_url, 1);
EXPECT_EQ("pass", tab->GetLastCommittedURL().ref());
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
browser(), GURL("about:blank"), 1);
EXPECT_EQ("", tab->GetLastCommittedURL().ref());
// Now use a ClientCertStoreStub that always returns an empty list.
ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
->set_client_cert_store_factory_for_testing(
base::BindRepeating(&CreateEmptyCertStore));
// Visiting the page which requires client certs should still work (either
// due to the socket still being open, or due to the SSL client auth cache).
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
https_url, 1);
EXPECT_EQ("pass", tab->GetLastCommittedURL().ref());
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
browser(), GURL("about:blank"), 1);
EXPECT_EQ("", tab->GetLastCommittedURL().ref());
// Send a CertDBChanged notification.
net::CertDatabase::GetInstance()->NotifyObserversCertDBChanged();
// Visiting the page which requires client certs should fail, as the socket
// pool has been flushed and SSL client auth cache has been cleared due to
// the CertDBChanged observer.
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
https_url, 1);
EXPECT_EQ("", tab->GetLastCommittedURL().ref());
}
// Open a page with a HTTPS error in a tab with no prior navigation (through a
// link with a blank target). This is to test that the lack of navigation entry
// does not cause any problems (it was causing a crasher, see
// http://crbug.com/19941).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSErrorWithNoNavEntry) {
ASSERT_TRUE(https_server_expired_.Start());
const GURL url = https_server_expired_.GetURL("/ssl/google.htm");
WebContents* tab2 =
chrome::AddSelectedTabWithURL(browser(), url, ui::PAGE_TRANSITION_TYPED);
content::WaitForLoadStop(tab2);
// Verify our assumption that there was no prior navigation.
EXPECT_FALSE(chrome::CanGoBack(browser()));
// We should have an interstitial page showing.
WaitForInterstitial(tab2);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab2));
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestBadHTTPSDownload) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_expired_.Start());
GURL url_non_dangerous = embedded_test_server()->GetURL("/title1.html");
GURL url_dangerous =
https_server_expired_.GetURL("/downloads/dangerous/dangerous.exe");
// Visit a non-dangerous page.
ui_test_utils::NavigateToURL(browser(), url_non_dangerous);
// Now, start a transition to dangerous download.
{
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
NavigateParams navigate_params(browser(), url_dangerous,
ui::PAGE_TRANSITION_TYPED);
Navigate(&navigate_params);
observer.Wait();
}
// Proceed through the SSL interstitial. This doesn't use
// ProceedThroughInterstitial() since no page load will commit.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
{
// Wait for the download to complete after proceeding with the download.
// This serves to verify the download was initiated, and to let the
// test successfully shut down and cleanup. Exiting the browser with a
// download still in-progress can lead to test failues.
content::DownloadTestObserverTerminal dangerous_download_observer(
content::BrowserContext::GetDownloadManager(browser()->profile()), 1,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_ACCEPT);
SendInterstitialCommand(tab, security_interstitials::CMD_PROCEED);
dangerous_download_observer.WaitForFinished();
}
// There should still be an interstitial at this point. Press the
// back button on the browser. Note that this doesn't wait for a
// NAV_ENTRY_COMMITTED notification because going back with an
// active interstitial simply hides the interstitial.
ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(chrome::CanGoBack(browser()));
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
}
//
// Insecure content
//
// Visits a page that displays insecure content.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestDisplaysInsecureContent) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_content.html",
embedded_test_server()->host_port_pair());
// Load a page that displays insecure content.
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
ssl_test_util::CheckSecurityState(
browser()->tab_strip_model()->GetActiveWebContents(), CertError::NONE,
security_state::WARNING, AuthState::DISPLAYED_INSECURE_CONTENT);
}
// Visits a page that displays an insecure form.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestDisplaysInsecureForm) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_form.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
// If mixed form warnings are enabled, we display the lock icon on otherwise
// secure sites with an insecure form.
security_state::SecurityLevel expected_level =
base::FeatureList::IsEnabled(
security_interstitials::kInsecureFormSubmissionInterstitial) &&
base::FeatureList::IsEnabled(
autofill::features::kAutofillPreventMixedFormsFilling)
? security_state::SECURE
: security_state::NONE;
ssl_test_util::CheckSecurityState(
browser()->tab_strip_model()->GetActiveWebContents(), CertError::NONE,
expected_level, AuthState::DISPLAYED_FORM_WITH_INSECURE_ACTION);
}
// Verifies that an SSL interstitial generates SafeBrowsing extension api
// events.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestExtensionEvents) {
class ExtensionEventObserver : public extensions::EventRouter::TestObserver {
public:
ExtensionEventObserver() = default;
~ExtensionEventObserver() override = default;
// extensions::EventRouter::TestObserver:
void OnWillDispatchEvent(const extensions::Event& event) override {
event_names_.push_back(event.event_name);
}
void OnDidDispatchEventToProcess(const extensions::Event& event) override {}
const std::vector<std::string>& event_names() const { return event_names_; }
private:
std::vector<std::string> event_names_;
DISALLOW_COPY_AND_ASSIGN(ExtensionEventObserver);
};
ExtensionEventObserver observer;
extensions::EventRouter::Get(browser()->profile())
->AddObserverForTesting(&observer);
ASSERT_TRUE(https_server_expired_.Start());
GURL request_url = https_server_expired_.GetURL("/title1.html");
ui_test_utils::NavigateToURL(browser(), request_url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab != nullptr);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
// Verifies that security interstitial shown event is observed.
EXPECT_TRUE(base::Contains(observer.event_names(),
extensions::api::safe_browsing_private::
OnSecurityInterstitialShown::kEventName));
ProceedThroughInterstitial(tab);
// Verifies that security interstitial proceeded event is observed.
EXPECT_TRUE(base::Contains(observer.event_names(),
extensions::api::safe_browsing_private::
OnSecurityInterstitialProceeded::kEventName));
extensions::EventRouter::Get(browser()->profile())
->RemoveObserverForTesting(&observer);
}
// Test that a report is sent if the user closes the tab on an interstitial
// before making a decision to proceed or go back.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestBrokenHTTPSReportingCloseTab) {
ASSERT_TRUE(https_server_expired_.Start());
base::RunLoop run_loop;
certificate_reporting_test_utils::SSLCertReporterCallback reporter_callback(
&run_loop);
// Opt in to sending reports for invalid certificate chains.
certificate_reporting_test_utils::SetCertReportingOptIn(
browser(), certificate_reporting_test_utils::EXTENDED_REPORTING_OPT_IN);
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL("/title1.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab != nullptr);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
std::unique_ptr<SSLCertReporter> ssl_cert_reporter =
certificate_reporting_test_utils::CreateMockSSLCertReporter(
base::BindRepeating(&certificate_reporting_test_utils::
SSLCertReporterCallback::ReportSent,
base::Unretained(&reporter_callback)),
certificate_reporting_test_utils::CERT_REPORT_EXPECTED);
SSLBlockingPage* interstitial_page =
static_cast<SSLBlockingPage*>(GetInterstitialPage(tab));
ASSERT_TRUE(interstitial_page);
interstitial_page->SetSSLCertReporterForTesting(std::move(ssl_cert_reporter));
EXPECT_EQ(std::string(), reporter_callback.GetLatestHostnameReported());
// Leave the interstitial by closing the tab.
chrome::CloseWebContents(browser(), tab, false);
// Check that the mock reporter received a request to send a report.
run_loop.Run();
EXPECT_EQ(https_server_expired_.GetURL("/title1.html").host(),
reporter_callback.GetLatestHostnameReported());
}
// Test that if the user proceeds and the checkbox is checked, a report
// is sent or not sent depending on the Finch config.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestBrokenHTTPSProceedReporting) {
certificate_reporting_test_utils::ExpectReport expect_report =
certificate_reporting_test_utils::GetReportExpectedFromFinch();
TestBrokenHTTPSReporting(
certificate_reporting_test_utils::EXTENDED_REPORTING_OPT_IN,
SSL_INTERSTITIAL_PROCEED, expect_report, browser());
}
// Test that if the user goes back and the checkbox is checked, a report
// is sent or not sent depending on the Finch config.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestBrokenHTTPSGoBackReporting) {
certificate_reporting_test_utils::ExpectReport expect_report =
certificate_reporting_test_utils::GetReportExpectedFromFinch();
TestBrokenHTTPSReporting(
certificate_reporting_test_utils::EXTENDED_REPORTING_OPT_IN,
SSL_INTERSTITIAL_DO_NOT_PROCEED, expect_report, browser());
}
// User proceeds, checkbox is shown but unchecked. Reports should never
// be sent, regardless of Finch config.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestBrokenHTTPSProceedReportingWithNoOptIn) {
TestBrokenHTTPSReporting(
certificate_reporting_test_utils::EXTENDED_REPORTING_DO_NOT_OPT_IN,
SSL_INTERSTITIAL_PROCEED,
certificate_reporting_test_utils::CERT_REPORT_NOT_EXPECTED, browser());
}
// User goes back, checkbox is shown but unchecked. Reports should never
// be sent, regardless of Finch config.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestBrokenHTTPSGoBackShowYesCheckNoParamYesReportNo) {
TestBrokenHTTPSReporting(
certificate_reporting_test_utils::EXTENDED_REPORTING_DO_NOT_OPT_IN,
SSL_INTERSTITIAL_DO_NOT_PROCEED,
certificate_reporting_test_utils::CERT_REPORT_NOT_EXPECTED, browser());
}
// User proceeds, checkbox is not shown but checked -> we expect no
// report.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestBrokenHTTPSProceedShowNoCheckYesReportNo) {
if (base::FieldTrialList::FindFullName(
CertReportHelper::kFinchExperimentName) ==
CertReportHelper::kFinchGroupDontShowDontSend) {
TestBrokenHTTPSReporting(
certificate_reporting_test_utils::EXTENDED_REPORTING_OPT_IN,
SSL_INTERSTITIAL_PROCEED,
certificate_reporting_test_utils::CERT_REPORT_NOT_EXPECTED, browser());
}
}
// Browser is incognito, user proceeds, checkbox has previously opted in
// -> no report, regardless of Finch config.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestBrokenHTTPSInIncognitoReportNo) {
TestBrokenHTTPSReporting(
certificate_reporting_test_utils::EXTENDED_REPORTING_OPT_IN,
SSL_INTERSTITIAL_PROCEED,
certificate_reporting_test_utils::CERT_REPORT_NOT_EXPECTED,
CreateIncognitoBrowser());
}
// Test that reports don't get sent when extended reporting opt-in is
// disabled by policy.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestBrokenHTTPSNoReportingWhenDisallowed) {
browser()->profile()->GetPrefs()->SetBoolean(
prefs::kSafeBrowsingExtendedReportingOptInAllowed, false);
TestBrokenHTTPSReporting(
certificate_reporting_test_utils::EXTENDED_REPORTING_OPT_IN,
SSL_INTERSTITIAL_PROCEED,
certificate_reporting_test_utils::CERT_REPORT_NOT_EXPECTED, browser());
}
// Checkbox is shown but unchecked. Reports should never be sent, regardless of
// Finch config.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestBadClockReportingWithNoOptIn) {
TestBadClockReporting(
certificate_reporting_test_utils::EXTENDED_REPORTING_DO_NOT_OPT_IN,
certificate_reporting_test_utils::CERT_REPORT_NOT_EXPECTED, browser());
}
// Test that when the interstitial closes and the checkbox is checked, a report
// is sent or not sent depending on the Finch config.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestBadClockReportingWithOptIn) {
certificate_reporting_test_utils::ExpectReport expect_report =
certificate_reporting_test_utils::GetReportExpectedFromFinch();
TestBadClockReporting(
certificate_reporting_test_utils::EXTENDED_REPORTING_OPT_IN,
expect_report, browser());
}
// Test that when enhanced protection is on, hide the checkbox.
IN_PROC_BROWSER_TEST_F(SSLUITestWithExtendedReporting,
TestCheckboxHiddenInEnhancedProtection) {
ASSERT_TRUE(https_server_expired_.Start());
certificate_reporting_test_utils::SetCertReportingOptIn(
browser(), certificate_reporting_test_utils::EXTENDED_REPORTING_OPT_IN);
safe_browsing::SetEnhancedProtectionPrefForTests(
browser()->profile()->GetPrefs(), true);
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL("/title1.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab);
ExpectInterstitialElementHidden(tab, "extended-reporting-opt-in",
true /* expect_hidden */);
}
// Visits a page that runs insecure content and tries to suppress the insecure
// content warnings by randomizing location.hash.
// Based on http://crbug.com/8706
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRunsInsecuredContentRandomizeHash) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(
browser(), https_server_.GetURL("/ssl/page_runs_insecure_content.html"));
ssl_test_util::CheckAuthenticationBrokenState(
browser()->tab_strip_model()->GetActiveWebContents(), CertError::NONE,
AuthState::RAN_INSECURE_CONTENT);
}
// Visits an SSL page twice, once with subresources served over good SSL and
// once over bad SSL.
// - For the good SSL case, the iframe and images should be properly displayed.
// - For the bad SSL case, the iframe contents shouldn't be displayed and images
// and scripts should be filtered out entirely.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnsafeContents) {
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_expired_.Start());
// Enable popups without user gesture.
HostContentSettingsMapFactory::GetForProfile(browser()->profile())
->SetDefaultContentSetting(ContentSettingsType::POPUPS,
CONTENT_SETTING_ALLOW);
{
// First visit the page with its iframe and subresources served over good
// SSL. This is a sanity check to make sure these resources aren't already
// broken in the good case.
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_with_unsafe_contents.html", https_server_.host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// The state is expected to be authenticated.
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// The iframe should be able to open a popup.
EXPECT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
// In order to check that the image was loaded, check its width.
// The actual image (Google logo) is 276 pixels wide.
int img_width = 0;
EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
tab, "window.domAutomationController.send(ImageWidth());", &img_width));
EXPECT_EQ(img_width, 276);
// Check that variable |foo| is set.
bool js_result = false;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
tab, "window.domAutomationController.send(IsFooSet());", &js_result));
EXPECT_TRUE(js_result);
}
{
// Now visit the page with its iframe and subresources served over bad
// SSL. Iframes, images, and scripts should all be blocked.
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_with_unsafe_contents.html",
https_server_expired_.host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// When the bad content is filtered, the state is expected to be
// authenticated.
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// The iframe attempts to open a popup window, but it shouldn't be able to.
// Previous popup is still open.
EXPECT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
// The broken image width is zero.
int img_width = 99;
EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
tab, "window.domAutomationController.send(ImageWidth());", &img_width));
EXPECT_EQ(img_width, 16);
// Check that variable |foo| is not set.
bool js_result = false;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
tab, "window.domAutomationController.send(IsFooSet());", &js_result));
EXPECT_FALSE(js_result);
}
}
// Visits a page with insecure content loaded by JS (after the initial page
// load).
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
// flaky http://crbug.com/396462
#define MAYBE_TestDisplaysInsecureContentLoadedFromJS \
DISABLED_TestDisplaysInsecureContentLoadedFromJS
#else
#define MAYBE_TestDisplaysInsecureContentLoadedFromJS \
TestDisplaysInsecureContentLoadedFromJS
#endif
IN_PROC_BROWSER_TEST_F(SSLUITest,
MAYBE_TestDisplaysInsecureContentLoadedFromJS) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
net::HostPortPair replacement_pair = embedded_test_server()->host_port_pair();
replacement_pair.set_host("example.test");
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_with_dynamic_insecure_content.html", replacement_pair);
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// Load the insecure image.
bool js_result = false;
EXPECT_TRUE(
content::ExecuteScriptAndExtractBool(tab, "loadBadImage();", &js_result));
EXPECT_TRUE(js_result);
// We should now have insecure content.
ssl_test_util::CheckSecurityState(tab, CertError::NONE,
security_state::WARNING,
AuthState::DISPLAYED_INSECURE_CONTENT);
}
// Visits two pages from the same origin: one that displays insecure content and
// one that doesn't. The test checks that we do not propagate the insecure
// content state from one to the other.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestDisplaysInsecureContentTwoTabs) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/blank_page.html"));
WebContents* tab1 = browser()->tab_strip_model()->GetActiveWebContents();
// This tab should be fine.
ssl_test_util::CheckAuthenticatedState(tab1, AuthState::NONE);
// Create a new tab.
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_content.html",
embedded_test_server()->host_port_pair());
GURL url = https_server_.GetURL(replacement_path);
NavigateParams params(browser(), url, ui::PAGE_TRANSITION_TYPED);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
params.tabstrip_index = 0;
params.source_contents = tab1;
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
Navigate(&params);
WebContents* tab2 = params.navigated_or_inserted_contents;
observer.Wait();
// The new tab has insecure content.
ssl_test_util::CheckSecurityState(tab2, CertError::NONE,
security_state::WARNING,
AuthState::DISPLAYED_INSECURE_CONTENT);
// The original tab should not be contaminated.
ssl_test_util::CheckAuthenticatedState(tab1, AuthState::NONE);
}
// Visits two pages from the same origin: one that runs insecure content and one
// that doesn't. The test checks that we propagate the insecure content state
// from one to the other.
// TODO(crbug.com/1112300): Flaky
IN_PROC_BROWSER_TEST_F(SSLUITest, DISABLED_TestRunsInsecureContentTwoTabs) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/blank_page.html"));
WebContents* tab1 = browser()->tab_strip_model()->GetActiveWebContents();
// This tab should be fine.
ssl_test_util::CheckAuthenticatedState(tab1, AuthState::NONE);
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_runs_insecure_content.html",
embedded_test_server()->host_port_pair());
// Create a new tab in the same process. Using a NEW_FOREGROUND_TAB
// disposition won't usually stay in the same process, but this works
// because we are using process-per-site in SetUpCommandLine.
GURL url = https_server_.GetURL(replacement_path);
NavigateParams params(browser(), url, ui::PAGE_TRANSITION_TYPED);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
params.source_contents = tab1;
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
Navigate(&params);
WebContents* tab2 = params.navigated_or_inserted_contents;
observer.Wait();
// Both tabs should have the same process.
EXPECT_EQ(tab1->GetMainFrame()->GetProcess(),
tab2->GetMainFrame()->GetProcess());
// The new tab has insecure content.
ssl_test_util::CheckAuthenticationBrokenState(
tab2, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);
// Which means the origin for the first tab has also been contaminated with
// insecure content.
ssl_test_util::CheckAuthenticationBrokenState(
tab1, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);
}
// Visits a page with an image over http. Visits another page over https
// referencing that same image over http (hoping it is coming from the webcore
// memory cache).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestDisplaysCachedInsecureContent) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_content.html",
embedded_test_server()->host_port_pair());
// Load original page over HTTP.
const GURL url_http = embedded_test_server()->GetURL(replacement_path);
ui_test_utils::NavigateToURL(browser(), url_http);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
// Load again but over SSL. It should be marked as displaying insecure
// content (even though the image comes from the WebCore memory cache).
const GURL url_https = https_server_.GetURL(replacement_path);
ui_test_utils::NavigateToURL(browser(), url_https);
ssl_test_util::CheckSecurityState(tab, CertError::NONE,
security_state::WARNING,
AuthState::DISPLAYED_INSECURE_CONTENT);
}
// Visits a page with script over http. Visits another page over https
// referencing that same script over http (hoping it is coming from the webcore
// memory cache).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRunsCachedInsecureContent) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_runs_insecure_content.html",
embedded_test_server()->host_port_pair());
// Load original page over HTTP.
const GURL url_http = embedded_test_server()->GetURL(replacement_path);
ui_test_utils::NavigateToURL(browser(), url_http);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
// Load again but over SSL. It should be marked as displaying insecure
// content (even though the image comes from the WebCore memory cache).
const GURL url_https = https_server_.GetURL(replacement_path);
ui_test_utils::NavigateToURL(browser(), url_https);
ssl_test_util::CheckAuthenticationBrokenState(
tab, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);
}
// This test ensures the CN invalid status does not 'stick' to a certificate
// (see bug #1044942) and that it depends on the host-name.
// Test if disabled due to flakiness http://crbug.com/368280 .
IN_PROC_BROWSER_TEST_F(SSLUITest, DISABLED_TestCNInvalidStickiness) {
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_mismatched_.Start());
// First we hit the server with hostname, this generates an invalid policy
// error.
ui_test_utils::NavigateToURL(
browser(), https_server_mismatched_.GetURL("/ssl/google.html"));
// We get an interstitial page as a result.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_COMMON_NAME_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);
// Now we try again with the right host name this time.
GURL url(https_server_.GetURL("/ssl/google.html"));
ui_test_utils::NavigateToURL(browser(), url);
// Security state should be OK.
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// Now try again the broken one to make sure it is still broken.
ui_test_utils::NavigateToURL(
browser(), https_server_mismatched_.GetURL("/ssl/google.html"));
// Since we OKed the interstitial last time, we get right to the page.
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);
}
// Test that navigating to a #ref does not change a bad security state.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRefNavigation) {
ASSERT_TRUE(https_server_expired_.Start());
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/page_with_refs.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
// Now navigate to a ref in the page, the security state should not have
// changed.
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/page_with_refs.html#jp"));
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
}
// Tests that closing a page that opened a pop-up with an interstitial does not
// crash the browser (crbug.com/1966).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestCloseTabWithUnsafePopup) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_expired_.Start());
// Enable popups without user gesture.
HostContentSettingsMapFactory::GetForProfile(browser()->profile())
->SetDefaultContentSetting(ContentSettingsType::POPUPS,
CONTENT_SETTING_ALLOW);
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_with_unsafe_popup.html",
https_server_expired_.host_port_pair());
WebContents* tab1 = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(
https_server_expired_.GetURL("/ssl/bad_iframe.html"));
nav_observer.StartWatchingNewWebContents();
ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(replacement_path));
ASSERT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
// Last activated browser should be the popup.
Browser* popup_browser = chrome::FindBrowserWithProfile(browser()->profile());
WebContents* popup = popup_browser->tab_strip_model()->GetActiveWebContents();
EXPECT_NE(popup, tab1);
nav_observer.Wait();
WaitForInterstitial(popup);
ASSERT_TRUE(popup->GetController().GetVisibleEntry());
EXPECT_EQ(https_server_expired_.GetURL("/ssl/bad_iframe.html"),
popup->GetController().GetVisibleEntry()->GetURL());
EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(popup));
// Add another tab to make sure the browser does not exit when we close
// the first tab.
GURL url = embedded_test_server()->GetURL("/ssl/google.html");
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
chrome::AddSelectedTabWithURL(browser(), url, ui::PAGE_TRANSITION_TYPED);
observer.Wait();
// Close the first tab.
chrome::CloseWebContents(browser(), tab1, false);
}
// Visit a page over bad https that is a redirect to a page with good https.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectBadToGoodHTTPS) {
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_expired_.Start());
GURL url1 = https_server_expired_.GetURL("/server-redirect?");
GURL url2 = https_server_.GetURL("/ssl/google.html");
ui_test_utils::NavigateToURL(browser(), GURL(url1.spec() + url2.spec()));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
// We have been redirected to the good page.
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Visit a page over good https that is a redirect to a page with bad https.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectGoodToBadHTTPS) {
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_expired_.Start());
GURL url1 = https_server_.GetURL("/server-redirect?");
GURL url2 = https_server_expired_.GetURL("/ssl/google.html");
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(), GURL(url1.spec() + url2.spec()));
WaitForInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
}
// Visit a page over http that is a redirect to a page with good HTTPS.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectHTTPToGoodHTTPS) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// HTTP redirects to good HTTPS.
GURL http_url = embedded_test_server()->GetURL("/server-redirect?");
GURL good_https_url = https_server_.GetURL("/ssl/google.html");
ui_test_utils::NavigateToURL(browser(),
GURL(http_url.spec() + good_https_url.spec()));
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Visit a page over http that is a redirect to a page with bad HTTPS.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectHTTPToBadHTTPS) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
const GURL http_url = embedded_test_server()->GetURL("/server-redirect?");
const GURL bad_https_url = https_server_expired_.GetURL("/ssl/google.html");
ui_test_utils::NavigateToURL(browser(),
GURL(http_url.spec() + bad_https_url.spec()));
WaitForInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
}
// Visit a page over https that is a redirect to a page with http (to make sure
// we don't keep the secure state).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectHTTPSToHTTP) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
GURL https_url = https_server_.GetURL("/server-redirect?");
GURL http_url = embedded_test_server()->GetURL("/ssl/google.html");
ui_test_utils::NavigateToURL(browser(),
GURL(https_url.spec() + http_url.spec()));
ssl_test_util::CheckUnauthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}
// Visit a page over https that is a redirect to a non-existent page with http
// (to make sure we don't keep the secure state when redirecting to an error).
// Regression test for crbug.com/1154754.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectHTTPSToInvalidHTTP) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
GURL https_url = https_server_.GetURL("/server-redirect?");
// Test runners might have servers listening in localhost, and the test
// constructor routes all URLs to localhost, so use close-socket to make
// sure we always get an error page.
GURL invalid_url = embedded_test_server()->GetURL("/close-socket");
ui_test_utils::NavigateToURL(browser(),
GURL(https_url.spec() + invalid_url.spec()));
auto* helper = SecurityStateTabHelper::FromWebContents(
browser()->tab_strip_model()->GetActiveWebContents());
// Check we don't keep the previous certificate state around.
EXPECT_FALSE(helper->GetVisibleSecurityState()->certificate);
EXPECT_EQ(helper->GetSecurityLevel(), security_state::SecurityLevel::NONE);
}
class SSLUITestWaitForDOMNotification : public SSLUITestIgnoreCertErrors,
public content::NotificationObserver {
public:
SSLUITestWaitForDOMNotification()
: SSLUITestIgnoreCertErrors(), run_loop_(nullptr) {}
~SSLUITestWaitForDOMNotification() override { registrar_.RemoveAll(); }
void SetUpOnMainThread() override {
SSLUITestIgnoreCertErrors::SetUpOnMainThread();
registrar_.Add(this, content::NOTIFICATION_DOM_OPERATION_RESPONSE,
content::NotificationService::AllSources());
}
void set_expected_notification(const std::string& expected_notification) {
expected_notification_ = expected_notification;
}
void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; }
// content::NotificationObserver
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
DCHECK(run_loop_);
if (type == content::NOTIFICATION_DOM_OPERATION_RESPONSE) {
content::Details<std::string> dom_op_result(details);
if (*dom_op_result.ptr() == expected_notification_) {
run_loop_->Quit();
}
}
}
private:
content::NotificationRegistrar registrar_;
std::string expected_notification_;
base::RunLoop* run_loop_;
DISALLOW_COPY_AND_ASSIGN(SSLUITestWaitForDOMNotification);
};
// Tests that a mixed resource which includes HTTP in the redirect chain
// is marked as mixed content, even if the end result is HTTPS.
IN_PROC_BROWSER_TEST_F(SSLUITestWaitForDOMNotification,
TestMixedContentWithHTTPInRedirectChain) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/blank_page.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// Construct a URL which will be dynamically added to the page as an
// image. The URL redirects through HTTP, though it ends up at an
// HTTPS resource.
GURL http_url = embedded_test_server()->GetURL("/server-redirect?");
GURL::Replacements http_url_replacements;
// Be sure to use a non-localhost name for the mixed content request,
// since local hostnames are not considered mixed content.
http_url_replacements.SetHostStr("example.test");
std::string http_url_query =
EncodeQuery(https_server_.GetURL("/ssl/google_files/logo.gif").spec());
http_url_replacements.SetQueryStr(http_url_query);
http_url = http_url.ReplaceComponents(http_url_replacements);
GURL https_url = https_server_.GetURL("/server-redirect?");
GURL::Replacements https_url_replacements;
std::string https_url_query = EncodeQuery(http_url.spec());
https_url_replacements.SetQueryStr(https_url_query);
https_url = https_url.ReplaceComponents(https_url_replacements);
base::RunLoop run_loop;
// Load the image. It starts at |https_server_|, which redirects to an
// embedded_test_server() HTTP URL, which redirects back to
// |https_server_| for the final HTTPS image. Because the redirect
// chain passes through HTTP, the page should be marked as mixed
// content.
set_expected_notification("\"mixed-image-loaded\"");
set_run_loop(&run_loop);
ASSERT_TRUE(content::ExecuteScript(
tab,
"var loaded = function () {"
" window.domAutomationController.send('mixed-image-loaded');"
"};"
"var img = document.createElement('img');"
"img.onload = loaded;"
"img.src = '" +
https_url.spec() +
"';"
"document.body.appendChild(img);"));
run_loop.Run();
ssl_test_util::CheckSecurityState(tab, CertError::NONE,
security_state::WARNING,
AuthState::DISPLAYED_INSECURE_CONTENT);
}
// Visits a page to which we could not connect (bad port) over http and https
// and make sure the security style is correct.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestConnectToBadPort) {
ui_test_utils::NavigateToURL(browser(), GURL("http://localhost:17"));
ssl_test_util::CheckUnauthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(),
AuthState::SHOWING_ERROR);
// Same thing over HTTPS.
ui_test_utils::NavigateToURL(browser(), GURL("https://localhost:17"));
ssl_test_util::CheckUnauthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(),
AuthState::SHOWING_ERROR);
}
//
// Frame navigation
//
// From a good HTTPS top frame:
// - navigate to an OK HTTPS frame
// - navigate to a bad HTTPS (expect unsafe content and filtered frame), then
// back
// - navigate to HTTP (expect insecure content), then back
IN_PROC_BROWSER_TEST_F(SSLUITest, TestGoodFrameNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_expired_.Start());
// SetUpOnMainThread adds this hostname to the resolver so that it's not
// blocked (browser_test_base.cc has a resolver that blocks all non-local
// hostnames by default to ensure tests don't hit the network). This is
// critical to do because the request would otherwise get cancelled in the
// browser before the renderer sees it.
std::string top_frame_path = GetTopFramePath(
*embedded_test_server(), https_server_, https_server_expired_);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(), https_server_.GetURL(top_frame_path));
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
bool success = false;
// Now navigate inside the frame.
{
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
tab, "window.domAutomationController.send(clickLink('goodHTTPSLink'));",
&success));
ASSERT_TRUE(success);
observer.Wait();
}
// We should still be fine.
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// Now let's hit a bad page.
{
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
tab, "window.domAutomationController.send(clickLink('badHTTPSLink'));",
&success));
ASSERT_TRUE(success);
observer.Wait();
}
// The security style should still be secure.
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// And the frame should be blocked.
bool is_content_evil = true;
content::RenderFrameHost* content_frame = content::FrameMatchingPredicate(
tab, base::BindRepeating(&content::FrameMatchesName, "contentFrame"));
std::string is_evil_js(
"window.domAutomationController.send("
"document.getElementById('evilDiv') != null);");
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(content_frame, is_evil_js,
&is_content_evil));
EXPECT_FALSE(is_content_evil);
// Now go back, our state should still be OK.
{
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
tab->GetController().GoBack();
observer.Wait();
}
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// Navigate to a page served over HTTP.
{
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
tab, "window.domAutomationController.send(clickLink('HTTPLink'));",
&success));
ASSERT_TRUE(success);
observer.Wait();
}
// Our state should be unauthenticated (in the ran mixed script sense). Note
// this also displays images from the http page (google.com).
ssl_test_util::CheckAuthenticationBrokenState(
tab, CertError::NONE,
AuthState::RAN_INSECURE_CONTENT | AuthState::DISPLAYED_INSECURE_CONTENT |
AuthState::DISPLAYED_FORM_WITH_INSECURE_ACTION);
// Go back, our state should be unchanged.
{
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
tab->GetController().GoBack();
observer.Wait();
}
ssl_test_util::CheckAuthenticationBrokenState(
tab, CertError::NONE,
AuthState::RAN_INSECURE_CONTENT | AuthState::DISPLAYED_INSECURE_CONTENT |
AuthState::DISPLAYED_FORM_WITH_INSECURE_ACTION);
}
// From a bad HTTPS top frame:
// - navigate to an OK HTTPS frame (expected to be still authentication broken).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestBadFrameNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_expired_.Start());
std::string top_frame_path = GetTopFramePath(
*embedded_test_server(), https_server_, https_server_expired_);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL(top_frame_path));
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
// Navigate to a good frame.
bool success = false;
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
tab, "window.domAutomationController.send(clickLink('goodHTTPSLink'));",
&success));
ASSERT_TRUE(success);
observer.Wait();
// We should still be authentication broken.
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
}
// From an HTTP top frame, navigate to good and bad HTTPS (security state should
// stay unauthenticated).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnauthenticatedFrameNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_expired_.Start());
std::string top_frame_path = GetTopFramePath(
*embedded_test_server(), https_server_, https_server_expired_);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(),
embedded_test_server()->GetURL(top_frame_path));
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
// Now navigate inside the frame to a secure HTTPS frame.
{
bool success = false;
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
tab, "window.domAutomationController.send(clickLink('goodHTTPSLink'));",
&success));
ASSERT_TRUE(success);
observer.Wait();
}
// We should still be unauthenticated.
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
// Now navigate to a bad HTTPS frame.
{
bool success = false;
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
tab, "window.domAutomationController.send(clickLink('badHTTPSLink'));",
&success));
ASSERT_TRUE(success);
observer.Wait();
}
// State should not have changed.
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
// And the frame should have been blocked (see bug #2316).
bool is_content_evil = true;
content::RenderFrameHost* content_frame = content::FrameMatchingPredicate(
tab, base::BindRepeating(&content::FrameMatchesName, "contentFrame"));
std::string is_evil_js(
"window.domAutomationController.send("
"document.getElementById('evilDiv') != null);");
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(content_frame, is_evil_js,
&is_content_evil));
EXPECT_FALSE(is_content_evil);
}
enum class OffMainThreadFetchMode { kEnabled, kDisabled };
enum class SSLUIWorkerFetchTestType { kUseFetch, kUseImportScripts };
class SSLUIWorkerFetchTest
: public testing::WithParamInterface<SSLUIWorkerFetchTestType>,
public SSLUITestBase {
public:
SSLUIWorkerFetchTest() {
EXPECT_TRUE(tmp_dir_.CreateUniqueTempDir());
}
~SSLUIWorkerFetchTest() override {}
void SetUpOnMainThread() override {
SSLUITestBase::SetUpOnMainThread();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
SSLUITestBase::SetUpCommandLine(command_line);
scoped_feature_list_.InitAndDisableFeature(
blink::features::kMixedContentAutoupgrade);
}
protected:
void WriteFile(const base::FilePath::StringType& filename,
base::StringPiece contents) {
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::WriteFile(tmp_dir_.GetPath().Append(filename), contents));
}
void WriteTestFiles(const net::EmbeddedTestServer& remote_server,
const std::string& hostname) {
WriteFile(FILE_PATH_LITERAL("worker_test.html"),
"<script>"
"var worker = new Worker('worker.js');"
"worker.addEventListener("
" 'message',"
" event => { document.title = event.data; });"
"</script>");
switch (GetParam()) {
case SSLUIWorkerFetchTestType::kUseFetch:
WriteFile(FILE_PATH_LITERAL("worker_test_data.txt.mock-http-headers"),
"HTTP/1.1 200 OK\n"
"Content-Type: text/plain\n"
"Access-Control-Allow-Origin: *");
WriteFile(FILE_PATH_LITERAL("worker_test_data.txt"), "LOADED");
WriteFile(FILE_PATH_LITERAL("worker.js"),
base::StringPrintf(
"fetch('%s')"
" .then(res => res.text())"
" .then(text => postMessage(text))"
" .catch(_ => postMessage('FAILED'))",
remote_server.GetURL(hostname, "/worker_test_data.txt")
.spec()
.c_str()));
break;
case SSLUIWorkerFetchTestType::kUseImportScripts: {
WriteFile(FILE_PATH_LITERAL("imported.js"), "data = 'LOADED';");
WriteFile(
FILE_PATH_LITERAL("worker.js"),
base::StringPrintf(
"var data = 'FAILED';"
"try {"
" importScripts('%s')"
"} catch(e) {}"
"postMessage(data);",
remote_server.GetURL(hostname, "/imported.js").spec().c_str()));
} break;
}
}
void RunMixedContentSettingsTest(
ChromeContentBrowserClientForMixedContentTest* browser_client,
bool allow_running_insecure_content,
bool strict_mixed_content_checking,
bool strictly_block_blockable_mixed_content,
bool expected_load,
bool expected_show_blocked,
bool expected_show_dangerous,
bool expected_load_after_allow,
bool expected_show_blocked_after_allow,
bool expected_show_dangerous_after_allow) {
SCOPED_TRACE(
::testing::Message()
<< "RunMixedContentSettingsTest :"
<< "allow_running_insecure_content="
<< (allow_running_insecure_content ? "true " : "false ")
<< "strict_mixed_content_checking="
<< (strict_mixed_content_checking ? "true " : "false ")
<< "strictly_block_blockable_mixed_content="
<< (strictly_block_blockable_mixed_content ? "true " : "false "));
// Run the tests in a new tab. This forces each call of
// RunMixedContentSettingsTest in a single test case to use different tabs
// and thus different processes, bypassing a subtle race condition where
// processes can get re-used under Site Isolation and retain their mixed
// content status (see crbug.com/890372). This ensures all error state is
// cleared.
chrome::NewTab(browser());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(tab));
CheckErrorStateIsCleared();
browser_client->SetMixedContentSettings(
allow_running_insecure_content, strict_mixed_content_checking,
strictly_block_blockable_mixed_content);
tab->OnWebPreferencesChanged();
CheckMixedContentSettings(allow_running_insecure_content,
strict_mixed_content_checking,
strictly_block_blockable_mixed_content);
const base::string16 loaded_title = base::ASCIIToUTF16("LOADED");
const base::string16 failed_title = base::ASCIIToUTF16("FAILED");
{
// First load.
content::TitleWatcher watcher(tab, loaded_title);
watcher.AlsoWaitForTitle(failed_title);
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/worker_test.html"));
EXPECT_EQ(expected_load ? loaded_title : failed_title,
watcher.WaitAndGetTitle());
}
EXPECT_EQ(expected_show_blocked,
content_settings::PageSpecificContentSettings::GetForFrame(
tab->GetMainFrame())
->IsContentBlocked(ContentSettingsType::MIXEDSCRIPT));
ssl_test_util::CheckSecurityState(
tab, CertError::NONE,
expected_show_dangerous ? security_state::DANGEROUS
: security_state::SECURE,
expected_show_dangerous ? AuthState::RAN_INSECURE_CONTENT
: AuthState::NONE);
// Clears title.
ASSERT_TRUE(
content::ExecuteScript(tab->GetMainFrame(), "document.title = \"\";"));
{
// SetAllowRunningInsecureContent will reload the page.
content::TitleWatcher watcher(tab, loaded_title);
watcher.AlsoWaitForTitle(failed_title);
SetAllowRunningInsecureContent();
tab->OnWebPreferencesChanged();
EXPECT_EQ(expected_load_after_allow ? loaded_title : failed_title,
watcher.WaitAndGetTitle());
}
EXPECT_EQ(expected_show_blocked_after_allow,
content_settings::PageSpecificContentSettings::GetForFrame(
tab->GetMainFrame())
->IsContentBlocked(ContentSettingsType::MIXEDSCRIPT));
ssl_test_util::CheckSecurityState(
tab, CertError::NONE,
expected_show_dangerous_after_allow ? security_state::DANGEROUS
: security_state::SECURE,
expected_show_dangerous_after_allow ? AuthState::RAN_INSECURE_CONTENT
: AuthState::NONE);
chrome::CloseTab(browser());
}
base::ScopedTempDir tmp_dir_;
private:
void SetAllowRunningInsecureContent() {
content::RenderFrameHost* render_frame_host =
browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
mojo::AssociatedRemote<content_settings::mojom::ContentSettingsAgent> agent;
render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&agent);
agent->SetAllowRunningInsecureContent();
}
void CheckErrorStateIsCleared() {
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(content_settings::PageSpecificContentSettings::GetForFrame(
tab->GetMainFrame())
->IsContentBlocked(ContentSettingsType::MIXEDSCRIPT));
ssl_test_util::CheckSecurityState(tab, CertError::NONE,
security_state::NONE, AuthState::NONE);
EXPECT_FALSE(SecurityStateTabHelper::FromWebContents(tab)
->GetVisibleSecurityState()
->ran_mixed_content);
}
void CheckMixedContentSettings(bool allow_running_insecure_content,
bool strict_mixed_content_checking,
bool strictly_block_blockable_mixed_content) {
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
const blink::web_pref::WebPreferences& prefs =
tab->GetOrCreateWebPreferences();
ASSERT_EQ(prefs.strictly_block_blockable_mixed_content,
strictly_block_blockable_mixed_content);
ASSERT_EQ(prefs.allow_running_insecure_content,
allow_running_insecure_content);
ASSERT_EQ(prefs.strict_mixed_content_checking,
strict_mixed_content_checking);
}
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(SSLUIWorkerFetchTest);
};
IN_PROC_BROWSER_TEST_P(SSLUIWorkerFetchTest,
TestUnsafeContentsInWorkerFiltered) {
https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
https_server_expired_.ServeFilesFromDirectory(tmp_dir_.GetPath());
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_expired_.Start());
WriteTestFiles(https_server_expired_, "localhost");
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
const base::string16 loaded_title = base::ASCIIToUTF16("LOADED");
const base::string16 failed_title = base::ASCIIToUTF16("FAILED");
content::TitleWatcher watcher(tab, loaded_title);
watcher.AlsoWaitForTitle(failed_title);
// This page will spawn a Worker which will try to load content from
// BadCertServer.
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/worker_test.html"));
// Expect Worker not to load insecure content.
EXPECT_EQ(failed_title, watcher.WaitAndGetTitle());
// The bad content is filtered, expect the state to be authenticated.
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// This test, and the related test TestUnsafeContentsWithUserException, verify
// that if unsafe content is loaded but the host of that unsafe content has a
// user exception, the content runs and the security style is downgraded.
// TODO(crbug.com/1107659): Disabled due to flakiness.
IN_PROC_BROWSER_TEST_P(SSLUIWorkerFetchTest,
DISABLED_TestUnsafeContentsInWorkerWithUserException) {
https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
https_server_mismatched_.ServeFilesFromDirectory(tmp_dir_.GetPath());
ASSERT_TRUE(https_server_.Start());
// Note that it is necessary to user https_server_mismatched_ here over the
// other invalid cert servers. This is because the test relies on the two
// servers having different hosts since SSL exceptions are per-host, not per
// origin, and https_server_mismatched_ uses 'localhost' rather than
// '127.0.0.1'.
ASSERT_TRUE(https_server_mismatched_.Start());
WriteTestFiles(https_server_mismatched_, "localhost");
// Navigate to an unsafe site. Proceed with interstitial page to indicate
// the user approves the bad certificate.
ui_test_utils::NavigateToURL(
browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_COMMON_NAME_INVALID,
AuthState::SHOWING_INTERSTITIAL);
content::TestNavigationObserver nav_observer(tab, 1);
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->CommandReceived(
base::NumberToString(security_interstitials::CMD_PROCEED));
nav_observer.Wait();
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);
SecurityStateTabHelper* tab_helper =
SecurityStateTabHelper::FromWebContents(tab);
ASSERT_TRUE(tab_helper);
std::unique_ptr<security_state::VisibleSecurityState> visible_security_state =
tab_helper->GetVisibleSecurityState();
EXPECT_FALSE(visible_security_state->ran_mixed_content);
EXPECT_FALSE(visible_security_state->displayed_mixed_content);
EXPECT_FALSE(visible_security_state->ran_content_with_cert_errors);
EXPECT_FALSE(visible_security_state->displayed_content_with_cert_errors);
const base::string16 loaded_title = base::ASCIIToUTF16("LOADED");
const base::string16 failed_title = base::ASCIIToUTF16("FAILED");
content::TitleWatcher watcher(tab, loaded_title);
watcher.AlsoWaitForTitle(failed_title);
// Navigate to safe page that has Worker loading unsafe content.
// Expect content to load but be marked as auth broken due to running insecure
// content.
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/worker_test.html"));
// Worker loads insecure content
EXPECT_EQ(loaded_title, watcher.WaitAndGetTitle());
ssl_test_util::CheckAuthenticationBrokenState(tab, CertError::NONE,
AuthState::NONE);
visible_security_state = tab_helper->GetVisibleSecurityState();
EXPECT_FALSE(visible_security_state->ran_mixed_content);
EXPECT_FALSE(visible_security_state->displayed_mixed_content);
EXPECT_TRUE(visible_security_state->ran_content_with_cert_errors);
EXPECT_FALSE(visible_security_state->displayed_content_with_cert_errors);
}
// This test checks the behavior of mixed content blocking for the requests
// from a dedicated worker by changing the settings in WebPreferences
// with allow_running_insecure_content = true.
// Flaky. See https://crbug.com/1145674.
IN_PROC_BROWSER_TEST_P(
SSLUIWorkerFetchTest,
DISABLED_MixedContentSettings_AllowRunningInsecureContent) {
ChromeContentBrowserClientForMixedContentTest browser_client;
content::ContentBrowserClient* old_browser_client =
content::SetBrowserClientForTesting(&browser_client);
https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
embedded_test_server()->ServeFilesFromDirectory(tmp_dir_.GetPath());
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(embedded_test_server()->Start());
WriteTestFiles(*embedded_test_server(), "example.com");
for (bool strict_mixed_content_checking : {true, false}) {
for (bool strictly_block_blockable_mixed_content : {true, false}) {
if (strict_mixed_content_checking) {
RunMixedContentSettingsTest(
&browser_client, true /* allow_running_insecure_content */,
strict_mixed_content_checking,
strictly_block_blockable_mixed_content, false /* expected_load */,
false /* expected_show_blocked */,
false /* expected_show_dangerous */,
false /* expected_load_after_allow */,
false /* expected_show_blocked_after_allow */,
false /* expected_show_dangerous_after_allow */);
} else {
RunMixedContentSettingsTest(
&browser_client, true /* allow_running_insecure_content */,
strict_mixed_content_checking,
strictly_block_blockable_mixed_content, true /* expected_load */,
false /* expected_show_blocked */,
true /* expected_show_dangerous */,
true /* expected_load_after_allow */,
false /* expected_show_blocked_after_allow */,
true /* expected_show_dangerous_after_allow */);
}
}
}
content::SetBrowserClientForTesting(old_browser_client);
}
// This test checks the behavior of mixed content blocking for the requests
// from a dedicated worker by changing the settings in WebPreferences
// with allow_running_insecure_content = false.
// Disabled due to being flaky. crbug.com/1116670
#if defined(OS_MAC)
#define MAYBE_MixedContentSettings_DisallowRunningInsecureContent \
DISABLED_MixedContentSettings_DisallowRunningInsecureContent
#else
#define MAYBE_MixedContentSettings_DisallowRunningInsecureContent \
MixedContentSettings_DisallowRunningInsecureContent
#endif
IN_PROC_BROWSER_TEST_P(
SSLUIWorkerFetchTest,
MAYBE_MixedContentSettings_DisallowRunningInsecureContent) {
ChromeContentBrowserClientForMixedContentTest browser_client;
content::ContentBrowserClient* old_browser_client =
content::SetBrowserClientForTesting(&browser_client);
https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
embedded_test_server()->ServeFilesFromDirectory(tmp_dir_.GetPath());
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(embedded_test_server()->Start());
WriteTestFiles(*embedded_test_server(), "example.com");
for (bool strict_mixed_content_checking : {true, false}) {
for (bool strictly_block_blockable_mixed_content : {true, false}) {
if (strict_mixed_content_checking) {
RunMixedContentSettingsTest(
&browser_client, false /* allow_running_insecure_content */,
strict_mixed_content_checking,
strictly_block_blockable_mixed_content, false /* expected_load */,
false /* expected_show_blocked */,
false /* expected_show_dangerous */,
false /* expected_load_after_allow */,
false /* expected_show_blocked_after_allow */,
false /* expected_show_dangerous_after_allow */);
} else if (strictly_block_blockable_mixed_content) {
RunMixedContentSettingsTest(
&browser_client, false /* allow_running_insecure_content */,
strict_mixed_content_checking,
strictly_block_blockable_mixed_content, false /* expected_load */,
false /* expected_show_blocked */,
false /* expected_show_dangerous */,
false /* expected_load_after_allow */,
false /* expected_show_blocked_after_allow */,
false /* expected_show_dangerous_after_allow */);
} else {
RunMixedContentSettingsTest(
&browser_client, false /* allow_running_insecure_content */,
strict_mixed_content_checking,
strictly_block_blockable_mixed_content, false /* expected_load */,
true /* expected_show_blocked */,
false /* expected_show_dangerous */,
true /* expected_load_after_allow */,
false /* expected_show_blocked_after_allow */,
true /* expected_show_dangerous_after_allow */);
}
}
}
content::SetBrowserClientForTesting(old_browser_client);
}
// This test checks that all mixed content requests from a dedicated worker are
// blocked regardless of the settings in WebPreferences when
// block-all-mixed-content CSP is set with allow_running_insecure_content=true.
IN_PROC_BROWSER_TEST_P(
SSLUIWorkerFetchTest,
MixedContentSettingsWithBlockingCSP_AllowRunningInsecureContent) {
ChromeContentBrowserClientForMixedContentTest browser_client;
content::ContentBrowserClient* old_browser_client =
content::SetBrowserClientForTesting(&browser_client);
https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
embedded_test_server()->ServeFilesFromDirectory(tmp_dir_.GetPath());
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(embedded_test_server()->Start());
WriteTestFiles(*embedded_test_server(), "example.com");
WriteFile(FILE_PATH_LITERAL("worker_test.html.mock-http-headers"),
"HTTP/1.1 200 OK\n"
"Content-Type: text/html\n"
"Content-Security-Policy: block-all-mixed-content;");
for (bool strict_mixed_content_checking : {true, false}) {
for (bool strictly_block_blockable_mixed_content : {true, false}) {
RunMixedContentSettingsTest(
&browser_client, true /* allow_running_insecure_content */,
strict_mixed_content_checking, strictly_block_blockable_mixed_content,
false /* expected_load */, false /* expected_show_blocked */,
false /* expected_show_dangerous */,
false /* expected_load_after_allow */,
false /* expected_show_blocked_after_allow */,
false /* expected_show_dangerous_after_allow */);
}
}
content::SetBrowserClientForTesting(old_browser_client);
}
// This test checks that all mixed content requests from a dedicated worker are
// blocked regardless of the settings in WebPreferences when
// block-all-mixed-content CSP is set with allow_running_insecure_content=false.
IN_PROC_BROWSER_TEST_P(
SSLUIWorkerFetchTest,
MixedContentSettingsWithBlockingCSP_DisallowRunningInsecureContent) {
ChromeContentBrowserClientForMixedContentTest browser_client;
content::ContentBrowserClient* old_browser_client =
content::SetBrowserClientForTesting(&browser_client);
https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
embedded_test_server()->ServeFilesFromDirectory(tmp_dir_.GetPath());
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(embedded_test_server()->Start());
WriteTestFiles(*embedded_test_server(), "example.com");
WriteFile(FILE_PATH_LITERAL("worker_test.html.mock-http-headers"),
"HTTP/1.1 200 OK\n"
"Content-Type: text/html\n"
"Content-Security-Policy: block-all-mixed-content;");
for (bool strict_mixed_content_checking : {true, false}) {
for (bool strictly_block_blockable_mixed_content : {true, false}) {
RunMixedContentSettingsTest(
&browser_client, false /* allow_running_insecure_content */,
strict_mixed_content_checking, strictly_block_blockable_mixed_content,
false /* expected_load */, false /* expected_show_blocked */,
false /* expected_show_dangerous */,
false /* expected_load_after_allow */,
false /* expected_show_blocked_after_allow */,
false /* expected_show_dangerous_after_allow */);
}
}
content::SetBrowserClientForTesting(old_browser_client);
}
// This test checks that all mixed content requests from a dedicated worker
// which is started from a subframe are blocked if
// allow_running_insecure_content setting is false or
// strict_mixed_content_checking setting is true.
// TODO(carlosil): Re-enable to check if this triggers flakiness due to
// committed interstitials.
IN_PROC_BROWSER_TEST_P(SSLUIWorkerFetchTest, DISABLED_MixedContentSubFrame) {
ChromeContentBrowserClientForMixedContentTest browser_client;
content::ContentBrowserClient* old_browser_client =
content::SetBrowserClientForTesting(&browser_client);
https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
embedded_test_server()->ServeFilesFromDirectory(tmp_dir_.GetPath());
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(embedded_test_server()->Start());
WriteTestFiles(*embedded_test_server(), "example.com");
WriteFile(FILE_PATH_LITERAL("worker_iframe.html"),
"<script>"
"var worker = new Worker('worker.js');"
"worker.addEventListener("
" 'message',"
" event => { parent.postMessage(event.data, '*'); });"
"</script>");
WriteFile(FILE_PATH_LITERAL("worker_test.html"),
"<script>"
"window.addEventListener("
" 'message',"
" event => { document.title = event.data; });"
"</script>"
"<iframe src=\"./worker_iframe.html\" />");
for (bool allow_running_insecure_content : {true, false}) {
for (bool strict_mixed_content_checking : {true, false}) {
for (bool strictly_block_blockable_mixed_content : {true, false}) {
if (allow_running_insecure_content && !strict_mixed_content_checking) {
RunMixedContentSettingsTest(
&browser_client, allow_running_insecure_content,
strict_mixed_content_checking,
strictly_block_blockable_mixed_content, true /* expected_load */,
false /* expected_show_blocked */,
true /* expected_show_dangerous */,
true /* expected_load_after_allow */,
false /* expected_show_blocked_after_allow */,
true /* expected_show_dangerous_after_allow */);
} else {
RunMixedContentSettingsTest(
&browser_client, allow_running_insecure_content,
strict_mixed_content_checking,
strictly_block_blockable_mixed_content, false /* expected_load */,
false /* expected_show_blocked */,
false /* expected_show_dangerous */,
false /* expected_load_after_allow */,
false /* expected_show_blocked_after_allow */,
false /* expected_show_dangerous_after_allow */);
}
}
}
}
content::SetBrowserClientForTesting(old_browser_client);
}
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
SSLUIWorkerFetchTest,
::testing::Values(SSLUIWorkerFetchTestType::kUseFetch,
SSLUIWorkerFetchTestType::kUseImportScripts));
// Visits a page with unsafe content and makes sure that if a user exception
// to the certificate error is present, the image is loaded and script
// executes.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnsafeContentsWithUserException) {
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_NO_FATAL_FAILURE(SetUpUnsafeContentsWithUserException(
"/ssl/page_with_unsafe_contents.html"));
ssl_test_util::CheckAuthenticationBrokenState(tab, CertError::NONE,
AuthState::NONE);
SecurityStateTabHelper* helper = SecurityStateTabHelper::FromWebContents(tab);
ASSERT_TRUE(helper);
std::unique_ptr<security_state::VisibleSecurityState> visible_security_state =
helper->GetVisibleSecurityState();
EXPECT_FALSE(visible_security_state->ran_mixed_content);
EXPECT_FALSE(visible_security_state->displayed_mixed_content);
EXPECT_TRUE(visible_security_state->ran_content_with_cert_errors);
EXPECT_TRUE(visible_security_state->displayed_content_with_cert_errors);
int img_width;
EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
tab, "window.domAutomationController.send(ImageWidth());", &img_width));
// In order to check that the image was loaded, we check its width.
// The actual image (Google logo) is 114 pixels wide, so we assume a good
// image is greater than 100.
EXPECT_GT(img_width, 100);
bool js_result = false;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
tab, "window.domAutomationController.send(IsFooSet());", &js_result));
EXPECT_TRUE(js_result);
// Test that active subresources with the same certificate errors as
// the main resources also get noted in |content_with_cert_errors_status|.
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_with_unsafe_contents.html",
https_server_mismatched_.host_port_pair());
ui_test_utils::NavigateToURL(
browser(), https_server_mismatched_.GetURL(replacement_path));
js_result = false;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
tab, "window.domAutomationController.send(IsFooSet());", &js_result));
EXPECT_TRUE(js_result);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);
visible_security_state = helper->GetVisibleSecurityState();
EXPECT_FALSE(visible_security_state->ran_mixed_content);
EXPECT_FALSE(visible_security_state->displayed_mixed_content);
EXPECT_TRUE(visible_security_state->ran_content_with_cert_errors);
EXPECT_TRUE(visible_security_state->displayed_content_with_cert_errors);
}
// Like the test above, but only displaying inactive content (an image).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnsafeImageWithUserException) {
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_NO_FATAL_FAILURE(
SetUpUnsafeContentsWithUserException("/ssl/page_with_unsafe_image.html"));
SecurityStateTabHelper* helper = SecurityStateTabHelper::FromWebContents(tab);
ASSERT_TRUE(helper);
std::unique_ptr<security_state::VisibleSecurityState> visible_security_state =
helper->GetVisibleSecurityState();
EXPECT_FALSE(visible_security_state->ran_mixed_content);
EXPECT_FALSE(visible_security_state->displayed_mixed_content);
EXPECT_FALSE(visible_security_state->ran_content_with_cert_errors);
EXPECT_TRUE(visible_security_state->displayed_content_with_cert_errors);
EXPECT_EQ(0u, visible_security_state->cert_status);
int img_width;
EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
tab, "window.domAutomationController.send(ImageWidth());", &img_width));
// In order to check that the image was loaded, we check its width.
// The actual image (Google logo) is 114 pixels wide, so we assume a good
// image is greater than 100.
EXPECT_GT(img_width, 100);
}
// Test that when the browser blocks displaying insecure content (iframes),
// the indicator shows a secure page, because the blocking made the otherwise
// unsafe page safe (the notification of this state is handled by other means)
IN_PROC_BROWSER_TEST_F(SSLUITestBlock, TestBlockDisplayingInsecureIframe) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_iframe.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
ssl_test_util::CheckAuthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}
// Test that when the browser blocks running insecure content, the
// indicator shows a secure page, because the blocking made the otherwise
// unsafe page safe (the notification of this state is handled by other
// means).
IN_PROC_BROWSER_TEST_F(SSLUITestBlock, TestBlockRunningInsecureContent) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_runs_insecure_content.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
ssl_test_util::CheckAuthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}
// Visit a page and establish a WebSocket connection over bad https with
// --ignore-certificate-errors. The connection should be established without
// interstitial page showing.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrors, TestWSS) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(wss_server_expired_.Start());
// Setup page title observer.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TitleWatcher watcher(tab, base::ASCIIToUTF16("PASS"));
watcher.AlsoWaitForTitle(base::ASCIIToUTF16("FAIL"));
// Visit bad HTTPS page.
GURL::Replacements replacements;
replacements.SetSchemeStr("https");
ui_test_utils::NavigateToURL(browser(),
wss_server_expired_.GetURL("connect_check.html")
.ReplaceComponents(replacements));
// We shouldn't have an interstitial page showing here.
// Test page run a WebSocket wss connection test. The result will be shown
// as page title.
const base::string16 result = watcher.WaitAndGetTitle();
EXPECT_TRUE(base::LowerCaseEqualsASCII(result, "pass"));
}
// Visit a page and establish a WebSocket connection over bad https with
// --ignore-certificate-errors-spki-list. The connection should be established
// without interstitial page showing.
#if !BUILDFLAG(IS_CHROMEOS_ASH) // Chrome OS does not support the flag.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrorsBySPKIWSS, TestWSSExpired) {
ASSERT_TRUE(wss_server_expired_.Start());
// Setup page title observer.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TitleWatcher watcher(tab, base::ASCIIToUTF16("PASS"));
watcher.AlsoWaitForTitle(base::ASCIIToUTF16("FAIL"));
// Visit bad HTTPS page.
GURL::Replacements replacements;
replacements.SetSchemeStr("https");
ui_test_utils::NavigateToURL(browser(),
wss_server_expired_.GetURL("connect_check.html")
.ReplaceComponents(replacements));
// We shouldn't have an interstitial page showing here.
// Test page run a WebSocket wss connection test. The result will be shown
// as page title.
const base::string16 result = watcher.WaitAndGetTitle();
EXPECT_TRUE(base::LowerCaseEqualsASCII(result, "pass"));
}
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
// Test that HTTPS pages with a bad certificate don't show an interstitial if
// the public key matches a value from --ignore-certificate-errors-spki-list.
#if !BUILDFLAG(IS_CHROMEOS_ASH) // Chrome OS does not support the flag.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrorsBySPKIHTTPS, TestHTTPS) {
ASSERT_TRUE(https_server_mismatched_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(),
https_server_mismatched_.GetURL("/ssl/page_with_subresource.html"));
// We should see no interstitial. The script tag in the page should have
// loaded and ran (and wasn't blocked by the certificate error).
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
base::string16 title;
ui_test_utils::GetCurrentTabTitle(browser(), &title);
EXPECT_EQ(title, base::ASCIIToUTF16("This script has loaded"));
}
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
// Test subresources from an origin with a bad certificate are loaded if the
// public key matches a value from --ignore-certificate-errors-spki-list.
#if !BUILDFLAG(IS_CHROMEOS_ASH) // Chrome OS does not support the flag.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrorsBySPKIHTTPS,
TestInsecureSubresource) {
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_mismatched_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_with_unsafe_image.html",
https_server_mismatched_.host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
// We should see no interstitial.
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
// In order to check that the image was loaded, check its width.
// The actual image (Google logo) is 276 pixels wide.
int img_width = 0;
EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
tab, "window.domAutomationController.send(ImageWidth());", &img_width));
EXPECT_GT(img_width, 200);
}
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
// Verifies that the interstitial can proceed, even if JavaScript is disabled.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestInterstitialJavaScriptProceeds) {
HostContentSettingsMapFactory::GetForProfile(browser()->profile())
->SetDefaultContentSetting(ContentSettingsType::JAVASCRIPT,
CONTENT_SETTING_BLOCK);
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
const std::string javascript =
"window.certificateErrorPageController.proceed();";
EXPECT_TRUE(content::ExecuteScript(tab, javascript));
observer.Wait();
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
}
// Verifies that the interstitial can go back, even if JavaScript is disabled.
// http://crbug.com/322948
IN_PROC_BROWSER_TEST_F(SSLUITest, TestInterstitialJavaScriptGoesBack) {
HostContentSettingsMapFactory::GetForProfile(browser()->profile())
->SetDefaultContentSetting(ContentSettingsType::JAVASCRIPT,
CONTENT_SETTING_BLOCK);
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<content::NavigationController>(&tab->GetController()));
const std::string javascript =
"window.certificateErrorPageController.dontProceed();";
EXPECT_TRUE(content::ExecuteScript(tab, javascript));
observer.Wait();
EXPECT_EQ("about:blank", tab->GetVisibleURL().spec());
}
// Verifies that an overridable interstitial has a proceed link.
IN_PROC_BROWSER_TEST_F(SSLUITest, ProceedLinkOverridable) {
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
WaitForInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(chrome_browser_interstitials::InterstitialHasProceedLink(
tab->GetMainFrame()));
}
IN_PROC_BROWSER_TEST_F(SSLUITest, TestLearnMoreLinkContainsErrorCode) {
ASSERT_TRUE(https_server_expired_.Start());
// Navigate to a site that causes an interstitial.
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL("/title1.html"));
WaitForInterstitial(browser()->tab_strip_model()->GetActiveWebContents());
// Simulate clicking the learn more link.
SendInterstitialCommand(browser()->tab_strip_model()->GetActiveWebContents(),
security_interstitials::CMD_OPEN_HELP_CENTER);
EXPECT_EQ(browser()
->tab_strip_model()
->GetActiveWebContents()
->GetVisibleURL()
.ref(),
base::NumberToString(net::ERR_CERT_DATE_INVALID));
}
// Checks that interstitials are not used for subframe SSL errors. Regression
// test for https://crbug.com/808797.
IN_PROC_BROWSER_TEST_F(SSLUITest, SubframeCertError) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(),
embedded_test_server()->GetURL("/title1.html"));
// Insert a broken-HTTPS iframe on the page and check that a generic net
// error, not a certificate error page, is shown.
content::TestNavigationObserver observer(tab, 1);
std::string insert_frame = base::StringPrintf(
"var i = document.createElement('iframe');"
"i.src = '%s';"
"document.body.appendChild(i);",
https_server_expired_.GetURL("/ssl/google.html").spec().c_str());
EXPECT_TRUE(content::ExecuteScript(tab, insert_frame));
observer.Wait();
content::RenderFrameHost* child =
content::ChildFrameAt(tab->GetMainFrame(), 0);
ASSERT_TRUE(child);
int result = security_interstitials::CMD_ERROR;
const std::string javascript = base::StringPrintf(
"domAutomationController.send("
"(document.querySelector(\"#proceed-link\") === null) "
"? (%d) : (%d))",
security_interstitials::CMD_TEXT_NOT_FOUND,
security_interstitials::CMD_TEXT_FOUND);
ASSERT_TRUE(content::ExecuteScriptAndExtractInt(child, javascript, &result));
EXPECT_EQ(security_interstitials::CMD_TEXT_NOT_FOUND, result);
}
// Verifies that a non-overridable interstitial does not have a proceed link.
IN_PROC_BROWSER_TEST_F(SSLUITestHSTS, TestInterstitialOptionsNonOverridable) {
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
GURL::Replacements replacements;
replacements.SetHostStr(kHstsTestHostName);
GURL url = https_server_expired_.GetURL("/ssl/google.html")
.ReplaceComponents(replacements);
ui_test_utils::NavigateToURL(browser(), url);
WaitForInterstitial(tab);
// Since we are connecting to a different domain than the test server default,
// we also expect CERT_STATUS_COMMON_NAME_INVALID.
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID | net::CERT_STATUS_COMMON_NAME_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
int result = security_interstitials::CMD_ERROR;
const std::string javascript = base::StringPrintf(
"domAutomationController.send("
"(document.querySelector(\"#proceed-link\") === null) "
"? (%d) : (%d))",
security_interstitials::CMD_TEXT_NOT_FOUND,
security_interstitials::CMD_TEXT_FOUND);
ASSERT_TRUE(content::ExecuteScriptAndExtractInt(tab->GetMainFrame(),
javascript, &result));
EXPECT_EQ(security_interstitials::CMD_TEXT_NOT_FOUND, result);
}
// Verifies that links in the interstitial open in a new tab.
// https://crbug.com/717616
IN_PROC_BROWSER_TEST_F(SSLUITest, TestInterstitialLinksOpenInNewTab) {
ASSERT_TRUE(https_server_.Start());
ASSERT_TRUE(https_server_expired_.Start());
WebContents* interstitial_tab =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
WaitForInterstitial(browser()->tab_strip_model()->GetActiveWebContents());
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingSSLInterstitial(interstitial_tab));
ssl_test_util::CheckAuthenticationBrokenState(
interstitial_tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
content::TestNavigationObserver nav_observer(nullptr);
nav_observer.StartWatchingNewWebContents();
SSLBlockingPage* ssl_interstitial =
static_cast<SSLBlockingPage*>(GetInterstitialPage(interstitial_tab));
security_interstitials::SecurityInterstitialControllerClient* client =
GetControllerClientFromSSLBlockingPage(ssl_interstitial);
// Mock out the help center URL so that our test will hit the test server
// instead of a real server.
// NOTE: The CMD_OPEN_HELP_CENTER code in
// components/security_interstitials/core/ssl_error_ui.cc ends up appending
// a path to whatever URL is passed to it. Since that path doesn't exist on
// our test server, this results in a 404. This is expected behavior, and
// things are still working as expected so long as the test passes!
const GURL mock_help_center_url = https_server_.GetURL("/title1.html");
client->SetBaseHelpCenterUrlForTesting(mock_help_center_url);
EXPECT_EQ(1, browser()->tab_strip_model()->count());
SendInterstitialCommand(interstitial_tab,
security_interstitials::CMD_OPEN_HELP_CENTER);
nav_observer.Wait();
EXPECT_EQ(2, browser()->tab_strip_model()->count());
WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(new_tab);
EXPECT_EQ(mock_help_center_url.host(), new_tab->GetURL().host());
}
// Verifies that switching tabs, while showing interstitial page, will not
// affect the visibility of the interstitial.
// https://crbug.com/381439
IN_PROC_BROWSER_TEST_F(SSLUITest, InterstitialNotAffectedByHideShow) {
ASSERT_TRUE(https_server_expired_.Start());
ASSERT_TRUE(https_server_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(tab->GetRenderWidgetHostView()->IsShowing());
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
EXPECT_TRUE(tab->GetRenderWidgetHostView()->IsShowing());
AddTabAtIndex(0, https_server_.GetURL("/ssl/google.html"),
ui::PAGE_TRANSITION_TYPED);
EXPECT_EQ(2, browser()->tab_strip_model()->count());
EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
EXPECT_EQ(tab, browser()->tab_strip_model()->GetWebContentsAt(1));
EXPECT_FALSE(tab->GetRenderWidgetHostView()->IsShowing());
browser()->tab_strip_model()->ActivateTabAt(
1, {TabStripModel::GestureType::kOther});
EXPECT_TRUE(tab->GetRenderWidgetHostView()->IsShowing());
}
// Verifies that if a bad certificate is seen for a host and the user proceeds
// through the interstitial, the decision to proceed is initially remembered.
// However, if this is followed by another visit, and a good certificate
// is seen for the same host, the original exception is forgotten.
IN_PROC_BROWSER_TEST_F(SSLUITest, BadCertFollowedByGoodCert) {
// It is necessary to use |https_server_expired_| rather than
// |https_server_mismatched| because the former shares a host with
// |https_server_| and cert exceptions are per host.
ASSERT_TRUE(https_server_expired_.Start());
ASSERT_TRUE(https_server_.Start());
std::string https_server_expired_host =
https_server_expired_.GetURL("/ssl/google.html").host();
std::string https_server_host =
https_server_.GetURL("/ssl/google.html").host();
ASSERT_EQ(https_server_expired_host, https_server_host);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
StatefulSSLHostStateDelegate* state =
static_cast<StatefulSSLHostStateDelegate*>(
profile->GetSSLHostStateDelegate());
// First check that frame requests revoke the decision.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html")));
ProceedThroughInterstitial(tab);
EXPECT_TRUE(state->HasAllowException(https_server_host, tab));
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_.GetURL("/ssl/google.html")));
EXPECT_FALSE(state->HasAllowException(https_server_host, tab));
// Rarely, an open connection with the bad cert might be reused for the next
// navigation, which is supposed to show an interstitial. Close open
// connections to ensure a fresh connection (and certificate validation) for
// the next navigation. See https://crbug.com/1150592. A deeper fix for this
// issue would be to unify certificate bypass logic which is currently split
// between the net stack and content layer; see https://crbug.com/488043.
state->RevokeUserAllowExceptionsHard(https_server_host);
// Now check that subresource requests revoke the decision.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html")));
ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
ProceedThroughInterstitial(tab);
EXPECT_TRUE(state->HasAllowException(https_server_host, tab));
GURL image = https_server_.GetURL("/ssl/google_files/logo.gif");
bool result = false;
EXPECT_TRUE(ExecuteScriptAndExtractBool(
tab,
std::string("var img = document.createElement('img');img.src ='") +
image.spec() +
"';img.onload=function() { "
"window.domAutomationController.send(true); };"
"document.body.appendChild(img);",
&result));
EXPECT_TRUE(result);
EXPECT_FALSE(state->HasAllowException(https_server_host, tab));
}
// Verifies that if a bad certificate is seen for a host and the user proceeds
// through the interstitial, the decision to proceed is not forgotten once blob
// URLs are loaded (blob loads never have certificate errors). This is a
// regression test for https://crbug.com/1049625.
IN_PROC_BROWSER_TEST_F(SSLUITest, BadCertFollowedByBlobUrl) {
ASSERT_TRUE(https_server_expired_.Start());
std::string https_server_host =
https_server_expired_.GetURL("/ssl/google.html").host();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
StatefulSSLHostStateDelegate* state =
static_cast<StatefulSSLHostStateDelegate*>(
profile->GetSSLHostStateDelegate());
// Proceed through the interstitial, accepting the broken cert.
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
ProceedThroughInterstitial(tab);
ASSERT_TRUE(state->HasAllowException(https_server_host, tab));
// Load a blob URL.
content::WebContentsConsoleObserver console_observer(tab);
console_observer.SetPattern("hello from blob");
const char kScript[] = R"(
new Promise(function (resolvePromise, rejectPromise) {
var blob = new Blob(['console.log("hello from blob")'],
{type : 'application/javascript'});
script = document.createElement('script');
script.onerror = rejectPromise;
script.onload = () => resolvePromise('success');
script.src = URL.createObjectURL(blob);
document.body.appendChild(script);
});
)";
ASSERT_EQ("success", content::EvalJs(tab, kScript));
// Verify that the script from the blob has successfully run.
console_observer.Wait();
// Verify that the decision to accept the broken cert has not been revoked
// (this is a regression test for https://crbug.com/1049625).
EXPECT_TRUE(state->HasAllowException(https_server_host, tab));
}
// Tests that the SSLStatus of a navigation entry for an SSL
// interstitial matches the navigation entry once the interstitial is
// clicked through. https://crbug.com/529456
IN_PROC_BROWSER_TEST_F(SSLUITest,
SSLStatusMatchesOnInterstitialAndAfterProceed) {
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
WaitForInterstitial(tab);
EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
ASSERT_TRUE(entry);
content::SSLStatus interstitial_ssl_status = entry->GetSSL();
ProceedThroughInterstitial(tab);
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));
entry = tab->GetController().GetLastCommittedEntry();
ASSERT_TRUE(entry);
content::SSLStatus after_interstitial_ssl_status = entry->GetSSL();
EXPECT_TRUE(ComparePreAndPostInterstitialSSLStatuses(
interstitial_ssl_status, after_interstitial_ssl_status));
}
// As above, but for a bad clock interstitial. Tests that a clock
// interstitial's SSLStatus matches the SSLStatus of the HTTPS page
// after proceeding through a normal SSL interstitial.
IN_PROC_BROWSER_TEST_F(SSLUITest,
SSLStatusMatchesonClockInterstitialAndAfterProceed) {
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
// Set up the build and current clock times to be more than a year apart.
base::SimpleTestClock mock_clock;
mock_clock.SetNow(base::Time::NowFromSystemTime());
mock_clock.Advance(base::TimeDelta::FromDays(367));
SSLErrorHandler::SetClockForTesting(&mock_clock);
ssl_errors::SetBuildTimeForTesting(base::Time::NowFromSystemTime());
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL("/title1.html"));
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingBadClockInterstitial(tab));
// Grab the SSLStatus on the clock interstitial.
content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
ASSERT_TRUE(entry);
content::SSLStatus clock_interstitial_ssl_status = entry->GetSSL();
// Put the clock back to normal, trigger a normal SSL interstitial,
// and proceed through it.
mock_clock.SetNow(base::Time::NowFromSystemTime());
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL("/title1.html"));
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
ProceedThroughInterstitial(tab);
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));
// Grab the SSLStatus from the page and check that it is the same as
// on the clock interstitial.
entry = tab->GetController().GetLastCommittedEntry();
ASSERT_TRUE(entry);
content::SSLStatus after_interstitial_ssl_status = entry->GetSSL();
EXPECT_TRUE(ComparePreAndPostInterstitialSSLStatuses(
clock_interstitial_ssl_status, after_interstitial_ssl_status));
}
// A fixture for testing on-demand network time queries on SSL
// certificate date errors. It can simulate a delayed network time
// request, and it allows the user to configure the experimental
// parameters of the NetworkTimeTracker. Expects only one network time
// request to be issued during the test.
class SSLNetworkTimeBrowserTest : public SSLUITest {
public:
SSLNetworkTimeBrowserTest() : SSLUITest() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
network_time::kNetworkTimeServiceQuerying,
{{"FetchBehavior", "on-demand-only"}});
}
~SSLNetworkTimeBrowserTest() override = default;
void SetUpOnMainThread() override {
SSLUITest::SetUpOnMainThread();
controllable_response_ =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/", true);
ASSERT_TRUE(embedded_test_server()->Start());
g_browser_process->network_time_tracker()->SetTimeServerURLForTesting(
embedded_test_server()->GetURL("/"));
}
protected:
void TriggerTimeResponse() {
std::string response = "HTTP/1.1 200 OK\nContent-type: text/plain\n";
response += base::StringPrintf(
"Content-Length: %1d\n",
static_cast<int>(strlen(network_time::kGoodTimeResponseBody[0])));
response +=
"x-cup-server-proof: " +
std::string(network_time::kGoodTimeResponseServerProofHeader[0]);
response += "\n\n";
response += std::string(network_time::kGoodTimeResponseBody[0]);
controllable_response_->WaitForRequest();
controllable_response_->Send(response);
}
// Asserts that the first time request to the server is currently pending.
void CheckTimeQueryPending() {
base::Time unused_time;
base::TimeDelta unused_uncertainty;
ASSERT_EQ(network_time::NetworkTimeTracker::NETWORK_TIME_FIRST_SYNC_PENDING,
g_browser_process->network_time_tracker()->GetNetworkTime(
&unused_time, &unused_uncertainty));
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<net::test_server::ControllableHttpResponse>
controllable_response_;
DISALLOW_COPY_AND_ASSIGN(SSLNetworkTimeBrowserTest);
};
// Tests that if an on-demand network time fetch returns that the clock
// is okay, a normal SSL interstitial is shown.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest, OnDemandFetchClockOk) {
ASSERT_TRUE(https_server_expired_.Start());
// Use a testing clock set to the time that GoodTimeResponseHandler
// returns, to simulate the system clock matching the network time.
base::SimpleTestClock testing_clock;
SSLErrorHandler::SetClockForTesting(&testing_clock);
testing_clock.SetNow(
base::Time::FromJsTime(network_time::kGoodTimeResponseHandlerJsTime[0]));
// Set the build time to match the testing clock, to ensure that the
// build time heuristic doesn't fire.
ssl_errors::SetBuildTimeForTesting(testing_clock.Now());
// Set a long timeout to ensure that the on-demand time fetch completes.
SSLErrorHandler::SetInterstitialDelayForTesting(
base::TimeDelta::FromHours(1));
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
SSLInterstitialTimerObserver interstitial_timer_observer(contents);
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
ui_test_utils::NavigateToURLWithDisposition(
browser(), https_server_expired_.GetURL("/"),
WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE);
// Once |interstitial_timer_observer| has fired, the request has been
// sent. Override the nonce that NetworkTimeTracker expects so that
// when the response comes back, it will validate. The nonce can only
// be overriden for the current in-flight request, so the test must
// call OverrideNonceForTesting() after the request has been sent and
// before the response has been received.
interstitial_timer_observer.WaitForTimerStarted();
g_browser_process->network_time_tracker()->OverrideNonceForTesting(123123123);
TriggerTimeResponse();
EXPECT_TRUE(contents->IsLoading());
observer.Wait();
WaitForInterstitial(contents);
EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(contents));
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
}
// Tests that if an on-demand network time fetch returns that the clock
// is wrong, a bad clock interstitial is shown.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest, OnDemandFetchClockWrong) {
ASSERT_TRUE(https_server_expired_.Start());
// Use a testing clock set to a time that is different from what
// GoodTimeResponseHandler returns, simulating a system clock that is
// 30 days ahead of the network time.
base::SimpleTestClock testing_clock;
SSLErrorHandler::SetClockForTesting(&testing_clock);
testing_clock.SetNow(
base::Time::FromJsTime(network_time::kGoodTimeResponseHandlerJsTime[0]));
testing_clock.Advance(base::TimeDelta::FromDays(30));
// Set the build time to match the testing clock, to ensure that the
// build time heuristic doesn't fire.
ssl_errors::SetBuildTimeForTesting(testing_clock.Now());
// Set a long timeout to ensure that the on-demand time fetch completes.
SSLErrorHandler::SetInterstitialDelayForTesting(
base::TimeDelta::FromHours(1));
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
SSLInterstitialTimerObserver interstitial_timer_observer(contents);
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
ui_test_utils::NavigateToURLWithDisposition(
browser(), https_server_expired_.GetURL("/"),
WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE);
// Once |interstitial_timer_observer| has fired, the request has been
// sent. Override the nonce that NetworkTimeTracker expects so that
// when the response comes back, it will validate. The nonce can only
// be overriden for the current in-flight request, so the test must
// call OverrideNonceForTesting() after the request has been sent and
// before the response has been received.
interstitial_timer_observer.WaitForTimerStarted();
g_browser_process->network_time_tracker()->OverrideNonceForTesting(123123123);
TriggerTimeResponse();
EXPECT_TRUE(contents->IsLoading());
observer.Wait();
WaitForInterstitial(contents);
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingBadClockInterstitial(contents));
}
// Tests that if the timeout expires before the network time fetch
// returns, then a normal SSL interstitial is shown.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest,
TimeoutExpiresBeforeFetchCompletes) {
ASSERT_TRUE(https_server_expired_.Start());
// Set the timer to fire immediately.
SSLErrorHandler::SetInterstitialDelayForTesting(base::TimeDelta());
ui_test_utils::NavigateToURL(browser(), https_server_expired_.GetURL("/"));
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
WaitForInterstitial(contents);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
// Navigate away, and then trigger the network time response; no crash should
// occur.
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(browser(), https_server_.GetURL("/"));
ASSERT_NO_FATAL_FAILURE(CheckTimeQueryPending());
TriggerTimeResponse();
}
// Tests that if the user stops the page load before either the network
// time fetch completes or the timeout expires, then there is no interstitial.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest, StopBeforeTimeoutExpires) {
ASSERT_TRUE(https_server_expired_.Start());
// Set the timer to a long delay.
SSLErrorHandler::SetInterstitialDelayForTesting(
base::TimeDelta::FromHours(1));
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
SSLInterstitialTimerObserver interstitial_timer_observer(contents);
ui_test_utils::NavigateToURLWithDisposition(
browser(), https_server_expired_.GetURL("/"),
WindowOpenDisposition::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();
// Make sure that the |SSLErrorHandler| is deleted.
EXPECT_FALSE(SSLErrorHandler::FromWebContents(contents));
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
EXPECT_FALSE(contents->IsLoading());
// Navigate away, and then trigger the network time response; no crash should
// occur.
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(browser(), https_server_.GetURL("/title1.html"));
ASSERT_NO_FATAL_FAILURE(CheckTimeQueryPending());
TriggerTimeResponse();
}
// Tests that if the user reloads the page before either the network
// time fetch completes or the timeout expires, then there is no interstitial.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest, ReloadBeforeTimeoutExpires) {
ASSERT_TRUE(https_server_expired_.Start());
// Set the timer to a long delay.
SSLErrorHandler::SetInterstitialDelayForTesting(
base::TimeDelta::FromHours(1));
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(contents);
ui_test_utils::NavigateToURLWithDisposition(
browser(), https_server_expired_.GetURL("/"),
WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE);
interstitial_timer_observer.WaitForTimerStarted();
EXPECT_TRUE(contents->IsLoading());
content::TestNavigationObserver observer(contents);
chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
observer.Wait();
// Make sure that the |SSLErrorHandler| is deleted.
EXPECT_FALSE(SSLErrorHandler::FromWebContents(contents));
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
EXPECT_FALSE(contents->IsLoading());
// Navigate away, and then trigger the network time response and wait
// for the response; no crash should occur.
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(browser(), https_server_.GetURL("/"));
ASSERT_NO_FATAL_FAILURE(CheckTimeQueryPending());
TriggerTimeResponse();
}
// Tests that if the user navigates away before either the network time
// fetch completes or the timeout expires, then there is no
// interstitial.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest,
NavigateAwayBeforeTimeoutExpires) {
ASSERT_TRUE(https_server_expired_.Start());
ASSERT_TRUE(https_server_.Start());
// Set the timer to a long delay.
SSLErrorHandler::SetInterstitialDelayForTesting(
base::TimeDelta::FromHours(1));
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(contents);
ui_test_utils::NavigateToURLWithDisposition(
browser(), https_server_expired_.GetURL("/"),
WindowOpenDisposition::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(
https_server_.GetURL("/"), content::Referrer(),
WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false));
observer.Wait();
// Make sure that the |SSLErrorHandler| is deleted.
EXPECT_FALSE(SSLErrorHandler::FromWebContents(contents));
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
EXPECT_FALSE(contents->IsLoading());
// Navigate away, and then trigger the network time response and wait
// for the response; no crash should occur.
ui_test_utils::NavigateToURL(browser(), https_server_.GetURL("/"));
ASSERT_NO_FATAL_FAILURE(CheckTimeQueryPending());
TriggerTimeResponse();
}
// Tests that if the user closes the tab before the network time fetch
// completes, it doesn't cause a crash.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest,
CloseTabBeforeNetworkFetchCompletes) {
ASSERT_TRUE(https_server_expired_.Start());
// Set the timer to fire immediately.
SSLErrorHandler::SetInterstitialDelayForTesting(base::TimeDelta());
ui_test_utils::NavigateToURL(browser(), https_server_expired_.GetURL("/"));
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
WaitForInterstitial(contents);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
// Open a second tab, close the first, and then trigger the network time
// response and wait for the response; no crash should occur.
ASSERT_TRUE(https_server_.Start());
AddTabAtIndex(1, https_server_.GetURL("/"), ui::PAGE_TRANSITION_TYPED);
chrome::CloseWebContents(browser(), contents, false);
ASSERT_NO_FATAL_FAILURE(CheckTimeQueryPending());
TriggerTimeResponse();
}
class CommonNameMismatchBrowserTest : public CertVerifierBrowserTest {
public:
CommonNameMismatchBrowserTest() : CertVerifierBrowserTest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
CertVerifierBrowserTest::SetUpCommandLine(command_line);
// Enable finch experiment for SSL common name mismatch handling.
command_line->AppendSwitchASCII(switches::kForceFieldTrials,
"SSLCommonNameMismatchHandling/Enabled/");
}
void SetUpOnMainThread() override {
CertVerifierBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
void TearDownOnMainThread() override {
CertVerifierBrowserTest::TearDownOnMainThread();
}
};
// 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::EmbeddedTestServer https_server_example_domain(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_example_domain.ServeFilesFromSourceDirectory(
GetChromeTestDataDir());
ASSERT_TRUE(https_server_example_domain.Start());
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.
const GURL https_server_url =
https_server_example_domain.GetURL("/ssl/google.html?a=b#anchor");
GURL::Replacements replacements;
replacements.SetHostStr("www.mail.example.com");
const GURL https_server_mismatched_url =
https_server_url.ReplaceComponents(replacements);
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver observer(contents, 1);
ui_test_utils::NavigateToURL(browser(), https_server_mismatched_url);
observer.Wait();
ssl_test_util::CheckSecurityState(contents, CertError::NONE,
security_state::SECURE, 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 www.mail.example.com on a server that presents an invalid
// certificate for mail.example.com. Verify that the page shows an interstitial
// for www.mail.example.com with no crash.
IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
NoCrashIfBothSubdomainsHaveCommonNameErrors) {
net::EmbeddedTestServer https_server_example_domain(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_example_domain.ServeFilesFromSourceDirectory(
GetChromeTestDataDir());
ASSERT_TRUE(https_server_example_domain.Start());
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);
// Request to "mail.example.com" should also result in
// |net::ERR_CERT_COMMON_NAME_INVALID| error.
mock_cert_verifier()->AddResultForCertAndHost(
cert.get(), "mail.example.com", verify_result,
net::ERR_CERT_COMMON_NAME_INVALID);
// Use a complex URL to ensure the path, etc., are preserved. The path itself
// does not matter.
const GURL https_server_url =
https_server_example_domain.GetURL("/ssl/google.html?a=b#anchor");
GURL::Replacements replacements;
replacements.SetHostStr("www.mail.example.com");
const GURL https_server_mismatched_url =
https_server_url.ReplaceComponents(replacements);
// Should simply show an interstitial, because both subdomains have common
// name errors.
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(), https_server_mismatched_url);
WaitForInterstitial(contents);
ssl_test_util::CheckSecurityState(
contents, net::CERT_STATUS_COMMON_NAME_INVALID, security_state::DANGEROUS,
AuthState::SHOWING_INTERSTITIAL);
}
// 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::EmbeddedTestServer https_server_example_domain(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_example_domain.ServeFilesFromSourceDirectory(
GetChromeTestDataDir());
ASSERT_TRUE(https_server_example_domain.Start());
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);
const GURL https_server_url =
https_server_example_domain.GetURL("/ssl/google.html?a=b");
GURL::Replacements replacements;
replacements.SetHostStr("example.org");
const GURL https_server_mismatched_url =
https_server_url.ReplaceComponents(replacements);
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver observer(contents, 1);
ui_test_utils::NavigateToURL(browser(), https_server_mismatched_url);
observer.Wait();
ssl_test_util::CheckSecurityState(contents, CertError::NONE,
security_state::SECURE, AuthState::NONE);
}
namespace {
// Redirects incoming request to http://example.org.
std::unique_ptr<net::test_server::HttpResponse> HTTPSToHTTPRedirectHandler(
const net::EmbeddedTestServer* test_server,
const net::test_server::HttpRequest& request) {
GURL::Replacements replacements;
replacements.SetHostStr("example.org");
replacements.SetSchemeStr("http");
const GURL redirect_url =
test_server->base_url().ReplaceComponents(replacements);
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader("Location", redirect_url.spec());
return std::move(http_response);
}
} // namespace
// Common name mismatch handling feature should ignore redirects when pinging
// the suggested hostname. Visit the URL example.org on a server that presents a
// valid certificate for www.example.org. In this case, www.example.org
// redirects to http://example.org, and the SSL error should not be redirected
// to this URL.
IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
WWWSubdomainMismatch_StopOnRedirects) {
net::EmbeddedTestServer https_server_example_domain(
net::EmbeddedTestServer::TYPE_HTTPS);
// Redirect all URLs to http://example.org. Since this test will trigger only
// one request to check the suggested URL, redirecting all requests is OK.
// We would normally use content::SetupCrossSiteRedirector here, but that
// function does not support https to http redirects.
// This must be done before ServeFilesFromSourceDirectory(), otherwise the
// test server will serve files instead of redirecting requests to them.
https_server_example_domain.RegisterRequestHandler(base::BindRepeating(
&HTTPSToHTTPRedirectHandler, &https_server_example_domain));
https_server_example_domain.ServeFilesFromSourceDirectory(
GetChromeTestDataDir());
ASSERT_TRUE(https_server_example_domain.Start());
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);
// The user will visit https://example.org:port/ssl/blank.html.
GURL::Replacements replacements;
replacements.SetHostStr("example.org");
const GURL https_server_mismatched_url =
https_server_example_domain.GetURL("/ssl/blank.html")
.ReplaceComponents(replacements);
// Should simply show an interstitial, because the suggested URL
// (https://www.example.org) redirected to http://example.org.
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(), https_server_mismatched_url);
WaitForInterstitial(contents);
ssl_test_util::CheckSecurityState(
contents, net::CERT_STATUS_COMMON_NAME_INVALID, security_state::DANGEROUS,
AuthState::SHOWING_INTERSTITIAL);
}
// 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::EmbeddedTestServer https_server_example_domain(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_example_domain.ServeFilesFromSourceDirectory(
GetChromeTestDataDir());
ASSERT_TRUE(https_server_example_domain.Start());
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);
const GURL https_server_url =
https_server_example_domain.GetURL("/ssl/google.html?a=b");
GURL::Replacements replacements;
replacements.SetHostStr("www.mail.example.com");
const 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);
// Set delay long enough so that the page appears loading.
SSLErrorHandler::SetInterstitialDelayForTesting(
base::TimeDelta::FromHours(1));
SSLInterstitialTimerObserver interstitial_timer_observer(contents);
ui_test_utils::NavigateToURLWithDisposition(
browser(), https_server_mismatched_url,
WindowOpenDisposition::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(chrome_browser_interstitials::IsShowingInterstitial(contents));
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::EmbeddedTestServer https_server_example_domain(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_example_domain.ServeFilesFromSourceDirectory(
GetChromeTestDataDir());
ASSERT_TRUE(https_server_example_domain.Start());
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);
const GURL https_server_url =
https_server_example_domain.GetURL("/ssl/google.html?a=b");
GURL::Replacements replacements;
replacements.SetHostStr("www.mail.example.com");
const 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);
// Set delay long enough so that the page appears loading.
SSLErrorHandler::SetInterstitialDelayForTesting(
base::TimeDelta::FromHours(1));
SSLInterstitialTimerObserver interstitial_timer_observer(contents);
ui_test_utils::NavigateToURLWithDisposition(
browser(), https_server_mismatched_url,
WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE);
interstitial_timer_observer.WaitForTimerStarted();
EXPECT_TRUE(contents->IsLoading());
content::TestNavigationObserver observer(contents, 1);
chrome::Reload(browser(), WindowOpenDisposition::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(chrome_browser_interstitials::IsShowingInterstitial(contents));
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::EmbeddedTestServer https_server_example_domain(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_example_domain.ServeFilesFromSourceDirectory(
GetChromeTestDataDir());
ASSERT_TRUE(https_server_example_domain.Start());
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);
const GURL https_server_url =
https_server_example_domain.GetURL("/ssl/google.html?a=b");
GURL::Replacements replacements;
replacements.SetHostStr("www.mail.example.com");
const 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);
// Set delay long enough so that the page appears loading.
SSLErrorHandler::SetInterstitialDelayForTesting(
base::TimeDelta::FromHours(1));
SSLInterstitialTimerObserver interstitial_timer_observer(contents);
ui_test_utils::NavigateToURLWithDisposition(
browser(), https_server_mismatched_url,
WindowOpenDisposition::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(),
WindowOpenDisposition::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(chrome_browser_interstitials::IsShowingInterstitial(contents));
EXPECT_FALSE(contents->IsLoading());
}
class SSLBlockingPageIDNTest
: public chrome_browser_interstitials::SecurityInterstitialIDNTest {
protected:
// chrome_browser_interstitials::SecurityInterstitialIDNTest:
security_interstitials::SecurityInterstitialPage* CreateInterstitial(
WebContents* contents,
const GURL& request_url) const override {
net::SSLInfo ssl_info;
ssl_info.cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
ChromeSecurityBlockingPageFactory blocking_page_factory;
return blocking_page_factory
.CreateSSLPage(contents, net::ERR_CERT_CONTAINS_ERRORS, ssl_info,
request_url, 0, base::Time::NowFromSystemTime(), GURL(),
nullptr)
.release();
}
};
// Flaky on mac OS and Windows: https://crbug.com/689846
#if defined(OS_MAC) || defined(OS_WIN)
#define MAYBE_SSLBlockingPageDecodesIDN DISABLED_SSLBlockingPageDecodesIDN
#else
#define MAYBE_SSLBlockingPageDecodesIDN SSLBlockingPageDecodesIDN
#endif
IN_PROC_BROWSER_TEST_F(SSLBlockingPageIDNTest,
MAYBE_SSLBlockingPageDecodesIDN) {
EXPECT_TRUE(VerifyIDNDecoded());
}
IN_PROC_BROWSER_TEST_F(CertVerifierBrowserTest, MockCertVerifierSmokeTest) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
ASSERT_TRUE(https_server.Start());
mock_cert_verifier()->set_default_result(
net::ERR_CERT_NAME_CONSTRAINT_VIOLATION);
ui_test_utils::NavigateToURL(browser(),
https_server.GetURL("/ssl/google.html"));
ssl_test_util::CheckSecurityState(
browser()->tab_strip_model()->GetActiveWebContents(),
net::CERT_STATUS_NAME_CONSTRAINT_VIOLATION, security_state::DANGEROUS,
AuthState::SHOWING_INTERSTITIAL);
}
IN_PROC_BROWSER_TEST_F(SSLUITest, RestoreHasSSLState) {
ASSERT_TRUE(https_server_.Start());
GURL url(https_server_.GetURL("/ssl/google.html"));
ui_test_utils::NavigateToURL(browser(), url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
content::NavigationEntry* entry =
tab->GetController().GetLastCommittedEntry();
std::unique_ptr<content::NavigationEntry> restored_entry =
content::NavigationController::CreateNavigationEntry(
url, content::Referrer(), base::nullopt, ui::PAGE_TRANSITION_RELOAD,
false, std::string(), tab->GetBrowserContext(),
nullptr /* blob_url_loader_factory */);
restored_entry->SetPageState(entry->GetPageState());
WebContents::CreateParams params(tab->GetBrowserContext());
std::unique_ptr<WebContents> tab2 = WebContents::Create(params);
WebContents* raw_tab2 = tab2.get();
tab->GetDelegate()->AddNewContents(nullptr, std::move(tab2), url,
WindowOpenDisposition::NEW_FOREGROUND_TAB,
gfx::Rect(), false, nullptr);
std::vector<std::unique_ptr<content::NavigationEntry>> entries;
entries.push_back(std::move(restored_entry));
content::TestNavigationObserver observer(raw_tab2);
raw_tab2->GetController().Restore(entries.size() - 1,
content::RestoreType::kRestored, &entries);
raw_tab2->GetController().LoadIfNecessary();
observer.Wait();
ssl_test_util::CheckAuthenticatedState(raw_tab2, AuthState::NONE);
}
void SetupRestoredTabWithNavigation(
net::test_server::EmbeddedTestServer* https_server,
Browser* browser) {
ASSERT_TRUE(https_server->Start());
GURL url(https_server->GetURL("/ssl/google.html"));
ui_test_utils::NavigateToURL(browser, url);
WebContents* tab = browser->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver observer(tab);
EXPECT_TRUE(ExecuteScript(tab, "history.pushState({}, '', '');"));
observer.Wait();
ui_test_utils::NavigateToURLWithDisposition(
browser, GURL("about:blank"), WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
chrome::CloseTab(browser);
WebContents* blank_tab = browser->tab_strip_model()->GetActiveWebContents();
// Restore the tab.
ui_test_utils::TabAddedWaiter tab_added_waiter(browser);
content::WindowedNotificationObserver tab_loaded_observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
chrome::RestoreTab(browser);
tab_added_waiter.Wait();
tab_loaded_observer.Wait();
tab = browser->tab_strip_model()->GetActiveWebContents();
EXPECT_NE(tab, blank_tab);
}
// Simulate a browser-initiated in-page navigation in a restored tab.
// https://crbug.com/662267
IN_PROC_BROWSER_TEST_F(SSLUITest,
BrowserInitiatedExistingPageAfterRestoreHasSSLState) {
SetupRestoredTabWithNavigation(&https_server_, browser());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(content::WaitForLoadStop(tab));
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Simulate a renderer-initiated in-page navigation in a restored tab.
IN_PROC_BROWSER_TEST_F(SSLUITest,
RendererInitiatedExistingPageAfterRestoreHasSSLState) {
SetupRestoredTabWithNavigation(&https_server_, browser());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
content::TestNavigationObserver observer(tab);
ASSERT_TRUE(content::ExecuteScript(
tab, "location.replace(window.location.href + '#1')"));
observer.Wait();
EXPECT_TRUE(content::WaitForLoadStop(tab));
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
namespace {
// A handler which changes the response. The first time it's called for
// |relative_url| it'll give an empty response. The second time it'll
// redirect to |redirect_url|.
std::unique_ptr<net::test_server::HttpResponse> ChangingHandler(
int* count,
const std::string& relative_url,
const GURL& redirect_url,
const net::test_server::HttpRequest& request) {
if (request.relative_url != relative_url)
return nullptr;
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
if ((*count)++) {
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader("Location", redirect_url.spec());
}
return std::move(http_response);
}
} // namespace
// Check that SSL state isn't stale when navigating to an existing page that
// gives a different response. This covers the case of going from http to
// https. http://crbug.com/792221
IN_PROC_BROWSER_TEST_F(SSLUITest, ExistingPageHTTPToHTTPSSSLState) {
ASSERT_TRUE(https_server_.Start());
int count = 0;
std::string relative_url = "/foo";
GURL redirect_url = https_server_.GetURL("/simple.html");
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(ChangingHandler, &count, relative_url, redirect_url));
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url = embedded_test_server()->GetURL(relative_url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(), url);
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
content::TestNavigationObserver observer(tab, 1);
chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
observer.Wait();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Check that SSL state isn't stale when navigating to an existing page that
// gives a different response. This covers the case of going from https to
// http URL. http://crbug.com/792221
IN_PROC_BROWSER_TEST_F(SSLUITest, ExistingPageHTTPSToHTTPSSLState) {
ASSERT_TRUE(embedded_test_server()->Start());
int count = 0;
std::string relative_url = "/foo";
GURL redirect_url = embedded_test_server()->GetURL("/simple.html");
https_server_.RegisterRequestHandler(
base::BindRepeating(ChangingHandler, &count, relative_url, redirect_url));
ASSERT_TRUE(https_server_.Start());
const GURL url = https_server_.GetURL(relative_url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(), url);
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
content::TestNavigationObserver observer(tab, 1);
chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
observer.Wait();
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
// We also manually check the cert on the NavigationEntry, since in the case
// of http URLs GetSecurityLevelForRequest will return SecurityLevel::NONE for
// http URLs.
content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
ASSERT_FALSE(entry->GetSSL().certificate);
}
// Checks that a restore followed immediately by a history navigation doesn't
// lose SSL state.
// Disabled since this is a test for bug 738177.
IN_PROC_BROWSER_TEST_F(SSLUITest, DISABLED_RestoreThenNavigateHasSSLState) {
ASSERT_TRUE(https_server_.Start());
GURL url1(https_server_.GetURL("/ssl/google.html"));
GURL url2(https_server_.GetURL("/ssl/page_with_refs.html"));
ui_test_utils::NavigateToURLWithDisposition(
browser(), url1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
ui_test_utils::NavigateToURL(browser(), url2);
chrome::CloseTab(browser());
ui_test_utils::TabAddedWaiter tab_added_waiter(browser());
chrome::RestoreTab(browser());
tab_added_waiter.Wait();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationManager observer(tab, url1);
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
observer.WaitForNavigationFinished();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Simulate the URL changing when the user presses enter in the omnibox. This
// could happen when the user's login is expired and the server redirects them
// to a login page. This will be considered a same document navigation but we
// do want to update the SSL state.
IN_PROC_BROWSER_TEST_F(SSLUITest, SameDocumentHasSSLState) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Navigate to a simple page and then perform an in-page navigation.
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
ui_test_utils::NavigateToURL(browser(), start_url);
GURL fragment_change_url(embedded_test_server()->GetURL("/title1.html#foo"));
ui_test_utils::NavigateToURL(browser(), fragment_change_url);
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
// Replace the URL of the current NavigationEntry with one that will cause
// a server redirect when loaded.
{
GURL redirect_dest_url(https_server_.GetURL("/ssl/google.html"));
content::TestNavigationObserver observer(tab);
std::string script = "history.replaceState({}, '', '/server-redirect?" +
redirect_dest_url.spec() + "')";
EXPECT_TRUE(ExecuteScript(tab, script));
observer.Wait();
}
// Simulate the user hitting Enter in the omnibox without changing the URL.
{
content::TestNavigationObserver observer(tab);
tab->GetController().LoadURL(tab->GetLastCommittedURL(),
content::Referrer(), ui::PAGE_TRANSITION_LINK,
std::string());
observer.Wait();
}
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Simulate the user revisiting a page without triggering a reload (e.g., when
// clicking a bookmark with an anchor hash twice). As this is a same document
// navigation, the SSL state should be left intact despite not triggering a
// network request. Regression test for https://crbug.com/877618.
IN_PROC_BROWSER_TEST_F(SSLUITest, SameDocumentHasSSLStateNoLoad) {
ASSERT_TRUE(https_server_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
GURL start_url(https_server_.GetURL("/ssl/google.html#foo"));
ui_test_utils::NavigateToURL(browser(), start_url);
// Simulate clicking on a bookmark.
{
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
NavigateParams navigate_params(browser(), start_url,
ui::PAGE_TRANSITION_AUTO_BOOKMARK);
Navigate(&navigate_params);
observer.Wait();
}
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Checks that if a client redirect occurs while the page is loading, the SSL
// state reflects the final URL.
IN_PROC_BROWSER_TEST_F(SSLUITest, ClientRedirectSSLState) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
GURL https_url = https_server_.GetURL("/ssl/redirect.html?");
GURL http_url = embedded_test_server()->GetURL("/ssl/google.html");
GURL url(https_url.spec() + http_url.spec());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationManager navigation_observer_https(tab, url);
content::TestNavigationManager navigation_observer_http(tab, http_url);
tab->GetController().LoadURL(url, content::Referrer(),
ui::PAGE_TRANSITION_LINK, std::string());
navigation_observer_https.WaitForNavigationFinished();
navigation_observer_http.WaitForNavigationFinished();
EXPECT_TRUE(content::WaitForLoadStop(tab));
ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
}
// Checks that if a redirect occurs while the page is loading from a mixed
// content to a valid HTTPS page, the SSL state reflects the final URL.
IN_PROC_BROWSER_TEST_F(SSLUITest, ClientRedirectFromMixedContentSSLState) {
ASSERT_TRUE(https_server_.Start());
GURL url = GURL(
https_server_.GetURL("/ssl/redirect_with_mixed_content.html").spec() +
"?" + https_server_.GetURL("/ssl/google.html").spec());
// Load a page that displays insecure content.
ui_test_utils::NavigateToURL(browser(), url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Checks that if a redirect occurs while the page is loading from a valid HTTPS
// page to a mixed content page, the SSL state reflects the final URL.
IN_PROC_BROWSER_TEST_F(SSLUITest, ClientRedirectToMixedContentSSLState) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
GURL redirect(https_server_.GetURL("/ssl/redirect.html"));
GURL final_url(
https_server_.GetURL("/ssl/page_displays_insecure_content.html"));
GURL url = GURL(redirect.spec() + "?" + final_url.spec());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationManager navigation_manager_redirect(tab, url);
content::TestNavigationManager navigation_manager_final_url(tab, final_url);
tab->GetController().LoadURL(url, content::Referrer(),
ui::PAGE_TRANSITION_LINK, std::string());
navigation_manager_redirect.WaitForNavigationFinished();
navigation_manager_final_url.WaitForNavigationFinished();
EXPECT_TRUE(content::WaitForLoadStop(tab));
ssl_test_util::CheckSecurityState(tab, CertError::NONE,
security_state::WARNING,
AuthState::DISPLAYED_INSECURE_CONTENT);
}
// Checks that same-document navigations during page load preserve SSL state.
IN_PROC_BROWSER_TEST_F(SSLUITest, SameDocumentNavigationDuringLoadSSLState) {
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(
browser(),
https_server_.GetURL("/ssl/same_document_navigation_during_load.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Checks that same-document navigations after the page load preserve SSL
// state.
IN_PROC_BROWSER_TEST_F(SSLUITest, SameDocumentNavigationAfterLoadSSLState) {
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(content::ExecuteScript(tab, "location.hash = Math.random()"));
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
// Checks that navigations after pushState maintain the SSL status.
// Flaky, see https://crbug.com/872029 and https://crbug.com/872030.
IN_PROC_BROWSER_TEST_F(SSLUITest, DISABLED_PushStateSSLState) {
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
content::TestNavigationObserver observer(tab);
EXPECT_TRUE(ExecuteScript(tab, "history.pushState({}, '', '');"));
observer.Wait();
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(content::WaitForLoadStop(tab));
ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
class SSLUITestNoCert : public SSLUITest,
public CertificateManagerModel::Observer {
public:
SSLUITestNoCert() = default;
~SSLUITestNoCert() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kDisableTestCerts);
net::TestRootCerts::GetInstance()->Clear();
SSLUITest::SetUpCommandLine(command_line);
}
// CertificateManagerModel::Observer implementation:
void CertificatesRefreshed() override {}
};
// Checks that a newly-added certificate authority is usable immediately.
IN_PROC_BROWSER_TEST_F(SSLUITestNoCert, NewCertificateAuthority) {
if (!content::IsOutOfProcessNetworkService())
return;
ASSERT_TRUE(https_server_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
std::unique_ptr<CertificateManagerModel> model;
base::RunLoop run_loop;
CertificateManagerModel::Create(
browser()->profile(), this,
base::BindLambdaForTesting(
[&](std::unique_ptr<CertificateManagerModel> model2) {
model = std::move(model2);
run_loop.Quit();
}));
run_loop.Run();
scoped_refptr<net::X509Certificate> cert;
net::ScopedCERTCertificateList nss_certs;
{
base::ScopedAllowBlockingForTesting allow_io;
cert = net::CreateCertificateChainFromFile(
net::GetTestCertsDirectory(), "root_ca_cert.pem",
net::X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
nss_certs = net::x509_util::CreateCERTCertificateListFromX509Certificate(
cert.get());
}
net::NSSCertDatabase::ImportCertFailureList not_imported;
EXPECT_TRUE(model->ImportCACerts(nss_certs, net::NSSCertDatabase::TRUSTED_SSL,
&not_imported));
EXPECT_TRUE(not_imported.empty());
content::FlushNetworkServiceInstanceForTesting();
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));
}
// A test class which prepares two profiles and allows importing certificates
// into their NSS databases.
class SSLUITestCustomCACerts : public SSLUITestNoCert {
public:
SSLUITestCustomCACerts() = default;
~SSLUITestCustomCACerts() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
SSLUITestNoCert::SetUpCommandLine(command_line);
// Don't require policy for our sessions - this is required so the policy
// code knows not to expect cached policy for the secondary profile.
command_line->AppendSwitchASCII(chromeos::switches::kProfileRequiresPolicy,
"false");
}
void SetUpOnMainThread() override {
SSLUITestNoCert::SetUpOnMainThread();
profile_1_ = browser()->profile();
// Create a second profile.
{
static const char kSecondProfileAccount[] = "profile2@test.com";
static const char kSecondProfileGaiaId[] = "9876543210";
static const char kSecondProfileHash[] = "testProfile2";
ON_CALL(policy_for_profile_2_, IsInitializationComplete(testing::_))
.WillByDefault(testing::Return(true));
ON_CALL(policy_for_profile_2_, IsFirstPolicyLoadComplete(testing::_))
.WillByDefault(testing::Return(true));
policy::PushProfilePolicyConnectorProviderForTesting(
&policy_for_profile_2_);
base::FilePath user_data_directory;
base::PathService::Get(chrome::DIR_USER_DATA, &user_data_directory);
session_manager::SessionManager::Get()->CreateSession(
AccountId::FromUserEmailGaiaId(kSecondProfileAccount,
kSecondProfileGaiaId),
kSecondProfileHash, false);
// Set up the secondary profile.
base::FilePath profile_dir = user_data_directory.Append(
chromeos::ProfileHelper::GetUserProfileDir(kSecondProfileHash)
.BaseName());
profile_2_ =
g_browser_process->profile_manager()->GetProfile(profile_dir);
}
// Get cert databases for both profiles.
{
base::RunLoop loop;
GetNSSCertDatabaseForProfile(
profile_1_,
base::BindOnce(&SSLUITestCustomCACerts::DidGetCertDatabase,
base::Unretained(this), &loop, &profile_1_cert_db_));
loop.Run();
}
{
base::RunLoop loop;
GetNSSCertDatabaseForProfile(
profile_2_,
base::BindOnce(&SSLUITestCustomCACerts::DidGetCertDatabase,
base::Unretained(this), &loop, &profile_2_cert_db_));
loop.Run();
}
// Double-check that the profile initialization was correct and the two
// profiles have distinct NSS databases with distinc NSS public slots.
EXPECT_NE(profile_1_cert_db_, profile_2_cert_db_);
EXPECT_NE(profile_1_cert_db_->GetPublicSlot().get(),
profile_2_cert_db_->GetPublicSlot().get());
}
protected:
void ImportCACertAsTrusted(const std::string& cert_file_name,
net::NSSCertDatabase* cert_db) {
base::ScopedAllowBlockingForTesting allow_blocking;
net::ScopedCERTCertificateList ca_cert_list =
net::CreateCERTCertificateListFromFile(
net::GetTestCertsDirectory(), cert_file_name,
net::X509Certificate::FORMAT_AUTO);
ASSERT_FALSE(ca_cert_list.empty());
net::NSSCertDatabase::ImportCertFailureList failures;
ASSERT_TRUE(cert_db->ImportCACerts(
ca_cert_list, net::NSSCertDatabase::TRUSTED_SSL, &failures));
ASSERT_TRUE(failures.empty());
}
// The first profile.
Profile* profile_1_;
// The second profile.
Profile* profile_2_;
// The NSSCertDatabase for |profile_1_|.
net::NSSCertDatabase* profile_1_cert_db_;
// The NSSCertDatabase for |profile_2_|.
net::NSSCertDatabase* profile_2_cert_db_;
// Policy provider for |profile_2_|. Overrides any other policy providers.
testing::NiceMock<policy::MockConfigurationPolicyProvider>
policy_for_profile_2_;
private:
void DidGetCertDatabase(base::RunLoop* loop,
net::NSSCertDatabase** out_cert_db,
net::NSSCertDatabase* cert_db) {
*out_cert_db = cert_db;
loop->Quit();
}
DISALLOW_COPY_AND_ASSIGN(SSLUITestCustomCACerts);
};
// Imports a trusted CA certiifcate into a profile's NSS database.
// Verifies that the certificate is trusted in the context of the profile it was
// imported for.
// Verifies that the certificate is *not* trusted in the context of a different
// profile.
IN_PROC_BROWSER_TEST_F(SSLUITestCustomCACerts,
TrustedCertOnlyRespectedInProfileThatOwnsIt) {
ASSERT_TRUE(https_server_.Start());
ASSERT_NO_FATAL_FAILURE(
ImportCACertAsTrusted("root_ca_cert.pem", profile_2_cert_db_));
// Flush the network service instance so persistent NSS Database changes are
// reflected in the network service.
content::FlushNetworkServiceInstanceForTesting();
// The certificate that is trusted in |profile_2_| should not be respected in
// browsers that belong to |profile_1_|.
Browser* browser_for_profile_1 = CreateBrowser(profile_1_);
ui_test_utils::NavigateToURL(browser_for_profile_1,
https_server_.GetURL("/ssl/google.html"));
WebContents* tab_for_profile_1 =
browser_for_profile_1->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab_for_profile_1);
ssl_test_util::CheckAuthenticationBrokenState(
tab_for_profile_1, net::CERT_STATUS_AUTHORITY_INVALID,
AuthState::SHOWING_INTERSTITIAL);
// The certificate that is trusted in |profile_2_| should be respected in
// browsers that belong to |profile_2_|.
Browser* browser_for_profile_2 = CreateBrowser(profile_2_);
ui_test_utils::NavigateToURL(browser_for_profile_2,
https_server_.GetURL("/ssl/google.html"));
WebContents* tab_for_profile_2 =
browser_for_profile_2->tab_strip_model()->GetActiveWebContents();
ssl_test_util::CheckAuthenticatedState(tab_for_profile_2, AuthState::NONE);
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// Regression test for http://crbug.com/635833 (crash when a window with no
// NavigationEntry commits).
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreLocalhostCertErrors,
NoCrashOnLoadWithNoNavigationEntry) {
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/ssl/google.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(content::ExecuteScript(tab, "window.open()"));
}
class SSLUICaptivePortalListEnabledTest : public SSLUITest {
public:
SSLUICaptivePortalListEnabledTest() {
feature_list_.InitWithFeatures(
{kCaptivePortalCertificateList} /* enabled */, {} /* disabled */);
}
private:
base::test::ScopedFeatureList feature_list_;
};
class SSLUICaptivePortalListDisabledTest : public SSLUITest {
public:
SSLUICaptivePortalListDisabledTest() {
feature_list_.InitWithFeatures(
{} /* enabled */, {kCaptivePortalCertificateList} /* disabled */);
}
private:
base::test::ScopedFeatureList feature_list_;
};
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig>
MakeCaptivePortalConfig(int version_id,
const std::set<std::string>& spki_hashes) {
auto config_proto =
std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
config_proto->set_version_id(version_id);
for (const std::string& hash : spki_hashes) {
config_proto->add_captive_portal_cert()->set_sha256_hash(hash);
}
return config_proto;
}
// Tests that the captive portal certificate list is not used when the feature
// is disabled via Finch. The list is passed to SSLErrorHandler via a proto.
IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListDisabledTest, Disabled) {
ASSERT_TRUE(https_server_mismatched_.Start());
base::HistogramTester histograms;
// Mark the server's cert as a captive portal cert.
const net::HashValue server_spki_hash =
GetSPKIHash(https_server_mismatched_.GetCertificate()->cert_buffer());
SSLErrorHandler::SetErrorAssistantProto(MakeCaptivePortalConfig(
kLargeVersionId, std::set<std::string>{server_spki_hash.ToString()}));
ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() > 0);
// Navigate to an unsafe page on the server. A normal SSL interstitial should
// be displayed since CaptivePortalCertificateList feature is disabled.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(
browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html"));
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(interstitial_timer_observer.timer_started());
// Check that the histogram for the SSL interstitial was recorded.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::CAPTIVE_PORTAL_CERT_FOUND, 0);
}
// Tests that the captive portal certificate list is used when the feature
// is enabled via Finch. The list is passed to SSLErrorHandler via a proto.
IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListEnabledTest, Enabled_FromProto) {
ASSERT_TRUE(https_server_mismatched_.Start());
base::HistogramTester histograms;
// Mark the server's cert as a captive portal cert.
const net::HashValue server_spki_hash =
GetSPKIHash(https_server_mismatched_.GetCertificate()->cert_buffer());
SSLErrorHandler::SetErrorAssistantProto(MakeCaptivePortalConfig(
kLargeVersionId, std::set<std::string>{server_spki_hash.ToString()}));
ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() > 0);
// Navigate to an unsafe page on the server. The captive portal interstitial
// should be displayed since CaptivePortalCertificateList feature is enabled.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(
browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html"));
WaitForInterstitial(tab);
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingCaptivePortalInterstitial(tab));
EXPECT_FALSE(interstitial_timer_observer.timer_started());
// Check that the histogram for the captive portal cert was recorded.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 3);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::CAPTIVE_PORTAL_CERT_FOUND, 1);
}
// Tests the scenario where the OS reports a captive portal. A captive portal
// interstitial should be displayed. The test then switches OS captive portal
// status to false and reloads the page. This time, a normal SSL interstitial
// will be displayed.
IN_PROC_BROWSER_TEST_F(SSLUITest, OSReportsCaptivePortal) {
ASSERT_TRUE(https_server_mismatched_.Start());
base::HistogramTester histograms;
bool netwok_connectivity_reported = false;
SSLErrorHandler::SetOSReportsCaptivePortalForTesting(true);
SSLErrorHandler::SetReportNetworkConnectivityCallbackForTesting(
base::BindLambdaForTesting([&]() {
SSLErrorHandler::SetOSReportsCaptivePortalForTesting(false);
netwok_connectivity_reported = true;
}));
// Navigate to an unsafe page on the server.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(
browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html"));
WaitForInterstitial(tab);
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingCaptivePortalInterstitial(tab));
EXPECT_FALSE(interstitial_timer_observer.timer_started());
// Check that the histogram for the captive portal cert was recorded.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 3);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::OS_REPORTS_CAPTIVE_PORTAL, 1);
// Reload the URL. This time the OS should not report a captive portal.
ui_test_utils::NavigateToURL(
browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html"));
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(netwok_connectivity_reported);
}
class SSLUITestWithCaptivePortalInterstitialDisabled : public SSLUITest {
public:
SSLUITestWithCaptivePortalInterstitialDisabled() {
feature_list_.InitWithFeatures({} /* enabled */,
{kCaptivePortalInterstitial} /* disabled */);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Tests the scenario where the OS reports a captive portal but captive portal
// interstitial feature is disabled. A captive portal interstitial should not be
// displayed.
IN_PROC_BROWSER_TEST_F(SSLUITestWithCaptivePortalInterstitialDisabled,
OSReportsCaptivePortal_FeatureDisabled) {
ASSERT_TRUE(https_server_mismatched_.Start());
base::HistogramTester histograms;
SSLErrorHandler::SetOSReportsCaptivePortalForTesting(true);
// Navigate to an unsafe page on the server.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(
browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html"));
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_FALSE(interstitial_timer_observer.timer_started());
// Check that the histogram for the SSL interstitial was recorded.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE, 0);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::OS_REPORTS_CAPTIVE_PORTAL, 0);
}
// Tests that the committed interstitial flag triggers the code path to show an
// error PageType instead of an interstitial PageType.
IN_PROC_BROWSER_TEST_F(SSLUITest, ErrorPage) {
ASSERT_TRUE(https_server_expired_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
ssl_test_util::CheckSecurityState(tab, net::CERT_STATUS_DATE_INVALID,
security_state::DANGEROUS,
AuthState::SHOWING_ERROR);
content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
EXPECT_EQ(content::PAGE_TYPE_ERROR, entry->GetPageType());
}
class SSLUITestWithInsecureFormsWarningEnabled : public SSLUITest {
public:
SSLUITestWithInsecureFormsWarningEnabled() {
feature_list_.InitAndEnableFeature(
security_interstitials::kInsecureFormSubmissionInterstitial);
}
private:
base::test::ScopedFeatureList feature_list_;
};
using security_interstitials::InsecureFormNavigationThrottle;
// Visits a page that displays an insecure form, submits the form, and checks an
// interstitial is shown.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabled,
TestDisplaysInsecureFormSubmissionWarning) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_form.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Check this was logged correctly as a non-redirect interstitial.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(interstitial_histogram,
InsecureFormNavigationThrottle::
InterstitialTriggeredState::kMixedFormDirect,
1);
}
// Check warning is not displayed for redirects if redirects mode is not
// enabled, but metrics are logged.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabled,
TestDisplaysInsecureFormSubmissionWarningRedirect) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_insecure.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
// There should have been no interstitial triggered.
EXPECT_FALSE(helper);
// Check this was logged correctly as a redirect mixed form.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectWithFormData,
1);
}
// Visits a page that displays an insecure form inside an iframe, attempts to
// submit the form, and checks an interstitial is not shown (submissions of
// mixed forms inside iframes are separately blocked, and that behavior is
// tested in mixed_content_navigation_throttle_unittest.cc).
IN_PROC_BROWSER_TEST_F(
SSLUITestWithInsecureFormsWarningEnabled,
TestDoesNotDisplayInsecureFormSubmissionWarningInIframe) {
ChromeContentBrowserClientForMixedContentTest browser_client;
browser_client.SetMixedContentSettings(
false, /* allow_running_insecure_content */
false, /* strict_mixed_content_checking */
false /*strictly_block_blockable_mixed_content */);
content::ContentBrowserClient* old_browser_client =
content::SetBrowserClientForTesting(&browser_client);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
tab->OnWebPreferencesChanged();
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_form_in_iframe.html",
https_server_.host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
content::TestNavigationObserver nav_observer(tab, 1);
content::WebContentsConsoleObserver console_observer(tab);
console_observer.SetPattern(
"Mixed Content: The page at * was loaded over a secure connection, but "
"contains a form that targets an insecure endpoint "
"'http://does-not-exist.test/ssl/google_files/logo.gif'. This endpoint "
"should be made available over a secure connection.");
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
// We shouldn't be displaying an interstitial.
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_FALSE(helper);
// Check console message was printed.
EXPECT_EQ(console_observer.messages().size(), 1u);
content::SetBrowserClientForTesting(old_browser_client);
}
// Checks insecure form warning works for forms that submit on a new tab.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabled,
TestDisplaysInsecureFormSubmissionWarningTargetBlank) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_form_target_blank.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
nav_observer.StartWatchingNewWebContents();
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
tab = browser()->tab_strip_model()->GetActiveWebContents();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
}
// Checks reloading the interstitial is not treated as proceeding.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabled,
TestReloadInsecureFormSubmissionWarningIsNotProceed) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_form.html",
embedded_test_server()->host_port_pair());
// Navigate to an insecure form, make sure we get a warning.
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Reload the interstitial.
content::TestNavigationObserver reload_observer(tab, 1);
tab->GetController().Reload(content::ReloadType::NORMAL, false);
reload_observer.Wait();
// Check we get another interstitial.
helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
}
// Check proceed works correctly on insecure form warning.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabled,
ProceedThroughInsecureFormWarning) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_form.html",
embedded_test_server()->host_port_pair());
GURL form_target_url("http://does-not-exist.test/ssl/google_files/logo.gif");
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// After clicking Proceed, we should not be on an interstitial, and be
// on the form target url;
ProceedThroughInterstitial(tab);
EXPECT_FALSE(helper->IsDisplayingInterstitial());
EXPECT_EQ(tab->GetVisibleURL(), form_target_url);
}
// Check don't proceed works correctly on insecure form warning.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabled,
GoBackFromInsecureFormWarning) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_form.html",
embedded_test_server()->host_port_pair());
GURL form_site_url = https_server_.GetURL(replacement_path);
ui_test_utils::NavigateToURL(browser(), form_site_url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// After clicking Don't Proceed, we should not be on an interstitial, and be
// back on the site containing the insecure form.
DontProceedThroughInterstitial(tab);
EXPECT_FALSE(helper->IsDisplayingInterstitial());
EXPECT_EQ(tab->GetVisibleURL(), form_site_url);
}
class SSLUITestWithInsecureFormsWarningEnabledWithAllRedirects
: public SSLUITest {
public:
SSLUITestWithInsecureFormsWarningEnabledWithAllRedirects() {
feature_list_.InitAndEnableFeatureWithParameters(
security_interstitials::kInsecureFormSubmissionInterstitial,
{{"mode", "include-redirects"}});
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Checks mixed form warnings work correctly for non-redirects when redirect
// enforcement is enabled.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabledWithAllRedirects,
TestDisplaysInsecureFormSubmissionWarning) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_form.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Check this was logged correctly as a non-redirect interstitial.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(interstitial_histogram,
InsecureFormNavigationThrottle::
InterstitialTriggeredState::kMixedFormDirect,
1);
}
// Checks interstitial is shown for mixed forms caused by a 307 POST http
// redirect, and that metrics are logged.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabledWithAllRedirects,
TestDisplaysInsecureFormSubmissionWarningRedirect) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_insecure.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Check this was logged correctly as a redirect mixed form that may expose
// form data.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectWithFormData,
1);
}
// Checks interstitial is shown for mixed forms caused by a 308 POST http
// redirect, and that metrics are logged.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabledWithAllRedirects,
TestDisplaysInsecureFormSubmissionWarningRedirect308) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_308_insecure.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Check this was logged correctly as a redirect mixed form that may expose
// form data.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectWithFormData,
1);
}
// Checks interstitial is shown for mixed forms caused for a POST form with a
// 301 redirect, and that metrics are logged correctly.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabledWithAllRedirects,
TestDisplaysInsecureFormSubmissionWarningRedirect301) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_301_insecure.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Check this was logged correctly as a redirect mixed form that would not
// expose form data.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectNoFormData,
1);
}
// Checks interstitial is shown for mixed forms caused for a POST form with a
// 302 redirect, and that metrics are logged correctly.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabledWithAllRedirects,
TestDisplaysInsecureFormSubmissionWarningRedirect302) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_302_insecure.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Check this was logged correctly as a redirect mixed form that would not
// expose form data.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectNoFormData,
1);
}
namespace {
// Redirects (with 307 code) requests with a redirect_to_http path to
// http://example.org. This custom handler is required for tests that include
// GET method forms to the redirect URL, since the built in /server-redirect-307
// handler takes the redirect-to URL as a query parameter, so it is not usable
// for GET method forms.
std::unique_ptr<net::test_server::HttpResponse> FormActionHTTPRedirectHandler(
const net::EmbeddedTestServer* test_server,
const net::test_server::HttpRequest& request) {
GURL absolute_url = test_server->GetURL(request.relative_url);
if (absolute_url.path() != "/redirect_to_http")
return nullptr;
GURL::Replacements replacements;
replacements.SetHostStr("example.org");
replacements.SetSchemeStr("http");
const GURL redirect_url =
test_server->base_url().ReplaceComponents(replacements);
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
http_response->AddCustomHeader("Location", redirect_url.spec());
return std::move(http_response);
}
} // namespace
// Checks interstitial is shown for mixed forms caused for a GET form with a
// 307 redirect to http, and that metrics are logged correctly.
IN_PROC_BROWSER_TEST_F(SSLUITestWithInsecureFormsWarningEnabledWithAllRedirects,
TestDisplaysInsecureFormSubmissionWarningRedirectGet) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
https_server_.RegisterRequestHandler(
base::BindRepeating(&FormActionHTTPRedirectHandler, &https_server_));
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_insecure_get.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Check this was logged correctly as a redirect mixed form that would not
// expose form data.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectNoFormData,
1);
}
class SSLUITestWithInsecureFormsWarningEnabledForRedirectsWithFormData
: public SSLUITest {
public:
SSLUITestWithInsecureFormsWarningEnabledForRedirectsWithFormData() {
feature_list_.InitAndEnableFeatureWithParameters(
security_interstitials::kInsecureFormSubmissionInterstitial,
{{"mode", "include-redirects-with-form-data"}});
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Checks mixed form warnings work correctly for non-redirects when redirect
// enforcement is enabled.
IN_PROC_BROWSER_TEST_F(
SSLUITestWithInsecureFormsWarningEnabledForRedirectsWithFormData,
TestDisplaysInsecureFormSubmissionWarning) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_form.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Check this was logged correctly as a non-redirect interstitial.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(interstitial_histogram,
InsecureFormNavigationThrottle::
InterstitialTriggeredState::kMixedFormDirect,
1);
}
// Checks interstitial is shown for mixed forms caused by a 307 POST http
// redirect, and that metrics are logged.
IN_PROC_BROWSER_TEST_F(
SSLUITestWithInsecureFormsWarningEnabledForRedirectsWithFormData,
TestDisplaysInsecureFormSubmissionWarningRedirect) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_insecure.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Check this was logged correctly as a redirect mixed form that may expose
// form data.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectWithFormData,
1);
}
// Checks interstitial is shown for mixed forms caused by a 308 POST http
// redirect, and that metrics are logged.
IN_PROC_BROWSER_TEST_F(
SSLUITestWithInsecureFormsWarningEnabledForRedirectsWithFormData,
TestDisplaysInsecureFormSubmissionWarningRedirect308) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_308_insecure.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(helper->IsDisplayingInterstitial());
EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
->GetTypeForTesting(),
security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
// Check this was logged correctly as a redirect mixed form that may expose
// form data.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectWithFormData,
1);
}
// Checks no interstitial is shown for mixed forms caused for a POST form with a
// 301 redirect, and that metrics are logged correctly.
IN_PROC_BROWSER_TEST_F(
SSLUITestWithInsecureFormsWarningEnabledForRedirectsWithFormData,
TestDisplaysInsecureFormSubmissionWarningRedirect301) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_301_insecure.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
// There should have been no interstitial triggered.
EXPECT_FALSE(helper);
// Check this was logged correctly as a redirect mixed form that would not
// expose form data.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectNoFormData,
1);
}
// Checks no interstitial is shown for mixed forms caused for a POST form with a
// 302 redirect, and that metrics are logged correctly.
IN_PROC_BROWSER_TEST_F(
SSLUITestWithInsecureFormsWarningEnabledForRedirectsWithFormData,
TestDisplaysInsecureFormSubmissionWarningRedirect302) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_302_insecure.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
// There should have been no interstitial triggered.
EXPECT_FALSE(helper);
// Check this was logged correctly as a redirect mixed form that would not
// expose form data.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectNoFormData,
1);
}
// Checks no interstitial is shown for mixed forms caused for a GET form with
// a 307 redirect to http, and that metrics are logged correctly.
IN_PROC_BROWSER_TEST_F(
SSLUITestWithInsecureFormsWarningEnabledForRedirectsWithFormData,
TestDisplaysInsecureFormSubmissionWarningRedirectGet) {
base::HistogramTester histograms;
const std::string interstitial_histogram =
"Security.MixedForm.InterstitialTriggerState";
ASSERT_TRUE(embedded_test_server()->Start());
https_server_.RegisterRequestHandler(
base::BindRepeating(&FormActionHTTPRedirectHandler, &https_server_));
ASSERT_TRUE(https_server_.Start());
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_form_redirects_insecure_get.html",
embedded_test_server()->host_port_pair());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
// There should have been no interstitial triggered.
EXPECT_FALSE(helper);
// Check this was logged correctly as a redirect mixed form that would not
// expose form data.
histograms.ExpectTotalCount(interstitial_histogram, 1);
histograms.ExpectBucketCount(
interstitial_histogram,
InsecureFormNavigationThrottle::InterstitialTriggeredState::
kMixedFormRedirectNoFormData,
1);
}
class MixedFormsPolicyTest : public policy::PolicyTest {
public:
MixedFormsPolicyTest() {
feature_list_.InitAndEnableFeature(
security_interstitials::kInsecureFormSubmissionInterstitial);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Check no warning is shown if disabled by policy.
IN_PROC_BROWSER_TEST_F(MixedFormsPolicyTest, NoWarningOptOutPolicy) {
ASSERT_TRUE(embedded_test_server()->Start());
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server.Start());
// Check pref is set to true by default.
EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kMixedFormsWarningsEnabled));
// Set policy to disable mixed form warnings.
policy::PolicyMap policies;
policies.Set(policy::key::kInsecureFormsWarningsEnabled,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
policy::POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
UpdateProviderPolicy(policies);
// Pref should now be set to false.
EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kMixedFormsWarningsEnabled));
std::string replacement_path =
SSLUITestBase::GetFilePathWithHostAndPortReplacement(
"/ssl/page_displays_insecure_form.html",
embedded_test_server()->host_port_pair());
GURL form_site_url = https_server.GetURL(replacement_path);
GURL form_target_url("http://does-not-exist.test/ssl/google_files/logo.gif");
// Navigate to site with insecure form and submit it.
ui_test_utils::NavigateToURL(browser(), form_site_url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(tab, "submitForm();"));
nav_observer.Wait();
// No interstitial should be shown, and we should be in the form action URL.
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
EXPECT_TRUE(!helper || !helper->IsDisplayingInterstitial());
EXPECT_EQ(tab->GetVisibleURL(), form_target_url);
}
namespace {
// SPKI hash to captive-portal.badssl.com leaf certificate. This
// doesn't match the actual cert (ok_cert.pem) but is good enough for testing.
const char kCaptivePortalSPKI[] =
"sha256/fjZPHewEHTrMDX3I1ecEIeoy3WFxHyGplOLv28kIbtI=";
// Test class that mimics a URL request with a certificate whose SPKI hash is in
// ssl_error_assistant.asciipb resource. A better way of testing the SPKI hashes
// inside the resource bundle would be to serve the actual certificate from the
// embedded test server, but the test server can only serve a limited number of
// predefined certificates.
class SSLUICaptivePortalListResourceBundleTest
: public CertVerifierBrowserTest {
public:
SSLUICaptivePortalListResourceBundleTest()
: CertVerifierBrowserTest(),
https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
feature_list_.InitWithFeatures(
{kCaptivePortalCertificateList} /* enabled */, {} /* disabled */);
https_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
}
void TearDown() override {
SSLErrorHandler::ResetConfigForTesting();
CertVerifierBrowserTest::TearDown();
}
protected:
// Checks that a captive portal interstitial isn't displayed, even though the
// server's certificate is marked as a captive portal certificate.
void TestNoCaptivePortalInterstitial(net::CertStatus cert_status,
int net_error) {
ASSERT_TRUE(https_server()->Start());
base::HistogramTester histograms;
// Mark the server's cert as a captive portal cert.
SetUpCertVerifier(cert_status, net_error, kCaptivePortalSPKI);
// Navigate to an unsafe page on the server. CaptivePortalCertificateList
// feature is enabled but either the error is not name-mismatch, or it's not
// the only error, so a generic SSL interstitial should be displayed.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(interstitial_timer_observer.timer_started());
// Check that the histogram for the captive portal cert was recorded.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(),
2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1);
}
void SetUpCertVerifier(net::CertStatus cert_status,
int net_result,
const std::string& spki_hash) {
scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate());
net::CertVerifyResult verify_result;
verify_result.is_issued_by_known_root =
(net_result != net::ERR_CERT_AUTHORITY_INVALID);
verify_result.verified_cert = cert;
verify_result.cert_status = cert_status;
// Set the SPKI hash to captive-portal.badssl.com leaf certificate.
if (!spki_hash.empty()) {
net::HashValue hash;
ASSERT_TRUE(hash.FromString(spki_hash));
verify_result.public_key_hashes.push_back(hash);
}
mock_cert_verifier()->AddResultForCert(cert, verify_result, net_result);
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
private:
base::test::ScopedFeatureList feature_list_;
net::EmbeddedTestServer https_server_;
};
} // namespace
// Same as CaptivePortalCertificateList_Enabled_FromProto, but this time the
// cert's SPKI hash is listed in ssl_error_assistant.asciipb.
IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListResourceBundleTest, Enabled) {
ASSERT_TRUE(https_server()->Start());
base::HistogramTester histograms;
// Mark the server's cert as a captive portal cert.
SetUpCertVerifier(net::CERT_STATUS_COMMON_NAME_INVALID,
net::ERR_CERT_COMMON_NAME_INVALID, kCaptivePortalSPKI);
// Navigate to an unsafe page on the server. The captive portal interstitial
// should be displayed since CaptivePortalCertificateList feature is enabled.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingCaptivePortalInterstitial(tab));
EXPECT_FALSE(interstitial_timer_observer.timer_started());
// Check that the histogram for the captive portal cert was recorded.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 3);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::CAPTIVE_PORTAL_CERT_FOUND, 1);
}
// Same as SSLUICaptivePortalListResourceBundleTest. Enabled, but this time the
// proto is dynamically updated (e.g. by the component updater). The dynamic
// update should always override the proto loaded from the resource bundle.
IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListResourceBundleTest,
Enabled_DynamicUpdate) {
ASSERT_TRUE(https_server()->Start());
// Mark the server's cert as a captive portal cert.
SetUpCertVerifier(net::CERT_STATUS_COMMON_NAME_INVALID,
net::ERR_CERT_COMMON_NAME_INVALID, kCaptivePortalSPKI);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
{
// Dynamically update the SSL error assistant config, do not include the
// captive portal SPKI hash.
SSLErrorHandler::SetErrorAssistantProto(MakeCaptivePortalConfig(
kLargeVersionId,
std::set<std::string>{"sha256/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sha256/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}));
ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() >
0);
// Navigate to an unsafe page on the server. A generic SSL interstitial
// should be displayed because the dynamic update doesn't contain the hash
// of the captive portal certificate.
base::HistogramTester histograms;
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(interstitial_timer_observer.timer_started());
// Check that the histogram was recorded for an SSL interstitial.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(),
2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1);
}
{
// Dynamically update the error assistant config and add the captive portal
// SPKI hash.
SSLErrorHandler::SetErrorAssistantProto(MakeCaptivePortalConfig(
kLargeVersionId + 1,
std::set<std::string>{"sha256/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sha256/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
kCaptivePortalSPKI}));
ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() >
0);
// Navigate to the unsafe page again. This time, a captive portal
// interstitial should be displayed.
base::HistogramTester histograms;
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingCaptivePortalInterstitial(tab));
EXPECT_FALSE(interstitial_timer_observer.timer_started());
// Check that the histogram was recorded for a captive portal interstitial.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(),
3);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::CAPTIVE_PORTAL_CERT_FOUND, 1);
}
{
// Try dynamically updating the error assistant config with an empty config
// with the same version number. The update should be ignored, and a captive
// portal interstitial should still be displayed.
SSLErrorHandler::SetErrorAssistantProto(
MakeCaptivePortalConfig(kLargeVersionId + 1, std::set<std::string>()));
ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() >
0);
// Navigate to the unsafe page again. This time, a captive portal
// interstitial should be displayed.
base::HistogramTester histograms;
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingCaptivePortalInterstitial(tab));
EXPECT_FALSE(interstitial_timer_observer.timer_started());
// Check that the histogram was recorded for a captive portal interstitial.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(),
3);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::CAPTIVE_PORTAL_CERT_FOUND, 1);
}
}
// Same as SSLUICaptivePortalNameMismatchTest, but this time the error is
// authority-invalid. Captive portal interstitial should not be shown.
IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListResourceBundleTest,
Enabled_AuthorityInvalid) {
TestNoCaptivePortalInterstitial(net::CERT_STATUS_AUTHORITY_INVALID,
net::ERR_CERT_AUTHORITY_INVALID);
}
// Same as SSLUICaptivePortalListResourceBundleTest.Enabled_AuthorityInvalid,
// but this time there are two errors (name mismatch + weak key). Captive portal
// interstitial should not be shown when name mismatch isn't the only error.
IN_PROC_BROWSER_TEST_F(SSLUICaptivePortalListResourceBundleTest,
Enabled_NameMismatchAndWeakKey) {
const net::CertStatus cert_status =
net::CERT_STATUS_COMMON_NAME_INVALID | net::CERT_STATUS_WEAK_KEY;
// Sanity check that COMMON_NAME_INVALID is seen as the net error, since the
// test is designed to verify that SSLErrorHandler notices other errors in the
// CertStatus even when COMMON_NAME_INVALID is the net error.
ASSERT_EQ(net::ERR_CERT_COMMON_NAME_INVALID,
net::MapCertStatusToNetError(cert_status));
TestNoCaptivePortalInterstitial(cert_status,
net::ERR_CERT_COMMON_NAME_INVALID);
}
namespace {
char kTestMITMSoftwareName[] = "Misconfigured Firewall";
class SSLUIMITMSoftwareTest : public CertVerifierBrowserTest {
public:
SSLUIMITMSoftwareTest()
: CertVerifierBrowserTest(),
https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
~SSLUIMITMSoftwareTest() override {}
void SetUpOnMainThread() override {
CertVerifierBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ssl_test_util::SetHSTSForHostName(browser()->profile(), kHstsTestHostName);
}
// Set up the cert verifier to return the error passed in as the cert_error
// parameter.
void SetUpCertVerifier(net::CertStatus cert_error) {
net::CertVerifyResult verify_result;
verify_result.verified_cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
ASSERT_TRUE(verify_result.verified_cert);
verify_result.cert_status = cert_error;
mock_cert_verifier()->AddResultForCert(
https_server()->GetCertificate().get(), verify_result,
net::MapCertStatusToNetError(cert_error));
}
// Sets up an SSLErrorAssistantProto that lists |https_server_|'s default
// certificate as a MITM software certificate.
void SetUpMITMSoftwareCertList(uint32_t version_id) {
auto config_proto =
std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
config_proto->set_version_id(version_id);
chrome_browser_ssl::MITMSoftware* mitm_software =
config_proto->add_mitm_software();
mitm_software->set_name(kTestMITMSoftwareName);
mitm_software->set_issuer_common_name_regex(
https_server()->GetCertificate().get()->issuer().common_name);
mitm_software->set_issuer_organization_regex(
https_server()->GetCertificate().get()->issuer().organization_names[0]);
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() >
0);
}
// Returns a URL which triggers an interstitial with the host name that has
// HSTS set.
GURL GetHSTSTestURL() const {
GURL::Replacements replacements;
replacements.SetHostStr(kHstsTestHostName);
return https_server()
->GetURL("/ssl/blank_page.html")
.ReplaceComponents(replacements);
}
void TestMITMSoftwareInterstitial() {
base::HistogramTester histograms;
ASSERT_TRUE(https_server()->Start());
ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() >
0);
// Navigate to an unsafe page on the server. Mock out the URL host name to
// equal the one set for HSTS.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(), GetHSTSTestURL());
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingMITMInterstitial(tab));
EXPECT_FALSE(interstitial_timer_observer.timer_started());
// Check that the histograms for the MITM software interstitial were
// recorded.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(),
2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE, 0);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL, 1);
}
void TestNoMITMSoftwareInterstitial() {
base::HistogramTester histograms;
ASSERT_TRUE(https_server()->Start());
ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() >
0);
// Navigate to an unsafe page on the server. Mock out the URL host name to
// equal the one set for HSTS.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(), GetHSTSTestURL());
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(interstitial_timer_observer.timer_started());
// Check that a MITM software interstitial was not recorded in histogram.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(),
2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 0);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL, 0);
}
// Returns the https server. Guaranteed to be non-NULL.
const net::EmbeddedTestServer* https_server() const { return &https_server_; }
net::EmbeddedTestServer* https_server() { return &https_server_; }
private:
net::EmbeddedTestServer https_server_;
DISALLOW_COPY_AND_ASSIGN(SSLUIMITMSoftwareTest);
};
// The SSLUIMITMSoftwareEnabled and Disabled test classes exist so that the
// scoped feature list can be instantiated in the set up method of the class
// rather than in the test itself. Bug crbug.com/713390 was causing some of the
// tests in SSLUIMITMSoftwareTest to be flaky. Refactoring these tests so that
// the scoped feature list initialization is done in the set up method fixes
// this flakiness.
class SSLUIMITMSoftwareEnabledTest : public SSLUIMITMSoftwareTest {
public:
SSLUIMITMSoftwareEnabledTest() {
scoped_feature_list_.InitWithFeatures(
{kMITMSoftwareInterstitial} /* enabled */, {} /* disabled */);
}
~SSLUIMITMSoftwareEnabledTest() override {}
void SetUpOnMainThread() override {
SSLUIMITMSoftwareTest::SetUpOnMainThread();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(SSLUIMITMSoftwareEnabledTest);
};
class SSLUIMITMSoftwareDisabledTest : public SSLUIMITMSoftwareTest {
public:
SSLUIMITMSoftwareDisabledTest() {
scoped_feature_list_.InitWithFeatures(
{} /* enabled */, {kMITMSoftwareInterstitial} /* disabled */);
}
~SSLUIMITMSoftwareDisabledTest() override {}
void SetUpOnMainThread() override {
SSLUIMITMSoftwareTest::SetUpOnMainThread();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(SSLUIMITMSoftwareDisabledTest);
};
} // namespace
// Tests that the MITM software interstitial is not displayed when the feature
// is disabled by Finch.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareDisabledTest, DisabledWithFinch) {
SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
SetUpMITMSoftwareCertList(kLargeVersionId);
TestNoMITMSoftwareInterstitial();
}
// Tests that the MITM software interstitial is displayed when the feature is
// enabled by Finch.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest, EnabledWithFinch) {
SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
SetUpMITMSoftwareCertList(kLargeVersionId);
TestMITMSoftwareInterstitial();
}
// Tests that if a certificates matches the common name of a known MITM software
// cert on the list but not the organization name, the MITM software
// interstitial will not be displayed.
IN_PROC_BROWSER_TEST_F(
SSLUIMITMSoftwareEnabledTest,
CertificateCommonNameMatchOnly_NoMITMSoftwareInterstitial) {
base::HistogramTester histograms;
SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
ASSERT_TRUE(https_server()->Start());
// Set up an error assistant proto with a list of MITM software regexed that
// the certificate issued by our server won't match.
auto config_proto =
std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
config_proto->set_version_id(kLargeVersionId);
chrome_browser_ssl::MITMSoftware* mitm_software =
config_proto->add_mitm_software();
mitm_software->set_name(kTestMITMSoftwareName);
mitm_software->set_issuer_common_name_regex(
https_server()->GetCertificate().get()->issuer().common_name);
mitm_software->set_issuer_organization_regex(
"pattern-that-does-not-match-anything");
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
// Navigate to an unsafe page on the server. Mock out the URL host name to
// equal the one set for HSTS.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(), GetHSTSTestURL());
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(interstitial_timer_observer.timer_started());
// Check that a MITM software interstitial was not recorded in histogram.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL,
0);
}
// Tests that if a certificates matches the organization name of a known MITM
// software cert on the list but not the common name, the MITM software
// interstitial will not be displayed.
IN_PROC_BROWSER_TEST_F(
SSLUIMITMSoftwareEnabledTest,
CertificateOrganizationMatchOnly_NoMITMSoftwareInterstitial) {
base::HistogramTester histograms;
SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
ASSERT_TRUE(https_server()->Start());
// Set up an error assistant proto with a list of MITM software regexed that
// the certificate issued by our server won't match.
auto config_proto =
std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
config_proto->set_version_id(kLargeVersionId);
chrome_browser_ssl::MITMSoftware* mitm_software =
config_proto->add_mitm_software();
mitm_software->set_name(kTestMITMSoftwareName);
mitm_software->set_issuer_common_name_regex(
"pattern-that-does-not-match-anything");
mitm_software->set_issuer_organization_regex(
https_server()->GetCertificate().get()->issuer().organization_names[0]);
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
// Navigate to an unsafe page on the server. Mock out the URL host name to
// equal the one set for HSTS.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(), GetHSTSTestURL());
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(interstitial_timer_observer.timer_started());
// Check that a MITM software interstitial was not recorded in histogram.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL,
0);
}
// Tests that if the certificate does not match any entry on the list of known
// MITM software, the MITM software interstitial will not be displayed.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest,
NonMatchingCertificate_NoMITMSoftwareInterstitial) {
base::HistogramTester histograms;
SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
ASSERT_TRUE(https_server()->Start());
// Set up an error assistant proto with a list of MITM software regexes that
// the certificate issued by our server won't match.
auto config_proto =
std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
config_proto->set_version_id(kLargeVersionId);
chrome_browser_ssl::MITMSoftware* mitm_software =
config_proto->add_mitm_software();
mitm_software->set_name("Non-Matching MITM Software");
mitm_software->set_issuer_common_name_regex(
"pattern-that-does-not-match-anything");
mitm_software->set_issuer_organization_regex(
"pattern-that-does-not-match-anything");
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
// Navigate to an unsafe page on the server. Mock out the URL host name to
// equal the one set for HSTS.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(), GetHSTSTestURL());
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(interstitial_timer_observer.timer_started());
// Check that a MITM software interstitial was not recorded in histogram.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL,
0);
}
// Tests that if there is more than one error on the certificate the MITM
// software interstitial will not be displayed.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest,
TwoCertErrors_NoMITMSoftwareInterstitial) {
SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID |
net::CERT_STATUS_COMMON_NAME_INVALID);
SetUpMITMSoftwareCertList(kLargeVersionId);
TestNoMITMSoftwareInterstitial();
}
// Tests that a certificate error other than |CERT_STATUS_AUTHORITY_INVALID|
// will not trigger the MITM software interstitial.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest,
WrongCertError_NoMITMSoftwareInterstitial) {
SetUpCertVerifier(net::CERT_STATUS_COMMON_NAME_INVALID);
SetUpMITMSoftwareCertList(kLargeVersionId);
TestNoMITMSoftwareInterstitial();
}
// Tests that if the error on the certificate served is overridable the MITM
// software interstitial will not be displayed.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest,
OverridableError_NoMITMSoftwareInterstitial) {
base::HistogramTester histograms;
SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
ASSERT_TRUE(https_server()->Start());
SetUpMITMSoftwareCertList(kLargeVersionId);
// Navigate to an unsafe page to trigger an interstitial, but don't replace
// the host name with the one set for HSTS.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SSLInterstitialTimerObserver interstitial_timer_observer(tab);
ui_test_utils::NavigateToURL(browser(),
https_server()->GetURL("/ssl/blank_page.html"));
WaitForInterstitial(tab);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
EXPECT_TRUE(interstitial_timer_observer.timer_started());
// Check that the histogram for an overridable SSL interstitial was
// recorded.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(
SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL,
0);
}
// Tests that the correct strings are displayed on the interstitial in the
// enterprise managed case.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest, EnterpriseManaged) {
SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
ChromeSecurityBlockingPageFactory::SetEnterpriseManagedForTesting(true);
SetUpMITMSoftwareCertList(kLargeVersionId);
TestMITMSoftwareInterstitial();
const std::string expected_primary_paragraph = l10n_util::GetStringFUTF8(
IDS_MITM_SOFTWARE_PRIMARY_PARAGRAPH_ENTERPRISE,
net::EscapeForHTML(base::UTF8ToUTF16(kTestMITMSoftwareName)));
const std::string expected_explanation = l10n_util::GetStringFUTF8(
IDS_MITM_SOFTWARE_EXPLANATION_ENTERPRISE,
net::EscapeForHTML(base::UTF8ToUTF16(kTestMITMSoftwareName)),
l10n_util::GetStringUTF16(IDS_MITM_SOFTWARE_EXPLANATION));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(chrome_browser_interstitials::IsInterstitialDisplayingText(
tab->GetMainFrame(), expected_explanation));
EXPECT_TRUE(chrome_browser_interstitials::IsInterstitialDisplayingText(
tab->GetMainFrame(), expected_primary_paragraph));
}
// Tests that the correct strings are displayed on the interstitial in the
// non-enterprise managed case.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest, NotEnterpriseManaged) {
SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
ChromeSecurityBlockingPageFactory::SetEnterpriseManagedForTesting(false);
SetUpMITMSoftwareCertList(kLargeVersionId);
TestMITMSoftwareInterstitial();
// Don't check the primary paragraph in the non-enterprise case, because it
// has escaped HTML characters which throw an error.
const std::string expected_explanation = l10n_util::GetStringFUTF8(
IDS_MITM_SOFTWARE_EXPLANATION_NONENTERPRISE,
net::EscapeForHTML(base::UTF8ToUTF16(kTestMITMSoftwareName)),
l10n_util::GetStringUTF16(IDS_MITM_SOFTWARE_EXPLANATION));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(chrome_browser_interstitials::IsInterstitialDisplayingText(
tab->GetMainFrame(), expected_explanation));
}
// Initialize MITMSoftware certificate list but set the version_id to zero. This
// less than the version_id of the local resource bundle, so the dynamic
// update will be ignored and a non-MITM interstitial will be shown.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest,
IgnoreDynamicUpdateWithSmallVersionId) {
auto config_proto =
SSLErrorAssistant::GetErrorAssistantProtoFromResourceBundle();
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
ChromeSecurityBlockingPageFactory::SetEnterpriseManagedForTesting(false);
SetUpMITMSoftwareCertList(0u);
TestNoMITMSoftwareInterstitial();
}
class TLSLegacyVersionSSLUITest : public SSLUITest {
public:
TLSLegacyVersionSSLUITest() = default;
~TLSLegacyVersionSSLUITest() override = default;
void SetUpOnMainThread() override {
SSLUITest::SetUpOnMainThread();
mock_cert_verifier()->set_default_result(net::OK);
}
void SetUpCommandLine(base::CommandLine* command_line) override {
mock_cert_verifier_.SetUpCommandLine(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}
content::ContentMockCertVerifier::CertVerifier* mock_cert_verifier() {
return mock_cert_verifier_.mock_cert_verifier();
}
protected:
net::EmbeddedTestServer* https_server() { return &https_server_; }
void SetTLSVersion(uint16_t version) {
net::SSLServerConfig config;
config.version_max = version;
config.version_min = version;
https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_OK, config);
}
private:
content::ContentMockCertVerifier mock_cert_verifier_;
DISALLOW_COPY_AND_ASSIGN(TLSLegacyVersionSSLUITest);
};
// TLS 1.2 should not trigger a warning.
IN_PROC_BROWSER_TEST_F(TLSLegacyVersionSSLUITest, NoWarningTLS12) {
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1_2);
ASSERT_TRUE(https_server()->Start());
GURL url(https_server()->GetURL("/ssl/google.html"));
content::WebContentsConsoleObserver console_observer(
browser()->tab_strip_model()->GetActiveWebContents());
console_observer.SetPattern(base::StringPrintf(
"*The connection used to load resources from https://%s:%s*",
url.host().c_str(), url.port().c_str()));
ui_test_utils::NavigateToURL(browser(), url);
EXPECT_EQ(0u, console_observer.messages().size());
}
// TLS 1.1 should trigger a warning.
IN_PROC_BROWSER_TEST_F(TLSLegacyVersionSSLUITest, WarningTLS11) {
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1_1);
ASSERT_TRUE(https_server()->Start());
GURL url(https_server()->GetURL("/ssl/google.html"));
content::WebContentsConsoleObserver console_observer(
browser()->tab_strip_model()->GetActiveWebContents());
console_observer.SetPattern(base::StringPrintf(
"*The connection used to load resources from https://%s:%s*",
url.host().c_str(), url.port().c_str()));
ui_test_utils::NavigateToURL(browser(), url);
console_observer.Wait();
EXPECT_TRUE(base::MatchPattern(console_observer.GetMessageAt(0u),
"*will be disabled in the future*"));
EXPECT_TRUE(base::MatchPattern(console_observer.GetMessageAt(0u),
"*should enable TLS 1.2 or later*"));
}
// TLS 1.0 should trigger a warning.
IN_PROC_BROWSER_TEST_F(TLSLegacyVersionSSLUITest, WarningTLS1) {
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1);
ASSERT_TRUE(https_server()->Start());
GURL url(https_server()->GetURL("/ssl/google.html"));
content::WebContentsConsoleObserver console_observer(
browser()->tab_strip_model()->GetActiveWebContents());
console_observer.SetPattern(base::StringPrintf(
"*The connection used to load resources from https://%s:%s*",
url.host().c_str(), url.port().c_str()));
ui_test_utils::NavigateToURL(browser(), url);
console_observer.Wait();
EXPECT_TRUE(base::MatchPattern(console_observer.GetMessageAt(0u),
"*will be disabled in the future*"));
EXPECT_TRUE(base::MatchPattern(console_observer.GetMessageAt(0u),
"*should enable TLS 1.2 or later*"));
}
// Warnings should show for subresources, but cap after a limit.
IN_PROC_BROWSER_TEST_F(TLSLegacyVersionSSLUITest, ManySubresources) {
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1);
content::SetupCrossSiteRedirector(https_server());
ASSERT_TRUE(https_server()->Start());
GURL url(https_server()->GetURL("/ssl/page_with_many_subresources.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Observe the message for a cross-site subresource.
{
content::WebContentsConsoleObserver console_observer(tab);
console_observer.SetPattern("*https://a.test*");
ui_test_utils::NavigateToURL(browser(), url);
console_observer.Wait();
EXPECT_TRUE(base::MatchPattern(console_observer.GetMessageAt(0u),
"*will be disabled in the future*"));
EXPECT_TRUE(base::MatchPattern(console_observer.GetMessageAt(0u),
"*should enable TLS 1.2 or later*"));
}
// Observe that the message caps out after some number of subresources.
{
content::WebContentsConsoleObserver console_observer(tab);
console_observer.SetPattern("*Additional resources*");
ui_test_utils::NavigateToURL(browser(), url);
console_observer.Wait();
EXPECT_TRUE(base::MatchPattern(console_observer.GetMessageAt(0u),
"*will be disabled in the future*"));
EXPECT_TRUE(base::MatchPattern(console_observer.GetMessageAt(0u),
"*should enable TLS 1.2 or later*"));
}
}
class LegacyTLSInterstitialTest : public TLSLegacyVersionSSLUITest {
public:
LegacyTLSInterstitialTest() {
feature_list_.InitAndEnableFeature(net::features::kLegacyTLSEnforced);
}
void SetUpOnMainThread() override {
TLSLegacyVersionSSLUITest::SetUpOnMainThread();
// Set up browser and network service configs to be empty by default.
InitializeEmptyLegacyTLSConfig();
base::RunLoop run_loop;
InitializeEmptyLegacyTLSConfigNetworkService(&run_loop);
}
private:
base::test::ScopedFeatureList feature_list_;
DISALLOW_COPY_AND_ASSIGN(LegacyTLSInterstitialTest);
};
// When kLegacyTLSEnforcement is enabled, visiting a legacy TLS page should
// show the legacy TLS specific interstitial.
IN_PROC_BROWSER_TEST_F(LegacyTLSInterstitialTest, ShowsInterstitial) {
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1);
ASSERT_TRUE(https_server()->Start());
base::HistogramTester histograms;
ui_test_utils::NavigateToURL(browser(),
https_server()->GetURL("/ssl/google.html"));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab);
ASSERT_TRUE(
chrome_browser_interstitials::IsShowingLegacyTLSInterstitial(tab));
// Check that the histogram for the legacy TLS interstitial was recorded.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::HANDLE_ALL, 1);
histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
SSLErrorHandler::SHOW_LEGACY_TLS_INTERSTITIAL,
1);
// Click through the interstitial.
ProceedThroughInterstitial(tab);
// Navigating to the same page now shouldn't cause an interstitial.
ui_test_utils::NavigateToURL(browser(),
https_server()->GetURL("/ssl/google.html"));
tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(
chrome_browser_interstitials::IsShowingLegacyTLSInterstitial(tab));
// Check that no new interstitial metrics were recorded from this navigation.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
histograms.ExpectBucketCount("interstitial.legacy_tls.decision",
security_interstitials::MetricsHelper::PROCEED,
1);
}
// When kLegacyTLSEnforcement is enabled but the SSLVersionMin enterprise policy
// is set, the SSLVersionMin policy should also override the version we show the
// legacy TLS interstitial on.
IN_PROC_BROWSER_TEST_F(LegacyTLSInterstitialTest, PolicyOverridesInterstitial) {
// Set the SSLVersionMin policy and make sure that the network service has
// received the update.
base::Value policy_value("tls1"); // TLS 1.0
SetPolicy(policy::key::kSSLVersionMin, std::move(policy_value));
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1);
ASSERT_TRUE(https_server()->Start());
base::HistogramTester histograms;
ui_test_utils::NavigateToURL(browser(),
https_server()->GetURL("/ssl/google.html"));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(
chrome_browser_interstitials::IsShowingLegacyTLSInterstitial(tab));
// Interstitial metrics should not have been recorded from this navigation.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 0);
}
// Check that if we have bypassed the legacy TLS error previously and then the
// server responded with TLS 1.2, we drop the error exception.
IN_PROC_BROWSER_TEST_F(LegacyTLSInterstitialTest, FixedServerDropsBypass) {
GURL kSiteWithLegacyTLS("https://example.test/legacy-tls");
GURL kSiteWithModernTLS("https://example.test/modern-tls");
// EmbeddedTestServer can be flakey if forcing a specific port (as the port
// may no longer be available on the system). URLLoaderInterceptor can mock
// out the responses as needed instead, reusing the same port across variants.
auto url_loader_interceptor = std::make_unique<content::URLLoaderInterceptor>(
base::BindLambdaForTesting(
[=](content::URLLoaderInterceptor::RequestParams* params) {
network::URLLoaderCompletionStatus status;
status.ssl_info = net::SSLInfo();
status.ssl_info->cert = net::ImportCertFromFile(
net::GetTestCertsDirectory(), "ok_cert.pem");
status.ssl_info->unverified_cert = status.ssl_info->cert;
if (params->url_request.url == GURL(kSiteWithLegacyTLS)) {
status.error_code = net::ERR_SSL_OBSOLETE_VERSION;
status.ssl_info->cert_status = net::CERT_STATUS_LEGACY_TLS;
params->client->OnComplete(status);
return true;
}
status.error_code = net::OK;
std::string headers =
"HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\n";
std::string body = "<html><title>Success</title>Hello world</html>";
content::URLLoaderInterceptor::WriteResponse(headers, body,
params->client.get());
return true;
}));
// Connect over TLS 1.0 and proceed through the interstitial to set an error
// bypass.
ui_test_utils::NavigateToURL(browser(), kSiteWithLegacyTLS);
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab);
ProceedThroughInterstitial(tab);
// Connect over a "fixed" TLS 1.2 connection.
ui_test_utils::NavigateToURL(browser(), kSiteWithModernTLS);
EXPECT_FALSE(
chrome_browser_interstitials::IsShowingLegacyTLSInterstitial(tab));
// Go back to connecting over TLS 1.0. Visiting should once again show the
// legacy TLS interstitial
ui_test_utils::NavigateToURL(browser(), kSiteWithLegacyTLS);
WaitForInterstitial(tab);
EXPECT_TRUE(
chrome_browser_interstitials::IsShowingLegacyTLSInterstitial(tab));
}
// Check that cliking the "back to safety" button works for the legacy TLS
// interstitial.
IN_PROC_BROWSER_TEST_F(LegacyTLSInterstitialTest, BackToSafety) {
base::HistogramTester histograms;
// Connect over TLS 1.0 and don't proceed through the interstitial.
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1);
ASSERT_TRUE(https_server()->Start());
ui_test_utils::NavigateToURL(browser(),
https_server()->GetURL("/ssl/google.html"));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab);
DontProceedThroughInterstitial(tab);
EXPECT_NE(tab->GetVisibleURL(), https_server()->GetURL("/ssl/google.html"));
// Check that the histogram for the legacy TLS interstitial was recorded.
histograms.ExpectBucketCount(
"interstitial.legacy_tls.decision",
security_interstitials::MetricsHelper::DONT_PROCEED, 1);
}
// Check that we don't show the interstitial for sites in the control set.
IN_PROC_BROWSER_TEST_F(LegacyTLSInterstitialTest, ControlSiteNoInterstitial) {
InitializeLegacyTLSConfigWithControl();
base::RunLoop run_loop;
InitializeLegacyTLSConfigWithControlNetworkService(&run_loop);
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1);
ASSERT_TRUE(https_server()->Start());
base::HistogramTester histograms;
ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL(kLegacyTLSHost, "/ssl/google.html"));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(
chrome_browser_interstitials::IsShowingLegacyTLSInterstitial(tab));
// Interstitial metrics should not have been recorded from this navigation.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 0);
}
// A WebContentsObserver that allows the user to wait for a navigation, and
// check whether the resource for the navigation was loaded from cache or not.
class CacheNavigationObserver : public content::WebContentsObserver {
public:
explicit CacheNavigationObserver(WebContents* web_contents,
bool expect_cached)
: WebContentsObserver(web_contents), expect_cached_(expect_cached) {}
~CacheNavigationObserver() override = default;
void WaitForNavigation() { run_loop_.Run(); }
// WebContentsObserver:
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
ASSERT_EQ(expect_cached_, navigation_handle->WasResponseCached());
run_loop_.Quit();
}
private:
bool expect_cached_;
base::RunLoop run_loop_;
};
// Tests that resources loaded over legacy TLS should not be cached.
IN_PROC_BROWSER_TEST_F(LegacyTLSInterstitialTest, LegacyTLSPagesNotCached) {
// Connect over TLS 1.0 and proceed through the interstitial to set an error
// bypass.
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1);
ASSERT_TRUE(https_server()->Start());
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/cachetime"));
auto* tab1 = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab1);
ProceedThroughInterstitial(tab1);
// Open a new tab and navigate so that resources are fetched via the disk
// cache. Navigating to the same URL in the same tab triggers a refresh which
// will not check the disk cache.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("about:blank"), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB |
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
auto* tab2 = browser()->tab_strip_model()->GetActiveWebContents();
CacheNavigationObserver observer(tab2, false);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/cachetime"));
observer.WaitForNavigation(); // Will fail if resource is loaded from cache.
}
// Tests that resources from control sites are cached, even if they are loaded
// over legacy TLS.
IN_PROC_BROWSER_TEST_F(LegacyTLSInterstitialTest, ControlSitePagesCached) {
InitializeLegacyTLSConfigWithControl();
base::RunLoop run_loop;
InitializeLegacyTLSConfigWithControlNetworkService(&run_loop);
// Connect over TLS 1.0 to a site in the control list.
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1);
ASSERT_TRUE(https_server()->Start());
ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL(kLegacyTLSHost, "/cachetime"));
auto* tab1 = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(
chrome_browser_interstitials::IsShowingLegacyTLSInterstitial(tab1));
// Open a new tab and navigate so that resources are fetched via the disk
// cache. Navigating to the same URL in the same tab triggers a refresh which
// will not check the disk cache.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("about:blank"), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB |
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
auto* tab2 = browser()->tab_strip_model()->GetActiveWebContents();
CacheNavigationObserver observer(tab2, true);
ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL(kLegacyTLSHost, "/cachetime"));
observer.WaitForNavigation(); // Will fail if not loaded from cache.
}
// Tests that resources loaded over legacy TLS but are control sites should be
// cached.
IN_PROC_BROWSER_TEST_F(LegacyTLSInterstitialTest, NormalPagesCached) {
// Connect to a non-legacy TLS site.
ASSERT_TRUE(https_server()->Start());
ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL(kLegacyTLSHost, "/cachetime"));
auto* tab1 = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(
chrome_browser_interstitials::IsShowingLegacyTLSInterstitial(tab1));
// Open a new tab and navigate so that resources are fetched via the disk
// cache. Navigating to the same URL in the same tab triggers a refresh which
// will not check the disk cache.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("about:blank"), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB |
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
auto* tab2 = browser()->tab_strip_model()->GetActiveWebContents();
CacheNavigationObserver observer(tab2, true);
ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL(kLegacyTLSHost, "/cachetime"));
observer.WaitForNavigation(); // Will fail if not loaded from cache.
}
// Tests that a page with legacy TLS and HSTS shows a bypassable interstitial
// rather than a hard non-bypassable HSTS warning.
IN_PROC_BROWSER_TEST_F(LegacyTLSInterstitialTest, LegacyTLSNotFatal) {
// Set HSTS for the test page.
ssl_test_util::SetHSTSForHostName(browser()->profile(), kHstsTestHostName);
// Connect over TLS 1.0 and proceed through the interstitial.
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1);
ASSERT_TRUE(https_server()->Start());
ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL(kHstsTestHostName, "/ssl/google.html"));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab);
// Verify that there is a proceed link in the interstitial.
int result = security_interstitials::CMD_ERROR;
const std::string javascript = base::StringPrintf(
"domAutomationController.send("
"(document.querySelector(\"#proceed-link\") === null) "
"? (%d) : (%d))",
security_interstitials::CMD_TEXT_NOT_FOUND,
security_interstitials::CMD_TEXT_FOUND);
ASSERT_TRUE(content::ExecuteScriptAndExtractInt(tab->GetMainFrame(),
javascript, &result));
EXPECT_EQ(security_interstitials::CMD_TEXT_FOUND, result);
}
// Tests that the legacy TLS control config applies to subdomains if the
// registrable domain is in the control config.
IN_PROC_BROWSER_TEST_F(LegacyTLSInterstitialTest,
ControlConfigIncludesSubdomains) {
InitializeLegacyTLSConfigWithControl();
base::RunLoop run_loop;
InitializeLegacyTLSConfigWithControlNetworkService(&run_loop);
SetTLSVersion(net::SSL_PROTOCOL_VERSION_TLS1);
ASSERT_TRUE(https_server()->Start());
base::HistogramTester histograms;
ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL(std::string("www.") + kLegacyTLSHost,
"/ssl/google.html"));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(
chrome_browser_interstitials::IsShowingLegacyTLSInterstitial(tab));
// Interstitial metrics should not have been recorded from this navigation.
histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 0);
}
// Checks that SimpleURLLoader, which uses services/network/url_loader.cc, goes
// through the new NetworkServiceClient interface to deliver cert error
// notifications to the browser which then overrides the certificate error.
IN_PROC_BROWSER_TEST_F(SSLUITest, SimpleURLLoaderCertError) {
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_NO_FATAL_FAILURE(SetUpUnsafeContentsWithUserException(
"/ssl/page_with_unsafe_contents.html"));
ssl_test_util::CheckAuthenticationBrokenState(tab, CertError::NONE,
AuthState::NONE);
EXPECT_EQ(net::OK,
content::LoadBasicRequest(
tab->GetMainFrame(),
https_server_mismatched_.GetURL("/anchor_download_test.png")));
}
IN_PROC_BROWSER_TEST_F(SSLUITest, NetworkErrorDoesntRevokeExemptions) {
ASSERT_TRUE(https_server_expired_.Start());
GURL expired_url = https_server_expired_.GetURL("/title1.html");
int server_port = expired_url.IntPort();
// Navigate to the expired cert URL, make sure we get an interstitial.
ui_test_utils::NavigateToURL(browser(), expired_url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
// Click through the interstitial.
ProceedThroughInterstitial(tab);
// Shut down the server and navigate again to cause a network error.
ASSERT_TRUE(https_server_expired_.ShutdownAndWaitUntilComplete());
ui_test_utils::NavigateToURL(browser(), expired_url);
// Create a new server in the same url (including port), the certificate
// should still be invalid.
net::EmbeddedTestServer new_https_server(net::EmbeddedTestServer::TYPE_HTTPS);
new_https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
new_https_server.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(new_https_server.Start(server_port));
ui_test_utils::NavigateToURL(browser(), expired_url);
// We shouldn't get an interstitial this time.
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));
}
// Checks we don't attempt to show an interstitial (or crash) when visiting an
// SSL error related page in chrome://network-errors. Regression test for
// crbug.com/953812
IN_PROC_BROWSER_TEST_F(SSLUITest, NoInterstitialOnNetworkErrorPage) {
GURL invalid_cert_url(content::kChromeUINetworkErrorURL);
GURL::Replacements replacements;
replacements.SetPathStr("-207");
invalid_cert_url = invalid_cert_url.ReplaceComponents(replacements);
ui_test_utils::NavigateToURL(browser(), invalid_cert_url);
EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(
browser()->tab_strip_model()->GetActiveWebContents()));
}
// This SPKI hash is from a self signed certificate generated using the
// following openssl command:
// openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes
// openssl x509 -noout -in certificate.pem -pubkey | \
// openssl asn1parse -noout -inform pem -out public.key;
// openssl dgst -sha256 -binary public.key | openssl enc -base64
// The actual value of the hash doesn't matter as long it's a valid SPKI hash.
const char kMatchingDynamicInterstitialCert[] =
"sha256/eFi0afYJLdI0YsZFu4U8ra2B5/5ynzfKkI88M94iVFA=";
namespace {
class SSLUIDynamicInterstitialTest : public CertVerifierBrowserTest {
public:
SSLUIDynamicInterstitialTest()
: CertVerifierBrowserTest(),
https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
~SSLUIDynamicInterstitialTest() override {}
void SetUpCertVerifier() {
scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate());
net::CertVerifyResult verify_result;
verify_result.verified_cert = cert;
verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
net::HashValue hash;
ASSERT_TRUE(hash.FromString(kMatchingDynamicInterstitialCert));
verify_result.public_key_hashes.push_back(hash);
mock_cert_verifier()->AddResultForCert(cert, verify_result,
net::ERR_CERT_COMMON_NAME_INVALID);
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
// Creates and returns a SSLErrorAssistantConfig object.
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig>
CreateSSLErrorAssistantConfig() {
auto config_proto =
std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
config_proto->set_version_id(kLargeVersionId);
return config_proto;
}
// Adds a dynamic interstitial to |config_proto|. All of the dynamic
// interstitial's fields mismatch with |https_server_|'s SSL info.
void AddMismatchDynamicInterstitial(
chrome_browser_ssl::SSLErrorAssistantConfig* config_proto) {
chrome_browser_ssl::DynamicInterstitial* filter =
config_proto->add_dynamic_interstitial();
filter->set_interstitial_type(
chrome_browser_ssl::DynamicInterstitial::INTERSTITIAL_PAGE_SSL);
filter->set_cert_error(
chrome_browser_ssl::DynamicInterstitial::ERR_CERT_DATE_INVALID);
filter->add_sha256_hash("sha256/killdeer");
filter->add_sha256_hash("sha256/thickkne");
filter->set_issuer_common_name_regex("beeeater");
filter->set_issuer_organization_regex("honeycreeper");
filter->set_mitm_software_name(kTestMITMSoftwareName);
}
// Adds a dynamic interstitial to |config_proto| and returns it. All of the
// fields in the dynamic intersitial matches with |https_server_|'s
// SSL info. Optionally set the flag for triggering dynamic interstitials
// only on non-overridable errors.
chrome_browser_ssl::DynamicInterstitial* AddMatchingDynamicInterstitial(
chrome_browser_ssl::SSLErrorAssistantConfig* config_proto,
bool show_only_for_nonoverridable_errors = false) {
chrome_browser_ssl::DynamicInterstitial* filter =
config_proto->add_dynamic_interstitial();
filter->set_interstitial_type(chrome_browser_ssl::DynamicInterstitial::
INTERSTITIAL_PAGE_CAPTIVE_PORTAL);
filter->set_cert_error(
chrome_browser_ssl::DynamicInterstitial::ERR_CERT_COMMON_NAME_INVALID);
filter->add_sha256_hash("sha256/kingfisher");
filter->add_sha256_hash(kMatchingDynamicInterstitialCert);
filter->add_sha256_hash("sha256/flycatcher");
scoped_refptr<net::X509Certificate> cert = https_server_.GetCertificate();
filter->set_issuer_common_name_regex(cert.get()->issuer().common_name);
if (!cert.get()->issuer().organization_names.empty()) {
filter->set_issuer_organization_regex(
cert.get()->issuer().organization_names[0]);
}
filter->set_mitm_software_name(kTestMITMSoftwareName);
filter->set_support_url("https://google.com");
filter->set_show_only_for_nonoverridable_errors(
show_only_for_nonoverridable_errors);
return filter;
}
security_interstitials::SecurityInterstitialPage* GetInterstitialDelegate(
WebContents* tab) {
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
if (!helper)
return nullptr;
return helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting();
}
private:
net::EmbeddedTestServer https_server_;
DISALLOW_COPY_AND_ASSIGN(SSLUIDynamicInterstitialTest);
};
} // namespace
// Tests that the dynamic interstitial list is used when the feature is
// enabled via Finch. The list is passed to SSLErrorHandler via a proto.
IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, Match) {
ASSERT_TRUE(https_server()->Start());
SetUpCertVerifier();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
{
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
CreateSSLErrorAssistantConfig();
config_proto->set_version_id(kLargeVersionId);
AddMismatchDynamicInterstitial(config_proto.get());
AddMatchingDynamicInterstitial(config_proto.get());
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
security_interstitials::SecurityInterstitialPage* interstitial_page =
GetInterstitialDelegate(tab);
ASSERT_TRUE(interstitial_page);
ASSERT_EQ(CaptivePortalBlockingPage::kTypeForTesting,
interstitial_page->GetTypeForTesting());
}
}
IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, MatchUnknownCertError) {
ASSERT_TRUE(https_server()->Start());
SetUpCertVerifier();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
{
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
CreateSSLErrorAssistantConfig();
config_proto->set_version_id(kLargeVersionId);
AddMismatchDynamicInterstitial(config_proto.get());
// Add a matching dynamic interstitial with the UNKNOWN_CERT_ERROR status.
chrome_browser_ssl::DynamicInterstitial* match =
AddMatchingDynamicInterstitial(config_proto.get());
match->set_cert_error(
chrome_browser_ssl::DynamicInterstitial::UNKNOWN_CERT_ERROR);
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
security_interstitials::SecurityInterstitialPage* interstitial_page =
GetInterstitialDelegate(tab);
ASSERT_TRUE(interstitial_page);
ASSERT_EQ(CaptivePortalBlockingPage::kTypeForTesting,
interstitial_page->GetTypeForTesting());
}
}
IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest,
MatchEmptyCommonNameRegex) {
ASSERT_TRUE(https_server()->Start());
SetUpCertVerifier();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
{
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
CreateSSLErrorAssistantConfig();
config_proto->set_version_id(kLargeVersionId);
AddMismatchDynamicInterstitial(config_proto.get());
// Add a matching dynamic interstitial with an empty issuer common name
// regex.
chrome_browser_ssl::DynamicInterstitial* match =
AddMatchingDynamicInterstitial(config_proto.get());
match->set_issuer_common_name_regex(std::string());
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
security_interstitials::SecurityInterstitialPage* interstitial_page =
GetInterstitialDelegate(tab);
ASSERT_TRUE(interstitial_page);
ASSERT_EQ(CaptivePortalBlockingPage::kTypeForTesting,
interstitial_page->GetTypeForTesting());
}
}
IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest,
MatchEmptyOrganizationRegex) {
ASSERT_TRUE(https_server()->Start());
SetUpCertVerifier();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
{
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
CreateSSLErrorAssistantConfig();
config_proto->set_version_id(kLargeVersionId);
AddMismatchDynamicInterstitial(config_proto.get());
// Add a matching dynamic interstitial with an empty issuer organization
// name regex.
chrome_browser_ssl::DynamicInterstitial* match =
AddMatchingDynamicInterstitial(config_proto.get());
match->set_issuer_organization_regex(std::string());
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
security_interstitials::SecurityInterstitialPage* interstitial_page =
GetInterstitialDelegate(tab);
ASSERT_TRUE(interstitial_page);
ASSERT_EQ(CaptivePortalBlockingPage::kTypeForTesting,
interstitial_page->GetTypeForTesting());
}
}
IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, MismatchHash) {
ASSERT_TRUE(https_server()->Start());
SetUpCertVerifier();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
{
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
CreateSSLErrorAssistantConfig();
config_proto->set_version_id(kLargeVersionId);
AddMismatchDynamicInterstitial(config_proto.get());
// Add a dynamic interstitial with matching fields, except for the
// certificate hashes.
chrome_browser_ssl::DynamicInterstitial* match =
AddMatchingDynamicInterstitial(config_proto.get());
match->clear_sha256_hash();
match->add_sha256_hash("sha256/sapsucker");
match->add_sha256_hash("sha256/flowerpiercer");
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
security_interstitials::SecurityInterstitialPage* interstitial_page =
GetInterstitialDelegate(tab);
ASSERT_TRUE(interstitial_page);
EXPECT_NE(CaptivePortalBlockingPage::kTypeForTesting,
interstitial_page->GetTypeForTesting());
}
}
IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, MismatchCertError) {
ASSERT_TRUE(https_server()->Start());
SetUpCertVerifier();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
{
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
CreateSSLErrorAssistantConfig();
config_proto->set_version_id(kLargeVersionId);
AddMismatchDynamicInterstitial(config_proto.get());
// Add a dynamic interstitial with matching fields, except for the
// cert error field.
chrome_browser_ssl::DynamicInterstitial* match =
AddMatchingDynamicInterstitial(config_proto.get());
match->set_cert_error(
chrome_browser_ssl::DynamicInterstitial::ERR_CERT_DATE_INVALID);
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
security_interstitials::SecurityInterstitialPage* interstitial_page =
GetInterstitialDelegate(tab);
ASSERT_TRUE(interstitial_page);
EXPECT_NE(CaptivePortalBlockingPage::kTypeForTesting,
interstitial_page->GetTypeForTesting());
}
}
IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, MismatchCommonNameRegex) {
ASSERT_TRUE(https_server()->Start());
SetUpCertVerifier();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
{
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
CreateSSLErrorAssistantConfig();
config_proto->set_version_id(kLargeVersionId);
AddMismatchDynamicInterstitial(config_proto.get());
// Add a dynamic interstitial with matching fields, except for the
// issuer common name regex field.
chrome_browser_ssl::DynamicInterstitial* match =
AddMatchingDynamicInterstitial(config_proto.get());
match->set_issuer_common_name_regex("beeeater");
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
security_interstitials::SecurityInterstitialPage* interstitial_page =
GetInterstitialDelegate(tab);
ASSERT_TRUE(interstitial_page);
EXPECT_NE(CaptivePortalBlockingPage::kTypeForTesting,
interstitial_page->GetTypeForTesting());
}
}
IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest,
MismatchOrganizationRegex) {
ASSERT_TRUE(https_server()->Start());
SetUpCertVerifier();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
{
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
CreateSSLErrorAssistantConfig();
config_proto->set_version_id(kLargeVersionId);
AddMismatchDynamicInterstitial(config_proto.get());
// Add a dynamic interstitial with matching fields, except for the
// issuer organization name regex field.
chrome_browser_ssl::DynamicInterstitial* match =
AddMatchingDynamicInterstitial(config_proto.get());
match->set_issuer_organization_regex("honeycreeper");
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
security_interstitials::SecurityInterstitialPage* interstitial_page =
GetInterstitialDelegate(tab);
ASSERT_TRUE(interstitial_page);
EXPECT_NE(CaptivePortalBlockingPage::kTypeForTesting,
interstitial_page->GetTypeForTesting());
}
}
IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, MismatchWhenOverridable) {
ASSERT_TRUE(https_server()->Start());
SetUpCertVerifier();
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
{
std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
CreateSSLErrorAssistantConfig();
config_proto->set_version_id(kLargeVersionId);
AddMismatchDynamicInterstitial(config_proto.get());
// Add a matching dynamic interstitial, except for the
// show_only_for_nonoverridable_errors flag is set to true.
chrome_browser_ssl::DynamicInterstitial* match =
AddMatchingDynamicInterstitial(config_proto.get(), true);
match->set_cert_error(
chrome_browser_ssl::DynamicInterstitial::UNKNOWN_CERT_ERROR);
SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
kLargeVersionId);
ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/"));
WaitForInterstitial(tab);
security_interstitials::SecurityInterstitialPage* interstitial_page =
GetInterstitialDelegate(tab);
ASSERT_TRUE(interstitial_page);
EXPECT_NE(CaptivePortalBlockingPage::kTypeForTesting,
interstitial_page->GetTypeForTesting());
}
}
class SSLPKPBrowserTest : public CertVerifierBrowserTest {
public:
void SetUpOnMainThread() override {
CertVerifierBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule(kPreloadedPKPHost, "127.0.0.1");
host_resolver()->AddRule(kPreloadedReportHost, "127.0.0.1");
}
void TearDownOnMainThread() override {
if (content::IsOutOfProcessNetworkService()) {
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
content::GetNetworkService()->BindTestInterface(
network_service_test.BindNewPipeAndPassReceiver());
network_service_test->SetTransportSecurityStateSource(0);
} else {
RunOnIOThreadBlocking(base::BindOnce(
&SSLPKPBrowserTest::CleanUpOnIOThread, base::Unretained(this)));
}
CertVerifierBrowserTest::TearDownOnMainThread();
}
void EnableStaticPins(int reporting_port) {
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
content::StoragePartition* partition =
content::BrowserContext::GetDefaultStoragePartition(
browser()->profile());
partition->GetNetworkContext()->EnableStaticKeyPinningForTesting();
partition->FlushNetworkInterfaceForTesting();
if (content::IsOutOfProcessNetworkService()) {
mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
content::GetNetworkService()->BindTestInterface(
network_service_test.BindNewPipeAndPassReceiver());
network_service_test->SetTransportSecurityStateSource(reporting_port);
} else {
// TODO(https://crbug.com/1008175): This code is not threadsafe, as the
// network stack does not run on the IO thread. Ideally, the
// NetworkServiceTest object would be set up in-process on the network
// service's thread, and this path would be removed.
RunOnIOThreadBlocking(base::BindOnce(
&SSLPKPBrowserTest::SetTransportSecurityStateSourceOnIO,
base::Unretained(this), reporting_port));
}
}
private:
void RunOnIOThreadBlocking(base::OnceClosure task) {
base::RunLoop run_loop;
content::GetIOThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE, std::move(task), run_loop.QuitClosure());
run_loop.Run();
}
void SetTransportSecurityStateSourceOnIO(int reporting_port) {
transport_security_state_source_ =
std::make_unique<net::ScopedTransportSecurityStateSource>(
reporting_port);
}
void CleanUpOnIOThread() { transport_security_state_source_.reset(); }
// Only used when NetworkService is disabled. Accessed on IO thread.
std::unique_ptr<net::ScopedTransportSecurityStateSource>
transport_security_state_source_;
};
// Test case where a PKP report is sent.
IN_PROC_BROWSER_TEST_F(SSLPKPBrowserTest, SendPKPReport) {
base::RunLoop wait_for_report_loop;
// Server that PKP reports are sent to.
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&WaitForJsonRequest, wait_for_report_loop.QuitClosure(), false));
ASSERT_TRUE(embedded_test_server()->Start());
EnableStaticPins(embedded_test_server()->port());
// Server with static key pinning and a report-URI for pin validation
// failures.
net::EmbeddedTestServer pkp_test_server(net::EmbeddedTestServer::TYPE_HTTPS);
pkp_test_server.SetSSLConfig(
net::EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN);
ASSERT_TRUE(pkp_test_server.Start());
// Set the cert verifier to cause the PKP check to fail.
net::CertVerifyResult verify_result;
verify_result.verified_cert = pkp_test_server.GetCertificate();
net::SHA256HashValue hash = {{0x00, 0x01}};
verify_result.public_key_hashes.push_back(net::HashValue(hash));
verify_result.is_issued_by_known_root = true;
mock_cert_verifier()->AddResultForCertAndHost(
pkp_test_server.GetCertificate(), kPreloadedPKPHost, verify_result,
net::OK);
ui_test_utils::NavigateToURL(browser(),
pkp_test_server.GetURL(kPreloadedPKPHost, "/"));
wait_for_report_loop.Run();
// Shut down the test server, to make it unlikely this will end up in the same
// situation as the next test, though it's still theoretically possible.
ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
}
// Test case where a PKP report is sent, and the server hasn't replied by the
// time the profile is torn down. Test will crash if the URLRequestContext is
// torn down before the request is torn down.
IN_PROC_BROWSER_TEST_F(SSLPKPBrowserTest, SendPKPReportServerHangs) {
base::RunLoop wait_for_report_loop;
// Server that PKP reports are sent to. Have to use a class member to make
// sure that the test server outlives the IO thread.
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&WaitForJsonRequest, wait_for_report_loop.QuitClosure(), true));
ASSERT_TRUE(embedded_test_server()->Start());
EnableStaticPins(embedded_test_server()->port());
// Server with static key pinning and a report-URI for pin validation
// failures.
net::EmbeddedTestServer pkp_test_server(net::EmbeddedTestServer::TYPE_HTTPS);
pkp_test_server.SetSSLConfig(
net::EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN);
ASSERT_TRUE(pkp_test_server.Start());
// Set the cert verifier to cause the PKP check to fail.
net::CertVerifyResult verify_result;
verify_result.verified_cert = pkp_test_server.GetCertificate();
net::SHA256HashValue hash = {{0x00, 0x01}};
verify_result.public_key_hashes.push_back(net::HashValue(hash));
verify_result.is_issued_by_known_root = true;
mock_cert_verifier()->AddResultForCertAndHost(
pkp_test_server.GetCertificate(), kPreloadedPKPHost, verify_result,
net::OK);
ui_test_utils::NavigateToURL(browser(),
pkp_test_server.GetURL(kPreloadedPKPHost, "/"));
wait_for_report_loop.Run();
}
class RecurrentInterstitialBrowserTest : public CertVerifierBrowserTest {
public:
RecurrentInterstitialBrowserTest() : CertVerifierBrowserTest() {}
void SetUpOnMainThread() override {
CertVerifierBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
};
// Tests that a message is added to the interstitial when an error code recurs
// multiple times.
IN_PROC_BROWSER_TEST_F(RecurrentInterstitialBrowserTest,
RecurrentInterstitial) {
const char kRecurrentInterstitialHistogram[] =
"interstitial.ssl_overridable.is_recurrent_error";
const char kRecurrentInterstitialActionHistogram[] =
"interstitial.ssl_recurrent_error.action";
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
ASSERT_TRUE(https_server.Start());
mock_cert_verifier()->set_default_result(
net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED);
StatefulSSLHostStateDelegate* state =
static_cast<StatefulSSLHostStateDelegate*>(
browser()->profile()->GetSSLHostStateDelegate());
state->ResetRecurrentErrorCountForTesting();
base::HistogramTester histograms;
state->SetRecurrentInterstitialThresholdForTesting(2);
// Use different hostnames for the two test cases to avoid the clickthrough
// from one interfering with the other.
GURL url = https_server.GetURL("show_error_message.test", "/");
ui_test_utils::NavigateToURL(browser(), url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab);
ExpectInterstitialElementHidden(tab, "recurrent-error-message",
true /* expect_hidden */);
histograms.ExpectUniqueSample(kRecurrentInterstitialHistogram, false, 1);
ui_test_utils::NavigateToURL(browser(), url);
WaitForInterstitial(tab);
ExpectInterstitialElementHidden(tab, "recurrent-error-message",
false /* expect_hidden */);
histograms.ExpectBucketCount(kRecurrentInterstitialHistogram, true, 1);
histograms.ExpectUniqueSample(
kRecurrentInterstitialActionHistogram,
SSLErrorControllerClient::RecurrentErrorAction::kShow, 1);
// Proceed through the interstitial and observe that the histogram is
// recorded correctly.
content::TestNavigationObserver nav_observer(tab, 1);
ASSERT_TRUE(content::ExecuteScript(
tab, "window.certificateErrorPageController.proceed();"));
nav_observer.Wait();
histograms.ExpectBucketCount(
kRecurrentInterstitialActionHistogram,
SSLErrorControllerClient::RecurrentErrorAction::kProceed, 1);
}
// Tests that mixed content is tracked by origin, not by URL. This is tested by
// checking that mixed content flags are set appropriately for about:blank URLs
// (who inherit the origin of their opener).
//
// Note: we test that mixed content flags are propagated from an opener page to
// about:blank, but not the other way around. This is because there is no way
// for a mixed content flag to propagate from about:blank to a different
// tab. Passive mixed content flags are not propagated from one tab to another,
// and for active mixed content, there's no way to bypass mixed content blocking
// on about:blank pages, so there's no way that the origin would get flagged for
// active mixed content from an about:blank page. (There's no way to bypass
// mixed content blocking on about:blank pages because the bypass is implemented
// as a content setting, which doesn't apply to about:blank.)
IN_PROC_BROWSER_TEST_F(SSLUITest, ActiveMixedContentTrackedByOrigin) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
std::string replacement_path = GetFilePathWithHostAndPortReplacement(
"/ssl/page_runs_insecure_content.html",
embedded_test_server()->host_port_pair());
// The insecure script is allowed to load because SSLUITestBase sets the
// --allow-running-insecure-content flag.
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
ssl_test_util::CheckAuthenticationBrokenState(
tab, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);
// Open a new tab from the current page. After an initial navigation,
// navigate it to about:blank and check that the about:blank page is
// downgraded, because it shares an origin with |tab| which ran mixed
// content.
//
// Note that the security indicator is not downgraded on the initial
// about:blank navigation in the new tab. Initial about:blank navigations
// don't have navigation entries (yet), so there is no way to track the mixed
// content state for these navigations. See https://crbug.com/1038765.
ui_test_utils::TabAddedWaiter tab_waiter(browser());
ASSERT_TRUE(content::ExecJs(tab, "w = window.open()"));
tab_waiter.Wait();
WebContents* opened_tab = browser()->tab_strip_model()->GetWebContentsAt(1);
content::TestNavigationObserver first_navigation(opened_tab);
ASSERT_TRUE(content::ExecJs(
tab, content::JsReplace("w.location.href = $1",
embedded_test_server()->GetURL("/title1.html"))));
first_navigation.Wait();
ssl_test_util::CheckUnauthenticatedState(opened_tab, AuthState::NONE);
content::TestNavigationObserver about_blank_navigation(opened_tab);
ASSERT_TRUE(content::ExecJs(tab, "w.location.href = 'about:blank'"));
about_blank_navigation.Wait();
ssl_test_util::CheckAuthenticationBrokenState(
opened_tab, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);
}
class SSLUIAutoReloadTest : public SSLUITest {
public:
SSLUIAutoReloadTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kEnableAutoReload);
SSLUITest::SetUpCommandLine(command_line);
}
};
// SSL interstitials should disable autoreload timer.
IN_PROC_BROWSER_TEST_F(SSLUIAutoReloadTest, AutoReloadDisabled) {
ASSERT_TRUE(https_server_expired_.Start());
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(tab);
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
auto* reloader = error_page::NetErrorAutoReloader::FromWebContents(tab);
const base::Optional<base::OneShotTimer>& timer =
reloader->next_reload_timer_for_testing();
EXPECT_EQ(base::nullopt, timer);
}
class SSLUITestWithEnhancedProtectionMessage : public SSLUITest {
public:
SSLUITestWithEnhancedProtectionMessage() {
feature_list_.InitAndEnableFeature(
safe_browsing::kEnhancedProtectionMessageInInterstitials);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(SSLUITestWithEnhancedProtectionMessage,
VerifyEnhancedProtectionMessageShown) {
base::HistogramTester histograms;
const std::string interaction_histogram =
"interstitial.ssl_overridable.interaction";
safe_browsing::SetExtendedReportingPrefForTests(
browser()->profile()->GetPrefs(), true);
safe_browsing::SetSafeBrowsingState(
browser()->profile()->GetPrefs(),
safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
ASSERT_TRUE(https_server_expired_.Start());
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
WaitForInterstitial(contents);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
ExpectInterstitialElementHidden(contents, "extended-reporting-opt-in",
true /* expect_hidden */);
ExpectInterstitialElementHidden(contents, "enhanced-protection-message",
false /* expect_hidden */);
histograms.ExpectTotalCount(interaction_histogram, 2);
histograms.ExpectBucketCount(
interaction_histogram,
security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
histograms.ExpectBucketCount(
interaction_histogram,
security_interstitials::MetricsHelper::SHOW_ENHANCED_PROTECTION, 1);
}
IN_PROC_BROWSER_TEST_F(SSLUITestWithEnhancedProtectionMessage,
VerifyEnhancedProtectionMessageNotShownAlreadyInEp) {
safe_browsing::SetExtendedReportingPrefForTests(
browser()->profile()->GetPrefs(), true);
safe_browsing::SetSafeBrowsingState(
browser()->profile()->GetPrefs(),
safe_browsing::SafeBrowsingState::ENHANCED_PROTECTION);
ASSERT_TRUE(https_server_expired_.Start());
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
WaitForInterstitial(contents);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
ExpectInterstitialElementHidden(contents, "extended-reporting-opt-in",
true /* expect_hidden */);
ExpectInterstitialElementHidden(contents, "enhanced-protection-message",
true /* expect_hidden */);
}
IN_PROC_BROWSER_TEST_F(SSLUITestWithEnhancedProtectionMessage,
VerifyEnhancedProtectionMessageNotShownManaged) {
policy::PolicyMap policies;
policies.Set(policy::key::kSafeBrowsingProtectionLevel,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
policy::POLICY_SOURCE_CLOUD,
base::Value(/* standard protection */ 1), nullptr);
UpdateChromePolicy(policies);
ASSERT_TRUE(https_server_expired_.Start());
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
WaitForInterstitial(contents);
ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
ExpectInterstitialElementHidden(contents, "enhanced-protection-message",
true /* expect_hidden */);
}
// TODO(jcampan): more tests to do below.
// Visit a page over https that contains a frame with a redirect.
// XMLHttpRequest insecure content in synchronous mode.
// XMLHttpRequest insecure content in asynchronous mode.
// XMLHttpRequest over bad ssl in synchronous mode.
// XMLHttpRequest over OK ssl in synchronous mode.