blob: 08ac92ca6e042561dde0bb3937ff37483dc4aa76 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/component_updater/pki_metadata_component_installer.h"
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/string_view_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/net/secure_dns_config.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/certificate_transparency/certificate_transparency_config.pb.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/network_service_util.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/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_throttle_inserter.h"
#include "crypto/hash.h"
#include "crypto/keypair.h"
#include "net/cert/test_root_certs.h"
#include "net/cert/x509_certificate.h"
#include "net/dns/dns_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/dns/public/util.h"
#include "net/net_buildflags.h"
#include "net/ssl/ssl_server_config.h"
#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/test_data_directory.h"
#include "net/test/test_doh_server.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
#include "base/test/bind.h"
#include "chrome/browser/ssl/ssl_browsertest_util.h"
#include "net/base/features.h"
#include "net/cert/internal/trust_store_chrome.h"
#include "net/cert/root_store_proto_lite/root_store.pb.h"
#include "net/cert/x509_util.h"
#include "net/test/cert_builder.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
#endif
namespace {
enum class CTEnforcement {
// Enables CT enforcement.
kEnabled,
// Enables CT with Static CT API policy enforcement.
kEnabledWithStaticCTEnforcement,
// Disables CT enforcement via component updater proto.
kDisabledByProto,
// Disables CT enforcement via feature flag.
kDisabledByFeature
};
int64_t SecondsSinceEpoch(base::Time t) {
return (t - base::Time::UnixEpoch()).InSeconds();
}
// A CTLog generates a log identity private key, then computes and
// caches several properties from that key that are needed in test cases.
class CTLog {
public:
CTLog(std::string_view name,
base::Time start,
base::Time end,
chrome_browser_certificate_transparency::CTLog::LogType type)
: name_(name), start_(start), end_(end), type_(type) {}
std::string_view name() const { return name_; }
base::Time start() const { return start_; }
base::Time end() const { return end_; }
chrome_browser_certificate_transparency::CTLog::LogType type() const {
return type_;
}
base::span<const uint8_t> spki() const { return spki_; }
std::string_view spki_base64() const { return spki_base64_; }
// Even though the id is just a span of bytes, so this should theoretically
// return a base::span<const uint8_t> referencing the data we've cached, all the
// call sites want it as a string.
std::string id() const { return std::string(base::as_string_view(id_)); }
std::string_view id_base64() const { return id_base64_; }
bssl::UniquePtr<EVP_PKEY> key() { return bssl::UpRef(private_key_.key()); }
private:
const std::string name_;
const base::Time start_;
const base::Time end_;
const chrome_browser_certificate_transparency::CTLog::LogType type_;
// The generated private key and things derived from it. Note that the private
// key itself can't be const, because returning a reference to it in key()
// above requires mutating its inner refcount.
crypto::keypair::PrivateKey private_key_{
crypto::keypair::PrivateKey::GenerateEcP256()};
const std::vector<uint8_t> spki_{private_key_.ToSubjectPublicKeyInfo()};
const std::string spki_base64_{base::Base64Encode(spki_)};
const std::array<uint8_t, crypto::hash::kSha256Size> id_{
crypto::hash::Sha256(spki_)};
const std::string id_base64_{base::Base64Encode(id_)};
};
void AddLogToCTConfig(chrome_browser_certificate_transparency::CTConfig* config,
const CTLog& log) {
chrome_browser_certificate_transparency::CTLog* entry =
config->mutable_log_list()->add_logs();
entry->set_log_id(log.id_base64());
entry->set_key(log.spki_base64());
entry->set_purpose(chrome_browser_certificate_transparency::CTLog::PROD);
entry->set_log_type(log.type());
entry->mutable_temporal_interval()->mutable_start()->set_seconds(
SecondsSinceEpoch(log.start()));
entry->mutable_temporal_interval()->mutable_end()->set_seconds(
SecondsSinceEpoch(log.end()));
chrome_browser_certificate_transparency::CTLog_State* log_state =
entry->add_state();
log_state->set_current_state(
chrome_browser_certificate_transparency::CTLog::USABLE);
log_state->mutable_state_start()->set_seconds(SecondsSinceEpoch(log.start()));
chrome_browser_certificate_transparency::CTLog_OperatorChange*
operator_history = entry->add_operator_history();
operator_history->set_name(log.name());
operator_history->mutable_operator_start()->set_seconds(
SecondsSinceEpoch(log.start()));
}
} // namespace
namespace component_updater {
// TODO(crbug.com/341136041): add tests for pinning enforcement.
class PKIMetadataComponentUpdaterTest
: public InProcessBrowserTest,
public testing::WithParamInterface<CTEnforcement>,
public PKIMetadataComponentInstallerService::Observer {
public:
PKIMetadataComponentUpdaterTest() {
switch (GetParam()) {
case CTEnforcement::kEnabled:
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/
{features::kCertificateTransparencyAskBeforeEnabling},
/*disabled_features=*/{
net::features::kEnableStaticCTAPIEnforcement});
break;
case CTEnforcement::kEnabledWithStaticCTEnforcement:
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{features::
kCertificateTransparencyAskBeforeEnabling,
net::features::kEnableStaticCTAPIEnforcement},
/*disabled_features=*/{});
break;
case CTEnforcement::kDisabledByProto:
scoped_feature_list_.InitAndEnableFeature(
features::kCertificateTransparencyAskBeforeEnabling);
break;
case CTEnforcement::kDisabledByFeature:
scoped_feature_list_.InitAndDisableFeature(
features::kCertificateTransparencyAskBeforeEnabling);
break;
}
}
void SetUpInProcessBrowserTestFixture() override {
PKIMetadataComponentInstallerService::GetInstance()->AddObserver(this);
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
ASSERT_TRUE(component_dir_.CreateUniqueTempDir());
host_resolver()->AddRule("*", "127.0.0.1");
// Set up a configuration that will enable or disable CT enforcement
// depending on the test parameter.
chrome_browser_certificate_transparency::CTConfig ct_config;
ct_config.set_disable_ct_enforcement(GetParam() ==
CTEnforcement::kDisabledByProto);
ct_config.mutable_log_list()->mutable_timestamp()->set_seconds(
SecondsSinceEpoch(base::Time::Now()));
ASSERT_TRUE(PKIMetadataComponentInstallerService::GetInstance()
->WriteCTDataForTesting(component_dir_.GetPath(),
ct_config.SerializeAsString()));
}
void TearDownInProcessBrowserTestFixture() override {
PKIMetadataComponentInstallerService::GetInstance()->RemoveObserver(this);
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
// Wait for configuration set in `SetUpInProcessBrowserTestFixture` to load.
WaitForPKIConfiguration(1);
}
protected:
// Waits for the PKI to have been configured at least |expected_times|.
void WaitForPKIConfiguration(int expected_times) {
if (GetParam() == CTEnforcement::kDisabledByFeature) {
// When CT is disabled by the feature flag there are no callbacks to
// wait on, so just spin the runloop.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(pki_metadata_configured_times_, 0);
} else {
expected_pki_metadata_configured_times_ = expected_times;
if (pki_metadata_configured_times_ >=
expected_pki_metadata_configured_times_) {
return;
}
base::RunLoop run_loop;
pki_metadata_config_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
}
const base::FilePath& GetComponentDirPath() const {
return component_dir_.GetPath();
}
bool is_ct_enforced() const {
return GetParam() == CTEnforcement::kEnabled ||
GetParam() == CTEnforcement::kEnabledWithStaticCTEnforcement;
}
void DoTestAtLeastOneRFC6962LogPolicy(
chrome_browser_certificate_transparency::CTLog::LogType log_type,
bool expect_ct_error);
private:
void OnCTLogListConfigured() override {
++pki_metadata_configured_times_;
if (pki_metadata_config_closure_ &&
pki_metadata_configured_times_ >=
expected_pki_metadata_configured_times_) {
std::move(pki_metadata_config_closure_).Run();
}
}
base::test::ScopedFeatureList scoped_feature_list_;
base::ScopedTempDir component_dir_;
base::OnceClosure pki_metadata_config_closure_;
int expected_pki_metadata_configured_times_ = 0;
int pki_metadata_configured_times_ = 0;
};
// Tests that the PKI Metadata configuration is recovered after a network
// service restart.
IN_PROC_BROWSER_TEST_P(PKIMetadataComponentUpdaterTest,
ReloadsPKIMetadataConfigAfterCrash) {
// Network service is not running out of process, so cannot be crashed.
if (!content::IsOutOfProcessNetworkService()) {
return;
}
// Make the test root be interpreted as a known root so that CT will be
// required.
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
net::ScopedTestKnownRoot scoped_known_root(root_cert.get());
net::EmbeddedTestServer https_server_ok(net::EmbeddedTestServer::TYPE_HTTPS);
static constexpr char kHostname[] = "example.com";
https_server_ok.SetCertHostnames({kHostname});
https_server_ok.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_server_ok.Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL(kHostname, "/simple.html")));
// Check that the page is blocked depending on CT enforcement.
content::WebContents* tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
if (is_ct_enforced()) {
EXPECT_NE(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
} else {
EXPECT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
}
// Restart the network service.
SimulateNetworkServiceCrash();
// Wait for the restarted network service to load the component update data
// that is already on disk.
WaitForPKIConfiguration(2);
// Check that the page is still blocked depending on CT enforcement.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL(kHostname, "/simple.html")));
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
if (is_ct_enforced()) {
EXPECT_NE(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
} else {
EXPECT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
}
}
IN_PROC_BROWSER_TEST_P(PKIMetadataComponentUpdaterTest, TestCTUpdate) {
const base::Time kLogStart = base::Time::Now() - base::Days(1);
const base::Time kLogEnd = base::Time::Now() + base::Days(1);
CTLog log1("log operator 1", kLogStart, kLogEnd,
chrome_browser_certificate_transparency::CTLog::RFC6962);
CTLog log2(
"log operator 2", kLogStart, kLogEnd,
chrome_browser_certificate_transparency::CTLog::LOG_TYPE_UNSPECIFIED);
// Make the test root be interpreted as a known root so that CT will be
// required.
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
net::ScopedTestKnownRoot scoped_known_root(root_cert.get());
// Start a test server that uses a certificate with SCTs for the above test
// logs.
net::EmbeddedTestServer https_server_ok(net::EmbeddedTestServer::TYPE_HTTPS);
net::EmbeddedTestServer::ServerCertificateConfig server_config;
// The same hostname is used for each request, which verifies that the CT log
// updates cause verifier caches and socket pool invalidation, so that the
// next request for the same host will use the updated CT state.
server_config.dns_names = {"example.com"};
server_config.embedded_scts.emplace_back(log1.id(), log1.key(),
base::Time::Now());
server_config.embedded_scts.emplace_back(log2.id(), log2.key(),
base::Time::Now());
https_server_ok.SetSSLConfig(server_config);
https_server_ok.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_server_ok.Start());
// Check that the page is blocked depending on CT enforcement.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("example.com", "/simple.html")));
content::WebContents* tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
if (is_ct_enforced()) {
EXPECT_NE(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
} else {
EXPECT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
}
// Update with a CT configuration that trusts log1 and log2
//
// Set up a configuration that will enable or disable CT enforcement
// depending on the test parameter.
chrome_browser_certificate_transparency::CTConfig ct_config;
ct_config.set_disable_ct_enforcement(GetParam() ==
CTEnforcement::kDisabledByProto);
ct_config.mutable_log_list()->mutable_timestamp()->set_seconds(
SecondsSinceEpoch(base::Time::Now()));
AddLogToCTConfig(&ct_config, log1);
AddLogToCTConfig(&ct_config, log2);
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(PKIMetadataComponentInstallerService::GetInstance()
->WriteCTDataForTesting(GetComponentDirPath(),
ct_config.SerializeAsString()));
}
// Should be trusted now.
PKIMetadataComponentInstallerService::GetInstance()
->ReconfigureAfterNetworkRestart();
WaitForPKIConfiguration(2);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("example.com", "/simple.html")));
EXPECT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
// Update CT configuration again with the same CT logs but mark the 1st log
// as retired.
{
chrome_browser_certificate_transparency::CTLog* log =
ct_config.mutable_log_list()->mutable_logs(0);
log->clear_state();
// Log states are in reverse chronological order, so the most recent state
// comes first.
{
chrome_browser_certificate_transparency::CTLog_State* log_state =
log->add_state();
log_state->set_current_state(
chrome_browser_certificate_transparency::CTLog::RETIRED);
log_state->mutable_state_start()->set_seconds(
SecondsSinceEpoch(kLogStart) + 1);
}
{
chrome_browser_certificate_transparency::CTLog_State* log_state =
log->add_state();
log_state->set_current_state(
chrome_browser_certificate_transparency::CTLog::USABLE);
log_state->mutable_state_start()->set_seconds(
SecondsSinceEpoch(kLogStart));
}
}
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(PKIMetadataComponentInstallerService::GetInstance()
->WriteCTDataForTesting(GetComponentDirPath(),
ct_config.SerializeAsString()));
}
// Should be untrusted again since 2 logs are required for diversity. Both
// SCTs should verify successfully but only one of them is accepted as the
// other has a timestamp after the log retirement state change timestamp.
PKIMetadataComponentInstallerService::GetInstance()
->ReconfigureAfterNetworkRestart();
WaitForPKIConfiguration(3);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("example.com", "/simple.html")));
if (is_ct_enforced()) {
EXPECT_NE(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
} else {
EXPECT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
}
}
// Tests that at least one RFC6962 log policy is correctly applied when Static
// CT API enforcement is enabled. All logs in the test will be set to
// `log_type`. If `expect_ct_error_with_static_ct_api_enforcement` is true,
// CT checks with Static CT API enforcement should cause an SSL error.
void PKIMetadataComponentUpdaterTest::DoTestAtLeastOneRFC6962LogPolicy(
chrome_browser_certificate_transparency::CTLog::LogType log_type,
bool expect_ct_error_with_static_ct_api_enforcement) {
const base::Time kLogStart = base::Time::Now() - base::Days(1);
const base::Time kLogEnd = base::Time::Now() + base::Days(1);
CTLog log1("log operator 1", kLogStart, kLogEnd, log_type);
CTLog log2("log operator 2", kLogStart, kLogEnd, log_type);
// Make the test root be interpreted as a known root so that CT will be
// required.
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
net::ScopedTestKnownRoot scoped_known_root(root_cert.get());
// Start a test server that uses a certificate with SCTs for the above test
// logs.
net::EmbeddedTestServer https_server_ok(net::EmbeddedTestServer::TYPE_HTTPS);
net::EmbeddedTestServer::ServerCertificateConfig server_config;
// The same hostname is used for each request, which verifies that the CT log
// updates cause verifier caches and socket pool invalidation, so that the
// next request for the same host will use the updated CT state.
server_config.dns_names = {"example.com"};
server_config.embedded_scts.emplace_back(log1.id(), log1.key(),
base::Time::Now());
server_config.embedded_scts.emplace_back(log2.id(), log2.key(),
base::Time::Now());
https_server_ok.SetSSLConfig(server_config);
https_server_ok.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_server_ok.Start());
// Check that the page is blocked depending on CT enforcement.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("example.com", "/simple.html")));
content::WebContents* tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
if (is_ct_enforced()) {
EXPECT_NE(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
} else {
EXPECT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
}
// Update with a CT configuration that trusts log1 and log2. Neither of
// these logs is RFC6962, so the SCTs will not pass validation.
//
// Set up a configuration that will enable or disable CT enforcement
// depending on the test parameter.
chrome_browser_certificate_transparency::CTConfig ct_config;
ct_config.set_disable_ct_enforcement(GetParam() ==
CTEnforcement::kDisabledByProto);
ct_config.mutable_log_list()->mutable_timestamp()->set_seconds(
SecondsSinceEpoch(base::Time::Now()));
AddLogToCTConfig(&ct_config, log1);
AddLogToCTConfig(&ct_config, log2);
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(PKIMetadataComponentInstallerService::GetInstance()
->WriteCTDataForTesting(GetComponentDirPath(),
ct_config.SerializeAsString()));
}
PKIMetadataComponentInstallerService::GetInstance()
->ReconfigureAfterNetworkRestart();
WaitForPKIConfiguration(2);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("example.com", "/simple.html")));
if (GetParam() == CTEnforcement::kEnabledWithStaticCTEnforcement) {
if (expect_ct_error_with_static_ct_api_enforcement) {
EXPECT_NE(u"OK",
chrome_test_utils::GetActiveWebContents(this)->GetTitle());
} else {
EXPECT_EQ(u"OK",
chrome_test_utils::GetActiveWebContents(this)->GetTitle());
}
} else {
EXPECT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
}
}
IN_PROC_BROWSER_TEST_P(PKIMetadataComponentUpdaterTest,
TestAtLeastOneRFC6962LogPolicy_StaticCTAPILogs) {
// Test with all logs with Static CT API type. Since at least one RFC6962 log
// is expected, this should show an SSL error caused by CT.
DoTestAtLeastOneRFC6962LogPolicy(
chrome_browser_certificate_transparency::CTLog::STATIC_CT_API,
/*expect_ct_error_with_static_ct_api_enforcement=*/true);
}
IN_PROC_BROWSER_TEST_P(PKIMetadataComponentUpdaterTest,
TestAtLeastOneRFC6962LogPolicy_UnspecifiedLogTypes) {
// Test with all logs with unspecified type. These are treated as RFC6962
// logs so they shouldn't cause an SSL error.
// TODO(crbug.com/370724580): Disallow unspecified log type once all logs in
// the hardcoded and component updater protos have proper log types.
DoTestAtLeastOneRFC6962LogPolicy(
chrome_browser_certificate_transparency::CTLog::LOG_TYPE_UNSPECIFIED,
/*expect_ct_error_with_static_ct_api_enforcement=*/false);
}
INSTANTIATE_TEST_SUITE_P(
PKIMetadataComponentUpdater,
PKIMetadataComponentUpdaterTest,
testing::Values(CTEnforcement::kEnabled,
CTEnforcement::kEnabledWithStaticCTEnforcement,
CTEnforcement::kDisabledByProto,
CTEnforcement::kDisabledByFeature));
#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
class PKIMetadataComponentChromeRootStoreUpdateTest
: public InProcessBrowserTest,
public PKIMetadataComponentInstallerService::Observer {
public:
void SetUpInProcessBrowserTestFixture() override {
SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
false);
PKIMetadataComponentInstallerService::GetInstance()->AddObserver(this);
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
ASSERT_TRUE(component_dir_.CreateUniqueTempDir());
host_resolver()->AddRule("*", "127.0.0.1");
}
void TearDownInProcessBrowserTestFixture() override {
PKIMetadataComponentInstallerService::GetInstance()->RemoveObserver(this);
SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
std::nullopt);
}
class CRSWaiter {
public:
explicit CRSWaiter(PKIMetadataComponentChromeRootStoreUpdateTest* test) {
test_ = test;
test_->crs_config_closure_ = run_loop_.QuitClosure();
}
void Wait() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
raw_ptr<PKIMetadataComponentChromeRootStoreUpdateTest> test_;
};
void InstallCRSUpdate(chrome_root_store::RootStore root_store_proto) {
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(
PKIMetadataComponentInstallerService::GetInstance()
->WriteCRSDataForTesting(component_dir_.GetPath(),
root_store_proto.SerializeAsString()));
}
CRSWaiter waiter(this);
PKIMetadataComponentInstallerService::GetInstance()
->ConfigureChromeRootStore();
waiter.Wait();
}
void InstallCRSUpdate(const std::vector<std::string>& der_roots) {
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++last_used_crs_version_);
for (const auto& der_root : der_roots) {
root_store_proto.add_trust_anchors()->set_der(der_root);
}
InstallCRSUpdate(std::move(root_store_proto));
}
protected:
base::ScopedTempDir component_dir_;
private:
void OnChromeRootStoreConfigured() override {
if (crs_config_closure_) {
std::move(crs_config_closure_).Run();
}
}
base::OnceClosure crs_config_closure_;
int64_t last_used_crs_version_ = net::CompiledChromeRootStoreVersion();
};
IN_PROC_BROWSER_TEST_F(PKIMetadataComponentChromeRootStoreUpdateTest,
CheckCRSUpdate) {
net::EmbeddedTestServer https_server_ok(net::EmbeddedTestServer::TYPE_HTTPS);
net::EmbeddedTestServer::ServerCertificateConfig server_config;
server_config.dns_names = {"*.example.com"};
https_server_ok.SetSSLConfig(server_config);
https_server_ok.ServeFilesFromSourceDirectory("chrome/test/data");
// Clear test roots so that cert validation only happens with
// what's in Chrome Root Store.
net::TestRootCerts::GetInstance()->Clear();
ASSERT_TRUE(https_server_ok.Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("a.example.com", "/simple.html")));
// Check that the page is blocked depending on contents of Chrome Root Store.
content::WebContents* tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_NE(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_AUTHORITY_INVALID,
ssl_test_util::AuthState::SHOWING_INTERSTITIAL);
{
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
InstallCRSUpdate({std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer()))});
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("b.example.com", "/simple.html")));
// Check that the page is allowed due to contents of Chrome Root Store.
tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_EQ(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
ssl_test_util::CheckAuthenticatedState(tab, ssl_test_util::AuthState::NONE);
{
// We reject empty CRS updates, so create a new cert root that doesn't match
// what the test server uses.
auto [leaf, root] = net::CertBuilder::CreateSimpleChain2();
InstallCRSUpdate({root->GetDER()});
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("c.example.com", "/simple.html")));
// Check that the page is blocked depending on contents of Chrome Root Store.
tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_NE(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_AUTHORITY_INVALID,
ssl_test_util::AuthState::SHOWING_INTERSTITIAL);
}
// Similar to CheckCRSUpdate, except using the same hostname for all requests.
// This tests whether the CRS update causes cached verification results to be
// disregarded.
IN_PROC_BROWSER_TEST_F(PKIMetadataComponentChromeRootStoreUpdateTest,
CheckCRSUpdateAffectsCachedVerifications) {
net::EmbeddedTestServer https_server_ok(net::EmbeddedTestServer::TYPE_HTTPS);
net::EmbeddedTestServer::ServerCertificateConfig server_config;
server_config.dns_names = {"*.example.com"};
https_server_ok.SetSSLConfig(server_config);
https_server_ok.ServeFilesFromSourceDirectory("chrome/test/data");
// Clear test roots so that cert validation only happens with
// what's in Chrome Root Store.
net::TestRootCerts::GetInstance()->Clear();
static constexpr char kHostname[] = "a.example.com";
ASSERT_TRUE(https_server_ok.Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL(kHostname, "/simple.html")));
// Check that the page is blocked depending on contents of Chrome Root Store.
content::WebContents* tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_NE(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_AUTHORITY_INVALID,
ssl_test_util::AuthState::SHOWING_INTERSTITIAL);
{
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
InstallCRSUpdate({std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer()))});
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL(kHostname, "/title2.html")));
// Check that the page is allowed due to contents of Chrome Root Store.
tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_EQ(chrome_test_utils::GetActiveWebContents(this)->GetTitle(),
u"Title Of Awesomeness");
ssl_test_util::CheckAuthenticatedState(tab, ssl_test_util::AuthState::NONE);
{
// We reject empty CRS updates, so create a new cert root that doesn't match
// what the test server uses.
auto [leaf, root] = net::CertBuilder::CreateSimpleChain2();
InstallCRSUpdate({root->GetDER()});
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL(kHostname, "/title3.html")));
// Check that the page is blocked depending on contents of Chrome Root Store.
tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_NE(chrome_test_utils::GetActiveWebContents(this)->GetTitle(),
u"Title Of Awesomeness");
EXPECT_NE(chrome_test_utils::GetActiveWebContents(this)->GetTitle(),
u"Title Of More Awesomeness");
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_AUTHORITY_INVALID,
ssl_test_util::AuthState::SHOWING_INTERSTITIAL);
}
IN_PROC_BROWSER_TEST_F(PKIMetadataComponentChromeRootStoreUpdateTest,
UpdateTrustAnchorIDs) {
content::StoragePartition* partition =
chrome_test_utils::GetActiveWebContents(this)
->GetBrowserContext()
->GetDefaultStoragePartition();
int64_t crs_version = net::CompiledChromeRootStoreVersion();
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
scoped_refptr<net::X509Certificate> intermediate1 = net::ImportCertFromFile(
net::GetTestCertsDirectory(), "intermediate_ca_cert.pem");
ASSERT_TRUE(intermediate1);
scoped_refptr<net::X509Certificate> intermediate2 = net::ImportCertFromFile(
net::GetTestCertsDirectory(), "verisign_intermediate_ca_2016.pem");
ASSERT_TRUE(intermediate2);
// Test that the initial set of Trust Anchor IDs comes from the compiled-in
// root store.
{
std::vector<std::vector<uint8_t>> expected_trust_anchor_ids =
net::TrustStoreChrome::GetTrustAnchorIDsFromCompiledInRootStore();
base::test::TestFuture<const std::vector<std::vector<uint8_t>>&> future;
partition->GetNetworkContext()->GetTrustAnchorIDsForTesting(
future.GetCallback());
EXPECT_THAT(future.Get(),
testing::UnorderedElementsAreArray(expected_trust_anchor_ids));
}
// Install CRS update that contains no trusted Trust Anchor IDs.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer())));
InstallCRSUpdate(std::move(root_store_proto));
// Ensure that SSLConfigClients have been notified of the new trust anchor
// IDs.
SystemNetworkContextManager::GetInstance()
->FlushSSLConfigManagerForTesting();
base::test::TestFuture<const std::vector<std::vector<uint8_t>>&> future;
partition->GetNetworkContext()->GetTrustAnchorIDsForTesting(
future.GetCallback());
EXPECT_TRUE(future.Get().empty());
}
// Install CRS update that contains two trusted Trust Anchor IDs.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer())));
anchor->set_trust_anchor_id({0x01, 0x02, 0x03});
chrome_root_store::TrustAnchor* additional_cert1 =
root_store_proto.add_additional_certs();
additional_cert1->set_der(
std::string(net::x509_util::CryptoBufferAsStringPiece(
intermediate1->cert_buffer())));
additional_cert1->set_trust_anchor_id({0x01, 0x02});
// `additional_cert1`'s trust anchor ID should be ignored because it is not
// configured as a TLS trust anchor.
additional_cert1->set_tls_trust_anchor(false);
chrome_root_store::TrustAnchor* additional_cert2 =
root_store_proto.add_additional_certs();
additional_cert2->set_der(
std::string(net::x509_util::CryptoBufferAsStringPiece(
intermediate2->cert_buffer())));
additional_cert2->set_trust_anchor_id({0x02, 0x03});
additional_cert2->set_tls_trust_anchor(true);
InstallCRSUpdate(std::move(root_store_proto));
// Ensure that SSLConfigClients have been notified of the new trust anchor
// IDs.
SystemNetworkContextManager::GetInstance()
->FlushSSLConfigManagerForTesting();
base::test::TestFuture<const std::vector<std::vector<uint8_t>>&> future;
partition->GetNetworkContext()->GetTrustAnchorIDsForTesting(
future.GetCallback());
EXPECT_THAT(future.Get(), testing::UnorderedElementsAre(
std::vector<uint8_t>({0x01, 0x02, 0x3}),
std::vector<uint8_t>({0x02, 0x03})));
}
}
// Tests that when new network contexts are created after a Trust Anchor IDs
// component update is received, the new network context uses the Trust Anchor
// IDs from the component updater.
IN_PROC_BROWSER_TEST_F(PKIMetadataComponentChromeRootStoreUpdateTest,
NewNetworkContextAfterUpdatingTrustAnchorIDs) {
// This test is only works with an out-of-process network service because it
// uses a network service crash/restart to test what happens when a new
// network context is created.
if (content::IsInProcessNetworkService()) {
return;
}
content::StoragePartition* partition =
chrome_test_utils::GetActiveWebContents(this)
->GetBrowserContext()
->GetDefaultStoragePartition();
int64_t crs_version = net::CompiledChromeRootStoreVersion();
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
// Install CRS update that contains one trusted Trust Anchor IDs.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer())));
anchor->set_trust_anchor_id(
{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08});
InstallCRSUpdate(std::move(root_store_proto));
// Ensure that SSLConfigClients have been notified of the new trust anchor
// IDs.
SystemNetworkContextManager::GetInstance()
->FlushSSLConfigManagerForTesting();
base::test::TestFuture<const std::vector<std::vector<uint8_t>>&> future;
partition->GetNetworkContext()->GetTrustAnchorIDsForTesting(
future.GetCallback());
EXPECT_THAT(future.Get(),
testing::UnorderedElementsAre(std::vector<uint8_t>(
{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08})));
}
network::mojom::NetworkContext* old_network_context =
partition->GetNetworkContext();
// Simulate a network service crash and restart, and check that the newly
// created network service uses the Trust Anchor ID from the prior component
// update.
SimulateNetworkServiceCrash();
// Flush the interface to make sure it notices the crash.
partition->FlushNetworkInterfaceForTesting();
{
// Just to be sure that the test is testing what it intends to, check that a
// new network context has been created.
ASSERT_NE(old_network_context, partition->GetNetworkContext());
base::test::TestFuture<const std::vector<std::vector<uint8_t>>&> future;
partition->GetNetworkContext()->GetTrustAnchorIDsForTesting(
future.GetCallback());
EXPECT_THAT(future.Get(),
testing::UnorderedElementsAre(std::vector<uint8_t>(
{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08})));
}
}
IN_PROC_BROWSER_TEST_F(PKIMetadataComponentChromeRootStoreUpdateTest,
CheckCRSUpdateDnsConstraint) {
net::EmbeddedTestServer https_server_ok(net::EmbeddedTestServer::TYPE_HTTPS);
net::EmbeddedTestServer::ServerCertificateConfig server_config;
server_config.dns_names = {"*.example.com"};
https_server_ok.SetSSLConfig(server_config);
https_server_ok.ServeFilesFromSourceDirectory("chrome/test/data");
// Clear test roots so that cert validation only happens with
// what's in Chrome Root Store.
net::TestRootCerts::GetInstance()->Clear();
ASSERT_TRUE(https_server_ok.Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("a.example.com", "/simple.html")));
// The page should be blocked as the test root is not trusted yet.
content::WebContents* tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_NE(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_AUTHORITY_INVALID,
ssl_test_util::AuthState::SHOWING_INTERSTITIAL);
int64_t crs_version = net::CompiledChromeRootStoreVersion();
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
// Install CRS update that trusts root with a constraint that matches the
// leaf's subjectAltName.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer())));
anchor->add_constraints()->add_permitted_dns_names("example.com");
InstallCRSUpdate(std::move(root_store_proto));
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("b.example.com", "/simple.html")));
// Check that the page is allowed now.
tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_EQ(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
ssl_test_util::CheckAuthenticatedState(tab, ssl_test_util::AuthState::NONE);
// Install CRS update that trusts root with a constraint that does not match
// the leaf's subjectAltName.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer())));
anchor->add_constraints()->add_permitted_dns_names("example.org");
InstallCRSUpdate(std::move(root_store_proto));
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("c.example.com", "/simple.html")));
// Check that the page is blocked now.
tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_NE(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
ssl_test_util::CheckAuthenticationBrokenState(
tab, net::CERT_STATUS_AUTHORITY_INVALID,
ssl_test_util::AuthState::SHOWING_INTERSTITIAL);
}
class PKIMetadataComponentChromeRootStoreUpdateQwacTest
: public PKIMetadataComponentChromeRootStoreUpdateTest,
public testing::WithParamInterface<bool> {
public:
PKIMetadataComponentChromeRootStoreUpdateQwacTest() {
if (GetParam()) {
feature_list_.InitAndEnableFeature(net::features::kVerifyQWACs);
} else {
feature_list_.InitAndDisableFeature(net::features::kVerifyQWACs);
}
}
private:
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(,
PKIMetadataComponentChromeRootStoreUpdateQwacTest,
testing::Bool());
IN_PROC_BROWSER_TEST_P(PKIMetadataComponentChromeRootStoreUpdateQwacTest,
CheckCrsEutlUpdate) {
net::EmbeddedTestServer https_server_ok(net::EmbeddedTestServer::TYPE_HTTPS);
net::EmbeddedTestServer::ServerCertificateConfig server_config;
server_config.dns_names = {"*.example.com"};
// Set policy OIDs and QWAC QC types on the leaf so that it will validate as
// a QWAC. Also include an intermediate so we can set the intermediate as
// part of the EUTL trust store in the CRS update.
// OIDs: CABF OV, ETSI QNCP-w
server_config.policy_oids = {"2.23.140.1.2.2", "0.4.0.194112.1.5"};
server_config.qwac_qc_types = {bssl::der::Input(net::kEtsiQctWebOid)};
server_config.intermediate =
net::EmbeddedTestServer::IntermediateType::kInHandshake;
https_server_ok.SetSSLConfig(server_config);
https_server_ok.ServeFilesFromSourceDirectory("chrome/test/data");
// Install only the root cert as a trust anchor in CRS and check that the
// page load is successful but the cert is not a valid QWAC.
net::TestRootCerts::GetInstance()->Clear();
int64_t crs_version = net::CompiledChromeRootStoreVersion();
{
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
auto* trust_anchor = root_store_proto.add_trust_anchors();
trust_anchor->set_der(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer()));
InstallCRSUpdate(std::move(root_store_proto));
}
ASSERT_TRUE(https_server_ok.Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("a.example.com", "/simple.html")));
// Check that the page's cert status is not a QWAC.
content::WebContents* tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_EQ(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
ssl_test_util::CheckAuthenticatedState(tab, ssl_test_util::AuthState::NONE);
content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
net::CertStatus cert_status = entry->GetSSL().cert_status;
EXPECT_FALSE(cert_status & net::CERT_STATUS_IS_QWAC);
// Install CRS update that has the root as a trust anchor in CRS and the
// intermediate as a QWAC issuer.
{
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
scoped_refptr<net::X509Certificate> intermediate_cert =
https_server_ok.GetGeneratedIntermediate();
ASSERT_TRUE(intermediate_cert);
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
auto* trust_anchor = root_store_proto.add_trust_anchors();
trust_anchor->set_der(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer()));
auto* additional_cert = root_store_proto.add_additional_certs();
additional_cert->set_der(net::x509_util::CryptoBufferAsStringPiece(
intermediate_cert->cert_buffer()));
additional_cert->set_eutl(true);
InstallCRSUpdate(std::move(root_store_proto));
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("b.example.com", "/simple.html")));
// Check the page's cert status is a QWAC (if net::features::kVerifyQWACs is
// enabled).
tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_EQ(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
ssl_test_util::CheckAuthenticatedState(tab, ssl_test_util::AuthState::NONE);
cert_status = tab->GetController().GetVisibleEntry()->GetSSL().cert_status;
EXPECT_EQ(GetParam(), !!(cert_status & net::CERT_STATUS_IS_QWAC));
// Install a CRS update that has the root as both a trust anchor in CRS and
// a QWAC issuer
{
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
auto* trust_anchor = root_store_proto.add_trust_anchors();
trust_anchor->set_der(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer()));
trust_anchor->set_eutl(true);
InstallCRSUpdate(std::move(root_store_proto));
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("c.example.com", "/simple.html")));
// Check the page's cert status is a QWAC (if net::features::kVerifyQWACs is
// enabled).
tab = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(WaitForRenderFrameReady(tab->GetPrimaryMainFrame()));
EXPECT_EQ(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
ssl_test_util::CheckAuthenticatedState(tab, ssl_test_util::AuthState::NONE);
cert_status = tab->GetController().GetVisibleEntry()->GetSSL().cert_status;
EXPECT_EQ(GetParam(), !!(cert_status & net::CERT_STATUS_IS_QWAC));
}
// Test suite for tests that depend on both Certificate Transparency and Chrome
// Root Store updates.
class PKIMetadataComponentCtAndCrsUpdaterTest
: public InProcessBrowserTest,
public testing::WithParamInterface<CTEnforcement>,
public PKIMetadataComponentInstallerService::Observer {
public:
PKIMetadataComponentCtAndCrsUpdaterTest() {
if (GetParam() == CTEnforcement::kDisabledByFeature) {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/
{
#if BUILDFLAG(CHROME_ROOT_STORE_OPTIONAL)
net::features::kChromeRootStoreUsed
#endif
},
/*disabled_features=*/{
features::kCertificateTransparencyAskBeforeEnabling});
} else {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/
{features::kCertificateTransparencyAskBeforeEnabling,
#if BUILDFLAG(CHROME_ROOT_STORE_OPTIONAL)
net::features::kChromeRootStoreUsed
#endif
},
/*disabled_features=*/{});
}
}
void SetUpInProcessBrowserTestFixture() override {
PKIMetadataComponentInstallerService::GetInstance()->AddObserver(this);
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
ASSERT_TRUE(component_dir_.CreateUniqueTempDir());
host_resolver()->AddRule("*", "127.0.0.1");
}
void TearDownInProcessBrowserTestFixture() override {
PKIMetadataComponentInstallerService::GetInstance()->RemoveObserver(this);
}
protected:
// Waits for the CT log lists to have been configured at least
// |expected_times|.
void WaitForCtConfiguration(int expected_times) {
if (GetParam() == CTEnforcement::kDisabledByFeature) {
// When CT is disabled by the feature flag there are no callbacks to
// wait on, so just spin the runloop.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(ct_log_list_configured_times_, 0);
} else {
expected_ct_log_list_configured_times_ = expected_times;
if (ct_log_list_configured_times_ >=
expected_ct_log_list_configured_times_) {
return;
}
base::RunLoop run_loop;
pki_metadata_config_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
}
const base::FilePath& GetComponentDirPath() const {
return component_dir_.GetPath();
}
void InstallCRSUpdate(chrome_root_store::RootStore root_store_proto) {
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(
PKIMetadataComponentInstallerService::GetInstance()
->WriteCRSDataForTesting(component_dir_.GetPath(),
root_store_proto.SerializeAsString()));
}
CRSWaiter waiter(this);
PKIMetadataComponentInstallerService::GetInstance()
->ConfigureChromeRootStore();
waiter.Wait();
}
private:
void OnCTLogListConfigured() override {
++ct_log_list_configured_times_;
if (pki_metadata_config_closure_ &&
ct_log_list_configured_times_ >=
expected_ct_log_list_configured_times_) {
std::move(pki_metadata_config_closure_).Run();
}
}
void OnChromeRootStoreConfigured() override {
if (crs_config_closure_) {
std::move(crs_config_closure_).Run();
}
}
class CRSWaiter {
public:
explicit CRSWaiter(PKIMetadataComponentCtAndCrsUpdaterTest* test) {
test_ = test;
test_->crs_config_closure_ = run_loop_.QuitClosure();
}
void Wait() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
raw_ptr<PKIMetadataComponentCtAndCrsUpdaterTest> test_;
};
base::test::ScopedFeatureList scoped_feature_list_;
base::ScopedTempDir component_dir_;
base::OnceClosure pki_metadata_config_closure_;
int expected_ct_log_list_configured_times_ = 0;
int ct_log_list_configured_times_ = 0;
base::OnceClosure crs_config_closure_;
int64_t last_used_crs_version_ = net::CompiledChromeRootStoreVersion();
};
IN_PROC_BROWSER_TEST_P(PKIMetadataComponentCtAndCrsUpdaterTest,
TestChromeRootStoreConstraintsSct) {
const base::Time kLogStart = base::Time::Now() - base::Days(1);
const base::Time kLogEnd = base::Time::Now() + base::Days(1);
CTLog log1("log operator 1", kLogStart, kLogEnd,
chrome_browser_certificate_transparency::CTLog::RFC6962);
CTLog log2(
"log operator 2", kLogStart, kLogEnd,
chrome_browser_certificate_transparency::CTLog::LOG_TYPE_UNSPECIFIED);
CTLog unknown_log(
"unknown log operator", kLogStart, kLogEnd,
chrome_browser_certificate_transparency::CTLog::LOG_TYPE_UNSPECIFIED);
const base::Time kSctTime0UnknownLog = base::Time::Now() - base::Minutes(30);
const base::Time kSctTime1 = base::Time::Now() - base::Minutes(20);
const base::Time kSctTime2 = base::Time::Now() - base::Minutes(10);
// Start a test server that uses a certificate with SCTs for the above test
// logs.
net::EmbeddedTestServer https_server_ok(net::EmbeddedTestServer::TYPE_HTTPS);
net::EmbeddedTestServer::ServerCertificateConfig server_config;
server_config.dns_names = {"*.example.com"};
server_config.embedded_scts.emplace_back(log1.id(), log1.key(), kSctTime1);
server_config.embedded_scts.emplace_back(log2.id(), log2.key(), kSctTime2);
server_config.embedded_scts.emplace_back(unknown_log.id(), unknown_log.key(),
kSctTime0UnknownLog);
https_server_ok.SetSSLConfig(server_config);
https_server_ok.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_server_ok.Start());
// Clear test roots so that cert validation only happens with
// what's in Chrome Root Store.
net::TestRootCerts::GetInstance()->Clear();
scoped_refptr<net::X509Certificate> root_cert =
net::ImportCertFromFile(net::EmbeddedTestServer::GetRootCertPemPath());
ASSERT_TRUE(root_cert);
int64_t crs_version = net::CompiledChromeRootStoreVersion();
// Install CRS update that trusts root without constraints.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer())));
InstallCRSUpdate(std::move(root_store_proto));
}
// Install CT configuration that trusts log1 and log2.
//
// Set up a configuration that will enable or disable CT enforcement
// depending on the test parameter.
chrome_browser_certificate_transparency::CTConfig ct_config;
ct_config.set_disable_ct_enforcement(GetParam() ==
CTEnforcement::kDisabledByProto);
ct_config.mutable_log_list()->mutable_timestamp()->set_seconds(
SecondsSinceEpoch(base::Time::Now()));
AddLogToCTConfig(&ct_config, log1);
AddLogToCTConfig(&ct_config, log2);
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(PKIMetadataComponentInstallerService::GetInstance()
->WriteCTDataForTesting(GetComponentDirPath(),
ct_config.SerializeAsString()));
}
PKIMetadataComponentInstallerService::GetInstance()
->ReconfigureAfterNetworkRestart();
WaitForCtConfiguration(1);
// Should be trusted.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("b.example.com", "/simple.html")));
EXPECT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
// Install CRS update that trusts root with a SCTNotAfter constraint.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer())));
anchor->add_constraints()->set_sct_not_after_sec(
SecondsSinceEpoch(kSctTime1 + base::Seconds(1)));
InstallCRSUpdate(std::move(root_store_proto));
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("c.example.com", "/simple.html")));
// Should be trusted if CT is enabled since the SCTNotAfter constraint is
// satisfied by the SCT from log1. Should be trusted if CT feature is
// disabled since SCTNotAfter fails open when CT is disabled.
EXPECT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
// Install CRS update that trusts root with a SCTNotAfter constraint that is
// before both of the valid SCTs.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer())));
anchor->add_constraints()->set_sct_not_after_sec(
SecondsSinceEpoch(kSctTime0UnknownLog + base::Seconds(1)));
InstallCRSUpdate(std::move(root_store_proto));
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("c.example.com", "/simple.html")));
switch (GetParam()) {
case CTEnforcement::kEnabled:
case CTEnforcement::kEnabledWithStaticCTEnforcement:
// Should be distrusted if CT is enabled. The SCTNotAfter constraint is
// not satisfied by any valid SCT. The SCT from the unknown log is not
// counted even though the timestamp matches the constraint.
EXPECT_NE(u"OK",
chrome_test_utils::GetActiveWebContents(this)->GetTitle());
break;
case CTEnforcement::kDisabledByProto:
case CTEnforcement::kDisabledByFeature:
// Should be trusted if CT feature is disabled since SCTNotAfter fails
// open when CT is disabled.
EXPECT_EQ(u"OK",
chrome_test_utils::GetActiveWebContents(this)->GetTitle());
break;
}
// Install CRS update that trusts root with a SCTAllAfter constraint that is
// before both of the valid SCTs.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer())));
anchor->add_constraints()->set_sct_all_after_sec(
SecondsSinceEpoch(kSctTime1 - base::Seconds(1)));
InstallCRSUpdate(std::move(root_store_proto));
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("c.example.com", "/simple.html")));
// Should be trusted if CT is enabled since the SCTAlltAfter constraint is
// satisfied by the SCT from both logs.
// Should be trusted if CT feature is disabled since SCTAllAfter fails
// open when CT is disabled.
EXPECT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
// Install CRS update that trusts root with a SCTAllAfter constraint that is
// before one of the SCTs but after the other.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(
net::x509_util::CryptoBufferAsStringPiece(root_cert->cert_buffer())));
anchor->add_constraints()->set_sct_all_after_sec(
SecondsSinceEpoch(kSctTime1 + base::Seconds(1)));
InstallCRSUpdate(std::move(root_store_proto));
}
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server_ok.GetURL("c.example.com", "/simple.html")));
switch (GetParam()) {
case CTEnforcement::kEnabled:
case CTEnforcement::kEnabledWithStaticCTEnforcement:
// Should be distrusted since one of the SCTs was before the SCTAllAfter
// constraint.
EXPECT_NE(u"OK",
chrome_test_utils::GetActiveWebContents(this)->GetTitle());
break;
case CTEnforcement::kDisabledByProto:
case CTEnforcement::kDisabledByFeature:
// Should be trusted if CT feature is disabled since SCTAllAfter fails
// open when CT is disabled.
EXPECT_EQ(u"OK",
chrome_test_utils::GetActiveWebContents(this)->GetTitle());
break;
}
}
INSTANTIATE_TEST_SUITE_P(
PKIMetadataComponentUpdater,
PKIMetadataComponentCtAndCrsUpdaterTest,
testing::Values(CTEnforcement::kEnabled,
CTEnforcement::kEnabledWithStaticCTEnforcement,
CTEnforcement::kDisabledByProto,
CTEnforcement::kDisabledByFeature));
std::string X509CertificateToString(scoped_refptr<net::X509Certificate> cert) {
std::vector<std::string> pem_encoded_chain;
EXPECT_TRUE(cert->GetPEMEncodedChain(&pem_encoded_chain));
return base::JoinString(pem_encoded_chain, "\n");
}
// Checks that navigation responses were served over a connection where the
// server provided the given `expected_server_certificate_chain`. Note that this
// checks the certificate chain that the server served, not the chain that the
// client built while validating the server's certificate.
class CertificateCheckingThrottle : public content::NavigationThrottle {
public:
CertificateCheckingThrottle(
content::NavigationThrottleRegistry& registry,
scoped_refptr<net::X509Certificate> expected_server_certificate_chain,
base::OnceCallback<void(uint8_t)> report_num_responses_callback)
: content::NavigationThrottle(registry),
expected_server_certificate_chain_(expected_server_certificate_chain),
report_num_responses_callback_(
std::move(report_num_responses_callback)) {}
CertificateCheckingThrottle(const CertificateCheckingThrottle&) = delete;
CertificateCheckingThrottle& operator=(const CertificateCheckingThrottle&) =
delete;
~CertificateCheckingThrottle() override {
std::move(report_num_responses_callback_).Run(num_responses_);
}
uint8_t num_responses() const { return num_responses_; }
protected:
const char* GetNameForLogging() override {
return "CertificateCheckingThrottle";
}
ThrottleCheckResult WillProcessResponse() override {
EXPECT_TRUE(navigation_handle()
->GetSSLInfo()
->unverified_cert->EqualsIncludingChain(
expected_server_certificate_chain_.get()))
<< "\n\nExpected server chain: "
<< X509CertificateToString(expected_server_certificate_chain_)
<< "\n\nObserved unverified server chain: "
<< X509CertificateToString(
navigation_handle()->GetSSLInfo()->unverified_cert);
++num_responses_;
return content::NavigationThrottle::PROCEED;
}
private:
scoped_refptr<net::X509Certificate> expected_server_certificate_chain_;
uint8_t num_responses_ = 0;
base::OnceCallback<void(uint8_t)> report_num_responses_callback_;
};
class TestDnsOverHttpsConfigSource : public DnsOverHttpsConfigSource {
public:
TestDnsOverHttpsConfigSource(std::string dns_over_https_templates,
std::string dns_over_https_mode)
: dns_over_https_templates_(std::move(dns_over_https_templates)),
dns_over_https_mode_(std::move(dns_over_https_mode)) {}
TestDnsOverHttpsConfigSource(const TestDnsOverHttpsConfigSource&) = delete;
TestDnsOverHttpsConfigSource& operator=(const TestDnsOverHttpsConfigSource&) =
delete;
~TestDnsOverHttpsConfigSource() override = default;
// DnsOverHttpsConfigSource:
std::string GetDnsOverHttpsMode() const override {
return dns_over_https_mode_;
}
std::string GetDnsOverHttpsTemplates() const override {
return dns_over_https_templates_;
}
bool IsConfigManaged() const override {
// Return managed=true, otherwise the test config will be ignored if the
// test is run on an enterprise enrolled device.
return true;
}
void SetDohChangeCallback(base::RepeatingClosure callback) override {}
private:
std::string dns_over_https_templates_;
std::string dns_over_https_mode_;
};
// Test fixture for testing Trust Anchor IDs, including a test DoH server for
// advertising Trust Anchor IDs in DNS.
class PKIMetadataComponentChromeRootStoreUpdateWithDoHServerTest
: public PKIMetadataComponentChromeRootStoreUpdateTest {
public:
static constexpr std::string_view kDohServerHostname = "doh.test";
static constexpr std::string_view kHostname = "a.com";
PKIMetadataComponentChromeRootStoreUpdateWithDoHServerTest()
: PKIMetadataComponentChromeRootStoreUpdateTest() {
feature_list_.InitAndEnableFeature(net::features::kTLSTrustAnchorIDs);
}
void SetUpOnMainThread() override {
// Set up an HTTPS server that has two certificate chains.
// The first is directly issued by a unique root with the trust anchor ID
// `kIntermediateTrustAnchorId`.
// The second chain has a leaf and intermediate issued by the default test
// root cert, and has no trust anchor ID.
net::SSLServerConfig server_config;
// TODO(crbug.com/431064813): this callback just adds some debugging
// info to try to investigate a flake. It can be removed once the cause of
// the flake is found.
server_config.client_hello_callback_for_testing =
base::BindLambdaForTesting([&](const SSL_CLIENT_HELLO* client_hello) {
const uint8_t* data = nullptr;
size_t len = 0;
SSL_early_callback_ctx_extension_get(
client_hello, TLSEXT_TYPE_trust_anchors, &data, &len);
LOG(ERROR) << "Trust anchor IDs from Client Hello: "
<< base::HexEncode(data, len);
return true;
});
net::EmbeddedTestServer::ServerCertificateConfig tai_cert_config;
tai_cert_config.intermediate =
net::EmbeddedTestServer::IntermediateType::kNone;
tai_cert_config.root = net::EmbeddedTestServer::RootType::kUniqueRoot;
tai_cert_config.trust_anchor_id =
base::ToVector(kIntermediateTrustAnchorId);
tai_cert_config.dns_names.emplace_back(kHostname);
net::EmbeddedTestServer::ServerCertificateConfig default_cert_config;
default_cert_config.intermediate =
net::EmbeddedTestServer::IntermediateType::kInHandshake;
default_cert_config.root = net::EmbeddedTestServer::RootType::kUniqueRoot;
default_cert_config.dns_names.emplace_back(kHostname);
trust_anchor_ids_server_.SetSSLConfig(
{tai_cert_config, default_cert_config}, server_config);
trust_anchor_ids_server_.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(trust_anchor_ids_server_.Start());
// Start a DoH server, which ensures we use a resolver with HTTPS RR
// support. Configure it to serve records for `trust_anchor_ids_server_`.
doh_server_.SetHostname(kDohServerHostname);
url::SchemeHostPort tai_host(
trust_anchor_ids_server_.GetURL(kHostname, "/"));
doh_server_.AddAddressRecord(tai_host.host(),
net::IPAddress::IPv4Localhost());
doh_server_.AddRecord(net::BuildTestHttpsServiceRecord(
net::dns_util::GetNameForHttpsQuery(tai_host),
/*priority=*/1, /*service_name=*/tai_host.host(),
{net::BuildTestHttpsServiceTrustAnchorIDsParam(
GetTrustAnchorIDsForDns())}));
ASSERT_TRUE(doh_server_.Start());
doh_config_source_ = std::make_unique<TestDnsOverHttpsConfigSource>(
doh_server_.GetTemplate(), SecureDnsConfig::kModeSecure);
SystemNetworkContextManager::GetStubResolverConfigReader()
->SetOverrideDnsOverHttpsConfigSource(std::move(doh_config_source_));
// The net stack doesn't enable DoH when it can't find a system DNS config
// (see https://crbug.com/1251715).
SetReplaceSystemDnsConfig();
// Ensure that the DoH configuration is picked up.
content::FlushNetworkServiceInstanceForTesting();
// Add a single bootstrapping rule so we can resolve the DoH server.
host_resolver()->AddRule(kDohServerHostname, "127.0.0.1");
}
void UpdateNumObservedResponses(uint8_t num_responses) {
num_observed_responses_ += num_responses;
}
protected:
// The Trust Anchor ID configured by `trust_anchor_ids_server_` for the
// intermediate that it uses in its certificate chain.
static constexpr uint8_t kIntermediateTrustAnchorId[] = {0x01, 0x02, 0x03};
// A Trust Anchor ID that is advertised for `trust_anchor_ids_server_` in DNS,
// but not actually associated with a certificate chain configured on the
// server.
static constexpr uint8_t kAdvertisedButNotServedTrustAnchorId[] = {0x04, 0x05,
0x06};
// A Trust Anchor ID that is neither advertised for `trust_anchor_ids_server_`
// in DNS, nor actually associated with a certificate chain configured on the
// server.
static constexpr uint8_t kNotAdvertisedAndNotServedTrustAnchorId[] = {
0x07, 0x08, 0x09};
static constexpr size_t kTaiCredentialNum = 0;
static constexpr size_t kDefaultCredentialNum = 1;
// By default, `kIntermediateTrustAnchorId` and
// `kAdvertisedButNotServedTrustAnchorId` are advertised for `kHostname` in an
// HTTPS record served by `doh_server_`. Subclasses can override this method
// to change which Trust Anchor IDs are advertised for this host.
virtual std::vector<std::vector<uint8_t>> GetTrustAnchorIDsForDns() {
return {base::ToVector(kAdvertisedButNotServedTrustAnchorId),
base::ToVector(kIntermediateTrustAnchorId)};
}
// Installs a navigation throttle that expects `certificate` to be the served
// certificate chain on successful responses. Overwrites previous calls to
// this method (i.e., only one certificate-checking throttle is in place at a
// time). When the navigation is finished and the inserted throttle is
// destroyed, UpdateNumObservedResponses() will be called, which allows tests
// to check that the throttle was successfully installed and observed a
// navigation.
void SetExpectedCertificateOnResponses(
scoped_refptr<net::X509Certificate> certificate) {
num_observed_responses_ = 0;
throttle_inserter_ =
std::make_unique<content::TestNavigationThrottleInserter>(
chrome_test_utils::GetActiveWebContents(this),
base::BindRepeating(
&PKIMetadataComponentChromeRootStoreUpdateWithDoHServerTest::
InsertThrottle,
base::Unretained(this), certificate));
}
void InsertThrottle(
scoped_refptr<net::X509Certificate> expected_server_certificate,
content::NavigationThrottleRegistry& registry) {
registry.AddThrottle(std::make_unique<CertificateCheckingThrottle>(
registry, expected_server_certificate,
base::BindOnce(
&PKIMetadataComponentChromeRootStoreUpdateWithDoHServerTest::
UpdateNumObservedResponses,
base::Unretained(this))));
}
// Checks that the most recently installed navigation throttle observed at
// least one response.
void CheckThrottleObservedNavigation() {
ASSERT_GT(num_observed_responses_, 0u);
}
net::EmbeddedTestServer trust_anchor_ids_server_{
net::EmbeddedTestServer::TYPE_HTTPS};
net::TestDohServer doh_server_;
private:
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<content::TestNavigationThrottleInserter> throttle_inserter_;
std::unique_ptr<TestDnsOverHttpsConfigSource> doh_config_source_;
// Tracks the number of responses observed by CertificateCheckingThrottles.
// Reset to 0 on each new `SetExpectedCertificateOnResponses()` call.
uint8_t num_observed_responses_ = 0;
};
IN_PROC_BROWSER_TEST_F(
PKIMetadataComponentChromeRootStoreUpdateWithDoHServerTest,
TrustAnchorIDs) {
int64_t crs_version = net::CompiledChromeRootStoreVersion();
{
// Install CRS update that contains only the default root and no trust
// anchor ids.
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(net::x509_util::CryptoBufferAsStringPiece(
trust_anchor_ids_server_.GetRoot(kDefaultCredentialNum)
->cert_buffer())));
InstallCRSUpdate(std::move(root_store_proto));
// Ensure that SSLConfigClients have been notified of the new trust anchor
// IDs.
SystemNetworkContextManager::GetInstance()
->FlushSSLConfigManagerForTesting();
// Before updating the root store with trust anchor IDs, the server should
// serve the default credential which has both a leaf and an intermediate.
scoped_refptr<net::X509Certificate> server_certificate =
trust_anchor_ids_server_.GetCertificate(kDefaultCredentialNum);
ASSERT_EQ(server_certificate->intermediate_buffers().size(), 1u);
SetExpectedCertificateOnResponses(server_certificate);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), trust_anchor_ids_server_.GetURL(kHostname, "/simple.html")));
ASSERT_EQ(chrome_test_utils::GetActiveWebContents(this)->GetTitle(), u"OK");
CheckThrottleObservedNavigation();
}
// Install CRS update that contains two trusted Trust Anchor IDs, including
// one that is advertised by the server corresponding to its root
// certificate.
{
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* anchor =
root_store_proto.add_trust_anchors();
anchor->set_der(std::string(net::x509_util::CryptoBufferAsStringPiece(
trust_anchor_ids_server_.GetRoot(kDefaultCredentialNum)
->cert_buffer())));
chrome_root_store::TrustAnchor* additional_cert1 =
root_store_proto.add_additional_certs();
additional_cert1->set_der(
std::string(net::x509_util::CryptoBufferAsStringPiece(
trust_anchor_ids_server_.GetRoot(kTaiCredentialNum)
->cert_buffer())));
additional_cert1->set_trust_anchor_id(
base::as_string_view(kIntermediateTrustAnchorId));
additional_cert1->set_tls_trust_anchor(true);
chrome_root_store::TrustAnchor* additional_cert2 =
root_store_proto.add_additional_certs();
scoped_refptr<net::X509Certificate> unused_intermediate =
net::ImportCertFromFile(net::GetTestCertsDirectory(),
"verisign_intermediate_ca_2016.pem");
additional_cert2->set_der(
std::string(net::x509_util::CryptoBufferAsStringPiece(
unused_intermediate->cert_buffer())));
additional_cert2->set_trust_anchor_id(
base::as_string_view(kNotAdvertisedAndNotServedTrustAnchorId));
additional_cert2->set_tls_trust_anchor(true);
InstallCRSUpdate(std::move(root_store_proto));
// Ensure that SSLConfigClients have been notified of the new trust anchor
// IDs.
SystemNetworkContextManager::GetInstance()
->FlushSSLConfigManagerForTesting();
// The server should now serve a single leaf, without any intermediates,
// because the client should signal that it trusts the intermediate as a
// trust anchor.
scoped_refptr<net::X509Certificate> server_certificate =
trust_anchor_ids_server_.GetCertificate(kTaiCredentialNum);
ASSERT_EQ(server_certificate->intermediate_buffers().size(), 0u);
SetExpectedCertificateOnResponses(server_certificate);
// TODO(crbug.com/431064813): remove after debugging test flake.
LOG(ERROR) << "Beginning navigation with Trust Anchor IDs";
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), trust_anchor_ids_server_.GetURL(kHostname, "/simple.html")));
ASSERT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
CheckThrottleObservedNavigation();
}
}
// Test fixture that simulates a stale DNS record, advertising a Trust Anchor ID
// that is not supported by the server. The root store does not trust the root
// for the full chain served by the server and accepts only an elided chain,
// for testing the Trust Anchor IDs retry flow.
class PKIMetadataComponentChromeRootStoreUpdateWithStaleDoHServerTest
: public PKIMetadataComponentChromeRootStoreUpdateWithDoHServerTest {
public:
PKIMetadataComponentChromeRootStoreUpdateWithStaleDoHServerTest() = default;
protected:
std::vector<std::vector<uint8_t>> GetTrustAnchorIDsForDns() override {
return {base::ToVector(kAdvertisedButNotServedTrustAnchorId)};
}
};
IN_PROC_BROWSER_TEST_F(
PKIMetadataComponentChromeRootStoreUpdateWithStaleDoHServerTest,
TrustAnchorIDsRetry) {
// Install CRS update that contains two trusted Trust Anchor IDs, including
// one that is advertised by the server corresponding to its intermediate
// certificate, and one that is advertised by the server but not actually used
// on the server (simulating, e.g., a stale DNS record that is out of sync
// with the server's actual credentials). The CRS update does NOT trust the
// test server's default (non-TAI) root.
int64_t crs_version = net::CompiledChromeRootStoreVersion();
chrome_root_store::RootStore root_store_proto;
root_store_proto.set_version_major(++crs_version);
chrome_root_store::TrustAnchor* additional_cert1 =
root_store_proto.add_additional_certs();
additional_cert1->set_der(
std::string(net::x509_util::CryptoBufferAsStringPiece(
trust_anchor_ids_server_.GetRoot(kTaiCredentialNum)->cert_buffer())));
additional_cert1->set_trust_anchor_id(
base::as_string_view(kIntermediateTrustAnchorId));
additional_cert1->set_tls_trust_anchor(true);
chrome_root_store::TrustAnchor* additional_cert2 =
root_store_proto.add_additional_certs();
scoped_refptr<net::X509Certificate> unused_intermediate =
net::ImportCertFromFile(net::GetTestCertsDirectory(),
"verisign_intermediate_ca_2016.pem");
ASSERT_TRUE(unused_intermediate);
additional_cert2->set_der(
std::string(net::x509_util::CryptoBufferAsStringPiece(
unused_intermediate->cert_buffer())));
additional_cert2->set_trust_anchor_id(
base::as_string_view(kAdvertisedButNotServedTrustAnchorId));
additional_cert2->set_tls_trust_anchor(true);
InstallCRSUpdate(std::move(root_store_proto));
// Ensure that SSLConfigClients have been notified of the new trust anchor
// IDs.
SystemNetworkContextManager::GetInstance()->FlushSSLConfigManagerForTesting();
// Send a request to the server. Initially, the client will advertise the
// intersection of what is advertised in DNS with its trust store -- i.e.,
// only `kAdvertisedButNotServedTrustAnchorID`. The server does not actually
// support this Trust Anchor ID, and thus will serve its full chain. This
// should result in a certificate error (since kDefaultCredentialNum root is
// not trusted), which will cause the client to retry using the Trust Anchor
// ID that the server actually supports. The final result is that the
// connection should succeed and serve the elided certificate chain.
SetExpectedCertificateOnResponses(
trust_anchor_ids_server_.GetCertificate(kTaiCredentialNum));
base::HistogramTester histogram_tester;
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), trust_anchor_ids_server_.GetURL(kHostname, "/simple.html")));
ASSERT_EQ(u"OK", chrome_test_utils::GetActiveWebContents(this)->GetTitle());
CheckThrottleObservedNavigation();
metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
// This test uses ExpectBucketCount rather than ExpectUniqueSample, since
// other results are likely to be recorded by the histogram during the
// navigation. The DoH lookups should record kNoDnsSuccessInitial.
// The connection done by the test should be the only one that has a
// possibility of recording kDnsSuccessRetry, so this will still verify that
// the test hit the expected result. After the connection is successful
// another entry may be recorded for the favicon fetch, but it should record
// kDnsSuccessInitial since it will use TLS session resumption.
//
// Sometimes (on builds where browser_tests isn't using
// fieldtrial_testing_config), the browser makes two connections. So just
// check that the bucket has been logged at least once.
EXPECT_GE(histogram_tester.GetBucketCount(
"Net.SSL.TrustAnchorIDsResult",
net::SSLClientSocket::TrustAnchorIDsResult::kDnsSuccessRetry),
1);
// TODO(crbug.com/427778127): when Trust Anchor ID netlogs are added, check
// them here.
}
// TODO(crbug.com/40816087) additional Chrome Root Store browser tests to
// add:
//
// * Test that AIA fetching still works after updating CRS.
#endif // BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
} // namespace component_updater