| // 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 |