| // Copyright 2023 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <string.h> | |
| #include <cstddef> | |
| #include <memory> | |
| #include <optional> | |
| #include <vector> | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include <openssl/bytestring.h> | |
| #include <openssl/pki/verify.h> | |
| #include <openssl/pki/verify_error.h> | |
| #include <openssl/pool.h> | |
| #include <openssl/sha2.h> | |
| #include "encode_values.h" | |
| #include "merkle_tree.h" | |
| #include "parse_certificate.h" | |
| #include "parsed_certificate.h" | |
| #include "string_util.h" | |
| #include "test_helpers.h" | |
| #include "trust_store.h" | |
| #include "trust_store_in_memory.h" | |
| BSSL_NAMESPACE_BEGIN | |
| static std::unique_ptr<VerifyTrustStore> MozillaRootStore() { | |
| std::string diagnostic; | |
| return VerifyTrustStore::FromDER( | |
| bssl::ReadTestFileToString( | |
| "testdata/verify_unittest/mozilla_roots.der"), | |
| &diagnostic); | |
| } | |
| using ::testing::UnorderedElementsAre; | |
| static std::string GetTestdata(std::string_view filename) { | |
| return bssl::ReadTestFileToString("testdata/verify_unittest/" + | |
| std::string(filename)); | |
| } | |
| static ::testing::AssertionResult ReadTestCertPem(const std::string &file_name, | |
| std::string *out_cert) { | |
| PemBlockMapping mappings[] = { | |
| {"CERTIFICATE", out_cert}, | |
| }; | |
| return ReadTestDataFromPemFile("testdata/verify_unittest/" + file_name, | |
| mappings); | |
| } | |
| TEST(VerifyTest, GoogleChain) { | |
| const std::string leaf = GetTestdata("google-leaf.der"); | |
| const std::string intermediate1 = GetTestdata("google-intermediate1.der"); | |
| const std::string intermediate2 = GetTestdata("google-intermediate2.der"); | |
| CertificateVerifyOptions opts; | |
| opts.leaf_cert = leaf; | |
| opts.intermediates = {intermediate1, intermediate2}; | |
| opts.time = 1499727444; | |
| std::unique_ptr<VerifyTrustStore> roots = MozillaRootStore(); | |
| opts.trust_store = roots.get(); | |
| VerifyError error; | |
| ASSERT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| opts.intermediates = {}; | |
| EXPECT_FALSE(CertificateVerify(opts, &error)); | |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) | |
| << error.DiagnosticString(); | |
| } | |
| TEST(VerifyTest, ExtraIntermediates) { | |
| const std::string leaf = GetTestdata("google-leaf.der"); | |
| const std::string intermediate1 = GetTestdata("google-intermediate1.der"); | |
| const std::string intermediate2 = GetTestdata("google-intermediate2.der"); | |
| CertificateVerifyOptions opts; | |
| opts.leaf_cert = leaf; | |
| std::string diagnostic; | |
| const auto cert_pool_status = CertPool::FromCerts( | |
| { | |
| intermediate1, | |
| intermediate2, | |
| }, | |
| &diagnostic); | |
| ASSERT_TRUE(cert_pool_status) << diagnostic; | |
| opts.extra_intermediates = cert_pool_status.get(); | |
| opts.time = 1499727444; | |
| std::unique_ptr<VerifyTrustStore> roots = MozillaRootStore(); | |
| opts.trust_store = roots.get(); | |
| VerifyError error; | |
| ASSERT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| } | |
| TEST(VerifyTest, AllPaths) { | |
| const std::string leaf = GetTestdata("lencr-leaf.der"); | |
| const std::string intermediate1 = GetTestdata("lencr-intermediate-r3.der"); | |
| const std::string intermediate2 = | |
| GetTestdata("lencr-root-x1-cross-signed.der"); | |
| const std::string root1 = GetTestdata("lencr-root-x1.der"); | |
| const std::string root2 = GetTestdata("lencr-root-dst-x3.der"); | |
| std::vector<std::string> expected_path1 = {leaf, intermediate1, root1}; | |
| std::vector<std::string> expected_path2 = {leaf, intermediate1, intermediate2, | |
| root2}; | |
| CertificateVerifyOptions opts; | |
| opts.leaf_cert = leaf; | |
| opts.intermediates = {intermediate1, intermediate2}; | |
| opts.time = 1699404611; | |
| std::unique_ptr<VerifyTrustStore> roots = MozillaRootStore(); | |
| opts.trust_store = roots.get(); | |
| auto paths = CertificateVerifyAllPaths(opts); | |
| ASSERT_TRUE(paths); | |
| EXPECT_EQ(2U, paths.value().size()); | |
| EXPECT_THAT(paths.value(), | |
| UnorderedElementsAre(expected_path1, expected_path2)); | |
| } | |
| TEST(VerifyTest, DepthLimit) { | |
| const std::string leaf = GetTestdata("google-leaf.der"); | |
| const std::string intermediate1 = GetTestdata("google-intermediate1.der"); | |
| const std::string intermediate2 = GetTestdata("google-intermediate2.der"); | |
| CertificateVerifyOptions opts; | |
| opts.leaf_cert = leaf; | |
| opts.intermediates = {intermediate1, intermediate2}; | |
| opts.time = 1499727444; | |
| // Set the `max_path_building_depth` explicitly to test the non-default case. | |
| // Depth of 5 is enough to successfully find a path. | |
| opts.max_path_building_depth = 5; | |
| std::unique_ptr<VerifyTrustStore> roots = MozillaRootStore(); | |
| opts.trust_store = roots.get(); | |
| VerifyError error; | |
| ASSERT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| // Depth of 2 is not enough to find a path. | |
| opts.max_path_building_depth = 2; | |
| EXPECT_FALSE(CertificateVerify(opts, &error)); | |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_DEPTH_LIMIT_REACHED) | |
| << error.DiagnosticString(); | |
| } | |
| TEST(VerifyTest, MldsaAlgorithms) { | |
| std::string root, intermediate, leaf; | |
| ASSERT_TRUE(ReadTestCertPem("mldsa-root.pem", &root)); | |
| ASSERT_TRUE(ReadTestCertPem("mldsa-intermediate.pem", &intermediate)); | |
| ASSERT_TRUE(ReadTestCertPem("mldsa-leaf.pem", &leaf)); | |
| std::string diagnostic; | |
| std::unique_ptr<VerifyTrustStore> trust_store = | |
| VerifyTrustStore::FromDER(root, &diagnostic); | |
| ASSERT_TRUE(trust_store) << diagnostic; | |
| CertificateVerifyOptions opts; | |
| opts.leaf_cert = leaf; | |
| opts.intermediates = {intermediate}; | |
| opts.trust_store = trust_store.get(); | |
| // April 6, 2026 (00:00Z) is the time used to generate the test certs; use | |
| // that time for verification. | |
| opts.time = 1775458800; | |
| VerifyError error; | |
| ASSERT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| } | |
| namespace { | |
| UniquePtr<CRYPTO_BUFFER> ExportPublicKeyFromSeed(Span<const uint8_t> seed) { | |
| UniquePtr<EVP_PKEY> pkey(EVP_PKEY_from_private_seed( | |
| EVP_pkey_ml_dsa_44(), seed.data(), seed.size())); | |
| if (!pkey) { | |
| return nullptr; | |
| } | |
| ScopedCBB cbb; | |
| if (!CBB_init(cbb.get(), 0) || | |
| !EVP_marshal_public_key(cbb.get(), pkey.get())) { | |
| return nullptr; | |
| } | |
| return UniquePtr<CRYPTO_BUFFER>( | |
| CRYPTO_BUFFER_new(CBB_data(cbb.get()), CBB_len(cbb.get()), nullptr)); | |
| } | |
| } // namespace | |
| // TODO(crbug.com/452986180): These MTC tests only test the landmark relative | |
| // path, the standalone MTC tests all ended up in path_builder_unittest. Should | |
| // probably have at least one or two standalone tests that go through this | |
| // interface. | |
| class VerifyMTCTest : public ::testing::Test { | |
| public: | |
| VerifyMTCTest() = default; | |
| void SetUp() override { | |
| static constexpr uint8_t kCaPrivateKeySeed[] = { | |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, | |
| 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, | |
| 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}; | |
| ca_spki_ = ExportPublicKeyFromSeed(kCaPrivateKeySeed); | |
| ASSERT_TRUE(ca_spki_); | |
| ASSERT_TRUE(ReadTestCertPem("mtc-leaf.pem", &generic_cert_)); | |
| ASSERT_TRUE(ReadTestCertPem("mtc-leaf-bitflip.pem", &bitflip_cert_)); | |
| ASSERT_TRUE(ReadTestCertPem("mtc-leaf-unused-bit.pem", &unused_bit_cert_)); | |
| ASSERT_TRUE(ReadTestCertPem("mtc-leaf-b.pem", &leaf_b_)); | |
| ASSERT_TRUE(ReadTestCertPem("mtc-leaf-c.pem", &leaf_c_)); | |
| ASSERT_TRUE( | |
| CreateTrustedSubtree("+g71+77yhXVIT2NWwsHWnTz8TFyJ6yT9TLDGOW+Vi6k=", 8, | |
| 13, &generic_cert_subtree_)); | |
| ASSERT_TRUE( | |
| CreateTrustedSubtree("sj2WUzCsih+fREf46unoF/8kyGr6tEpL/hlzn9D9994=", 8, | |
| 16, &leaf_b_subtree_)); | |
| ASSERT_TRUE( | |
| CreateTrustedSubtree("bGiRKKU8PI5RTxSYgzL2nGvnUE4d/l09y6JaLCbqgy8=", 16, | |
| 24, &leaf_c_subtree_)); | |
| } | |
| bool CreateTrustedSubtree(const std::string &hash_b64, uint64_t start, | |
| uint64_t end, TrustedSubtree *out_subtree) const { | |
| std::string subtree_hash; | |
| if (!string_util::Base64Decode(hash_b64, &subtree_hash)) { | |
| return false; | |
| } | |
| if (subtree_hash.size() != out_subtree->hash.size()) { | |
| return false; | |
| } | |
| memcpy(out_subtree->hash.data(), subtree_hash.data(), subtree_hash.size()); | |
| out_subtree->range = Subtree{start, end}; | |
| return true; | |
| } | |
| std::shared_ptr<const ParsedCertificate> CertFromString( | |
| const std::string &cert) const { | |
| UniquePtr<CRYPTO_BUFFER> cert_buf(CRYPTO_BUFFER_new( | |
| reinterpret_cast<const uint8_t *>(cert.data()), cert.size(), nullptr)); | |
| return ParsedCertificate::Create(std::move(cert_buf), | |
| ParseCertificateOptions{}, nullptr); | |
| } | |
| bool PrepareOptsForVerify(const std::string &cert, | |
| const VerifyTrustStore *trust_store, | |
| CertificateVerifyOptions *out_opts) const { | |
| out_opts->leaf_cert = cert; | |
| std::shared_ptr<const ParsedCertificate> parsed_cert = CertFromString(cert); | |
| if (!parsed_cert) { | |
| return false; | |
| } | |
| // out_opts->time is a std::optional<int64_t>. If we write directly to | |
| // *out_opts->time, the std::optional will still be a std::nullopt. | |
| int64_t time; | |
| if (!der::GeneralizedTimeToPosixTime(parsed_cert->tbs().validity_not_before, | |
| &time)) { | |
| return false; | |
| } | |
| out_opts->time = time; | |
| out_opts->trust_store = trust_store; | |
| return true; | |
| } | |
| std::unique_ptr<VerifyTrustStore> EmptyTrustStore() const { | |
| return VerifyTrustStore::FromDER("", nullptr); | |
| } | |
| protected: | |
| UniquePtr<CRYPTO_BUFFER> ca_spki_; | |
| std::string generic_cert_; | |
| std::string bitflip_cert_; | |
| std::string unused_bit_cert_; | |
| std::string leaf_b_; | |
| std::string leaf_c_; | |
| // the trusted subtree for [8, 13) that is used in `generic_cert_`, | |
| // `bitflip_cert_`. | |
| TrustedSubtree generic_cert_subtree_; | |
| // Subtree for `leaf_b_` which overlaps with `generic_cert_subtree_`. | |
| TrustedSubtree leaf_b_subtree_; | |
| // Subtree for `leaf_c_` which does not overlap with any other subtrees. | |
| TrustedSubtree leaf_c_subtree_; | |
| // Relative OID encoding of 32473.1, the CA ID used for the MTC Anchor that | |
| // issued the test MTCs in this test fixture. | |
| static constexpr uint8_t kCaId[] = {0x81, 0xfd, 0x59, 0x01}; | |
| static constexpr uint8_t kCaBitflipId[] = {0x81, 0xfd, 0x59, 0x00}; | |
| // The test certs for this test all use log number 1. | |
| static constexpr uint16_t kLogNumber = 1; | |
| }; | |
| TEST_F(VerifyMTCTest, SignaturelessMTC) { | |
| // Configure the trust store to trust the MTC anchor with the landmark subtree | |
| // for `generic_cert_`. | |
| std::unique_ptr<VerifyTrustStore> trust_store = EmptyTrustStore(); | |
| std::vector<LogTrustedSubtrees> subtrees = { | |
| {kLogNumber, {generic_cert_subtree_}}, | |
| }; | |
| auto mtc_anchor = std::make_shared<MTCAnchor>( | |
| MakeSpan(kCaId), SignatureAlgorithm::kMldsa44, UpRef(ca_spki_), subtrees); | |
| ASSERT_TRUE(trust_store->trust_store->AddMTCTrustAnchor(mtc_anchor)); | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(generic_cert_, trust_store.get(), &opts)); | |
| EXPECT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| } | |
| TEST_F(VerifyMTCTest, ExplicitlyTrustedLeaf) { | |
| // Configure the trust store to directly trust the `generic_cert_` leaf. | |
| std::unique_ptr<VerifyTrustStore> trust_store = EmptyTrustStore(); | |
| trust_store->trust_store->AddCertificate(CertFromString(generic_cert_), | |
| CertificateTrust::ForTrustedLeaf()); | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(generic_cert_, trust_store.get(), &opts)); | |
| EXPECT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| } | |
| TEST_F(VerifyMTCTest, ExplicitlyDistrustedLeaf) { | |
| // Configure the trust store to trust the MTC anchor for `generic_cert_`, but | |
| // also to explicitly distrust that leaf. | |
| std::unique_ptr<VerifyTrustStore> trust_store = EmptyTrustStore(); | |
| trust_store->trust_store->AddCertificate(CertFromString(generic_cert_), | |
| CertificateTrust::ForDistrusted()); | |
| std::vector<LogTrustedSubtrees> subtrees = { | |
| {kLogNumber, {generic_cert_subtree_}}, | |
| }; | |
| auto mtc_anchor = std::make_shared<MTCAnchor>( | |
| MakeSpan(kCaId), SignatureAlgorithm::kMldsa44, UpRef(ca_spki_), subtrees); | |
| ASSERT_TRUE(trust_store->trust_store->AddMTCTrustAnchor(mtc_anchor)); | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(generic_cert_, trust_store.get(), &opts)); | |
| EXPECT_FALSE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| EXPECT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND); | |
| } | |
| TEST_F(VerifyMTCTest, WrongProof) { | |
| std::unique_ptr<VerifyTrustStore> trust_store = EmptyTrustStore(); | |
| std::vector<LogTrustedSubtrees> subtrees = { | |
| {kLogNumber, {generic_cert_subtree_}}, | |
| }; | |
| auto mtc_anchor = std::make_shared<MTCAnchor>( | |
| MakeSpan(kCaId), SignatureAlgorithm::kMldsa44, UpRef(ca_spki_), subtrees); | |
| ASSERT_TRUE(trust_store->trust_store->AddMTCTrustAnchor(mtc_anchor)); | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(bitflip_cert_, trust_store.get(), &opts)); | |
| EXPECT_FALSE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| EXPECT_EQ(error.Code(), | |
| VerifyError::StatusCode::CERTIFICATE_INVALID_SIGNATURE); | |
| } | |
| TEST_F(VerifyMTCTest, UnusedBit) { | |
| std::unique_ptr<VerifyTrustStore> trust_store = EmptyTrustStore(); | |
| std::vector<LogTrustedSubtrees> subtrees = { | |
| {kLogNumber, {generic_cert_subtree_}}, | |
| }; | |
| auto mtc_anchor = std::make_shared<MTCAnchor>( | |
| MakeSpan(kCaId), SignatureAlgorithm::kMldsa44, UpRef(ca_spki_), subtrees); | |
| ASSERT_TRUE(trust_store->trust_store->AddMTCTrustAnchor(mtc_anchor)); | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(unused_bit_cert_, trust_store.get(), &opts)); | |
| EXPECT_FALSE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| EXPECT_EQ(error.Code(), | |
| VerifyError::StatusCode::CERTIFICATE_INVALID_SIGNATURE); | |
| } | |
| TEST_F(VerifyMTCTest, WrongCaId) { | |
| // Trust the correct subtree for `generic_cert_` but with the wrong CA ID. | |
| // Verifying the cert should fail because even though the proof evaluates to a | |
| // valid hash, the hash is for the wrong issuer. | |
| std::unique_ptr<VerifyTrustStore> trust_store = EmptyTrustStore(); | |
| std::vector<LogTrustedSubtrees> subtrees = { | |
| {kLogNumber, {generic_cert_subtree_}}, | |
| }; | |
| auto mtc_anchor = std::make_shared<MTCAnchor>(MakeSpan(kCaBitflipId), | |
| SignatureAlgorithm::kMldsa44, | |
| UpRef(ca_spki_), subtrees); | |
| ASSERT_TRUE(trust_store->trust_store->AddMTCTrustAnchor(mtc_anchor)); | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(generic_cert_, trust_store.get(), &opts)); | |
| EXPECT_FALSE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| EXPECT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND); | |
| } | |
| TEST_F(VerifyMTCTest, ExpiredMTC) { | |
| // Configure the trust store to trust the MTC anchor with the landmark subtree | |
| // for `generic_cert_`. | |
| std::unique_ptr<VerifyTrustStore> trust_store = EmptyTrustStore(); | |
| std::vector<LogTrustedSubtrees> subtrees = { | |
| {kLogNumber, {generic_cert_subtree_}}, | |
| }; | |
| auto mtc_anchor = std::make_shared<MTCAnchor>( | |
| MakeSpan(kCaId), SignatureAlgorithm::kMldsa44, UpRef(ca_spki_), subtrees); | |
| ASSERT_TRUE(trust_store->trust_store->AddMTCTrustAnchor(mtc_anchor)); | |
| CertificateVerifyOptions opts; | |
| opts.trust_store = trust_store.get(); | |
| opts.leaf_cert = generic_cert_; | |
| std::shared_ptr<const ParsedCertificate> parsed_cert = | |
| CertFromString(generic_cert_); | |
| ASSERT_TRUE(parsed_cert); | |
| int64_t time; | |
| ASSERT_TRUE(der::GeneralizedTimeToPosixTime( | |
| parsed_cert->tbs().validity_not_after, &time)); | |
| // set verify time to 1 second after the cert's validity period: | |
| opts.time = time + 1; | |
| VerifyError error; | |
| EXPECT_FALSE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| EXPECT_EQ(error.Code(), VerifyError::StatusCode::CERTIFICATE_EXPIRED); | |
| } | |
| TEST_F(VerifyMTCTest, TrustStoreConfiguration) { | |
| // Test that an MTC isn't trusted if there's no MTCAnchor set on the trust | |
| // store. | |
| std::unique_ptr<VerifyTrustStore> trust_store = EmptyTrustStore(); | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(generic_cert_, trust_store.get(), &opts)); | |
| EXPECT_FALSE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| EXPECT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND); | |
| } | |
| TEST_F(VerifyMTCTest, BadMTCAnchorHash) { | |
| // Test that an MTC isn't trusted if the MTCAnchor's TrustedSubtree has the | |
| // wrong hash. Configure the trust store to trust the MTC anchor with the | |
| // landmark subtree for `generic_cert_`. | |
| std::unique_ptr<VerifyTrustStore> trust_store = EmptyTrustStore(); | |
| std::vector<LogTrustedSubtrees> subtrees = { | |
| {kLogNumber, {generic_cert_subtree_}}, | |
| }; | |
| subtrees[0].trusted_subtrees[0].hash[0] ^= 1; | |
| auto mtc_anchor = std::make_shared<MTCAnchor>( | |
| MakeSpan(kCaId), SignatureAlgorithm::kMldsa44, UpRef(ca_spki_), subtrees); | |
| ASSERT_TRUE(trust_store->trust_store->AddMTCTrustAnchor(mtc_anchor)); | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(generic_cert_, trust_store.get(), &opts)); | |
| EXPECT_FALSE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| EXPECT_EQ(error.Code(), | |
| VerifyError::StatusCode::CERTIFICATE_INVALID_SIGNATURE); | |
| } | |
| TEST_F(VerifyMTCTest, SubtreeRangesMatch) { | |
| // generic_cert_ and leaf_b_ have proofs to subtree ranges with the same start | |
| // but different ends. Check that CertificateVerify only succeeds if the trust | |
| // store has the right MTCAnchor. | |
| std::vector<LogTrustedSubtrees> trusted_subtrees_a = { | |
| {kLogNumber, {generic_cert_subtree_}}, | |
| }; | |
| std::vector<LogTrustedSubtrees> trusted_subtrees_b = { | |
| {kLogNumber, {leaf_b_subtree_}}, | |
| }; | |
| auto mtc_anchor_a = | |
| std::make_shared<MTCAnchor>(MakeSpan(kCaId), SignatureAlgorithm::kMldsa44, | |
| UpRef(ca_spki_), trusted_subtrees_a); | |
| auto mtc_anchor_b = | |
| std::make_shared<MTCAnchor>(MakeSpan(kCaId), SignatureAlgorithm::kMldsa44, | |
| UpRef(ca_spki_), trusted_subtrees_b); | |
| std::unique_ptr<VerifyTrustStore> trust_store_a = EmptyTrustStore(); | |
| ASSERT_TRUE(trust_store_a->trust_store->AddMTCTrustAnchor(mtc_anchor_a)); | |
| { | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE( | |
| PrepareOptsForVerify(generic_cert_, trust_store_a.get(), &opts)); | |
| EXPECT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| } | |
| { | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(leaf_b_, trust_store_a.get(), &opts)); | |
| EXPECT_FALSE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| EXPECT_EQ(error.Code(), | |
| VerifyError::StatusCode::CERTIFICATE_INVALID_SIGNATURE); | |
| } | |
| std::unique_ptr<VerifyTrustStore> trust_store_b = EmptyTrustStore(); | |
| ASSERT_TRUE(trust_store_b->trust_store->AddMTCTrustAnchor(mtc_anchor_b)); | |
| { | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(leaf_b_, trust_store_b.get(), &opts)); | |
| EXPECT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| } | |
| { | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE( | |
| PrepareOptsForVerify(generic_cert_, trust_store_b.get(), &opts)); | |
| EXPECT_FALSE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| EXPECT_EQ(error.Code(), | |
| VerifyError::StatusCode::CERTIFICATE_INVALID_SIGNATURE); | |
| } | |
| } | |
| TEST_F(VerifyMTCTest, MultipleSubtrees) { | |
| std::vector<LogTrustedSubtrees> subtrees = { | |
| {kLogNumber, | |
| {generic_cert_subtree_, leaf_b_subtree_, leaf_c_subtree_}}, | |
| }; | |
| auto mtc_anchor = std::make_shared<MTCAnchor>( | |
| MakeSpan(kCaId), SignatureAlgorithm::kMldsa44, UpRef(ca_spki_), subtrees); | |
| std::unique_ptr<VerifyTrustStore> trust_store = EmptyTrustStore(); | |
| ASSERT_TRUE(trust_store->trust_store->AddMTCTrustAnchor(mtc_anchor)); | |
| // Check that generic_cert_, leaf_b_, and leaf_c_ are all trusted. | |
| std::vector<std::string> leafs = {generic_cert_, leaf_b_, leaf_c_}; | |
| for (size_t i = 0; i < leafs.size(); i++) { | |
| SCOPED_TRACE(testing::Message() << "Leaf " << i); | |
| CertificateVerifyOptions opts; | |
| VerifyError error; | |
| ASSERT_TRUE(PrepareOptsForVerify(leafs[i], trust_store.get(), &opts)); | |
| EXPECT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString(); | |
| } | |
| } | |
| BSSL_NAMESPACE_END |