blob: b7208b419ecb0b112610b46652140aa2d49e0e9d [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string_view>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/page_type.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/base/features.h"
#include "net/dns/dns_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/dns/public/util.h"
#include "net/ssl/ssl_config.h"
#include "net/ssl/ssl_server_config.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/ssl_test_util.h"
#include "net/test/test_doh_server.h"
#include "third_party/boringssl/src/include/openssl/nid.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
#include "url/gurl.h"
namespace policy {
class SSLPolicyTest : public PolicyTest {
public:
SSLPolicyTest() : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
protected:
struct LoadResult {
bool success;
std::u16string title;
};
bool StartTestServer(const net::SSLServerConfig& ssl_config) {
https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
https_server_.ServeFilesFromSourceDirectory("chrome/test/data");
return https_server_.Start();
}
bool StartTestServer(
const net::EmbeddedTestServer::ServerCertificateConfig& cert_config,
const net::SSLServerConfig& ssl_config) {
https_server_.SetSSLConfig(cert_config, ssl_config);
https_server_.ServeFilesFromSourceDirectory("chrome/test/data");
return https_server_.Start();
}
bool GetBooleanPref(const std::string& pref_name) {
return g_browser_process->local_state()->GetBoolean(pref_name);
}
std::optional<bool> GetManagedBooleanPref(const std::string& pref_name) {
if (g_browser_process->local_state()->IsManagedPreference(pref_name)) {
return GetBooleanPref(pref_name);
}
return std::nullopt;
}
LoadResult LoadPage(std::string_view path) {
return LoadPage(https_server_.GetURL(path));
}
LoadResult LoadPage(const GURL& url) {
EXPECT_TRUE(NavigateToUrl(url, this));
content::WebContents* web_contents =
chrome_test_utils::GetActiveWebContents(this);
if (web_contents->GetController().GetLastCommittedEntry()->GetPageType() ==
content::PAGE_TYPE_ERROR) {
return LoadResult{false, u""};
}
return LoadResult{true, web_contents->GetTitle()};
}
void ExpectVersionOrCipherMismatch() {
content::WebContents* web_contents =
chrome_test_utils::GetActiveWebContents(this);
EXPECT_TRUE(content::EvalJs(web_contents,
"document.body.innerHTML.indexOf('ERR_SSL_"
"VERSION_OR_CIPHER_MISMATCH') >= 0")
.ExtractBool());
}
private:
net::EmbeddedTestServer https_server_;
};
IN_PROC_BROWSER_TEST_F(SSLPolicyTest, PostQuantumEnabledPolicy) {
net::SSLServerConfig ssl_config;
ssl_config.curves_for_testing = {NID_X25519MLKEM768};
ASSERT_TRUE(StartTestServer(ssl_config));
// Should be able to load a page from the test server because policy is in
// the default state and feature is enabled.
EXPECT_EQ(GetManagedBooleanPref(prefs::kPostQuantumKeyAgreementEnabled),
std::nullopt);
LoadResult result = LoadPage("/title2.html");
EXPECT_TRUE(result.success);
EXPECT_EQ(u"Title Of Awesomeness", result.title);
// Disable the policy.
PolicyMap policies;
SetPolicy(&policies, key::kPostQuantumKeyAgreementEnabled,
base::Value(false));
UpdateProviderPolicy(policies);
content::FlushNetworkServiceInstanceForTesting();
// Page loads should now fail.
EXPECT_EQ(GetManagedBooleanPref(prefs::kPostQuantumKeyAgreementEnabled),
false);
result = LoadPage("/title3.html");
EXPECT_FALSE(result.success);
// Enable the policy.
PolicyMap policies2;
SetPolicy(&policies2, key::kPostQuantumKeyAgreementEnabled,
base::Value(true));
UpdateProviderPolicy(policies2);
content::FlushNetworkServiceInstanceForTesting();
// Page load should now succeed.
EXPECT_EQ(GetManagedBooleanPref(prefs::kPostQuantumKeyAgreementEnabled),
true);
result = LoadPage("/title2.html");
EXPECT_TRUE(result.success);
EXPECT_EQ(u"Title Of Awesomeness", result.title);
}
#if BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(SSLPolicyTest, DevicePostQuantumEnabledPolicy) {
net::SSLServerConfig ssl_config;
ssl_config.curves_for_testing = {NID_X25519MLKEM768};
ASSERT_TRUE(StartTestServer(ssl_config));
// Should be able to load a page from the test server because policy is in
// the default state and feature is enabled.
EXPECT_EQ(GetManagedBooleanPref(prefs::kPostQuantumKeyAgreementEnabled),
std::nullopt);
EXPECT_EQ(GetManagedBooleanPref(prefs::kDevicePostQuantumKeyAgreementEnabled),
std::nullopt);
LoadResult result = LoadPage("/title2.html");
EXPECT_TRUE(result.success);
EXPECT_EQ(u"Title Of Awesomeness", result.title);
{
// Disable the device policy.
PolicyMap policies;
SetPolicy(&policies, key::kDevicePostQuantumKeyAgreementEnabled,
base::Value(false));
UpdateProviderPolicy(policies);
content::FlushNetworkServiceInstanceForTesting();
}
// Page loads should now fail.
EXPECT_EQ(GetManagedBooleanPref(prefs::kPostQuantumKeyAgreementEnabled),
std::nullopt);
EXPECT_EQ(GetManagedBooleanPref(prefs::kDevicePostQuantumKeyAgreementEnabled),
false);
result = LoadPage("/title3.html");
EXPECT_FALSE(result.success);
{
// Try enabling the non-device policy, while the device policy is disabled.
PolicyMap policies;
SetPolicy(&policies, key::kPostQuantumKeyAgreementEnabled,
base::Value(true));
SetPolicy(&policies, key::kDevicePostQuantumKeyAgreementEnabled,
base::Value(false));
UpdateProviderPolicy(policies);
content::FlushNetworkServiceInstanceForTesting();
}
// Page loads should still fail since the device policy takes precedence.
EXPECT_EQ(GetManagedBooleanPref(prefs::kPostQuantumKeyAgreementEnabled),
true);
EXPECT_EQ(GetManagedBooleanPref(prefs::kDevicePostQuantumKeyAgreementEnabled),
false);
result = LoadPage("/title3.html");
EXPECT_FALSE(result.success);
{
// Enable the device policy.
PolicyMap policies;
SetPolicy(&policies, key::kDevicePostQuantumKeyAgreementEnabled,
base::Value(true));
UpdateProviderPolicy(policies);
content::FlushNetworkServiceInstanceForTesting();
}
// Page load should now succeed.
EXPECT_EQ(GetManagedBooleanPref(prefs::kPostQuantumKeyAgreementEnabled),
std::nullopt);
EXPECT_EQ(GetManagedBooleanPref(prefs::kDevicePostQuantumKeyAgreementEnabled),
true);
result = LoadPage("/title2.html");
EXPECT_TRUE(result.success);
EXPECT_EQ(u"Title Of Awesomeness", result.title);
{
// Try disabling the non-device policy, while the device policy is enabled.
PolicyMap policies;
SetPolicy(&policies, key::kPostQuantumKeyAgreementEnabled,
base::Value(false));
SetPolicy(&policies, key::kDevicePostQuantumKeyAgreementEnabled,
base::Value(true));
UpdateProviderPolicy(policies);
content::FlushNetworkServiceInstanceForTesting();
}
// Page load should still succeed since the device policy takes precedence.
EXPECT_EQ(GetManagedBooleanPref(prefs::kPostQuantumKeyAgreementEnabled),
false);
EXPECT_EQ(GetManagedBooleanPref(prefs::kDevicePostQuantumKeyAgreementEnabled),
true);
result = LoadPage("/title2.html");
EXPECT_TRUE(result.success);
EXPECT_EQ(u"Title Of Awesomeness", result.title);
}
#endif // BUILDFLAG(IS_CHROMEOS)
class ECHPolicyTest : public SSLPolicyTest {
public:
// a.test is covered by `CERT_TEST_NAMES`.
static constexpr std::string_view kHostname = "a.test";
static constexpr std::string_view kPublicName = "public-name.test";
static constexpr std::string_view kDohServerHostname = "doh.test";
static constexpr std::string_view kECHSuccessTitle = "Negotiated ECH";
static constexpr std::string_view kECHFailureTitle = "Did not negotiate ECH";
ECHPolicyTest() : ech_server_{net::EmbeddedTestServer::TYPE_HTTPS} {
scoped_feature_list_.InitWithFeaturesAndParameters(
/*enabled_features=*/
{{net::features::kUseDnsHttpsSvcb,
{{"UseDnsHttpsSvcbEnforceSecureResponse", "true"}}}},
/*disabled_features=*/{});
}
void SetUpOnMainThread() override {
// Configure `ech_server_` to enable and require ECH.
net::SSLServerConfig server_config;
std::vector<uint8_t> ech_config_list;
server_config.ech_keys = net::MakeTestEchKeys(
kPublicName, /*max_name_len=*/64, &ech_config_list);
ASSERT_TRUE(server_config.ech_keys);
ech_server_.RegisterRequestHandler(
base::BindRepeating(&ECHPolicyTest::HandleRequest));
ech_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES,
server_config);
ASSERT_TRUE(ech_server_.Start());
// Start a DoH server, which ensures we use a resolver with HTTPS RR
// support. Configure it to serve records for `ech_server_`.
doh_server_.SetHostname(kDohServerHostname);
url::SchemeHostPort ech_host(GetURL("/"));
doh_server_.AddAddressRecord(ech_host.host(),
net::IPAddress::IPv4Localhost());
doh_server_.AddRecord(net::BuildTestHttpsServiceRecord(
net::dns_util::GetNameForHttpsQuery(ech_host),
/*priority=*/1, /*service_name=*/ech_host.host(),
{net::BuildTestHttpsServiceEchConfigParam(ech_config_list)}));
ASSERT_TRUE(doh_server_.Start());
// Add a single bootstrapping rule so we can resolve the DoH server.
host_resolver()->AddRule(kDohServerHostname, "127.0.0.1");
// The net stack doesn't enable DoH when it can't find a system DNS config
// (see https://crbug.com/1251715).
SetReplaceSystemDnsConfig();
// Via policy, configure the network service to use `doh_server_`.
UpdateProviderPolicy(PolicyMapWithDohServer());
content::FlushNetworkServiceInstanceForTesting();
}
PolicyMap PolicyMapWithDohServer() {
PolicyMap policies;
SetPolicy(&policies, key::kDnsOverHttpsMode, base::Value("secure"));
SetPolicy(&policies, key::kDnsOverHttpsTemplates,
base::Value(doh_server_.GetTemplate()));
return policies;
}
GURL GetURL(std::string_view path) {
return ech_server_.GetURL(kHostname, path);
}
private:
static std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_content_type("text/html; charset=utf-8");
if (request.ssl_info->encrypted_client_hello) {
response->set_content(
base::StrCat({"<title>", kECHSuccessTitle, "</title>"}));
} else {
response->set_content(
base::StrCat({"<title>", kECHFailureTitle, "</title>"}));
}
return response;
}
base::test::ScopedFeatureList scoped_feature_list_;
net::TestDohServer doh_server_;
net::EmbeddedTestServer ech_server_;
};
IN_PROC_BROWSER_TEST_F(ECHPolicyTest, ECHEnabledPolicy) {
// By default, the policy does not inhibit ECH.
EXPECT_TRUE(GetBooleanPref(prefs::kEncryptedClientHelloEnabled));
LoadResult result = LoadPage(GetURL("/a"));
EXPECT_TRUE(result.success);
EXPECT_EQ(base::ASCIIToUTF16(kECHSuccessTitle), result.title);
// Disable the policy.
PolicyMap policies = PolicyMapWithDohServer();
SetPolicy(&policies, key::kEncryptedClientHelloEnabled, base::Value(false));
UpdateProviderPolicy(policies);
content::FlushNetworkServiceInstanceForTesting();
// ECH should no longer be enabled.
EXPECT_FALSE(GetBooleanPref(prefs::kEncryptedClientHelloEnabled));
result = LoadPage(GetURL("/b"));
EXPECT_TRUE(result.success);
EXPECT_EQ(base::ASCIIToUTF16(kECHFailureTitle), result.title);
}
// TLS13EarlyDataPolicyTest relies on the fact that EmbeddedTestServer
// uses HTTP/1.1 without connection reuse (unless the protocol is explicitly
// specified). If EmbeddedTestServer ever gains connection reuse by default,
// we'll need to force it off.
class TLS13EarlyDataPolicyTest : public SSLPolicyTest,
public ::testing::WithParamInterface<bool> {
public:
static constexpr std::string_view kHostname = "a.test";
static constexpr std::string_view kEarlyDataCheckPath = "/test-request";
static constexpr std::string_view kEarlyDataAcceptedTitle = "accepted";
static constexpr std::string_view kEarlyDataNotAcceptedTitle = "not accepted";
TLS13EarlyDataPolicyTest()
: test_server_{net::EmbeddedTestServer::TYPE_HTTPS} {
std::vector<base::test::FeatureRef> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
if (GetParam()) {
enabled_features.emplace_back(net::features::kHappyEyeballsV3);
} else {
disabled_features.emplace_back(net::features::kHappyEyeballsV3);
}
disabled_features.emplace_back(net::features::kEnableTLS13EarlyData);
feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
void SetUpOnMainThread() override {
net::SSLServerConfig server_config;
server_config.early_data_enabled = true;
test_server_.RegisterRequestHandler(
base::BindRepeating(&TLS13EarlyDataPolicyTest::HandleRequest));
test_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES,
server_config);
ASSERT_TRUE(test_server_.Start());
host_resolver()->AddRule(kHostname, "127.0.0.1");
}
GURL GetURL(std::string_view path) {
return test_server_.GetURL(kHostname, path);
}
void NavigateToTestPage() {
ASSERT_TRUE(NavigateToUrl(GetURL("/index.html"), this));
}
std::string FetchResourceForEarlyDataCheck(GURL url) {
content::EvalJsResult result =
content::EvalJs(chrome_test_utils::GetActiveWebContents(this),
content::JsReplace(R"(
fetch($1).then(res => res.text())
)",
GetURL(kEarlyDataCheckPath)));
return result.ExtractString();
}
private:
static std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->AddCustomHeader("Connection", "close");
if (request.GetURL().path() == kEarlyDataCheckPath) {
response->set_content_type("text/plain; charset=utf-8");
if (request.ssl_info->early_data_accepted) {
response->set_content(kEarlyDataAcceptedTitle);
} else {
response->set_content(kEarlyDataNotAcceptedTitle);
}
} else {
response->set_content_type("text/html; charset=utf-8");
response->set_content("<html></html>");
}
return response;
}
base::test::ScopedFeatureList feature_list_;
net::EmbeddedTestServer test_server_;
};
INSTANTIATE_TEST_SUITE_P(, TLS13EarlyDataPolicyTest, ::testing::Bool());
IN_PROC_BROWSER_TEST_P(TLS13EarlyDataPolicyTest,
TLS13EarlyDataPolicyNoOverride) {
EXPECT_FALSE(GetBooleanPref(prefs::kTLS13EarlyDataEnabled));
NavigateToTestPage();
EXPECT_EQ(FetchResourceForEarlyDataCheck(GetURL(kEarlyDataCheckPath)),
kEarlyDataNotAcceptedTitle);
}
// TODO(crbug.com/418717917, crbug.com/419211957): Flaky on Windows and Android.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
#define MAYBE_TLS13EarlyDataPolicyEnable DISABLED_TLS13EarlyDataPolicyEnable
#else
#define MAYBE_TLS13EarlyDataPolicyEnable TLS13EarlyDataPolicyEnable
#endif
IN_PROC_BROWSER_TEST_P(TLS13EarlyDataPolicyTest,
MAYBE_TLS13EarlyDataPolicyEnable) {
PolicyMap policies;
SetPolicy(&policies, key::kTLS13EarlyDataEnabled, base::Value(true));
UpdateProviderPolicy(policies);
EXPECT_TRUE(GetBooleanPref(prefs::kTLS13EarlyDataEnabled));
content::FlushNetworkServiceInstanceForTesting();
NavigateToTestPage();
EXPECT_EQ(FetchResourceForEarlyDataCheck(GetURL(kEarlyDataCheckPath)),
kEarlyDataAcceptedTitle);
}
IN_PROC_BROWSER_TEST_P(TLS13EarlyDataPolicyTest, TLS13EarlyDataPolicyDisable) {
PolicyMap policies;
SetPolicy(&policies, key::kTLS13EarlyDataEnabled, base::Value(false));
UpdateProviderPolicy(policies);
EXPECT_FALSE(GetBooleanPref(prefs::kTLS13EarlyDataEnabled));
NavigateToTestPage();
EXPECT_EQ(FetchResourceForEarlyDataCheck(GetURL(kEarlyDataCheckPath)),
kEarlyDataNotAcceptedTitle);
}
} // namespace policy