blob: b1601f8e5d739d297015839e5e1bfaec38940bf1 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/cert/internal/trust_store_chrome.h"
#include "base/containers/span.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "crypto/sha2.h"
#include "net/cert/root_store_proto_lite/root_store.pb.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/test/cert_builder.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/pki/cert_errors.h"
#include "third_party/boringssl/src/pki/parsed_certificate.h"
namespace net {
namespace {
#include "net/data/ssl/chrome_root_store/chrome-root-store-test-data-inc.cc"
std::shared_ptr<const bssl::ParsedCertificate> ToParsedCertificate(
const X509Certificate& cert) {
bssl::CertErrors errors;
std::shared_ptr<const bssl::ParsedCertificate> parsed =
bssl::ParsedCertificate::Create(
bssl::UpRef(cert.cert_buffer()),
x509_util::DefaultParseCertificateOptions(), &errors);
EXPECT_TRUE(parsed) << errors.ToDebugString();
return parsed;
}
scoped_refptr<X509Certificate> MakeTestRoot() {
auto builder = std::make_unique<CertBuilder>(nullptr, nullptr);
auto now = base::Time::Now();
builder->SetValidity(now - base::Days(1), now + base::Days(1));
builder->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/-1);
builder->SetKeyUsages(
{bssl::KEY_USAGE_BIT_KEY_CERT_SIGN, bssl::KEY_USAGE_BIT_CRL_SIGN});
return builder->GetX509Certificate();
}
std::shared_ptr<const bssl::ParsedCertificate>
FindParsedCertificateInCertificateList(const std::string& hash,
CertificateList certs) {
for (const auto& cert : certs) {
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*cert);
std::string sha256_hex = base::ToLowerASCII(
base::HexEncode(crypto::SHA256Hash(parsed->der_cert())));
if (sha256_hex == hash) {
return parsed;
}
}
return nullptr;
}
TEST(TrustStoreChromeTestNoFixture, ContainsCert) {
std::unique_ptr<TrustStoreChrome> trust_store_chrome =
TrustStoreChrome::CreateTrustStoreForTesting(
base::span<const ChromeRootCertInfo>(kChromeRootCertList),
base::span(kEutlRootCertList),
/*version=*/1);
// Check every certificate in test_store.certs is included.
CertificateList certs = CreateCertificateListFromFile(
GetTestNetDataDirectory().AppendASCII("ssl/chrome_root_store"),
"test_store.certs", X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
ASSERT_EQ(certs.size(), 6u);
size_t eutl_certs = 0;
for (const auto& cert : certs) {
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*cert);
ASSERT_TRUE(trust_store_chrome->Contains(parsed.get()));
bssl::CertificateTrust trust = trust_store_chrome->GetTrust(parsed.get());
EXPECT_TRUE(trust.IsTrustAnchor());
// Count how many certs are on the EUTL.
bssl::CertificateTrust eutl_trust =
trust_store_chrome->eutl_trust_store()->GetTrust(parsed.get());
if (eutl_trust.type == bssl::CertificateTrustType::TRUSTED_ANCHOR) {
eutl_certs++;
}
}
// There should be one cert from test_store.certs on the EUTL.
EXPECT_EQ(eutl_certs, 1);
// Other certificates should not be included. Which test cert used here isn't
// important as long as it isn't one of the certificates in the
// chrome_root_store/test_store.certs.
scoped_refptr<X509Certificate> other_cert =
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
ASSERT_TRUE(other_cert);
std::shared_ptr<const bssl::ParsedCertificate> other_parsed =
ToParsedCertificate(*other_cert);
ASSERT_FALSE(trust_store_chrome->Contains(other_parsed.get()));
bssl::CertificateTrust trust =
trust_store_chrome->GetTrust(other_parsed.get());
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust.ToDebugString());
}
TEST(TrustStoreChromeTestNoFixture, ContainsEutlCert) {
std::unique_ptr<TrustStoreChrome> trust_store_chrome =
TrustStoreChrome::CreateTrustStoreForTesting(
base::span<const ChromeRootCertInfo>(kChromeRootCertList),
base::span(kEutlRootCertList),
/*version=*/1);
const std::string kEUTLCertHash =
"f7c7e28fb5e79f314aaac6bbba932f15e1a72069f435d4c9e707f93ca1482ee3";
// Check that the EUTL certificate in test_additional.certs is included in
// the EUTL trust store, but not trusted for TLS connection establishment.
CertificateList certs = CreateCertificateListFromFile(
GetTestNetDataDirectory().AppendASCII("ssl/chrome_root_store"),
"test_additional.certs", X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
std::shared_ptr<const bssl::ParsedCertificate> parsed =
FindParsedCertificateInCertificateList(kEUTLCertHash, certs);
ASSERT_TRUE(parsed);
bssl::CertificateTrust eutl_trust =
trust_store_chrome->eutl_trust_store()->GetTrust(parsed.get());
EXPECT_EQ(bssl::CertificateTrust::ForTrustAnchor().ToDebugString(),
eutl_trust.ToDebugString());
EXPECT_FALSE(trust_store_chrome->Contains(parsed.get()));
bssl::CertificateTrust trust = trust_store_chrome->GetTrust(parsed.get());
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust.ToDebugString());
// Other certificates should not be included. Which test cert used here isn't
// important as long as it isn't one of the certificates in the
// chrome_root_store/test_store.certs.
scoped_refptr<X509Certificate> other_cert =
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
ASSERT_TRUE(other_cert);
std::shared_ptr<const bssl::ParsedCertificate> other_parsed =
ToParsedCertificate(*other_cert);
eutl_trust =
trust_store_chrome->eutl_trust_store()->GetTrust(other_parsed.get());
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
eutl_trust.ToDebugString());
}
TEST(TrustStoreChromeTestNoFixture, Constraints) {
std::unique_ptr<TrustStoreChrome> trust_store_chrome =
TrustStoreChrome::CreateTrustStoreForTesting(
base::span<const ChromeRootCertInfo>(kChromeRootCertList),
base::span(kEutlRootCertList),
/*version=*/1);
const std::string kUnconstrainedCertHash =
"568d6905a2c88708a4b3025190edcfedb1974a606a13c6e5290fcb2ae63edab5";
const std::string kConstrainedCertHash =
"6b9c08e86eb0f767cfad65cd98b62149e5494a67f5845e7bd1ed019f27b86bd6";
std::shared_ptr<const bssl::ParsedCertificate> constrained_cert;
std::shared_ptr<const bssl::ParsedCertificate> unconstrained_cert;
CertificateList certs = CreateCertificateListFromFile(
GetTestNetDataDirectory().AppendASCII("ssl/chrome_root_store"),
"test_store.certs", X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
for (const auto& cert : certs) {
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*cert);
std::string sha256_hex = base::ToLowerASCII(
base::HexEncode(crypto::SHA256Hash(parsed->der_cert())));
if (sha256_hex == kConstrainedCertHash) {
constrained_cert = parsed;
} else if (sha256_hex == kUnconstrainedCertHash) {
unconstrained_cert = parsed;
}
}
ASSERT_TRUE(unconstrained_cert);
EXPECT_TRUE(
trust_store_chrome->GetConstraintsForCert(unconstrained_cert.get())
.empty());
ASSERT_TRUE(constrained_cert);
base::span<const ChromeRootCertConstraints> constraints =
trust_store_chrome->GetConstraintsForCert(constrained_cert.get());
ASSERT_EQ(constraints.size(), 3U);
EXPECT_FALSE(constraints[0].sct_all_after.has_value());
ASSERT_TRUE(constraints[0].sct_not_after.has_value());
EXPECT_EQ(
constraints[0].sct_not_after.value().InMillisecondsSinceUnixEpoch() /
1000,
0x5af);
EXPECT_FALSE(constraints[0].min_version.has_value());
ASSERT_TRUE(constraints[0].max_version_exclusive.has_value());
EXPECT_EQ(constraints[0].max_version_exclusive.value().components(),
std::vector<uint32_t>({125, 0, 6368, 2}));
EXPECT_THAT(constraints[0].permitted_dns_names,
testing::ElementsAre("foo.example.com", "bar.example.com"));
EXPECT_FALSE(constraints[1].sct_not_after.has_value());
ASSERT_TRUE(constraints[1].sct_all_after.has_value());
EXPECT_EQ(
constraints[1].sct_all_after.value().InMillisecondsSinceUnixEpoch() /
1000,
0x2579);
ASSERT_TRUE(constraints[1].min_version.has_value());
EXPECT_FALSE(constraints[1].max_version_exclusive.has_value());
EXPECT_EQ(constraints[1].min_version.value().components(),
std::vector<uint32_t>({128}));
EXPECT_TRUE(constraints[1].permitted_dns_names.empty());
EXPECT_THAT(constraints[2].permitted_dns_names,
testing::ElementsAre("baz.example.com"));
// Other certificates should return nullptr if they are queried for CRS
// constraints. Which test cert used here isn't important as long as it isn't
// one of the certificates in the chrome_root_store/test_store.certs.
scoped_refptr<X509Certificate> other_cert =
ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
ASSERT_TRUE(other_cert);
std::shared_ptr<const bssl::ParsedCertificate> other_parsed =
ToParsedCertificate(*other_cert);
ASSERT_TRUE(other_parsed);
EXPECT_FALSE(trust_store_chrome->Contains(other_parsed.get()));
EXPECT_TRUE(
trust_store_chrome->GetConstraintsForCert(other_parsed.get()).empty());
}
TEST(TrustStoreChromeTestNoFixture, EnforceAnchorExpiryAndConstraints) {
std::unique_ptr<TrustStoreChrome> trust_store_chrome =
TrustStoreChrome::CreateTrustStoreForTesting(
base::span<const ChromeRootCertInfo>(kChromeRootCertList),
base::span(kEutlRootCertList),
/*version=*/1);
std::map<std::string /* SHA-256 hash of certificate */,
bssl::CertificateTrust>
tests = {
{"568d6905a2c88708a4b3025190edcfedb1974a606a13c6e5290fcb2ae63edab5",
bssl::CertificateTrust::ForTrustAnchor()},
{"d92e93252eabca950870b94331990963a2dd5db96d833c82b08e41afd1719178",
bssl::CertificateTrust::ForTrustAnchor().WithEnforceAnchorExpiry()},
{"68b9c761219a5b1f0131784474665db61bbdb109e00f05ca9f74244ee5f5f52b",
bssl::CertificateTrust::ForTrustAnchor()
.WithEnforceAnchorConstraints()},
{"687fa451382278fff0c8b11f8d43d576671c6eb2bceab413fb83d965d06d2ff2",
bssl::CertificateTrust::ForTrustAnchor()
.WithEnforceAnchorExpiry()
.WithEnforceAnchorConstraints()},
};
CertificateList certs = CreateCertificateListFromFile(
GetTestNetDataDirectory().AppendASCII("ssl/chrome_root_store"),
"test_store.certs", X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
for (const auto& test : tests) {
std::shared_ptr<const bssl::ParsedCertificate> cert =
FindParsedCertificateInCertificateList(test.first, certs);
bssl::CertificateTrust trust = trust_store_chrome->GetTrust(cert.get());
EXPECT_TRUE(trust.IsTrustAnchor());
EXPECT_EQ(trust.enforce_anchor_expiry, test.second.enforce_anchor_expiry);
EXPECT_EQ(trust.enforce_anchor_constraints,
test.second.enforce_anchor_constraints);
}
}
TEST(TrustStoreChromeTestNoFixture,
EnforceAnchorExpiryAndConstraintsFromProto) {
for (bool enforce_anchor_expiry : {false, true}) {
for (bool enforce_anchor_constraints : {false, true}) {
scoped_refptr<X509Certificate> root = MakeTestRoot();
chrome_root_store::RootStore root_store;
chrome_root_store::TrustAnchor* anchor = root_store.add_trust_anchors();
anchor->set_der(
net::x509_util::CryptoBufferAsStringPiece(root->cert_buffer()));
anchor->set_enforce_anchor_expiry(enforce_anchor_expiry);
anchor->set_enforce_anchor_constraints(enforce_anchor_constraints);
std::optional<ChromeRootStoreData> root_store_data =
ChromeRootStoreData::CreateFromRootStoreProto(root_store);
ASSERT_TRUE(root_store_data);
TrustStoreChrome trust_store_chrome(root_store_data.value());
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*root);
bssl::CertificateTrust trust = trust_store_chrome.GetTrust(parsed.get());
EXPECT_TRUE(trust.IsTrustAnchor());
EXPECT_EQ(trust.enforce_anchor_expiry, enforce_anchor_expiry);
EXPECT_EQ(trust.enforce_anchor_constraints, enforce_anchor_constraints);
}
}
}
// Tests that, for a compiled-in root store, certificates in |additional_certs|
// are compiled in as trust anchors when indicated, with associated trust anchor
// IDs when present, with |enforce_anchor_expiry| and
// |enforce_anchor_constraints| flags enforced.
TEST(TrustStoreChromeTestNoFixture,
LoadCompiledTrustAnchorsWithTrustAnchorIDs) {
std::unique_ptr<TrustStoreChrome> trust_store_chrome =
TrustStoreChrome::CreateTrustStoreForTesting(
base::span<const ChromeRootCertInfo>(kChromeRootCertList),
base::span(kEutlRootCertList),
/*version=*/1);
// Map of hex-encoded SHA-256 hashes of |trust_anchors| certificates that have
// associated Trust Anchor IDs to their expected CertificateTrust setting.
std::map<std::string, bssl::CertificateTrust>
expected_trust_anchor_trust_by_hash = {
{"687fa451382278fff0c8b11f8d43d576671c6eb2bceab413fb83d965d06d2ff2",
bssl::CertificateTrust::ForTrustAnchor()
.WithEnforceAnchorExpiry()
.WithEnforceAnchorConstraints()},
};
// Map of hex-encoded SHA-256 hashes of |additional_certs| certificates that
// are marked as trust anchors, and have associated Trust Anchor IDs, to their
// expected CertificateTrust setting.
std::map<std::string, bssl::CertificateTrust>
expected_additional_certificate_trust_by_hash = {
{"72a34ac2b424aed3f6b0b04755b88cc027dccc806fddb22b4cd7c47773973ec0",
bssl::CertificateTrust::ForTrustAnchor().WithEnforceAnchorExpiry()},
{"e6fe22bf45e4f0d3b85c59e02c0f495418e1eb8d3210f788d48cd5e1cb547cd4",
bssl::CertificateTrust::ForTrustAnchor()
.WithEnforceAnchorConstraints()},
{"973a41276ffd01e027a2aad49e34c37846d3e976ff6a620b6712e33832041aa6",
bssl::CertificateTrust::ForTrustAnchor()
.WithEnforceAnchorExpiry()
.WithEnforceAnchorConstraints()}};
CertificateList trust_anchor_certs = CreateCertificateListFromFile(
GetTestNetDataDirectory().AppendASCII("ssl/chrome_root_store"),
"test_store.certs", X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
CertificateList additional_certs = CreateCertificateListFromFile(
GetTestNetDataDirectory().AppendASCII("ssl/chrome_root_store"),
"test_additional.certs", X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
size_t certs_with_tai = 0;
for (const auto& cert : kChromeRootCertList) {
if (cert.trust_anchor_id.empty()) {
continue;
}
certs_with_tai++;
std::string hash = base::ToLowerASCII(
base::HexEncode(crypto::SHA256Hash(cert.root_cert_der)));
bool is_additional_cert =
expected_additional_certificate_trust_by_hash.contains(hash);
bssl::CertificateTrust expected_trust =
is_additional_cert ? expected_additional_certificate_trust_by_hash[hash]
: expected_trust_anchor_trust_by_hash[hash];
std::shared_ptr<const bssl::ParsedCertificate> parsed_cert =
is_additional_cert
? FindParsedCertificateInCertificateList(hash, additional_certs)
: FindParsedCertificateInCertificateList(hash, trust_anchor_certs);
ASSERT_TRUE(parsed_cert);
// Check that the certificate is present in the trust store as an anchor,
// with the expected settings for expiry and X.509 constraints.
// TODO(crbug.com/414630735): check that the correct Trust Anchor ID is
// stored in TrustStoreChrome, once implemented. (Right now TrustStoreChrome
// throws out Trust Anchor IDs and doesn't keep them around.)
bssl::CertificateTrust trust =
trust_store_chrome->GetTrust(parsed_cert.get());
EXPECT_TRUE(trust.IsTrustAnchor());
EXPECT_EQ(trust.enforce_anchor_expiry,
expected_trust.enforce_anchor_expiry);
EXPECT_EQ(trust.enforce_anchor_constraints,
expected_trust.enforce_anchor_constraints);
}
EXPECT_EQ(4u, certs_with_tai);
}
// Tests that, for a compiled-in root store, certificates in |additional_certs|
// are compiled in as trust anchors when indicated, even if they have no
// associated Trust Anchor ID.
TEST(TrustStoreChromeTestNoFixture,
LoadCompiledTrustAnchorsWithNoTrustAnchorID) {
std::unique_ptr<TrustStoreChrome> trust_store_chrome =
TrustStoreChrome::CreateTrustStoreForTesting(
base::span<const ChromeRootCertInfo>(kChromeRootCertList),
base::span(kEutlRootCertList),
/*version=*/1);
const std::string kAdditionalCertTrustAnchorWithNoTAIHash =
"19400be5b7a31fb733917700789d2f0a2471c0c9d506c0e504c06c16d7cb17c0";
CertificateList additional_certs = CreateCertificateListFromFile(
GetTestNetDataDirectory().AppendASCII("ssl/chrome_root_store"),
"test_additional.certs", X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
std::shared_ptr<const bssl::ParsedCertificate> parsed_cert =
FindParsedCertificateInCertificateList(
kAdditionalCertTrustAnchorWithNoTAIHash, additional_certs);
ASSERT_TRUE(parsed_cert);
bssl::CertificateTrust trust =
trust_store_chrome->GetTrust(parsed_cert.get());
EXPECT_TRUE(trust.IsTrustAnchor());
EXPECT_TRUE(trust.enforce_anchor_expiry);
EXPECT_TRUE(trust.enforce_anchor_constraints);
}
// Tests that, for a root loaded from a proto, certificates in
// |additional_certs| are loaded into TrustStoreChrome as trust anchors when
// indicated, with |enforce_anchor_expiry| and |enforce_anchor_constraints|
// flags enforced.
TEST(TrustStoreChromeTestNoFixture, LoadProtoAdditionalCertsAsTrustAnchors) {
for (bool enforce_anchor_expiry : {true, false}) {
for (bool enforce_anchor_constraints : {true, false}) {
scoped_refptr<X509Certificate> root = MakeTestRoot();
chrome_root_store::RootStore root_store;
chrome_root_store::TrustAnchor* anchor =
root_store.add_additional_certs();
anchor->set_der(
net::x509_util::CryptoBufferAsStringPiece(root->cert_buffer()));
anchor->set_enforce_anchor_expiry(enforce_anchor_expiry);
anchor->set_enforce_anchor_constraints(enforce_anchor_constraints);
anchor->set_tls_trust_anchor(true);
anchor->set_trust_anchor_id("\x01\x02\x03\x04");
std::optional<ChromeRootStoreData> root_store_data =
ChromeRootStoreData::CreateFromRootStoreProto(root_store);
ASSERT_TRUE(root_store_data);
TrustStoreChrome trust_store_chrome(root_store_data.value());
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*root);
bssl::CertificateTrust trust = trust_store_chrome.GetTrust(parsed.get());
EXPECT_TRUE(trust.IsTrustAnchor());
EXPECT_EQ(trust.enforce_anchor_expiry, enforce_anchor_expiry);
EXPECT_EQ(trust.enforce_anchor_constraints, enforce_anchor_constraints);
// TODO(crbug.com/414630735): check that the correct Trust Anchor ID is
// stored in TrustStoreChrome, once implemented. (Right now
// TrustStoreChrome throws out Trust Anchor IDs and doesn't keep them
// around.)
}
}
}
// Tests that, for a root loaded from a proto, certificates in
// |additional_certs| are loaded into TrustStoreChrome as trust anchors when
// indicated, even if there is no Trust Anchor ID set.
TEST(TrustStoreChromeTestNoFixture,
LoadProtoAdditionalCertsAsTrustAnchorsWithNoTrustAnchorID) {
scoped_refptr<X509Certificate> root = MakeTestRoot();
chrome_root_store::RootStore root_store;
chrome_root_store::TrustAnchor* anchor = root_store.add_additional_certs();
anchor->set_der(
net::x509_util::CryptoBufferAsStringPiece(root->cert_buffer()));
anchor->set_enforce_anchor_expiry(true);
anchor->set_enforce_anchor_constraints(true);
anchor->set_tls_trust_anchor(true);
// `trust_anchor_id` is left unset here.
std::optional<ChromeRootStoreData> root_store_data =
ChromeRootStoreData::CreateFromRootStoreProto(root_store);
ASSERT_TRUE(root_store_data);
TrustStoreChrome trust_store_chrome(root_store_data.value());
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*root);
bssl::CertificateTrust trust = trust_store_chrome.GetTrust(parsed.get());
EXPECT_TRUE(trust.IsTrustAnchor());
EXPECT_TRUE(trust.enforce_anchor_expiry);
EXPECT_TRUE(trust.enforce_anchor_constraints);
}
// Tests that, for a root loaded from a proto, certificates in
// |additional_certs| are not loaded into TrustStoreChrome as trust anchors when
// |tls_trust_anchor| is false.
TEST(TrustStoreChromeTestNoFixture, LoadProtoNonAnchorsAreNotTrusted) {
scoped_refptr<X509Certificate> root = MakeTestRoot();
chrome_root_store::RootStore root_store;
chrome_root_store::TrustAnchor* anchor = root_store.add_additional_certs();
anchor->set_der(
net::x509_util::CryptoBufferAsStringPiece(root->cert_buffer()));
anchor->set_enforce_anchor_expiry(true);
anchor->set_enforce_anchor_constraints(true);
// |tls_trust_anchor| is left unset here.
anchor->set_trust_anchor_id("\x01\x02\x03\x04");
std::optional<ChromeRootStoreData> root_store_data =
ChromeRootStoreData::CreateFromRootStoreProto(root_store);
ASSERT_TRUE(root_store_data);
TrustStoreChrome trust_store_chrome(root_store_data.value());
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*root);
EXPECT_FALSE(trust_store_chrome.Contains(parsed.get()));
// TODO(crbug.com/414630735): check that the above Trust Anchor ID is
// not present in TrustStoreChrome, once implemented. (Right now
// TrustStoreChrome throws out Trust Anchor IDs and doesn't keep them around.)
}
// Tests that TLS Trust Anchor IDs are loaded correctly from the compiled-in
// root store.
TEST(TrustStoreChromeTestNoFixture, LoadCompiledInTrustAnchorIDs) {
std::vector<std::vector<uint8_t>> trust_anchor_ids =
TrustStoreChrome::GetTrustAnchorIDsFromCompiledInRootStore(
base::span<const ChromeRootCertInfo>(kChromeRootCertList));
EXPECT_THAT(trust_anchor_ids,
testing::UnorderedElementsAre(
std::vector<uint8_t>({0x05u, 0x05u, 0x05u}),
std::vector<uint8_t>({0x01u, 0x01u, 0x01u, 0x01u}),
std::vector<uint8_t>({0x03u, 0x03u, 0x03u, 0x03u}),
std::vector<uint8_t>({0x02u, 0x02u, 0x02u, 0x02u})));
}
TEST(TrustStoreChromeTestNoFixture, OverrideConstraints) {
// Root1: has no constraints and no override constraints
// Root2: has constraints and no override constraints
// Root3: has no constraints and has override constraints
// Root4: has constraints and has override constraints
// Root5: not present in CRS and no override constraints
// Root6: not present in CRS but has override constraints
scoped_refptr<X509Certificate> root1 = MakeTestRoot();
scoped_refptr<X509Certificate> root2 = MakeTestRoot();
scoped_refptr<X509Certificate> root3 = MakeTestRoot();
scoped_refptr<X509Certificate> root4 = MakeTestRoot();
scoped_refptr<X509Certificate> root5 = MakeTestRoot();
scoped_refptr<X509Certificate> root6 = MakeTestRoot();
std::vector<StaticChromeRootCertConstraints> c2 = {{.min_version = "20"}};
std::vector<StaticChromeRootCertConstraints> c4 = {{.min_version = "40"}};
std::vector<ChromeRootCertInfo> root_cert_info = {
{root1->cert_span(), {}},
{root2->cert_span(), c2},
{root3->cert_span(), {}},
{root4->cert_span(), c4},
};
base::flat_map<std::array<uint8_t, crypto::kSHA256Length>,
std::vector<ChromeRootCertConstraints>>
override_constraints;
override_constraints[crypto::SHA256Hash(root3->cert_span())] = {
{std::nullopt,
std::nullopt,
std::nullopt,
/*max_version_exclusive=*/std::make_optional(base::Version("31")),
{}}};
override_constraints[crypto::SHA256Hash(root4->cert_span())] = {
{std::nullopt,
std::nullopt,
std::nullopt,
/*max_version_exclusive=*/std::make_optional(base::Version("41")),
{}}};
override_constraints[crypto::SHA256Hash(root6->cert_span())] = {
{std::nullopt,
std::nullopt,
std::nullopt,
/*max_version_exclusive=*/std::make_optional(base::Version("61")),
{}}};
std::unique_ptr<TrustStoreChrome> trust_store_chrome =
TrustStoreChrome::CreateTrustStoreForTesting(
std::move(root_cert_info),
/*eutl_certs=*/{},
/*version=*/1, std::move(override_constraints));
{
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*root1);
ASSERT_TRUE(parsed);
EXPECT_TRUE(trust_store_chrome->Contains(parsed.get()));
EXPECT_TRUE(
trust_store_chrome->GetConstraintsForCert(parsed.get()).empty());
}
{
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*root2);
ASSERT_TRUE(parsed);
EXPECT_TRUE(trust_store_chrome->Contains(parsed.get()));
base::span<const ChromeRootCertConstraints> constraints =
trust_store_chrome->GetConstraintsForCert(parsed.get());
ASSERT_EQ(constraints.size(), 1U);
EXPECT_EQ(constraints[0].min_version.value().components(),
std::vector<uint32_t>({20}));
EXPECT_FALSE(constraints[0].max_version_exclusive.has_value());
}
{
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*root3);
ASSERT_TRUE(parsed);
EXPECT_TRUE(trust_store_chrome->Contains(parsed.get()));
base::span<const ChromeRootCertConstraints> constraints =
trust_store_chrome->GetConstraintsForCert(parsed.get());
ASSERT_EQ(constraints.size(), 1U);
EXPECT_FALSE(constraints[0].min_version.has_value());
EXPECT_EQ(constraints[0].max_version_exclusive.value().components(),
std::vector<uint32_t>({31}));
}
{
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*root4);
ASSERT_TRUE(parsed);
EXPECT_TRUE(trust_store_chrome->Contains(parsed.get()));
base::span<const ChromeRootCertConstraints> constraints =
trust_store_chrome->GetConstraintsForCert(parsed.get());
ASSERT_EQ(constraints.size(), 1U);
EXPECT_FALSE(constraints[0].min_version.has_value());
EXPECT_EQ(constraints[0].max_version_exclusive.value().components(),
std::vector<uint32_t>({41}));
}
{
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*root5);
ASSERT_TRUE(parsed);
EXPECT_FALSE(trust_store_chrome->Contains(parsed.get()));
EXPECT_TRUE(
trust_store_chrome->GetConstraintsForCert(parsed.get()).empty());
}
{
std::shared_ptr<const bssl::ParsedCertificate> parsed =
ToParsedCertificate(*root6);
ASSERT_TRUE(parsed);
EXPECT_FALSE(trust_store_chrome->Contains(parsed.get()));
base::span<const ChromeRootCertConstraints> constraints =
trust_store_chrome->GetConstraintsForCert(parsed.get());
ASSERT_EQ(constraints.size(), 1U);
EXPECT_FALSE(constraints[0].min_version.has_value());
EXPECT_EQ(constraints[0].max_version_exclusive.value().components(),
std::vector<uint32_t>({61}));
}
}
TEST(TrustStoreChromeTestNoFixture, ParseCommandLineConstraintsEmpty) {
EXPECT_TRUE(TrustStoreChrome::ParseCrsConstraintsSwitch("").empty());
EXPECT_TRUE(TrustStoreChrome::ParseCrsConstraintsSwitch("invalid").empty());
EXPECT_TRUE(TrustStoreChrome::ParseCrsConstraintsSwitch(
"invalidhash:sctnotafter=123456")
.empty());
}
TEST(TrustStoreChromeTestNoFixture, ParseCommandLineConstraintsErrorHandling) {
auto constraints = TrustStoreChrome::ParseCrsConstraintsSwitch(
// Valid hash and valid constraint name with invalid value (missing `,`
// between constraints, so sctallafter value will not be parsable as an
// integer). Should result in a constraintset with every constraint
// being nullopt.
"568c8ef6b526d1394bca052ba3e4d1f4d7a8d9c88c55a1a9ab7ca0fae2dc5473:"
"sctallafter=9876543sctnotafter=1234567890+"
// Invalid hash (valid hex, but too short).
"37a9761b69457987abbc8636182d8273498719659716397401f98e019b20a9:"
"sctallafter=9876543+"
// Invalid hash (valid hex, but too long).
"37a9761b69457987abbc8636182d8273498719659716397401f98e019b20a91111:"
"sctallafter=9876543+"
// Invalid constraint mapping (missing `:` between hash and constraint).
"737a9761b69457987abbc8636182d8273498719659716397401f98e019b20a98"
"sctallafter=9876543+"
// Invalid and valid hashes with both invalid and valid constraints.
"11,a7e0c75d7f772fccf26a6ac1f7b0a86a482e2f3d326bc911c95d56ff3d4906d5,22:"
"invalidconstraint=hello,sctnotafter=789012345+"
// Missing `+` between constraint mappings.
// This will parse the next hash and minversion all as an invalid
// sctallafter value and then the maxversionexclusive will apply to the
// previous root hash.
"65ee41e8a8c27b71b6bfcf44653c8e8370ec5e106e272592c2fbcbadf8dc5763:"
"sctnotafter=123456,sctallafter=54321"
"3333333333333333333333333333333333333333333333333333333333333333:"
"minversion=1,maxversionexclusive=2.3");
EXPECT_EQ(constraints.size(), 3U);
{
constexpr uint8_t hash[] = {0x56, 0x8c, 0x8e, 0xf6, 0xb5, 0x26, 0xd1, 0x39,
0x4b, 0xca, 0x05, 0x2b, 0xa3, 0xe4, 0xd1, 0xf4,
0xd7, 0xa8, 0xd9, 0xc8, 0x8c, 0x55, 0xa1, 0xa9,
0xab, 0x7c, 0xa0, 0xfa, 0xe2, 0xdc, 0x54, 0x73};
auto it = constraints.find(base::span(hash));
ASSERT_NE(it, constraints.end());
ASSERT_EQ(it->second.size(), 1U);
const auto& constraint1 = it->second[0];
EXPECT_FALSE(constraint1.sct_not_after.has_value());
EXPECT_FALSE(constraint1.sct_all_after.has_value());
EXPECT_FALSE(constraint1.min_version.has_value());
EXPECT_FALSE(constraint1.max_version_exclusive.has_value());
EXPECT_THAT(constraint1.permitted_dns_names, testing::IsEmpty());
}
{
constexpr uint8_t hash[] = {0xa7, 0xe0, 0xc7, 0x5d, 0x7f, 0x77, 0x2f, 0xcc,
0xf2, 0x6a, 0x6a, 0xc1, 0xf7, 0xb0, 0xa8, 0x6a,
0x48, 0x2e, 0x2f, 0x3d, 0x32, 0x6b, 0xc9, 0x11,
0xc9, 0x5d, 0x56, 0xff, 0x3d, 0x49, 0x06, 0xd5};
auto it = constraints.find(base::span(hash));
ASSERT_NE(it, constraints.end());
ASSERT_EQ(it->second.size(), 1U);
const auto& constraint1 = it->second[0];
ASSERT_TRUE(constraint1.sct_not_after.has_value());
EXPECT_EQ(constraint1.sct_not_after->InMillisecondsSinceUnixEpoch() / 1000,
789012345);
EXPECT_FALSE(constraint1.sct_all_after.has_value());
EXPECT_FALSE(constraint1.min_version.has_value());
EXPECT_FALSE(constraint1.max_version_exclusive.has_value());
EXPECT_THAT(constraint1.permitted_dns_names, testing::IsEmpty());
}
{
unsigned char hash[] = {0x65, 0xee, 0x41, 0xe8, 0xa8, 0xc2, 0x7b, 0x71,
0xb6, 0xbf, 0xcf, 0x44, 0x65, 0x3c, 0x8e, 0x83,
0x70, 0xec, 0x5e, 0x10, 0x6e, 0x27, 0x25, 0x92,
0xc2, 0xfb, 0xcb, 0xad, 0xf8, 0xdc, 0x57, 0x63};
auto it = constraints.find(base::span(hash));
ASSERT_NE(it, constraints.end());
ASSERT_EQ(it->second.size(), 1U);
const auto& constraint = it->second[0];
ASSERT_TRUE(constraint.sct_not_after.has_value());
EXPECT_EQ(constraint.sct_not_after->InMillisecondsSinceUnixEpoch() / 1000,
123456);
EXPECT_FALSE(constraint.sct_all_after.has_value());
EXPECT_FALSE(constraint.min_version.has_value());
EXPECT_EQ(constraint.max_version_exclusive, base::Version({2, 3}));
EXPECT_THAT(constraint.permitted_dns_names, testing::IsEmpty());
}
}
TEST(TrustStoreChromeTestNoFixture,
ParseCommandLineConstraintsOneRootOneConstraint) {
auto constraints = TrustStoreChrome::ParseCrsConstraintsSwitch(
"65ee41e8a8c27b71b6bfcf44653c8e8370ec5e106e272592c2fbcbadf8dc5763:"
"sctnotafter=123456");
EXPECT_EQ(constraints.size(), 1U);
unsigned char hash[] = {0x65, 0xee, 0x41, 0xe8, 0xa8, 0xc2, 0x7b, 0x71,
0xb6, 0xbf, 0xcf, 0x44, 0x65, 0x3c, 0x8e, 0x83,
0x70, 0xec, 0x5e, 0x10, 0x6e, 0x27, 0x25, 0x92,
0xc2, 0xfb, 0xcb, 0xad, 0xf8, 0xdc, 0x57, 0x63};
auto it = constraints.find(base::span(hash));
ASSERT_NE(it, constraints.end());
ASSERT_EQ(it->second.size(), 1U);
const auto& constraint = it->second[0];
ASSERT_TRUE(constraint.sct_not_after.has_value());
EXPECT_EQ(constraint.sct_not_after->InMillisecondsSinceUnixEpoch() / 1000,
123456);
EXPECT_FALSE(constraint.sct_all_after.has_value());
EXPECT_FALSE(constraint.min_version.has_value());
EXPECT_FALSE(constraint.max_version_exclusive.has_value());
}
TEST(TrustStoreChromeTestNoFixture,
ParseCommandLineConstraintsMultipleRootsMultipleConstraints) {
auto constraints = TrustStoreChrome::ParseCrsConstraintsSwitch(
"784ecaa8b9dfcc826547f806f759abd6b4481582fc7e377dc3e6a0a959025126,"
"a7e0c75d7f772fccf26a6ac1f7b0a86a482e2f3d326bc911c95d56ff3d4906d5:"
"sctnotafter=123456,sctallafter=7689,"
"minversion=1.2.3.4,maxversionexclusive=10,"
"dns=foo.com,dns=bar.com+"
"a7e0c75d7f772fccf26a6ac1f7b0a86a482e2f3d326bc911c95d56ff3d4906d5,"
"568c8ef6b526d1394bca052ba3e4d1f4d7a8d9c88c55a1a9ab7ca0fae2dc5473:"
"sctallafter=9876543,sctnotafter=1234567890");
EXPECT_EQ(constraints.size(), 3U);
{
constexpr uint8_t hash1[] = {
0x78, 0x4e, 0xca, 0xa8, 0xb9, 0xdf, 0xcc, 0x82, 0x65, 0x47, 0xf8,
0x06, 0xf7, 0x59, 0xab, 0xd6, 0xb4, 0x48, 0x15, 0x82, 0xfc, 0x7e,
0x37, 0x7d, 0xc3, 0xe6, 0xa0, 0xa9, 0x59, 0x02, 0x51, 0x26};
auto it = constraints.find(base::span(hash1));
ASSERT_NE(it, constraints.end());
ASSERT_EQ(it->second.size(), 1U);
const auto& constraint1 = it->second[0];
ASSERT_TRUE(constraint1.sct_not_after.has_value());
EXPECT_EQ(constraint1.sct_not_after->InMillisecondsSinceUnixEpoch() / 1000,
123456);
ASSERT_TRUE(constraint1.sct_all_after.has_value());
EXPECT_EQ(constraint1.sct_all_after->InMillisecondsSinceUnixEpoch() / 1000,
7689);
EXPECT_EQ(constraint1.min_version, base::Version({1, 2, 3, 4}));
EXPECT_EQ(constraint1.max_version_exclusive, base::Version({10}));
EXPECT_THAT(constraint1.permitted_dns_names,
testing::ElementsAre("foo.com", "bar.com"));
}
{
constexpr uint8_t hash2[] = {
0xa7, 0xe0, 0xc7, 0x5d, 0x7f, 0x77, 0x2f, 0xcc, 0xf2, 0x6a, 0x6a,
0xc1, 0xf7, 0xb0, 0xa8, 0x6a, 0x48, 0x2e, 0x2f, 0x3d, 0x32, 0x6b,
0xc9, 0x11, 0xc9, 0x5d, 0x56, 0xff, 0x3d, 0x49, 0x06, 0xd5};
auto it = constraints.find(base::span(hash2));
ASSERT_NE(it, constraints.end());
ASSERT_EQ(it->second.size(), 2U);
const auto& constraint1 = it->second[0];
ASSERT_TRUE(constraint1.sct_not_after.has_value());
EXPECT_EQ(constraint1.sct_not_after->InMillisecondsSinceUnixEpoch() / 1000,
123456);
ASSERT_TRUE(constraint1.sct_all_after.has_value());
EXPECT_EQ(constraint1.sct_all_after->InMillisecondsSinceUnixEpoch() / 1000,
7689);
EXPECT_EQ(constraint1.min_version, base::Version({1, 2, 3, 4}));
EXPECT_EQ(constraint1.max_version_exclusive, base::Version({10}));
EXPECT_THAT(constraint1.permitted_dns_names,
testing::ElementsAre("foo.com", "bar.com"));
const auto& constraint2 = it->second[1];
ASSERT_TRUE(constraint2.sct_not_after.has_value());
EXPECT_EQ(constraint2.sct_not_after->InMillisecondsSinceUnixEpoch() / 1000,
1234567890);
ASSERT_TRUE(constraint2.sct_all_after.has_value());
EXPECT_EQ(constraint2.sct_all_after->InMillisecondsSinceUnixEpoch() / 1000,
9876543);
EXPECT_FALSE(constraint2.min_version.has_value());
EXPECT_FALSE(constraint2.max_version_exclusive.has_value());
EXPECT_THAT(constraint2.permitted_dns_names, testing::IsEmpty());
}
{
constexpr uint8_t hash3[] = {
0x56, 0x8c, 0x8e, 0xf6, 0xb5, 0x26, 0xd1, 0x39, 0x4b, 0xca, 0x05,
0x2b, 0xa3, 0xe4, 0xd1, 0xf4, 0xd7, 0xa8, 0xd9, 0xc8, 0x8c, 0x55,
0xa1, 0xa9, 0xab, 0x7c, 0xa0, 0xfa, 0xe2, 0xdc, 0x54, 0x73};
auto it = constraints.find(base::span(hash3));
ASSERT_NE(it, constraints.end());
ASSERT_EQ(it->second.size(), 1U);
const auto& constraint1 = it->second[0];
ASSERT_TRUE(constraint1.sct_not_after.has_value());
EXPECT_EQ(constraint1.sct_not_after->InMillisecondsSinceUnixEpoch() / 1000,
1234567890);
ASSERT_TRUE(constraint1.sct_all_after.has_value());
EXPECT_EQ(constraint1.sct_all_after->InMillisecondsSinceUnixEpoch() / 1000,
9876543);
EXPECT_FALSE(constraint1.min_version.has_value());
EXPECT_FALSE(constraint1.max_version_exclusive.has_value());
EXPECT_THAT(constraint1.permitted_dns_names, testing::IsEmpty());
}
}
} // namespace
} // namespace net