blob: b959c7d4c1ee27917a5db1ff9eeab03db05130c9 [file] [edit]
// 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::map<uint16_t, std::vector<TrustedSubtree>> subtrees;
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::map<uint16_t, std::vector<TrustedSubtree>> subtrees;
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::map<uint16_t, std::vector<TrustedSubtree>> subtrees;
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::map<uint16_t, std::vector<TrustedSubtree>> subtrees;
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::map<uint16_t, std::vector<TrustedSubtree>> subtrees;
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::map<uint16_t, std::vector<TrustedSubtree>> subtrees;
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::map<uint16_t, std::vector<TrustedSubtree>> subtrees;
subtrees[kLogNumber] = {generic_cert_subtree_};
subtrees[kLogNumber][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::map<uint16_t, std::vector<TrustedSubtree>> trusted_subtrees_a;
trusted_subtrees_a[kLogNumber] = {generic_cert_subtree_};
std::map<uint16_t, std::vector<TrustedSubtree>> trusted_subtrees_b;
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();
}
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();
}
}
TEST_F(VerifyMTCTest, MultipleSubtrees) {
std::map<uint16_t, std::vector<TrustedSubtree>> subtrees;
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