blob: cbc792bbe700594b59338a24f228a6728f60e408 [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/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/message_loop/message_loop_current.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/post_task.h"
#include "base/test/bind_test_util.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/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 "chrome/app/chrome_command_ids.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/extensions/browsertest_util.h"
#include "chrome/browser/interstitials/security_interstitial_page_test_utils.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "chrome/browser/ssl/bad_clock_blocking_page.h"
#include "chrome/browser/ssl/captive_portal_blocking_page.h"
#include "chrome/browser/ssl/cert_report_helper.h"
#include "chrome/browser/ssl/cert_verifier_browser_test.h"
#include "chrome/browser/ssl/certificate_reporting_test_utils.h"
#include "chrome/browser/ssl/chrome_ssl_host_state_delegate.h"
#include "chrome/browser/ssl/common_name_mismatch_handler.h"
#include "chrome/browser/ssl/mitm_software_blocking_page.h"
#include "chrome/browser/ssl/security_state_tab_helper.h"
#include "chrome/browser/ssl/ssl_blocking_page.h"
#include "chrome/browser/ssl/ssl_browsertest_util.h"
#include "chrome/browser/ssl/ssl_error_assistant.h"
#include "chrome/browser/ssl/ssl_error_assistant.pb.h"
#include "chrome/browser/ssl/ssl_error_controller_client.h"
#include "chrome/browser/ssl/ssl_error_handler.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_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/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/content_settings_renderer.mojom.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/web_application_info.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/certificate_transparency/pref_names.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_types.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/policy_constants.h"
#include "components/prefs/testing_pref_service.h"
#include "components/safe_browsing/common/safe_browsing_prefs.h"
#include "components/security_interstitials/content/security_interstitial_controller_client.h"
#include "components/security_interstitials/content/security_interstitial_tab_helper.h"
#include "components/security_interstitials/core/controller_client.h"
#include "components/security_interstitials/core/metrics_helper.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_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/interstitial_page.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/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_switches.h"
#include "content/public/common/network_service_util.h"
#include "content/public/common/page_state.h"
#include "content/public/common/service_manager_connection.h"
#include "content/public/common/service_names.mojom.h"
#include "content/public/common/web_preferences.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "crypto/sha2.h"
#include "extensions/common/extension.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "net/base/escape.h"
#include "net/base/host_port_pair.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/cert/asn1_util.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_response_headers.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_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 "net/url_request/url_request_context.h"
#include "net/url_request/url_request_filter.h"
#include "net/url_request/url_request_job.h"
#include "net/url_request/url_request_test_util.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 "services/service_manager/public/cpp/connector.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/base/l10n/l10n_util.h"
#if defined(USE_NSS_CERTS)
#include "chrome/browser/certificate_manager_model.h"
#include "chrome/browser/net/nss_context.h"
#include "net/cert/nss_cert_database.h"
#include "net/cert/x509_util_nss.h"
#endif // defined(USE_NSS_CERTS)
using namespace ssl_test_util;
using base::ASCIIToUTF16;
using chrome_browser_interstitials::SecurityInterstitialIDNTest;
using content::InterstitialPage;
using content::InterstitialPageDelegate;
using content::NavigationController;
using content::NavigationEntry;
using content::SSLStatus;
using content::WebContents;
using security_interstitials::SecurityInterstitialControllerClient;
using web_modal::WebContentsModalDialogManager;
namespace {
const base::FilePath::CharType kDocRoot[] =
FILE_PATH_LITERAL("chrome/test/data");
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
};
bool AreCommittedInterstitialsEnabled() {
return base::FeatureList::IsEnabled(features::kSSLCommittedInterstitials);
}
void CheckProceedLinkExists(WebContents* 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(
AreCommittedInterstitialsEnabled()
? tab->GetMainFrame()
: tab->GetInterstitialPage()->GetMainFrame(),
javascript, &result));
EXPECT_EQ(security_interstitials::CMD_TEXT_FOUND, result);
}
// This observer waits for the SSLErrorHandler to start an interstitial timer
// for the given web contents.
class SSLInterstitialTimerObserver {
public:
explicit SSLInterstitialTimerObserver(content::WebContents* web_contents)
: web_contents_(web_contents), message_loop_runner_(new base::RunLoop) {
callback_ = base::Bind(&SSLInterstitialTimerObserver::OnTimerStarted,
base::Unretained(this));
SSLErrorHandler::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(content::WebContents* web_contents) {
timer_started_ = true;
if (web_contents_ == web_contents)
message_loop_runner_->Quit();
}
bool timer_started_ = false;
const content::WebContents* web_contents_;
SSLErrorHandler::TimerStartedCallback callback_;
std::unique_ptr<base::RunLoop> message_loop_runner_;
DISALLOW_COPY_AND_ASSIGN(SSLInterstitialTimerObserver);
};
class HungJob : public net::URLRequestJob {
public:
HungJob(net::URLRequest* request, net::NetworkDelegate* network_delegate)
: net::URLRequestJob(request, network_delegate) {}
void Start() override {}
};
class FaviconFilter : public net::URLRequestInterceptor {
public:
FaviconFilter() {}
~FaviconFilter() override {}
// net::URLRequestInterceptor implementation
net::URLRequestJob* MaybeInterceptRequest(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override {
if (request->url().path() == "/favicon.ico")
return new HungJob(request, network_delegate);
return nullptr;
}
private:
DISALLOW_COPY_AND_ASSIGN(FaviconFilter);
};
class ChromeContentBrowserClientForMixedContentTest
: public ChromeContentBrowserClient {
public:
ChromeContentBrowserClientForMixedContentTest() {}
void OverrideWebkitPrefs(content::RenderViewHost* rvh,
content::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;
}
net::SpawnedTestServer::SSLOptions GetOCSPSSLOptions(
net::SpawnedTestServer::SSLOptions::OCSPStatus ocsp_status) {
net::SpawnedTestServer::SSLOptions ssl_options(
net::SpawnedTestServer::SSLOptions::CERT_AUTO);
ssl_options.ocsp_status = ocsp_status;
return ssl_options;
}
// 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 &&
one.peer_signature_algorithm == two.peer_signature_algorithm &&
one.connection_status == two.connection_status &&
one.pkp_bypassed == two.pkp_bypassed;
}
void SetHSTSForHostNameOnIO(
scoped_refptr<net::URLRequestContextGetter> context_getter,
const std::string& hostname,
base::Time expiry,
bool include_subdomains) {
net::TransportSecurityState* state =
context_getter->GetURLRequestContext()->transport_security_state();
EXPECT_FALSE(state->ShouldUpgradeToSSL(kHstsTestHostName));
state->AddHSTS(hostname, expiry, false);
EXPECT_TRUE(state->ShouldUpgradeToSSL(kHstsTestHostName));
}
// Set HSTS for the test host name, so that all errors thrown on this domain
// will be nonoverridable.
void SetHSTSForHostName(Profile* profile) {
std::string hostname = kHstsTestHostName;
const base::Time expiry = base::Time::Now() + base::TimeDelta::FromDays(1000);
bool include_subdomains = false;
if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
content::StoragePartition* partition =
content::BrowserContext::GetDefaultStoragePartition(profile);
base::RunLoop run_loop;
partition->GetNetworkContext()->AddHSTS(
hostname, expiry, include_subdomains, run_loop.QuitClosure());
run_loop.Run();
return;
}
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(SetHSTSForHostNameOnIO,
base::RetainedRef(profile->GetRequestContext()), hostname,
expiry, include_subdomains));
}
bool IsShowingInterstitial(content::WebContents* tab) {
if (AreCommittedInterstitialsEnabled()) {
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
if (!helper) {
return false;
}
return helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting() !=
nullptr;
}
return tab->GetInterstitialPage() != nullptr;
}
// 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(content::WebContents* tab) {
if (!AreCommittedInterstitialsEnabled()) {
content::WaitForInterstitialAttach(tab);
ASSERT_TRUE(IsShowingInterstitial(tab));
ASSERT_TRUE(
WaitForRenderFrameReady(tab->GetInterstitialPage()->GetMainFrame()));
} else {
ASSERT_TRUE(IsShowingInterstitial(tab));
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetMainFrame()));
}
}
void ExpectInterstitialElementHidden(content::WebContents* tab,
const std::string& element_id,
bool expect_hidden) {
if (!AreCommittedInterstitialsEnabled()) {
ASSERT_TRUE(tab->GetInterstitialPage());
}
content::RenderFrameHost* frame =
AreCommittedInterstitialsEnabled()
? tab->GetMainFrame()
: tab->GetInterstitialPage()->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);
}
void ExpectInterstitialHeading(content::WebContents* tab,
const std::string& expected_heading) {
if (!AreCommittedInterstitialsEnabled()) {
ASSERT_TRUE(tab->GetInterstitialPage());
}
content::RenderFrameHost* frame =
AreCommittedInterstitialsEnabled()
? tab->GetMainFrame()
: tab->GetInterstitialPage()->GetMainFrame();
EXPECT_TRUE(chrome_browser_interstitials::IsInterstitialDisplayingText(
frame, expected_heading));
}
// The functions below might start causing tests to fail if you change the
// strings that appear on interstitials. If that happens, it's fine to update
// the keywords that are checked for in each interstitial. But the keywords
// should remain fairly unique for each interstitial to ensure that the tests
// check that the proper interstitial comes up. For example, it wouldn't be good
// to simply look for the word "security" because that likely shows up on lots
// of different types of interstitials, not just the type being tested for.
void ExpectCaptivePortalInterstitial(content::WebContents* tab) {
ExpectInterstitialHeading(tab, "Connect to");
}
void ExpectSSLInterstitial(content::WebContents* tab) {
ExpectInterstitialHeading(tab, "Your connection is not private");
}
void ExpectMITMInterstitial(content::WebContents* tab) {
ExpectInterstitialHeading(tab, "An application is stopping");
}
void ExpectBadClockInterstitial(content::WebContents* tab) {
ExpectInterstitialHeading(tab, "Your clock is");
}
// 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::JSONReader json_reader;
std::unique_ptr<base::Value> value = json_reader.ReadToValue(request.content);
EXPECT_TRUE(value);
base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
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),
https_server_ocsp_ok_(
net::SpawnedTestServer::TYPE_HTTPS,
GetOCSPSSLOptions(net::SpawnedTestServer::SSLOptions::OCSP_OK),
base::FilePath(kDocRoot)),
https_server_ocsp_revoked_(
net::SpawnedTestServer::TYPE_HTTPS,
GetOCSPSSLOptions(net::SpawnedTestServer::SSLOptions::OCSP_REVOKED),
base::FilePath(kDocRoot)),
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()),
binding_(this) {
https_server_.AddDefaultHandlers(base::FilePath(kDocRoot));
https_server_expired_.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
https_server_expired_.AddDefaultHandlers(base::FilePath(kDocRoot));
https_server_mismatched_.SetSSLConfig(
net::EmbeddedTestServer::CERT_MISMATCHED_NAME);
https_server_mismatched_.AddDefaultHandlers(base::FilePath(kDocRoot));
https_server_sha1_.SetSSLConfig(net::EmbeddedTestServer::CERT_SHA1_LEAF);
https_server_sha1_.AddDefaultHandlers(base::FilePath(kDocRoot));
https_server_common_name_only_.SetSSLConfig(
net::EmbeddedTestServer::CERT_COMMON_NAME_ONLY);
https_server_common_name_only_.AddDefaultHandlers(base::FilePath(kDocRoot));
// Sometimes favicons load before tests check the authentication
// state, and sometimes they load after. This is problematic on
// tests that load pages with certificate errors, because the page
// will be marked as having displayed subresources with certificate
// errors only if the favicon loads before the test checks the
// authentication state. To avoid this non-determinism, add an
// interceptor to hang all favicon requests.
std::unique_ptr<net::URLRequestInterceptor> interceptor(new FaviconFilter);
net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
"https", "127.0.0.1", std::move(interceptor));
interceptor.reset(new FaviconFilter);
net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
"https", "localhost", std::move(interceptor));
}
void SetUp() override {
EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
.WillRepeatedly(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;
binding_.Bind(std::move(context_params->ssl_config_client_request));
}
void TearDownOnMainThread() override { binding_.Close(); }
void CheckAuthenticatedState(WebContents* tab,
int expected_authentication_state) {
CheckSecurityState(tab, CertError::NONE, security_state::SECURE,
expected_authentication_state);
}
void CheckUnauthenticatedState(WebContents* tab,
int expected_authentication_state) {
CheckSecurityState(tab, CertError::NONE, security_state::NONE,
expected_authentication_state);
}
void CheckAuthenticationBrokenState(WebContents* tab,
net::CertStatus error,
int expected_authentication_state) {
CheckSecurityState(tab, error, security_state::DANGEROUS,
expected_authentication_state);
// CERT_STATUS_UNABLE_TO_CHECK_REVOCATION doesn't lower the security level
// to DANGEROUS.
ASSERT_NE(net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION, error);
}
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);
WaitForInterstitialDetach(tab);
}
virtual void SendInterstitialCommand(
WebContents* tab,
security_interstitials::SecurityInterstitialCommand command) {
tab->GetInterstitialPage()->GetDelegateForTesting()->CommandReceived(
base::IntToString(command));
}
network::mojom::NetworkContextParamsPtr CreateDefaultNetworkContextParams() {
return g_browser_process->system_network_context_manager()
->CreateDefaultNetworkContextParams();
}
static void GetFilePathWithHostAndPortReplacement(
const std::string& original_file_path,
const net::HostPortPair& host_port_pair,
std::string* replacement_path) {
base::StringPairs replacement_text;
replacement_text.push_back(
make_pair("REPLACE_WITH_HOST_AND_PORT", host_port_pair.ToString()));
net::test_server::GetFilePathWithReplacements(
original_file_path, replacement_text, replacement_path);
}
static void GetTopFramePath(const net::EmbeddedTestServer& http_server,
const net::EmbeddedTestServer& good_https_server,
const net::EmbeddedTestServer& bad_https_server,
std::string* top_frame_path) {
// 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, &frame_left_path);
// 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));
net::test_server::GetFilePathWithReplacements(
"/ssl/top_frame.html", replacement_text_top_frame, top_frame_path);
}
virtual SSLBlockingPage* GetSSLBlockingPage(WebContents* tab) {
return static_cast<SSLBlockingPage*>(
tab->GetInterstitialPage()->GetDelegateForTesting());
}
virtual BadClockBlockingPage* GetBadClockBlockingPage(WebContents* tab) {
return static_cast<BadClockBlockingPage*>(
tab->GetInterstitialPage()->GetDelegateForTesting());
}
// 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);
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
std::unique_ptr<SSLCertReporter> ssl_cert_reporter =
certificate_reporting_test_utils::CreateMockSSLCertReporter(
base::Bind(&certificate_reporting_test_utils::
SSLCertReporterCallback::ReportSent,
base::Unretained(&reporter_callback)),
expect_report);
SSLBlockingPage* interstitial_page = GetSSLBlockingPage(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);
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
std::unique_ptr<SSLCertReporter> ssl_cert_reporter =
certificate_reporting_test_utils::CreateMockSSLCertReporter(
base::Bind(&certificate_reporting_test_utils::
SSLCertReporterCallback::ReportSent,
base::Unretained(&reporter_callback)),
expect_report);
ExpectBadClockInterstitial(tab);
BadClockBlockingPage* clock_page = GetBadClockBlockingPage(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,
std::make_unique<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();
}
// Checks that the TransportSecurityState associated with the
// net::URLRequestContext of the |context_getter| will return
// |expected_status| for |host|, given the certificate |cert|,
// |is_issued_by_known_root|, associated |hashes|, and a CT policy result of
// |policy_compliance|.
void CheckCTStatus(
scoped_refptr<net::URLRequestContextGetter> context_getter,
const std::string& host,
scoped_refptr<net::X509Certificate> cert,
bool is_issued_by_known_root,
const net::HashValueVector& hashes,
net::ct::CTPolicyCompliance policy_compliance,
net::TransportSecurityState::CTRequirementsStatus expected_status) {
RunOnIOThreadBlocking(base::BindOnce(
&SSLUITestBase::CheckCTStatusOnIOThread, base::Unretained(this),
context_getter, host, cert, is_issued_by_known_root, hashes,
policy_compliance, expected_status));
}
// 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(IsShowingInterstitial(new_tab));
CheckAuthenticationBrokenState(new_tab, net::CERT_STATUS_DATE_INVALID,
AuthState::NONE);
EXPECT_EQ(app_url, new_tab->GetVisibleURL());
}
void set_ssl_config_updated_callback(
const base::RepeatingClosure& ssl_config_updated_callback) {
ssl_config_updated_callback_ = std::move(ssl_config_updated_callback);
}
// network::mojom::SSLConfigClient implementation.
void OnSSLConfigUpdated(network::mojom::SSLConfigPtr ssl_config) override {
last_ssl_config_ = *ssl_config;
if (ssl_config_updated_callback_)
ssl_config_updated_callback_.Run();
}
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();
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_COMMON_NAME_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_COMMON_NAME_INVALID,
AuthState::NONE);
std::string replacement_path;
GetFilePathWithHostAndPortReplacement(
path, https_server_mismatched_.host_port_pair(), &replacement_path);
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL(replacement_path));
}
Browser* InstallAndOpenTestBookmarkApp(const GURL& app_url) {
WebApplicationInfo web_app_info;
web_app_info.app_url = app_url;
web_app_info.scope = app_url.GetWithoutFilename();
web_app_info.title = base::UTF8ToUTF16("Test app");
web_app_info.description = base::UTF8ToUTF16("Test description");
Profile* profile = browser()->profile();
const extensions::Extension* bookmark_app =
extensions::browsertest_util::InstallBookmarkApp(profile, web_app_info);
ui_test_utils::UrlLoadObserver url_observer(
app_url, content::NotificationService::AllSources());
Browser* app_browser =
extensions::browsertest_util::LaunchAppBrowser(profile, bookmark_app);
url_observer.Wait();
return app_browser;
}
void UpdateChromePolicy(const policy::PolicyMap& policies) {
policy_provider_.UpdateChromePolicy(policies);
ASSERT_TRUE(base::MessageLoopCurrent::Get());
base::RunLoop().RunUntilIdle();
if (base::FeatureList::IsEnabled(network::features::kNetworkService))
content::FlushNetworkServiceInstanceForTesting();
}
void RunOnIOThreadBlocking(base::OnceClosure task) {
base::RunLoop run_loop;
base::PostTaskWithTraitsAndReply(FROM_HERE, {content::BrowserThread::IO},
std::move(task), run_loop.QuitClosure());
run_loop.Run();
}
void CheckCTStatusOnIOThread(
scoped_refptr<net::URLRequestContextGetter> context_getter,
std::string host,
scoped_refptr<net::X509Certificate> cert,
bool known_root,
net::HashValueVector hashes,
net::ct::CTPolicyCompliance compliance_level,
net::TransportSecurityState::CTRequirementsStatus expected_status) {
net::URLRequestContext* context = context_getter->GetURLRequestContext();
ASSERT_TRUE(context);
net::TransportSecurityState* tss = context->transport_security_state();
ASSERT_TRUE(tss);
net::HostPortPair host_port_pair(host, 443);
EXPECT_EQ(expected_status,
tss->CheckCTRequirements(
host_port_pair, known_root, hashes, cert.get(), cert.get(),
net::SignedCertificateTimestampAndStatusList(),
net::TransportSecurityState::DISABLE_EXPECT_CT_REPORTS,
compliance_level));
}
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 https_server_ocsp_ok_;
net::SpawnedTestServer https_server_ocsp_revoked_;
net::SpawnedTestServer wss_server_expired_;
net::SpawnedTestServer wss_server_mismatched_;
policy::MockConfigurationPolicyProvider policy_provider_;
base::RepeatingClosure ssl_config_updated_callback_;
network::mojom::SSLConfig last_ssl_config_;
mojo::Binding<network::mojom::SSLConfigClient> binding_;
DISALLOW_COPY_AND_ASSIGN(SSLUITestBase);
};
class SSLUITest : public SSLUITestBase,
public testing::WithParamInterface<bool> {
public:
SSLUITest() : SSLUITestBase() {}
protected:
void SetUpOnMainThread() override {
SSLUITestBase::SetUpOnMainThread();
if (IsCommittedInterstitialTest()) {
scoped_feature_list_.InitAndEnableFeature(
features::kSSLCommittedInterstitials);
}
}
SSLBlockingPage* GetSSLBlockingPage(WebContents* tab) override {
if (AreCommittedInterstitialsEnabled()) {
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::
FromWebContents(tab);
if (!helper) {
return nullptr;
}
return static_cast<SSLBlockingPage*>(
helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting());
}
return SSLUITestBase::GetSSLBlockingPage(tab);
}
BadClockBlockingPage* GetBadClockBlockingPage(WebContents* tab) override {
if (AreCommittedInterstitialsEnabled()) {
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::
FromWebContents(tab);
if (!helper) {
return nullptr;
}
return static_cast<BadClockBlockingPage*>(
helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting());
}
return SSLUITestBase::GetBadClockBlockingPage(tab);
}
bool IsCommittedInterstitialTest() const { return GetParam(); }
void DontProceedThroughInterstitial(WebContents* tab) override {
if (AreCommittedInterstitialsEnabled()) {
content::TestNavigationObserver nav_observer(tab, 1);
SendInterstitialCommand(tab, security_interstitials::CMD_DONT_PROCEED);
nav_observer.Wait();
} else {
SSLUITestBase::DontProceedThroughInterstitial(tab);
}
}
void SendInterstitialCommand(
WebContents* tab,
security_interstitials::SecurityInterstitialCommand command) override {
if (AreCommittedInterstitialsEnabled()) {
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;
}
SSLUITestBase::SendInterstitialCommand(tab, command);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(SSLUITest);
};
INSTANTIATE_TEST_CASE_P(, SSLUITest, ::testing::Values(false, true));
using SSLUITestCommitted = SSLUITest;
INSTANTIATE_TEST_CASE_P(, SSLUITestCommitted, ::testing::Values(true));
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.
}
};
INSTANTIATE_TEST_CASE_P(, SSLUITestBlock, ::testing::Values(false, true));
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);
}
};
INSTANTIATE_TEST_CASE_P(,
SSLUITestIgnoreCertErrors,
::testing::Values(false, true));
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);
}
};
INSTANTIATE_TEST_CASE_P(,
SSLUITestIgnoreCertErrorsBySPKIHTTPS,
::testing::Values(false, true));
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);
}
};
INSTANTIATE_TEST_CASE_P(,
SSLUITestIgnoreCertErrorsBySPKIWSS,
::testing::Values(false, true));
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);
}
};
INSTANTIATE_TEST_CASE_P(,
SSLUITestIgnoreLocalhostCertErrors,
::testing::Values(false, true));
class SSLUITestWithExtendedReporting : public SSLUITest {
public:
SSLUITestWithExtendedReporting() : SSLUITest() {
// Certificate reports are only sent from official builds, unless this has
// been called.
CertReportHelper::SetFakeOfficialBuildForTesting();
}
};
INSTANTIATE_TEST_CASE_P(,
SSLUITestWithExtendedReporting,
::testing::Values(false, true));
class SSLUITestHSTS : public SSLUITest {
public:
void SetUpOnMainThread() override {
SSLUITest::SetUpOnMainThread();
SetHSTSForHostName(browser()->profile());
}
};
INSTANTIATE_TEST_CASE_P(, SSLUITestHSTS, ::testing::Values(false, true));
// Visits a regular page over http.
IN_PROC_BROWSER_TEST_P(SSLUITest, TestHTTP) {
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/ssl/google.html"));
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_P(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(),
&replacement_path);
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(replacement_path));
CheckUnauthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}
IN_PROC_BROWSER_TEST_P(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(), &replacement_path);
ui_test_utils::NavigateToURL(browser(),
https_server_expired_.GetURL(replacement_path));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
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_P(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"));
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
// Now check that the page is marked as having run insecure content.
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_DATE_INVALID,
AuthState::RAN_INSECURE_CONTENT);
}
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(content::WebContents* web_contents)
: content::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_P(SSLUITest, GoBackToMixedContent) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
// Navigate to a URL and dynamically load mixed content.
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
CheckAuthenticatedState(tab, AuthState::NONE);
SecurityStateWebContentsObserver observer(tab);
ASSERT_TRUE(content::ExecuteScript(tab,
"var i = document.createElement('img');"
"i.src = 'http://example.test';"
"document.body.appendChild(i);"));
observer.WaitForDidChangeVisibleSecurityState();
CheckSecurityState(tab, CertError::NONE, security_state::NONE,
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"));
CheckUnauthenticatedState(
browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
content::WaitForLoadStop(tab);
// After going back, the mixed content indicator should no longer be present.
CheckAuthenticatedState(tab, AuthState::NONE);
}
// Tests that the mixed content flags are not reset for an in-page navigation.
IN_PROC_BROWSER_TEST_P(SSLUITest, MixedContentWithSameDocumentNavigation) {
ASSERT_TRUE(https_server_.Start());
// Navigate to a URL and dynamically load mixed content.
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
CheckAuthenticatedState(tab, AuthState::NONE);
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();
CheckSecurityState(tab, CertError::NONE, security_state::NONE,
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();
CheckSecurityState(tab, CertError::NONE, security_state::NONE,
AuthState::DISPLAYED_INSECURE_CONTENT);
}
// Tests that the WebContents's flag for displaying content with cert
// errors get cleared upon navigation.
IN_PROC_BROWSER_TEST_P(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"));
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
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_P(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_P(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_P(SSLUITest, TestOKHTTPS) {
ASSERT_TRUE(https_server_.Start());
ui_test_utils::NavigateToURL(browser(),
https_server_.GetURL("/ssl/google.html"));
CheckAuthenticatedState(browser()->tab_strip_model()->GetActiveWebContents(),
AuthState::NONE);
}
// Visits a page with https error and proceed:
IN_PROC_BROWSER_TEST_P(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);
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(tab);
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:
IN_PROC_BROWSER_TEST_P(SSLUITest, InAppTestHTTPSExpiredCertAndProceed) {
auto feature_list = std::make_unique<base::test::ScopedFeatureList>();
feature_list->InitAndEnableFeature(features::kDesktopPWAWindowing);
ASSERT_TRUE(https_server_expired_.Start());
const GURL app_url = https_server_expired_.GetURL("/ssl/google.html");
Browser* app_browser = InstallAndOpenTestBookmarkApp(app_url);
WebContents* app_tab = app_browser->tab_strip_model()->GetActiveWebContents();
WaitForInterstitial(app_tab);
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_P(SSLUITestCommitted,
InAppTestHTTPSExpiredCertAndPreviouslyProceeded) {
auto feature_list = std::make_unique<base::test::ScopedFeatureList>();
feature_list->InitAndEnableFeature(features::kDesktopPWAWindowing);
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);
CheckAuthenticationBrokenState(initial_tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ProceedThroughInterstitial(initial_tab);
CheckAuthenticationBrokenState(initial_tab, net::CERT_STATUS_DATE_INVALID,
AuthState::NONE);
Browser* app_browser = InstallAndOpenTestBookmarkApp(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);
CheckAuthenticationBrokenState(app_tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
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_P(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);
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_COMMON_NAME_INVALID,
AuthState::SHOWING_INTERSTITIAL);
ASSERT_NO_FATAL_FAILURE(ExpectSSLInterstitial(tab));
// Simulate user clicking "Take me back".
DontProceedThroughInterstitial(tab);
// We should be back to the original good page.
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"));
CheckUnauthenticatedState(tab, AuthState::NONE);
}
// Test that localhost pages don't show an interstitial.
IN_PROC_BROWSER_TEST_P(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.
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_P(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_NO_FATAL_FAILURE(ExpectBadClockInterstitial(clock_tab));
CheckSecurityState(clock_tab, net::CERT_STATUS_DATE_INVALID,
security_state::DANGEROUS,
AuthState::SHOWING_INTERSTITIAL);
}
IN_PROC_BROWSER_TEST_P(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_NO_FATAL_FAILURE(ExpectBadClockInterstitial(clock_tab));
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_P(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();
content::RenderFrameHost* rfh = tab->GetMainFrame();
// Now go to a bad HTTPS page that shows an interstitial.
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
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);
content::WaitForLoadStop(tab);
if (!AreCommittedInterstitialsEnabled()) {
// Make sure we haven't changed the previous RFH. Prevents regression of
// http://crbug.com/82667. This is only applicable to pre-committed
// interstitials. With committed interstitials, the interstitial is a
// committed error page, so going back from it to a different site can be a
// cross-site transition.
EXPECT_EQ(rfh, tab->GetMainFrame());
}
// We should be back at the original good page.
EXPECT_FALSE(IsShowingInterstitial(tab));
CheckUnauthenticatedState(tab, AuthState::NONE);
}
// Visits a page with https error and then goes back using GoToOffset.
IN_PROC_BROWSER_TEST_P(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"));
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);
if (AreCommittedInterstitialsEnabled())
nav_observer.Wait();
// We should be back at the original good page.
EXPECT_FALSE(IsShowingInterstitial(tab));
CheckUnauthenticatedState(tab, AuthState::NONE);
}
// Visits a page with https error and then goes back using the DONT_PROCEED
// interstitial command.
IN_PROC_BROWSER_TEST_P(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"));
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
if (AreCommittedInterstitialsEnabled()) {
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<NavigationController>(&tab->GetController()));
SendInterstitialCommand(tab, security_interstitials::CMD_DONT_PROCEED);
observer.Wait();
} else {
SendInterstitialCommand(tab, security_interstitials::CMD_DONT_PROCEED);
}
// We should be back at the original good page.
CheckUnauthenticatedState(tab, AuthState::NONE);
}
// Visits a page with https error and then goes forward using GoToOffset.
//
// This test is not enabled for committed interstitials because committed
// interstitials wipe out forward history like other committed navigations and
// committed error pages.
IN_PROC_BROWSER_TEST_F(SSLUITestBase, TestHTTPSExpiredCertAndGoForward) {
if (AreCommittedInterstitialsEnabled())
return;
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_expired_.Start());
// First navigate to two HTTP pages.
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/ssl/google.html"));
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
NavigationEntry* entry1 = tab->GetController().GetLastCommittedEntry();
ASSERT_TRUE(entry1);
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/ssl/blank_page.html"));
NavigationEntry* entry2 = tab->GetController().GetLastCommittedEntry();
ASSERT_TRUE(entry2);
// Now go back so that a page is in the forward history.
{
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<NavigationController>(&tab->GetController()));
tab->GetController().GoBack();
observer.Wait();
}
ASSERT_TRUE(tab->GetController().CanGoForward());
NavigationEntry* entry3 = tab->GetController().GetLastCommittedEntry();
ASSERT_TRUE(entry1 == entry3);
// Now go to a bad HTTPS page that shows an interstitial.
ui_test_utils::NavigateToURL(
browser(), https_server_expired_.GetURL("/ssl/google.html"));
CheckAuthenticationBrokenState(tab, net::CERT_STATUS_DATE_INVALID,
AuthState::SHOWING_INTERSTITIAL);
// Simulate user clicking and holding on forward button.
{
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<NavigationController>(&tab->GetController()));
tab->GetController().GoToOffset(1);
observer.Wait();
}
// We should be showing the second good page.
EXPECT_FALSE(IsShowingInterstitial(tab));
CheckUnauthenticatedState(tab, AuthState::NONE);
EXPECT_FALSE(tab->GetController().CanGoForward());
NavigationEntry* entry4 = tab->GetController().GetLastCommittedEntry();
EXPECT_TRUE(entry2 == entry4);
}
// Visits a page with revocation checking enabled and a valid OCSP response.
IN_PROC_BROWSER_TEST_P(SSLUITest, TestHTTPSOCSPOk) {
// OCSP checking is disabled by default.
EXPECT_FALSE(last_ssl_config_.rev_checking_enabled);
EXPECT_FALSE(CreateDefaultNetworkContextParams()
->initial_ssl_config->rev_checking_enabled);
// Enable, and make sure the default network context params reflect the
// change.
base::RunLoop run_loop;
set_ssl_config_updated_callback(run_loop.QuitClosure());
ASSERT_NO_FATAL_FAILURE(
EnablePolicy(g_browser_process->local_state(),
policy::key::kEnableOnlineRevocationChecks,
prefs::kCertRevocationCheckingEnabled));
run_loop.Run();
EXPECT_TRUE(last_ssl_config_.rev_checking_enabled);
EXPECT_TRUE(CreateDefaultNetworkContextParams()
->initial_ssl_config->rev_checking_enabled);
ASSERT_TRUE(https_server_ocsp_ok_.Start());
ui_test_utils::NavigateToURL(
browser(), https_server_ocsp_ok_.GetURL("/ssl/google.html"));
CheckAuthenticatedState(browser()->tab_strip_model()->GetActiveWebContents(),
AuthState::NONE);
content::NavigationEntry* entry = browser()
->tab_strip_model()
->GetActiveWebContents()
->GetController()
.GetVisibleEntry();
ASSERT_TRUE(entry);
EXPECT_TRUE(entry->GetSSL().cert_status &
net::CERT_STATUS_REV_CHECKING_ENABLED);
}
// Visits a page with revocation checking enabled and a revoked OCSP response.
IN_PROC_BROWSER_TEST_P(SSLUITest, TestHTTPSOCSPRevoked) {
// OCSP checking is disabled by default.
EXPECT_FALSE(last_ssl_config_.rev_checking_enabled);
EXPECT_FALSE(CreateDefaultNetworkContextParams()
->initial_ssl_config->rev_checking_enabled);
// Enable, and make sure the default network context params reflect the
// change.
base::RunLoop run_loop;
set_ssl_config_updated_callback(run_loop.QuitClosure());
ASSERT_NO_FATAL_FAILURE(
EnablePolicy(g_browser_process->local_state(),
policy::key::kEnableOnlineRevocationChecks,
prefs::kCertRevocationCheckingEnabled));
run_loop.Run();
EXPECT_TRUE(last_ssl_config_.rev_checking_enabled);
EXPECT_TRUE(CreateDefaultNetworkContextParams()
->initial_ssl_config->rev_checking_enabled);
ASSERT_TRUE(https_server_ocsp_revoked_.Start());
ui_test_utils::NavigateToURL(
browser(), https_server_ocsp_revoked_.GetURL("/ssl/google.html"));
CheckAuthenticationBrokenState(
browser()->tab_strip_model()->GetActiveWebContents(),
net::CERT_STATUS_REVOKED, AuthState::SHOWING_INTERSTITIAL);
}
// Visits a page with revocation checking set to the default value (disabled)
// and a revoked OCSP response.
IN_PROC_BROWSER_TEST_P(SSLUITest, TestHTTPSOCSPRevokedButNotChecked) {
// OCSP checking is disabled by default.
EXPECT_FALSE(last_ssl_config_.rev_checking_enabled);
EXPECT_FALSE(CreateDefaultNetworkContextParams()
->initial_ssl_config->rev_checking_enabled);
ASSERT_TRUE(https_server_ocsp_revoked_.Start());
ui_test_utils::NavigateToURL(
browser(), https_server_ocsp_revoked_.GetURL("/ssl/google.html"));
CheckAuthenticatedState(browser()->tab_strip_model()->GetActiveWebContents(),
AuthState::NONE);
content::NavigationEntry* entry = browser()
->tab_strip_model()
->GetActiveWebContents()
->GetController()
.GetVisibleEntry();
ASSERT_TRUE(entry);
EXPECT_FALSE(entry->GetSSL().cert_status &
net::CERT_STATUS_REV_CHECKING_ENABLED);
}
// Visits a page that uses a SHA-1 leaf certificate, which should be rejected
// by default.
IN_PROC_BROWSER_TEST_P(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"));
CheckAuthenticationBrokenState(
browser()->tab_strip_model()->GetActiveWebContents(),
net::CERT_STATUS_WEAK_SIGNATURE_ALGORITHM,
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_P(SSLUITest, SymantecEnforcementIsNotDisabled) {
EXPECT_FALSE(last_ssl_config_.symantec_enforcement_disabled);
EXPECT_FALSE(CreateDefaultNetworkContextParams()
->initial_ssl_config->symantec_enforcement_disabled);
}
// Enables support for Symantec's Legacy PKI via policy, and then ensures that
// the SSLConfig is configured to trust the Legacy PKI.
IN_PROC_BROWSER_TEST_P(SSLUITest, SymantecPrefsCanEnable) {
EXPECT_FALSE(last_ssl_config_.symantec_enforcement_disabled);
EXPECT_FALSE(CreateDefaultNetworkContextParams()
->initial_ssl_config->symantec_enforcement_disabled);
// Enable, and make sure the default network context params reflect the
// change.
base::RunLoop run_loop;
set_ssl_config_updated_callback(run_loop.QuitClosure());
ASSERT_NO_FATAL_FAILURE(
EnablePolicy(g_browser_process->local_state(),
policy::key::kEnableSymantecLegacyInfrastructure,
prefs::kCertEnableSymantecLegacyInfrastructure));
run_loop.Run();
EXPECT_TRUE(last_ssl_config_.symantec_enforcement_disabled);
EXPECT_TRUE(CreateDefaultNetworkContextParams()
->initial_ssl_config->symantec_enforcement_disabled);
}
class CertificateTransparencySSLUITest : public CertVerifierBrowserTest {
public:
CertificateTransparencySSLUITest()
: CertVerifierBrowserTest(),
https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
~CertificateTransparencySSLUITest() override {}
void SetUpOnMainThread() override {
CertVerifierBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
https_server_.AddDefaultHandlers(base::FilePath(kDocRoot));
}
void SetUp() override {
EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
.WillRepeatedly(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) {
std::unique_ptr<base::ListValue> policy_value =
std::make_unique<base::ListValue>();
for (const auto& value : list_values) {
policy_value->GetList().emplace_back(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::MessageLoopCurrent::Get());
base::RunLoop().RunUntilIdle();
if (base::FeatureList::IsEnabled(network::features::kNetworkService))
content::FlushNetworkServiceInstanceForTesting();
}
net::EmbeddedTestServer https_server_;
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"));
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"));
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_P(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, ASCIIToUTF16("PASS"));
watcher.AlsoWaitForTitle(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_P(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, ASCIIToUTF16("PASS"));
watcher.AlsoWaitForTitle(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);
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"));
}
// Ensure that non-standard origins are marked as neutral when the
// MarkNonSecureAs Dangerous flag is enabled.
IN_PROC_BROWSER_TEST_P(SSLUITest, MarkFileAsNonSecure) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
security_state::features::kMarkHttpAsFeature,
{{security_state::features::kMarkHttpAsFeatureParameterName,
security_state::features::kMarkHttpAsParameterDangerous}});
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
SecurityStateTabHelper* helper =
SecurityStateTabHelper::FromWebContents(contents);
ASSERT_TRUE(helper);
ui_test_utils::NavigateToURL(browser(), GURL("file:///"));
security_state::SecurityInfo security_info;
helper->GetSecurityInfo(&security_info);
EXPECT_EQ(security_state::NONE, security_info.security_level);
}
// Ensure that about-protocol origins are marked as neutral when the
// MarkNonSecureAs Dangerous flag is enabled.
IN_PROC_BROWSER_TEST_P(SSLUITest, MarkAboutAsNonSecure) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
security_state::features::kMarkHttpAsFeature,
{{security_state::features::kMarkHttpAsFeatureParameterName,
security_state::features::kMarkHttpAsParameterDangerous}});
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
SecurityStateTabHelper* helper =
SecurityStateTabHelper::FromWebContents(contents);
ASSERT_TRUE(helper);
ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
security_state::SecurityInfo security_info;
helper->GetSecurityInfo(&security_info);
EXPECT_EQ(security_state::NONE, security_info.security_level);
}
// Data URLs should always be marked as non-secure.
IN_PROC_BROWSER_TEST_P(SSLUITest, MarkDataAsNonSecure) {
content::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"));
security_state::SecurityInfo security_info;
helper->GetSecurityInfo(&security_info);
EXPECT_EQ(security_state::HTTP_SHOW_WARNING, security_info.security_level);
}
// Ensure that HTTP-protocol origins are marked as Dangerous when the
// MarkNonSecureAs Dangerous flag is enabled.
IN_PROC_BROWSER_TEST_P(SSLUITest, MarkHTTPAsDangerous) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
security_state::features::kMarkHttpAsFeature,
{{security_state::features::kMarkHttpAsFeatureParameterName,
security_state::features::kMarkHttpAsParameterDangerous}});
ASSERT_TRUE(embedded_test_server()->Start());
// Navigate to a non-local HTTP page.
GURL url = embedded_test_server()->GetURL("/ssl/google.html");
GURL::Replacements http_url_replacements;
http_url_replacements.SetHostStr("example.test");
url = url.ReplaceComponents(http_url_replacements);
ui_test_utils::NavigateToURL(browser(), url);
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
SecurityStateTabHelper* helper = SecurityStateTabHelper::FromWebContents(tab);
ASSERT_TRUE(helper);
security_state::SecurityInfo security_info;
helper->GetSecurityInfo(&security_info);
EXPECT_EQ(security_state::DANGEROUS, security_info.security_level);
}
// Ensure that blob-protocol origins are marked as neutral when the
// MarkNonSecureAs Dangerous flag is enabled.
IN_PROC_BROWSER_TEST_P(SSLUITest, MarkBlobAsNonSecure) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
security_state::features::kMarkHttpAsFeature,
{{security_state::features::kMarkHttpAsFeatureParameterName,
security_state::features::kMarkHttpAsParameterDangerous}});
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
SecurityStateTabHelper* helper =
SecurityStateTabHelper::FromWebContents(contents);
ASSERT_TRUE(helper);
ui_test_utils::NavigateToURL(
browser(),
GURL("blob:chrome://newtab/49a463bb-fac8-476c-97bf-5d7076c3ea1a"));
security_state::SecurityInfo security_info;
helper->GetSecurityInfo(&security_info);
EXPECT_EQ(security_state::NONE, security_info.security_level);
}
#if defined(USE_NSS_CERTS)
class SSLUITestWithClientCert : public SSLUITestBase {
public:
SSLUITestWithClientCert() : cert_db_(NULL) {}
void SetUpOnMainThread() override {
SSLUITestBase::SetUpOnMainThread();
base::RunLoop loop;
GetNSSCertDatabaseForProfile(
browser()->profile(),
base::Bind(&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, ASCIIToUTF16("PASS"));
watcher.AlsoWaitForTitle(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->GetList().push_back(std::move(filter));
HostContentSettingsMapFactory::GetForProfile(profile)
->SetWebsiteSettingDefaultScope(
url, GURL(), CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE,
std::string(), std::move(setting));
// Visit a HTTPS page which requires client certs.
ui_test_utils::NavigateToURL(browser(), url);
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)
// 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,
const ClientCertListCallback& callback) override {
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_P(SSLUITest, TestBrowserUseClientCertStore) {
// Make the browser use the ClientCertStoreStub instead of the regular one.
ProfileIOData::FromResourceContext(browser()->profile()->GetResourceContext())
->set_client_cert_store_factory_for_testing(base::Bind(&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->GetList().push_back(base::DictionaryValue());
HostContentSettingsMapFactory::GetForProfile(profile)
->SetWebsiteSettingDefaultScope(
https_url, GURL(), CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE,
std::string(), 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_P(SSLUITest, TestClientAuthSigningFails) {
// Make the browser use the ClientCertStoreStub instead of the regular one.
ProfileIOData::FromResourceContext(browser()->profile()->GetResourceContext())
->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->GetList().push_back(base::DictionaryValue());
HostContentSettingsMapFactory::GetForProfile(profile)
->SetWebsiteSettingDefaultScope(
https_url, GURL(), CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE,
std::string(), 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_P(SSLUITest, TestClientAuthContinueWithoutCert) {
// Make the browser use a ClientCertStoreStub that returns no certs.
ProfileIOData::FromResourceContext(browser()->profile()->GetResourceContext())
->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_P(SSLUITest, TestCertDBChangedFlushesClientAuthCache) {
// Make the browser use the ClientCertStoreStub instead of the regular one.
ProfileIOData::FromResourceContext(browser()->profile()->GetResourceContext())
->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->GetList().push_back(base::DictionaryValue());
HostContentSettingsMapFactory::GetForProfile(profile)
->SetWebsiteSettingDefaultScope(
https_url, GURL(), CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE,
std::string(), 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.
ProfileIOData::FromResourceContext(browser()->profile()->GetResourceContext())
->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_P(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_NO_FATAL_FAILURE(ExpectSSLInterstitial(tab2));
}
IN_PROC_BROWSER_TEST_P(SSLUITest, TestBadHTTPSDownload) {