blob: adb6e644ad029e0f10b1a2d3076394abe7630d85 [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 <optional>
#include <string_view>
#include "base/run_loop.h"
#include "base/task/current_thread.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ssl/cert_verifier_browser_test.h"
#include "chrome/browser/ssl/ssl_browsertest_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/certificate_transparency/pref_names.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 "content/public/browser/network_service_instance.h"
#include "content/public/test/browser_test.h"
#include "crypto/sha2.h"
#include "net/base/hash_value.h"
#include "net/cert/asn1_util.h"
#include "net/cert/x509_util.h"
#include "net/dns/mock_host_resolver.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 "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
// Returns the Sha256 hash of the SPKI of |cert|.
net::HashValue GetSPKIHash(const CRYPTO_BUFFER* cert) {
std::string_view 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;
}
} // namespace
// Class used to run browser tests that verify SSL UI triggered due to
// Certificate Transparency verification failures/successes.
class CertificateTransparencyBrowserTest : public CertVerifierBrowserTest {
public:
CertificateTransparencyBrowserTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
true);
}
CertificateTransparencyBrowserTest(
const CertificateTransparencyBrowserTest&) = delete;
CertificateTransparencyBrowserTest& operator=(
const CertificateTransparencyBrowserTest&) = delete;
~CertificateTransparencyBrowserTest() override {
SystemNetworkContextManager::SetEnableCertificateTransparencyForTesting(
std::nullopt);
}
void SetUpOnMainThread() override {
CertVerifierBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
}
void SetUp() override {
policy_provider_.SetDefaultReturns(
/*is_initialization_complete_return=*/true,
/*is_first_policy_load_complete_return=*/true);
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
&policy_provider_);
CertVerifierBrowserTest::SetUp();
}
// Sets the policy identified by |policy_name| to the value specified by
// |list_values|, ensuring that the corresponding list pref |pref_name| is
// updated to match. |policy_name| must specify a policy that is a list of
// string values.
void ConfigureStringListPolicy(PrefService* pref_service,
const char* policy_name,
const char* pref_name,
const std::vector<std::string>& list_values) {
base::Value::List policy_value;
for (const auto& value : list_values) {
policy_value.Append(value);
}
policy::PolicyMap policy_map;
policy_map.Set(policy_name, policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
base::Value(std::move(policy_value)), nullptr);
EXPECT_NO_FATAL_FAILURE(UpdateChromePolicy(policy_map));
const base::Value::List& pref_value = pref_service->GetList(pref_name);
std::vector<std::string> pref_values;
for (const auto& value : pref_value) {
ASSERT_TRUE(value.is_string());
pref_values.push_back(value.GetString());
}
EXPECT_THAT(pref_values, testing::UnorderedElementsAreArray(list_values));
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
private:
void UpdateChromePolicy(const policy::PolicyMap& policies) {
policy_provider_.UpdateChromePolicy(policies);
ASSERT_TRUE(base::CurrentThread::Get());
base::RunLoop().RunUntilIdle();
content::FlushNetworkServiceInstanceForTesting();
}
net::EmbeddedTestServer https_server_;
testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
};
// 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(CertificateTransparencyBrowserTest,
EnforcedAfterApril2018) {
SystemNetworkContextManager::GetInstance()->SetCTLogListTimelyForTesting();
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_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("/ssl/google.html")));
ssl_test_util::CheckSecurityState(
browser()->tab_strip_model()->GetActiveWebContents(),
net::CERT_STATUS_CERTIFICATE_TRANSPARENCY_REQUIRED,
security_state::DANGEROUS,
ssl_test_util::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(CertificateTransparencyBrowserTest,
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()}));
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("/ssl/google.html")));
ssl_test_util::CheckSecurityState(
browser()->tab_strip_model()->GetActiveWebContents(),
ssl_test_util::CertError::NONE, security_state::SECURE,
ssl_test_util::AuthState::NONE);
}
// Visit an HTTPS page that has a certificate issued by a certificate authority
// that is trusted in a root store that Chrome does not consider consistently
// secure. In the case where the certificate was issued after the Certificate
// Transparency requirement date of April 2018 the connection would normally be
// blocked, as the server will not be providing CT details, and the Chrome CT
// Policy should be being enforced; however, because a policy configuration
// exists that disables CT enforcement for that Legacy cert, the connection
// should succeed. For more detail, see /net/docs/certificate-transparency.md
IN_PROC_BROWSER_TEST_F(CertificateTransparencyBrowserTest,
LegacyEnforcedAfterApril2018UnlessPoliciesSet) {
ASSERT_TRUE(https_server()->Start());
net::CertVerifyResult verify_result;
verify_result.verified_cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "may_2018.pem");
ASSERT_TRUE(verify_result.verified_cert);
verify_result.is_issued_by_known_root = true;
// We'll use a SPKI hash corresponding to the Federal Common Policy CA as
// captured at https://fpki.idmanagement.gov/announcements/mspkichanges/
const net::SHA256HashValue legacy_spki_hash = {
0x8e, 0x8b, 0x56, 0xf5, 0x91, 0x8a, 0x25, 0xbd, 0x85, 0xdc, 0xe7,
0x66, 0x63, 0xfd, 0x94, 0xcc, 0x23, 0x69, 0x0f, 0x10, 0xea, 0x95,
0x86, 0x61, 0x31, 0x71, 0xc6, 0xf8, 0x37, 0x88, 0x90, 0xd5};
verify_result.public_key_hashes.push_back(net::HashValue(legacy_spki_hash));
mock_cert_verifier()->AddResultForCert(https_server()->GetCertificate().get(),
verify_result, net::OK);
ASSERT_NO_FATAL_FAILURE(ConfigureStringListPolicy(
browser()->profile()->GetPrefs(),
policy::key::kCertificateTransparencyEnforcementDisabledForLegacyCas,
certificate_transparency::prefs::kCTExcludedLegacySPKIs,
{verify_result.public_key_hashes.back().ToString()}));
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server()->GetURL("/ssl/google.html")));
ssl_test_util::CheckSecurityState(
browser()->tab_strip_model()->GetActiveWebContents(),
ssl_test_util::CertError::NONE, security_state::SECURE,
ssl_test_util::AuthState::NONE);
}