// Copyright 2019 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/cert_verify_proc_builtin.h"

#include <algorithm>
#include <optional>
#include <string_view>

#include "base/containers/span.h"
#include "base/containers/to_vector.h"
#include "base/containers/transparent_hash.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/values_test_util.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "components/network_time/time_tracker/time_tracker.h"
#include "crypto/keypair.h"
#include "net/base/features.h"
#include "net/base/hash_value.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/cert_verify_proc.h"
#include "net/cert/crl_set.h"
#include "net/cert/do_nothing_ct_verifier.h"
#include "net/cert/ev_root_ca_metadata.h"
#include "net/cert/internal/system_trust_store.h"
#include "net/cert/sct_status_flags.h"
#include "net/cert/time_conversions.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/cert_net/cert_net_fetcher_url_request.h"
#include "net/http/transport_security_state.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_with_source.h"
#include "net/log/test_net_log.h"
#include "net/net_buildflags.h"
#include "net/test/cert_builder.h"
#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "net/test/gtest_util.h"
#include "net/test/revocation_builder.h"
#include "net/test/two_qwac_cert_binding_builder.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/pki/parse_certificate.h"
#include "third_party/boringssl/src/pki/signature_algorithm.h"
#include "third_party/boringssl/src/pki/trust_store.h"
#include "third_party/boringssl/src/pki/trust_store_collection.h"
#include "third_party/boringssl/src/pki/trust_store_in_memory.h"

#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
#include "base/version_info/version_info.h"  // nogncheck
#endif

using net::test::IsError;
using net::test::IsOk;

using testing::_;

namespace net {

namespace {

std::unique_ptr<test_server::HttpResponse> HangRequestAndCallback(
    base::OnceClosure callback,
    const test_server::HttpRequest& request) {
  std::move(callback).Run();
  return std::make_unique<test_server::HungResponse>();
}

void FailTest(const std::string& message) {
  ADD_FAILURE() << message;
}

std::unique_ptr<test_server::HttpResponse> FailRequestAndFailTest(
    const std::string& message,
    scoped_refptr<base::TaskRunner> main_task_runner,
    const test_server::HttpRequest& request) {
  main_task_runner->PostTask(FROM_HERE, base::BindOnce(FailTest, message));
  auto response = std::make_unique<test_server::BasicHttpResponse>();
  response->set_code(HTTP_NOT_ACCEPTABLE);
  return response;
}

std::unique_ptr<test_server::HttpResponse> ServeResponse(
    HttpStatusCode status_code,
    const std::string& content_type,
    const std::string& content,
    const test_server::HttpRequest& request) {
  auto http_response = std::make_unique<test_server::BasicHttpResponse>();

  http_response->set_code(status_code);
  http_response->set_content_type(content_type);
  http_response->set_content(content);
  return http_response;
}

std::string MakeRandomHexString(size_t num_bytes) {
  std::vector<uint8_t> rand_bytes(num_bytes);
  base::RandBytes(rand_bytes);
  return base::HexEncode(rand_bytes);
}

static std::string MakeRandomPath(std::string_view suffix) {
  return "/" + MakeRandomHexString(12) + std::string(suffix);
}

#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
// Parses a single PEM certificate from `*pem_value`, or adds a gtest failure
// and returns empty vector on error.
//
// Since the input from the test often comes from a base::Dict and thus may be
// null if the expected element isn't found, this takes a pointer as a
// convenience and will add a failure and an return empty vector if the input
// is null, so that each test expectation doesn't need to null-check the input
// before calling.
std::vector<uint8_t> ParsePemCertificate(const std::string* pem_value) {
  if (!pem_value) {
    ADD_FAILURE() << "pem_value is null";
    return {};
  }
  CertificateList certs = X509Certificate::CreateCertificateListFromBytes(
      base::as_byte_span(*pem_value),
      X509Certificate::Format::FORMAT_PEM_CERT_SEQUENCE);
  if (certs.empty()) {
    ADD_FAILURE() << "error decoding pem";
    return {};
  }
  if (certs.size() > 1) {
    ADD_FAILURE() << "multiple certs in pem";
    return {};
  }
  return base::ToVector(certs[0]->cert_span());
}

std::vector<std::string> ParseNetLogCertificatesList(
    const base::ListValue& list) {
  std::vector<std::string> result;
  for (const auto& pem_value : list) {
    if (!pem_value.is_string()) {
      result.push_back("Value is not a string");
      continue;
    }
    CertificateList certs = X509Certificate::CreateCertificateListFromBytes(
        base::as_byte_span(pem_value.GetString()),
        X509Certificate::Format::FORMAT_PEM_CERT_SEQUENCE);
    if (certs.empty()) {
      result.push_back("error decoding pem");
      continue;
    }
    if (certs.size() > 1) {
      result.push_back("multiple certs in pem");
      continue;
    }
    result.emplace_back(base::as_string_view(certs[0]->cert_span()));
  }
  return result;
}

std::vector<std::string> ParseNetLogCertificatesDict(
    const base::DictValue& dict) {
  auto* cert_list = dict.FindList("certificates");
  if (!cert_list) {
    ADD_FAILURE() << "no cerificates key in dict";
    return {};
  }
  return ParseNetLogCertificatesList(*cert_list);
}
#endif  // BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)

int VerifyOnWorkerThread(const scoped_refptr<CertVerifyProc>& verify_proc,
                         scoped_refptr<X509Certificate> cert,
                         const std::string& hostname,
                         const std::string& ocsp_response,
                         const std::string& sct_list,
                         int flags,
                         CertVerifyResult* verify_result,
                         NetLogSource* out_source) {
  base::ScopedAllowBaseSyncPrimitivesForTesting scoped_allow_blocking;
  NetLogWithSource net_log(NetLogWithSource::Make(
      net::NetLog::Get(), net::NetLogSourceType::CERT_VERIFIER_TASK));
  int error = verify_proc->Verify(cert.get(), hostname, ocsp_response, sct_list,
                                  flags, verify_result, net_log);
  *out_source = net_log.source();
  return error;
}

class MockSystemTrustStore : public SystemTrustStore {
 public:
  bssl::TrustStore* GetTrustStore() override { return &trust_store_; }

  bool IsKnownRoot(const bssl::ParsedCertificate* trust_anchor) const override {
    return mock_is_known_root_;
  }

  bool IsKnownMtcAnchor(const bssl::MTCAnchor* anchor) const override {
    return mock_is_known_mtc_anchor_;
  }

  void AddTrustStore(bssl::TrustStore* store) {
    trust_store_.AddTrustStore(store);
  }

  void SetMockIsKnownRoot(bool is_known_root) {
    mock_is_known_root_ = is_known_root;
  }

  void SetMockIsKnownMtcAnchor(bool is_known_root) {
    mock_is_known_mtc_anchor_ = is_known_root;
  }

#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
  net::PlatformTrustStore* GetPlatformTrustStore() override { return nullptr; }

  void SetMockIsLocallyTrustedRoot(bool is_locally_trusted_root) {
    mock_is_locally_trusted_root_ = is_locally_trusted_root;
  }

  bool IsLocallyTrustedRoot(
      const bssl::ParsedCertificate* trust_anchor) override {
    return mock_is_locally_trusted_root_;
  }

  void SetMockCRSVersion(int64_t crs_version) {
    mock_crs_version_ = crs_version;
  }

  int64_t chrome_root_store_version() const override {
    return mock_crs_version_;
  }

  void SetMockSignerSetTimestamp(std::optional<base::Time> timestamp) {
    mock_signer_set_timestamp_ = timestamp;
  }

  std::optional<base::Time> signer_set_timestamp() const override {
    return mock_signer_set_timestamp_;
  }

  void SetMockMtcMetadataUpdateTime(std::optional<base::Time> update_time) {
    mock_mtc_metadata_update_time_ = update_time;
  }

  std::optional<base::Time> mtc_metadata_update_time() const override {
    return mock_mtc_metadata_update_time_;
  }

  base::span<const ChromeRootCertConstraints> GetChromeRootConstraints(
      const bssl::CertPathBuilderResultPath* path) const override {
    return mock_chrome_root_constraints_;
  }

  void SetMockMTCAnchorData(TrustStoreChrome::MtcAnchorExtraData data) {
    mock_mtc_anchor_extra_data_ = std::move(data);
  }

  const TrustStoreChrome::MtcAnchorExtraData* GetMTCAnchorData(
      base::span<const uint8_t> log_id) const override {
    return mock_mtc_anchor_extra_data_.has_value()
               ? &mock_mtc_anchor_extra_data_.value()
               : nullptr;
  }

  void SetMockMtcMirrors(
      const std::vector<MtcLogBuilder::Cosigner*>& mock_mtc_mirrors) {
    mock_mtc_mirrors_.clear();
    for (const auto* cosigner : mock_mtc_mirrors) {
      mock_mtc_mirrors_[cosigner->id] =
          bssl::VerifyCertificateChainDelegate::MTCCosigner{
              cosigner->signature_algorithm,
              x509_util::CreateCryptoBuffer(
                  cosigner->key.ToSubjectPublicKeyInfo())};
    }
  }

  std::optional<bssl::VerifyCertificateChainDelegate::MTCCosigner>
  GetMtcMirrorKey(base::span<const uint8_t> cosigner_id) const override {
    auto it = mock_mtc_mirrors_.find(cosigner_id);
    if (it == mock_mtc_mirrors_.end()) {
      return std::nullopt;
    }
    return bssl::VerifyCertificateChainDelegate::MTCCosigner{
        it->second.signature_algorithm, bssl::UpRef(it->second.key)};
  }

  void SetMockIsMtcCosignerPolicySatisfied(bool value) {
    mock_is_mtc_cosigner_policy_satisfied_ = value;
  }

  std::optional<std::vector<std::vector<uint8_t>>>
  TakeLastValidAdditionalCosigners() {
    auto result = std::move(got_valid_additional_cosigners_);
    got_valid_additional_cosigners_ = std::nullopt;
    return result;
  }

  bool IsMtcCosignerPolicySatisfied(
      const bssl::ParsedCertificate& target_cert,
      base::Time current_time,
      const bssl::MTCAnchor* mtc_anchor,
      base::span<const std::vector<uint8_t>> valid_additional_cosigners,
      const NetLogWithSource& net_log) const override {
    got_valid_additional_cosigners_ =
        base::ToVector(valid_additional_cosigners);
    return mock_is_mtc_cosigner_policy_satisfied_;
  }

  void SetMockCrsRootId(std::optional<int32_t> crs_root_id) {
    mock_crs_root_id_ = crs_root_id;
  }

  std::optional<int32_t> GetCrsRootIdForCert(
      const bssl::CertPathBuilderResultPath* path) const override {
    return mock_crs_root_id_;
  }

  bssl::TrustStore* eutl_trust_store() override { return &eutl_trust_store_; }

  void SetMockChromeRootConstraints(
      base::span<const StaticChromeRootCertConstraints>
          chrome_root_constraints) {
    mock_chrome_root_constraints_.clear();
    for (const auto& constraint : chrome_root_constraints) {
      mock_chrome_root_constraints_.emplace_back(constraint);
    }
  }

  void AddMockEutlRoot(CRYPTO_BUFFER* der_cert) {
    auto parsed_cert =
        bssl::ParsedCertificate::Create(bssl::UpRef(der_cert), {}, nullptr);
    ASSERT_TRUE(parsed_cert);
    eutl_trust_store_.AddTrustAnchor(std::move(parsed_cert));
  }
#endif

 private:
  bssl::TrustStoreCollection trust_store_;
  bool mock_is_known_root_ = false;
  bool mock_is_known_mtc_anchor_ = false;
#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
  int64_t mock_crs_version_ = 0;
  std::optional<base::Time> mock_signer_set_timestamp_;
  std::optional<base::Time> mock_mtc_metadata_update_time_;
  std::optional<TrustStoreChrome::MtcAnchorExtraData>
      mock_mtc_anchor_extra_data_;
  bool mock_is_locally_trusted_root_ = false;
  absl::flat_hash_map<std::vector<uint8_t>,
                      bssl::VerifyCertificateChainDelegate::MTCCosigner,
                      base::TransparentHashAs<base::span<const uint8_t>>,
                      base::TransparentEqualAs<base::span<const uint8_t>>>
      mock_mtc_mirrors_;
  mutable std::optional<std::vector<std::vector<uint8_t>>>
      got_valid_additional_cosigners_;
  bool mock_is_mtc_cosigner_policy_satisfied_ = false;
  std::optional<int32_t> mock_crs_root_id_;
  std::vector<ChromeRootCertConstraints> mock_chrome_root_constraints_;
  bssl::TrustStoreInMemory eutl_trust_store_;
#endif
};

class BlockingTrustStore : public bssl::TrustStore {
 public:
  bssl::CertificateTrust GetTrust(
      const bssl::ParsedCertificate* cert) override {
    return backing_trust_store_.GetTrust(cert);
  }

  std::shared_ptr<const bssl::MTCAnchor> GetTrustedMTCIssuerOf(
      const bssl::ParsedCertificate* cert) override {
    return backing_trust_store_.GetTrustedMTCIssuerOf(cert);
  }

  void SyncGetIssuersOf(const bssl::ParsedCertificate* cert,
                        bssl::ParsedCertificateList* issuers) override {
    sync_get_issuer_started_event_.Signal();
    sync_get_issuer_ok_to_finish_event_.Wait();

    backing_trust_store_.SyncGetIssuersOf(cert, issuers);
  }

  base::WaitableEvent sync_get_issuer_started_event_;
  base::WaitableEvent sync_get_issuer_ok_to_finish_event_;
  bssl::TrustStoreInMemory backing_trust_store_;
};

class MockCTVerifier : public CTVerifier {
 public:
  MOCK_CONST_METHOD6(Verify,
                     void(X509Certificate*,
                          std::string_view,
                          std::string_view,
                          base::Time current_time,
                          SignedCertificateTimestampAndStatusList*,
                          const NetLogWithSource&));
};

class MockCTPolicyEnforcer : public CTPolicyEnforcer {
 public:
  MOCK_CONST_METHOD4(CheckCompliance,
                     ct::CTPolicyCompliance(X509Certificate* cert,
                                            const ct::SCTList&,
                                            base::Time,
                                            const NetLogWithSource&));
  MOCK_CONST_METHOD1(GetLogDisqualificationTime,
                     std::optional<base::Time>(std::string_view log_id));
  MOCK_CONST_METHOD0(IsCtEnabled, bool());
  MOCK_CONST_METHOD1(IsLogDataTimely, bool(base::Time current_time));

 protected:
  ~MockCTPolicyEnforcer() override = default;
};

class MockRequireCTDelegate : public RequireCTDelegate {
 public:
  MOCK_CONST_METHOD3(
      IsCTRequiredForHost,
      CTRequirementLevel(std::string_view host,
                         const X509Certificate* chain,
                         const std::vector<SHA256HashValue>& hashes));

 protected:
  ~MockRequireCTDelegate() override = default;
};

}  // namespace

class CertVerifyProcBuiltinTest : public ::testing::Test {
 public:
  void SetUp() override {
    cert_net_fetcher_ = base::MakeRefCounted<CertNetFetcherURLRequest>();

    InitializeVerifyProc(CreateParams({}));

    context_ = CreateTestURLRequestContextBuilder()->Build();

    cert_net_fetcher_->SetURLRequestContext(context_.get());
  }

  void TearDown() override { cert_net_fetcher_->Shutdown(); }

  CertVerifyProc::InstanceParams CreateParams(
      const CertificateList& additional_trust_anchors,
      const CertificateList&
          additional_trust_anchors_with_enforced_constraints = {},
      const CertificateList& additional_distrusted_certificates = {}) {
    CertVerifyProc::InstanceParams instance_params;
    instance_params.additional_trust_anchors =
        net::x509_util::ParseAllValidCerts(additional_trust_anchors);
    instance_params.additional_trust_anchors_with_enforced_constraints =
        net::x509_util::ParseAllValidCerts(
            additional_trust_anchors_with_enforced_constraints);
    std::vector<std::vector<uint8_t>> distrusted_spkis;
    for (const auto& x509_cert : additional_distrusted_certificates) {
      std::shared_ptr<const bssl::ParsedCertificate> cert =
          bssl::ParsedCertificate::Create(
              bssl::UpRef(x509_cert->cert_buffer()),
              net::x509_util::DefaultParseCertificateOptions(),
              /*errors=*/nullptr);
      EXPECT_TRUE(cert);
      distrusted_spkis.push_back(base::ToVector(cert->tbs().spki_tlv));
    }
    instance_params.additional_distrusted_spkis = distrusted_spkis;
    return instance_params;
  }

  void InitializeVerifyProc(
      const CertVerifyProc::InstanceParams& instance_params,
      std::optional<base::Time> current_time = std::nullopt) {
    auto mock_system_trust_store = std::make_unique<MockSystemTrustStore>();
    mock_system_trust_store_ = mock_system_trust_store.get();
    auto mock_ct_verifier = std::make_unique<MockCTVerifier>();
    mock_ct_verifier_ = mock_ct_verifier.get();
    mock_ct_policy_enforcer_ = base::MakeRefCounted<MockCTPolicyEnforcer>();
    std::optional<network_time::TimeTracker> time_tracker;
    if (current_time.has_value()) {
      time_tracker =
          network_time::TimeTracker(base::Time::Now(), base::TimeTicks::Now(),
                                    current_time.value(), base::TimeDelta());
    }
    verify_proc_ = CreateCertVerifyProcBuiltin(
        cert_net_fetcher_, CRLSet::EmptyCRLSetForTesting(),
        std::move(mock_ct_verifier), mock_ct_policy_enforcer_,
        std::move(mock_system_trust_store), instance_params, time_tracker);
  }

  void Verify(scoped_refptr<X509Certificate> cert,
              const std::string& hostname,
              int flags,
              CertVerifyResult* verify_result,
              NetLogSource* out_source,
              CompletionOnceCallback callback) {
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE,
        {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
        base::BindOnce(
            &VerifyOnWorkerThread, verify_proc_, std::move(cert), hostname,
            /*ocsp_response=*/std::string(),
            /*sct_list=*/std::string(), flags, verify_result, out_source),
        std::move(callback));
  }

  void Verify(scoped_refptr<X509Certificate> cert,
              const std::string& hostname,
              const std::string& ocsp_response,
              const std::string& sct_list,
              int flags,
              CertVerifyResult* verify_result,
              NetLogSource* out_source,
              CompletionOnceCallback callback) {
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE,
        {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
        base::BindOnce(&VerifyOnWorkerThread, verify_proc_, std::move(cert),
                       hostname, ocsp_response, sct_list, flags, verify_result,
                       out_source),
        std::move(callback));
  }

  scoped_refptr<X509Certificate> Verify2QwacBinding(
      std::string_view binding,
      const std::string& hostname,
      base::span<const uint8_t> tls_cert,
      NetLogSource* out_source) {
    // 2-QWAC verification does not do any blocking calls, so the unittest does
    // not need to run it on a worker thread.
    NetLogWithSource net_log(NetLogWithSource::Make(
        net::NetLog::Get(), net::NetLogSourceType::CERT_VERIFIER_TASK));
    *out_source = net_log.source();
    return verify_proc_->Verify2QwacBinding(binding, hostname, tls_cert,
                                            net_log);
  }

  int Verify2Qwac(scoped_refptr<X509Certificate> cert,
                  const std::string& hostname,
                  CertVerifyResult* verify_result,
                  NetLogSource* out_source) {
    // 2-QWAC verification does not do any blocking calls, so the unittest does
    // not need to run it on a worker thread.
    NetLogWithSource net_log(NetLogWithSource::Make(
        net::NetLog::Get(), net::NetLogSourceType::CERT_VERIFIER_TASK));
    *out_source = net_log.source();
    return verify_proc_->Verify2Qwac(cert.get(), hostname, verify_result,
                                     net_log);
  }

  base::test::TaskEnvironment& task_environment() { return task_environment_; }

  // Creates a CRL issued and signed by |crl_issuer|, marking |revoked_serials|
  // as revoked, and registers it to be served by the test server.
  // Returns the full URL to retrieve the CRL from the test server.
  GURL CreateAndServeCrl(EmbeddedTestServer* test_server,
                         CertBuilder* crl_issuer,
                         const std::vector<uint64_t>& revoked_serials,
                         std::optional<bssl::SignatureAlgorithm>
                             signature_algorithm = std::nullopt) {
    std::string crl = BuildCrl(crl_issuer->GetSubject(), crl_issuer->GetKey(),
                               revoked_serials, signature_algorithm);
    std::string crl_path = MakeRandomPath(".crl");
    test_server->RegisterRequestHandler(
        base::BindRepeating(&test_server::HandlePrefixedRequest, crl_path,
                            base::BindRepeating(ServeResponse, HTTP_OK,
                                                "application/pkix-crl", crl)));
    return test_server->GetURL(crl_path);
  }

  void AddTrustStore(bssl::TrustStore* store) {
    mock_system_trust_store_->AddTrustStore(store);
  }

  void SetMockIsKnownRoot(bool is_known_root) {
    mock_system_trust_store_->SetMockIsKnownRoot(is_known_root);
  }

  void SetMockIsKnownMtcAnchor(bool is_known_root) {
    mock_system_trust_store_->SetMockIsKnownMtcAnchor(is_known_root);
  }

#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
  void SetMockIsLocallyTrustedRoot(bool is_locally_trusted_root) {
    mock_system_trust_store_->SetMockIsLocallyTrustedRoot(
        is_locally_trusted_root);
  }

  void SetMockCRSVersion(int64_t crs_version) {
    mock_system_trust_store_->SetMockCRSVersion(crs_version);
  }

  void SetMockSignerSetTimestamp(std::optional<base::Time> timestamp) {
    mock_system_trust_store_->SetMockSignerSetTimestamp(timestamp);
  }

  void SetMockMtcMetadataUpdateTime(std::optional<base::Time> update_time) {
    mock_system_trust_store_->SetMockMtcMetadataUpdateTime(update_time);
  }

  void SetMockMTCAnchorData(TrustStoreChrome::MtcAnchorExtraData data) {
    mock_system_trust_store_->SetMockMTCAnchorData(std::move(data));
  }

  void SetMockMtcMirrors(
      const std::vector<MtcLogBuilder::Cosigner*>& mock_mtc_mirrors) {
    mock_system_trust_store_->SetMockMtcMirrors(mock_mtc_mirrors);
  }

  void SetMockIsMtcCosignerPolicySatisfied(bool value) {
    mock_system_trust_store_->SetMockIsMtcCosignerPolicySatisfied(value);
  }

  std::optional<std::vector<std::vector<uint8_t>>>
  TakeLastValidAdditionalCosigners() {
    return mock_system_trust_store_->TakeLastValidAdditionalCosigners();
  }

  void SetMockCrsRootId(std::optional<int32_t> crs_root_id) {
    mock_system_trust_store_->SetMockCrsRootId(crs_root_id);
  }

  void SetMockChromeRootConstraints(
      base::span<const StaticChromeRootCertConstraints>
          chrome_root_constraints) {
    mock_system_trust_store_->SetMockChromeRootConstraints(
        chrome_root_constraints);
  }

  void AddMockEutlRoot(CRYPTO_BUFFER* der_cert) {
    mock_system_trust_store_->AddMockEutlRoot(der_cert);
  }
#endif

  net::URLRequestContext* context() { return context_.get(); }

  MockCTVerifier* mock_ct_verifier() { return mock_ct_verifier_; }
  MockCTPolicyEnforcer* mock_ct_policy_enforcer() {
    return mock_ct_policy_enforcer_.get();
  }

 private:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME,
      base::test::TaskEnvironment::MainThreadType::IO,
  };

  CertVerifier::Config config_;
  std::unique_ptr<net::URLRequestContext> context_;

  // Must outlive `mock_ct_verifier_` and `mock_system_trust_store_`.
  scoped_refptr<CertVerifyProc> verify_proc_;

  raw_ptr<MockCTVerifier> mock_ct_verifier_ = nullptr;
  scoped_refptr<MockCTPolicyEnforcer> mock_ct_policy_enforcer_;
  raw_ptr<MockSystemTrustStore> mock_system_trust_store_ = nullptr;
  scoped_refptr<CertNetFetcherURLRequest> cert_net_fetcher_;
};

TEST_F(CertVerifyProcBuiltinTest, ShouldBypassHSTS) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTP);
  ASSERT_TRUE(test_server.InitializeAndListen());

  // CRL that marks leaf as revoked.
  leaf->SetCrlDistributionPointUrl(
      CreateAndServeCrl(&test_server, root.get(), {leaf->GetSerialNumber()}));

  test_server.StartAcceptingConnections();

  {
    scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
    ASSERT_TRUE(chain.get());

    NetLogSource verify_net_log_source;
    CertVerifyResult verify_result;
    TestCompletionCallback verify_callback;
    // Ensure HSTS upgrades for the domain which hosts the CRLs.
    context()->transport_security_state()->AddHSTS(
        test_server.base_url().GetHost(), base::Time::Now() + base::Seconds(30),
        /*include_subdomains=*/true);
    // Setting `is_top_level_nav` true prevents the upgrade from being blocked
    // by kHstsTopLevelNavigationsOnly.
    ASSERT_TRUE(context()->transport_security_state()->ShouldUpgradeToSSL(
        test_server.base_url().GetHost(), /*is_top_level_nav=*/true));
    Verify(chain.get(), "www.example.com",
           CertVerifyProc::VERIFY_REV_CHECKING_ENABLED,
           &verify_result, &verify_net_log_source, verify_callback.callback());

    int error = verify_callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
    EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
  }
}

TEST_F(CertVerifyProcBuiltinTest, SimpleSuccess) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  base::HistogramTester histograms;
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
  // When CRS is not being used (mock_crs_version_ == 0), the anchor usage
  // histogram should not be recorded.
  histograms.ExpectTotalCount("Net.Certificate.TrustAnchor2.Verify", 0u);
}

TEST_F(CertVerifyProcBuiltinTest, SimpleSignaturelessMtcSuccess) {
  constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
  constexpr uint16_t kLogNumber = 1;
  net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
  // TODO(crbug.com/469624806): improve interface for creating MTC cert
  // builders.
  std::unique_ptr<net::CertBuilder> mtc_leaf1 =
      std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
  uint64_t leaf_index = mtc_log.AddEntry(*mtc_leaf1);
  mtc_log.AdvanceLandmark();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  // The CA key isn't actually used by this test, but one is needed to
  // initialize the MTCAnchor object.
  auto ca_key = crypto::keypair::PrivateKey::GenerateEcP256();

  bssl::TrustStoreInMemory trust_store;
  auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
      kMtcCaId, bssl::SignatureAlgorithm::kEcdsaSha256,
      x509_util::CreateCryptoBuffer(ca_key.ToSubjectPublicKeyInfo()),
      mtc_log.GetPerLogLandmarkSubtreeHashes());
  ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
  AddTrustStore(&trust_store);

  auto leaf_der = mtc_log.CreateSignaturelessCertificate(leaf_index);
  ASSERT_TRUE(leaf_der);
  scoped_refptr<X509Certificate> chain =
      X509Certificate::CreateFromBytes(*leaf_der);
  ASSERT_TRUE(chain);

  // MTCs don't use IsKnownRoot, so this returning true shouldn't mark it as a
  // known root.
  SetMockIsKnownRoot(true);
  SetMockIsKnownMtcAnchor(false);

  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());

    EXPECT_EQ(2u, verify_result.verified_cert->cert_buffers().size());
    EXPECT_TRUE(chain->EqualsExcludingChain(verify_result.verified_cert.get()));
    EXPECT_TRUE(x509_util::CryptoBufferEqual(
        mtc_anchor->AsCert()->cert_buffer(),
        verify_result.verified_cert->cert_buffers()[1].get()));
    EXPECT_FALSE(verify_result.is_issued_by_known_root);
  }

  SetMockIsKnownRoot(false);
  SetMockIsKnownMtcAnchor(true);

  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
    EXPECT_TRUE(verify_result.is_issued_by_known_root);
  }
}

TEST_F(CertVerifyProcBuiltinTest, MtcNonTrivialProof) {
  constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
  constexpr uint16_t kLogNumber = 1;
  net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
  // TODO(crbug.com/469624806): improve interface for creating MTC cert
  // builders.
  std::unique_ptr<net::CertBuilder> mtc_leaf1 =
      std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);

  mtc_log.AddUnusedEntries(27);
  uint64_t leaf_index = mtc_log.AddEntry(*mtc_leaf1);
  mtc_log.AddUnusedEntries(13);
  mtc_log.AdvanceLandmark();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  // The CA key isn't actually used by this test, but one is needed to
  // initialize the MTCAnchor object.
  auto ca_key = crypto::keypair::PrivateKey::GenerateEcP256();

  bssl::TrustStoreInMemory trust_store;
  auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
      kMtcCaId, bssl::SignatureAlgorithm::kEcdsaSha256,
      x509_util::CreateCryptoBuffer(ca_key.ToSubjectPublicKeyInfo()),
      mtc_log.GetPerLogLandmarkSubtreeHashes());
  ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
  AddTrustStore(&trust_store);

  {
    scoped_refptr<X509Certificate> cert1 = X509Certificate::CreateFromBytes(
        *mtc_log.CreateSignaturelessCertificate(leaf_index));
    ASSERT_TRUE(cert1);

    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(cert1.get(), "www.example.com", /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
  }
}

TEST_F(CertVerifyProcBuiltinTest, StandaloneMtcVerification) {
  constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
  constexpr uint16_t kLogNumber = 1;
  net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
  // TODO(crbug.com/469624806): improve interface for creating MTC cert
  // builders.
  std::unique_ptr<net::CertBuilder> mtc_leaf1 =
      std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
  uint64_t leaf_index = mtc_log.AddEntry(*mtc_leaf1);
  mtc_log.AdvanceLandmark();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  MtcLogBuilder::Cosigner ca_cosigner = {
      base::ToVector(kMtcCaId), crypto::keypair::PrivateKey::GenerateEcP256(),
      bssl::SignatureAlgorithm::kEcdsaSha256};

  auto leaf_der =
      mtc_log.CreateStandaloneCertificate(leaf_index, {&ca_cosigner});
  ASSERT_TRUE(leaf_der);
  scoped_refptr<X509Certificate> leaf =
      X509Certificate::CreateFromBytes(*leaf_der);
  ASSERT_TRUE(leaf);

  SetMockIsKnownRoot(false);
  SetMockIsKnownMtcAnchor(false);

  bssl::TrustStoreInMemory trust_store;
  auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
      ca_cosigner.id, ca_cosigner.signature_algorithm,
      x509_util::CreateCryptoBuffer(ca_cosigner.key.ToSubjectPublicKeyInfo()),
      std::map<uint16_t, std::vector<bssl::TrustedSubtree>>());
  ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
  AddTrustStore(&trust_store);

  // Standalone MTC verifies successfully.
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf.get(), "www.example.com", /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());

    EXPECT_EQ(2u, verify_result.verified_cert->cert_buffers().size());
    EXPECT_TRUE(leaf->EqualsExcludingChain(verify_result.verified_cert.get()));
    EXPECT_TRUE(x509_util::CryptoBufferEqual(
        mtc_anchor->AsCert()->cert_buffer(),
        verify_result.verified_cert->cert_buffers()[1].get()));
    EXPECT_FALSE(verify_result.is_issued_by_known_root);
  }

  // Standalone MTC verification fails if the MTCAnchor in the trust store has
  // a different key than used to sign the MTC.
  trust_store.Clear();
  auto different_key = crypto::keypair::PrivateKey::GenerateEcP256();
  mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
      ca_cosigner.id, ca_cosigner.signature_algorithm,
      x509_util::CreateCryptoBuffer(different_key.ToSubjectPublicKeyInfo()),
      std::map<uint16_t, std::vector<bssl::TrustedSubtree>>());
  ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf.get(), "www.example.com", /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));

    EXPECT_EQ(2u, verify_result.verified_cert->cert_buffers().size());
    EXPECT_TRUE(leaf->EqualsExcludingChain(verify_result.verified_cert.get()));
    EXPECT_TRUE(x509_util::CryptoBufferEqual(
        mtc_anchor->AsCert()->cert_buffer(),
        verify_result.verified_cert->cert_buffers()[1].get()));
    EXPECT_FALSE(verify_result.is_issued_by_known_root);
  }
}

#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
TEST_F(CertVerifyProcBuiltinTest, StandaloneMtcCosignerPolicy) {
  constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
  constexpr uint16_t kLogNumber = 1;
  net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
  // TODO(crbug.com/469624806): improve interface for creating MTC cert
  // builders.
  std::unique_ptr<net::CertBuilder> mtc_leaf1 =
      std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
  mtc_log.AddUnusedEntries(27);
  uint64_t leaf_index = mtc_log.AddEntry(*mtc_leaf1);

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  MtcLogBuilder::Cosigner ca_cosigner = {
      base::ToVector(kMtcCaId), crypto::keypair::PrivateKey::GenerateMldsa44(),
      bssl::SignatureAlgorithm::kMldsa44};
  MtcLogBuilder::Cosigner mirror_cosigner = {
      {0x05, 0x04},
      crypto::keypair::PrivateKey::GenerateMldsa44(),
      bssl::SignatureAlgorithm::kMldsa44};
  MtcLogBuilder::Cosigner unknown_cosigner = {
      {0x06, 0x08},
      crypto::keypair::PrivateKey::GenerateMldsa44(),
      bssl::SignatureAlgorithm::kMldsa44};

  SetMockMtcMirrors({&mirror_cosigner});

  bssl::TrustStoreInMemory trust_store;
  auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
      ca_cosigner.id, ca_cosigner.signature_algorithm,
      x509_util::CreateCryptoBuffer(ca_cosigner.key.ToSubjectPublicKeyInfo()),
      std::map<uint16_t, std::vector<bssl::TrustedSubtree>>());
  ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
  AddTrustStore(&trust_store);

  auto leaf_with_ca_only_der =
      mtc_log.CreateStandaloneCertificate(leaf_index, {&ca_cosigner});
  ASSERT_TRUE(leaf_with_ca_only_der);
  scoped_refptr<X509Certificate> leaf_with_ca_only =
      X509Certificate::CreateFromBytes(*leaf_with_ca_only_der);
  ASSERT_TRUE(leaf_with_ca_only);

  auto leaf_with_ca_and_mirror_der = mtc_log.CreateStandaloneCertificate(
      leaf_index, {&ca_cosigner, &mirror_cosigner, &unknown_cosigner});
  ASSERT_TRUE(leaf_with_ca_and_mirror_der);
  scoped_refptr<X509Certificate> leaf_with_ca_and_mirror =
      X509Certificate::CreateFromBytes(*leaf_with_ca_and_mirror_der);
  ASSERT_TRUE(leaf_with_ca_and_mirror);

  auto leaf_with_mirror_only_der =
      mtc_log.CreateStandaloneCertificate(leaf_index, {&mirror_cosigner});
  ASSERT_TRUE(leaf_with_mirror_only_der);
  scoped_refptr<X509Certificate> leaf_with_mirror_only =
      X509Certificate::CreateFromBytes(*leaf_with_mirror_only_der);
  ASSERT_TRUE(leaf_with_mirror_only);

  // MTCs don't use IsKnownRoot, so this returning true shouldn't mark it as a
  // known root.
  SetMockIsKnownRoot(true);

  // If the MTC CA is not a known one, can verify successfully without
  // checking cosigner policy. Setting the IsMtcCosignerPolicySatisfied
  // result to false doesn't affect the result for the first round of tests
  // with SetMockIsKnownMtcAnchor(false) since it won't be called.
  SetMockIsKnownMtcAnchor(false);
  SetMockIsMtcCosignerPolicySatisfied(false);

  {
    // MTC with only CA signature should validate successfully when mirror
    // policy is not enforced.
    CertVerifyResult verify_result;
    RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf_with_ca_only.get(), "www.example.com", /*flags=*/0,
           &verify_result, &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());

    EXPECT_EQ(2u, verify_result.verified_cert->cert_buffers().size());
    EXPECT_TRUE(leaf_with_ca_only->EqualsExcludingChain(
        verify_result.verified_cert.get()));
    EXPECT_TRUE(x509_util::CryptoBufferEqual(
        mtc_anchor->AsCert()->cert_buffer(),
        verify_result.verified_cert->cert_buffers()[1].get()));
    EXPECT_FALSE(verify_result.is_issued_by_known_root);

    // In the case of locally trusted MTC anchors, the netlog is recorded
    // directly from PathBuilderDelegateImpl without calling the trust store
    // IsMtcCosignerPolicySatisfied.
    auto events = net_log_observer.GetEntriesWithType(
        NetLogEventType::CERT_MTC_COSIGNER_POLICY_CHECKED);
    ASSERT_EQ(1u, events.size());
    EXPECT_THAT(events[0].params, base::test::IsJson(R"({
      "is_valid": true,
      "reason": "locally trusted anchor",
    })"));
  }

  {
    // MTC with CA signature and a mirror signature should validate
    // successfully when mirror policy is not enforced. The mirror signature is
    // not required, but being present does not affect the result.
    CertVerifyResult verify_result;
    RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf_with_ca_and_mirror.get(), "www.example.com", /*flags=*/0,
           &verify_result, &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());

    EXPECT_EQ(2u, verify_result.verified_cert->cert_buffers().size());
    EXPECT_TRUE(leaf_with_ca_and_mirror->EqualsExcludingChain(
        verify_result.verified_cert.get()));
    EXPECT_TRUE(x509_util::CryptoBufferEqual(
        mtc_anchor->AsCert()->cert_buffer(),
        verify_result.verified_cert->cert_buffers()[1].get()));
    EXPECT_FALSE(verify_result.is_issued_by_known_root);

    // In the case of locally trusted MTC anchors, the netlog is recorded
    // directly from PathBuilderDelegateImpl without calling the trust store
    // IsMtcCosignerPolicySatisfied.
    auto events = net_log_observer.GetEntriesWithType(
        NetLogEventType::CERT_MTC_COSIGNER_POLICY_CHECKED);
    ASSERT_EQ(1u, events.size());
    EXPECT_THAT(events[0].params, base::test::IsJson(R"({
      "is_valid": true,
      "reason": "locally trusted anchor",
    })"));
  }

  {
    // MTC with no CA signature should fail regardless.
    CertVerifyResult verify_result;
    RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf_with_mirror_only.get(), "www.example.com", /*flags=*/0,
           &verify_result, &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
    EXPECT_EQ(0u, net_log_observer
                      .GetEntriesWithType(
                          NetLogEventType::CERT_MTC_COSIGNER_POLICY_CHECKED)
                      .size());
  }

  // Second round of tests, with KnownMtcAnchor=true.
  // These tests check that GetMtcMirrorKey and IsMtcCosignerPolicySatisfied
  // are correctly used.
  SetMockIsKnownRoot(false);
  SetMockIsKnownMtcAnchor(true);

  {
    // MTC validation should fail when mirror policy is enforced and not
    // satisfied.
    SetMockIsMtcCosignerPolicySatisfied(false);

    CertVerifyResult verify_result;
    RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf_with_ca_only.get(), "www.example.com", /*flags=*/0,
           &verify_result, &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
    EXPECT_EQ(std::vector<std::vector<uint8_t>>{},
              TakeLastValidAdditionalCosigners());
    // Since the trust store IsMtcCosignerPolicySatisfied is mocked and this is
    // a known anchor, the CERT_MTC_COSIGNER_POLICY_CHECKED netlog is not
    // recorded. (Those cases are tested in trust_store_chrome_unittest.cc.)
    EXPECT_EQ(0u, net_log_observer
                      .GetEntriesWithType(
                          NetLogEventType::CERT_MTC_COSIGNER_POLICY_CHECKED)
                      .size());
  }
  {
    // MTC validation should succeed when mirror policy is enforced and
    // satisfied.
    SetMockIsMtcCosignerPolicySatisfied(true);

    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf_with_ca_and_mirror.get(), "www.example.com", /*flags=*/0,
           &verify_result, &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
    EXPECT_TRUE(verify_result.is_issued_by_known_root);
    // IsCosignatureVerificationResultAcceptable should have received
    // indication that mirror cosignature validated successfully.
    EXPECT_EQ(std::vector<std::vector<uint8_t>>{mirror_cosigner.id},
              TakeLastValidAdditionalCosigners());
  }
  {
    // MTC with no CA signature should fail regardless.
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf_with_mirror_only.get(), "www.example.com", /*flags=*/0,
           &verify_result, &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
    // IsCosignatureVerificationResultAcceptable should not have been called,
    // since verification of the CA signature failed.
    EXPECT_EQ(std::nullopt, TakeLastValidAdditionalCosigners());
  }
}

TEST_F(CertVerifyProcBuiltinTest, MtcLogNumberLimits) {
  struct TestCase {
    uint16_t log_number;
    int min_log_number;
    int expected_result_for_local_root;
    int expected_result_for_known_root;
  } testcases[] = {
      // A log number of zero is not allowed. This should always fail (it's
      // enforced by the boringssl side of MTC verification.)
      {.log_number = 0,
       .min_log_number = 0,
       .expected_result_for_local_root = ERR_CERT_AUTHORITY_INVALID,
       .expected_result_for_known_root = ERR_CERT_AUTHORITY_INVALID},
      {.log_number = 0,
       .min_log_number = 1,
       .expected_result_for_local_root = ERR_CERT_AUTHORITY_INVALID,
       .expected_result_for_known_root = ERR_CERT_AUTHORITY_INVALID},

      // min_log_number doesn't explicitly require known root since it comes
      // from the MtcAnchorExtraData (in practice that data will only be
      // populated for known roots though.)
      {.log_number = 1,
       .min_log_number = 0,
       .expected_result_for_local_root = OK,
       .expected_result_for_known_root = OK},
      {.log_number = 1,
       .min_log_number = 1,
       .expected_result_for_local_root = OK,
       .expected_result_for_known_root = OK},
      {.log_number = 1,
       .min_log_number = 2,
       .expected_result_for_local_root = ERR_CERT_REVOKED,
       .expected_result_for_known_root = ERR_CERT_REVOKED},
      {.log_number = 5,
       .min_log_number = 2,
       .expected_result_for_local_root = OK,
       .expected_result_for_known_root = OK},
      {.log_number = 5,
       .min_log_number = 5,
       .expected_result_for_local_root = OK,
       .expected_result_for_known_root = OK},
      {.log_number = 5,
       .min_log_number = 6,
       .expected_result_for_local_root = ERR_CERT_REVOKED,
       .expected_result_for_known_root = ERR_CERT_REVOKED},

      // The max log number of 5 is a hard-coded requirement from CQRP, so it
      // is only enforced when known root is true.
      {.log_number = 6,
       .min_log_number = 0,
       .expected_result_for_local_root = OK,
       .expected_result_for_known_root = ERR_CERT_REVOKED},
      {.log_number = 6,
       .min_log_number = 5,
       .expected_result_for_local_root = OK,
       .expected_result_for_known_root = ERR_CERT_REVOKED},
      {.log_number = 6,
       .min_log_number = 6,
       .expected_result_for_local_root = OK,
       .expected_result_for_known_root = ERR_CERT_REVOKED},
      {.log_number = 6,
       .min_log_number = 7,
       .expected_result_for_local_root = ERR_CERT_REVOKED,
       .expected_result_for_known_root = ERR_CERT_REVOKED},
  };

  for (const auto& test : testcases) {
    SCOPED_TRACE(test.log_number);
    SCOPED_TRACE(test.min_log_number);
    constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
    net::MtcLogBuilder mtc_log(kMtcCaId, test.log_number);
    std::unique_ptr<net::CertBuilder> mtc_leaf1 =
        std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
    uint64_t leaf_index = mtc_log.AddEntry(*mtc_leaf1);
    mtc_log.AdvanceLandmark();

    InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

    MtcLogBuilder::Cosigner ca_cosigner = {
        base::ToVector(kMtcCaId),
        crypto::keypair::PrivateKey::GenerateMldsa44(),
        bssl::SignatureAlgorithm::kMldsa44};

    bssl::TrustStoreInMemory trust_store;
    auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
        ca_cosigner.id, ca_cosigner.signature_algorithm,
        x509_util::CreateCryptoBuffer(ca_cosigner.key.ToSubjectPublicKeyInfo()),
        mtc_log.GetPerLogLandmarkSubtreeHashes());
    ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
    AddTrustStore(&trust_store);

    Signer signer_config = Signer::CreateForTesting(
        chrome_root_store::SIGNER_TYPE_ISSUER, kMtcCaId);
    signer_config.min_log_number = test.min_log_number;
    TrustStoreChrome::MtcAnchorExtraData mtc_anchor_extra_data(
        std::move(signer_config));
    SetMockMTCAnchorData(mtc_anchor_extra_data);
    SetMockIsMtcCosignerPolicySatisfied(true);

    // Results should be the same for both landmark relative and standalone
    // certs.
    //
    // Landmark relative MTCs:
    scoped_refptr<X509Certificate> landmark_relative_cert =
        X509Certificate::CreateFromBytes(
            *mtc_log.CreateSignaturelessCertificate(leaf_index));
    ASSERT_TRUE(landmark_relative_cert);
    {
      SetMockIsKnownMtcAnchor(false);
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(landmark_relative_cert.get(), "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsError(test.expected_result_for_local_root));
    }
    {
      SetMockIsKnownMtcAnchor(true);
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(landmark_relative_cert.get(), "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsError(test.expected_result_for_known_root));
    }

    // Standalone MTCs:
    scoped_refptr<X509Certificate> standalone_cert =
        X509Certificate::CreateFromBytes(
            *mtc_log.CreateStandaloneCertificate(leaf_index, {&ca_cosigner}));
    ASSERT_TRUE(standalone_cert);
    {
      SetMockIsKnownMtcAnchor(false);
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(standalone_cert.get(), "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsError(test.expected_result_for_local_root));
    }
    {
      SetMockIsKnownMtcAnchor(true);
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(standalone_cert.get(), "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsError(test.expected_result_for_known_root));
    }
  }
}

TEST_F(CertVerifyProcBuiltinTest, MtcMaxCertLifetime) {
  struct TestCase {
    std::optional<base::TimeDelta> max_cert_lifetime;
    base::TimeDelta cert_lifetime;
    int expected_result;
  } testcases[] = {
      {.max_cert_lifetime = std::nullopt,
       .cert_lifetime = base::Days(10),
       .expected_result = OK},
      {.max_cert_lifetime = std::nullopt,
       .cert_lifetime = base::Days(100),
       .expected_result = OK},

      {.max_cert_lifetime = base::Days(10),
       .cert_lifetime = base::Days(10) - base::Seconds(1),
       .expected_result = OK},
      {.max_cert_lifetime = base::Days(10),
       .cert_lifetime = base::Days(10),
       .expected_result = OK},
      {.max_cert_lifetime = base::Days(10),
       .cert_lifetime = base::Days(10) + base::Seconds(1),
       .expected_result = ERR_CERT_VALIDITY_TOO_LONG},
      {.max_cert_lifetime = base::Days(10),
       .cert_lifetime = base::Days(100),
       .expected_result = ERR_CERT_VALIDITY_TOO_LONG},

      {.max_cert_lifetime = base::Days(47),
       .cert_lifetime = base::Days(47) - base::Seconds(1),
       .expected_result = OK},
      {.max_cert_lifetime = base::Days(47),
       .cert_lifetime = base::Days(47),
       .expected_result = OK},
      {.max_cert_lifetime = base::Days(47),
       .cert_lifetime = base::Days(47) + base::Seconds(1),
       .expected_result = ERR_CERT_VALIDITY_TOO_LONG},
      {.max_cert_lifetime = base::Days(47),
       .cert_lifetime = base::Days(100),
       .expected_result = ERR_CERT_VALIDITY_TOO_LONG},
  };

  base::Time start = base::Time::Now();
  constexpr int16_t kLogNumber = 1;

  for (const auto& test : testcases) {
    SCOPED_TRACE(base::ToString(test.max_cert_lifetime));
    SCOPED_TRACE(test.cert_lifetime);
    constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
    net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
    std::unique_ptr<net::CertBuilder> mtc_leaf1 =
        std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
    mtc_leaf1->SetValidity(start, start + test.cert_lifetime);
    uint64_t leaf_index = mtc_log.AddEntry(*mtc_leaf1);
    mtc_log.AdvanceLandmark();

    InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

    MtcLogBuilder::Cosigner ca_cosigner = {
        base::ToVector(kMtcCaId),
        crypto::keypair::PrivateKey::GenerateMldsa44(),
        bssl::SignatureAlgorithm::kMldsa44};

    bssl::TrustStoreInMemory trust_store;
    auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
        ca_cosigner.id, ca_cosigner.signature_algorithm,
        x509_util::CreateCryptoBuffer(ca_cosigner.key.ToSubjectPublicKeyInfo()),
        mtc_log.GetPerLogLandmarkSubtreeHashes());
    ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
    AddTrustStore(&trust_store);

    Signer signer_config = Signer::CreateForTesting(
        chrome_root_store::SIGNER_TYPE_ISSUER, kMtcCaId);
    signer_config.max_cert_lifetime = test.max_cert_lifetime;
    TrustStoreChrome::MtcAnchorExtraData mtc_anchor_extra_data(
        std::move(signer_config));
    SetMockMTCAnchorData(mtc_anchor_extra_data);
    SetMockIsMtcCosignerPolicySatisfied(true);

    // Results should be the same for both landmark relative and standalone
    // certs.
    //
    // Landmark relative MTCs:
    scoped_refptr<X509Certificate> landmark_relative_cert =
        X509Certificate::CreateFromBytes(
            *mtc_log.CreateSignaturelessCertificate(leaf_index));
    ASSERT_TRUE(landmark_relative_cert);
    {
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(landmark_relative_cert.get(), "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsError(test.expected_result));
    }

    // Standalone MTCs:
    scoped_refptr<X509Certificate> standalone_cert =
        X509Certificate::CreateFromBytes(
            *mtc_log.CreateStandaloneCertificate(leaf_index, {&ca_cosigner}));
    ASSERT_TRUE(standalone_cert);
    {
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(standalone_cert.get(), "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsError(test.expected_result));
    }
  }
}

TEST_F(CertVerifyProcBuiltinTest, CrsAnchorUsageHistogram) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  constexpr int32_t kFakeRootId = 8369;
  for (bool has_crs_root_id : {false, true}) {
    SCOPED_TRACE(has_crs_root_id);
    if (has_crs_root_id) {
      SetMockCrsRootId(kFakeRootId);
    } else {
      SetMockCrsRootId(std::nullopt);
    }
    for (bool is_known_root : {false, true}) {
      SCOPED_TRACE(is_known_root);
      SetMockCRSVersion(1);
      SetMockIsKnownRoot(is_known_root);
      base::HistogramTester histograms;
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
             &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsOk());
      if (has_crs_root_id) {
        histograms.ExpectUniqueSample("Net.Certificate.TrustAnchor2.Verify",
                                      kFakeRootId, 1u);
      } else {
        // When the root store does not have a crs_root_id set for the anchor,
        // one of the special values will be recorded, depending if it is a
        // known root or not.
        histograms.ExpectUniqueSample(
            "Net.Certificate.TrustAnchor2.Verify",
            is_known_root ? CertVerifyResult::kCrsRootIdUnknownId
                          : CertVerifyResult::kCrsRootIdPrivatelyTrustedRoot,
            1u);
      }
    }
  }
}

TEST_F(CertVerifyProcBuiltinTest, MtcCrsAnchorUsageHistogram) {
  constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
  constexpr uint16_t kLogNumber = 1;
  net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
  // TODO(crbug.com/469624806): improve interface for creating MTC cert
  // builders.
  std::unique_ptr<net::CertBuilder> mtc_leaf1 =
      std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
  uint64_t leaf_index = mtc_log.AddEntry(*mtc_leaf1);
  mtc_log.AdvanceLandmark();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  // The CA key isn't actually used by this test, but one is needed to
  // initialize the MTCAnchor object.
  auto ca_key = crypto::keypair::PrivateKey::GenerateEcP256();

  bssl::TrustStoreInMemory trust_store;
  auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
      kMtcCaId, bssl::SignatureAlgorithm::kEcdsaSha256,
      x509_util::CreateCryptoBuffer(ca_key.ToSubjectPublicKeyInfo()),
      mtc_log.GetPerLogLandmarkSubtreeHashes());
  ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
  AddTrustStore(&trust_store);

  auto leaf_der = mtc_log.CreateSignaturelessCertificate(leaf_index);
  ASSERT_TRUE(leaf_der);
  scoped_refptr<X509Certificate> chain =
      X509Certificate::CreateFromBytes(*leaf_der);
  ASSERT_TRUE(chain);

  constexpr int32_t kFakeRootId = 3691;
  for (bool has_crs_root_id : {false, true}) {
    SCOPED_TRACE(has_crs_root_id);
    if (has_crs_root_id) {
      SetMockCrsRootId(kFakeRootId);
    } else {
      SetMockCrsRootId(std::nullopt);
    }
    for (bool is_known_root : {false, true}) {
      SCOPED_TRACE(is_known_root);
      SetMockCRSVersion(1);
      SetMockIsKnownMtcAnchor(is_known_root);
      base::HistogramTester histograms;
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
             &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsOk());
      EXPECT_EQ(is_known_root, verify_result.is_issued_by_known_root);
      if (has_crs_root_id) {
        histograms.ExpectUniqueSample("Net.Certificate.TrustAnchor2.Verify",
                                      kFakeRootId, 1u);
      } else {
        // When the root store does not have a crs_root_id set for the anchor,
        // one of the special values will be recorded, depending if it is a
        // known root or not.
        histograms.ExpectUniqueSample(
            "Net.Certificate.TrustAnchor2.Verify",
            is_known_root ? CertVerifyResult::kCrsRootIdUnknownId
                          : CertVerifyResult::kCrsRootIdPrivatelyTrustedRoot,
            1u);
      }
    }
  }
}

TEST_F(CertVerifyProcBuiltinTest, MtcRevocation) {
  constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
  constexpr uint64_t kLogNumber = 1;

  MtcLogBuilder::Cosigner ca_cosigner = {
      base::ToVector(kMtcCaId), crypto::keypair::PrivateKey::GenerateEcP256(),
      bssl::SignatureAlgorithm::kEcdsaSha256};
  net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
  // TODO(crbug.com/469624806): improve interface for creating MTC cert
  // builders.
  std::unique_ptr<net::CertBuilder> mtc_leaf1 =
      std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
  struct TestCertData {
    uint64_t mtc_log_index;
    uint64_t mtc_serial;
    bool expect_is_revoked = false;
    scoped_refptr<X509Certificate> landmark_relative_cert;
    scoped_refptr<X509Certificate> standalone_cert;
  };
  std::array<TestCertData, 6> test_cert_data;
  for (TestCertData& data : test_cert_data) {
    data.mtc_log_index = mtc_log.AddEntry(*mtc_leaf1);
    data.mtc_serial = (kLogNumber << 48) + data.mtc_log_index;
  }

  mtc_log.AdvanceLandmark();

  for (TestCertData& data : test_cert_data) {
    data.landmark_relative_cert = X509Certificate::CreateFromBuffer(
        mtc_log.CreateSignaturelessCertificateBuffer(data.mtc_log_index), {});
    data.standalone_cert = X509Certificate::CreateFromBuffer(
        mtc_log.CreateStandaloneCertificateBuffer(data.mtc_log_index,
                                                  {&ca_cosigner}),
        {});
  }

  SetMockIsMtcCosignerPolicySatisfied(true);
  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  bssl::TrustStoreInMemory trust_store;
  auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
      ca_cosigner.id, ca_cosigner.signature_algorithm,
      x509_util::CreateCryptoBuffer(ca_cosigner.key.ToSubjectPublicKeyInfo()),
      mtc_log.GetPerLogLandmarkSubtreeHashes());
  ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
  AddTrustStore(&trust_store);

  // Test with GetMTCAnchorData returning a non-null MtcAnchorExtraData, but
  // with revoked_serials empty. Nothing should be revoked.
  TrustStoreChrome::MtcAnchorExtraData mtc_anchor_extra_data(
      Signer::CreateForTesting(chrome_root_store::SIGNER_TYPE_ISSUER,
                               kMtcCaId));
  SetMockMTCAnchorData(mtc_anchor_extra_data);
  for (const TestCertData& data : test_cert_data) {
    SCOPED_TRACE(data.mtc_log_index);

    {
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(data.landmark_relative_cert, "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsOk());
    }
    {
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(data.standalone_cert, "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsOk());
    }
  }

  // Set revoked_indicies to mark some certs as revoked.
  mtc_anchor_extra_data.revoked_serials = base::flat_map<uint64_t, uint64_t>({
      {test_cert_data[3].mtc_serial, test_cert_data[1].mtc_serial},
      {test_cert_data[5].mtc_serial, test_cert_data[4].mtc_serial},
  });
  test_cert_data[1].expect_is_revoked = true;
  test_cert_data[2].expect_is_revoked = true;
  test_cert_data[4].expect_is_revoked = true;
  SetMockMTCAnchorData(mtc_anchor_extra_data);
  for (const TestCertData& data : test_cert_data) {
    SCOPED_TRACE(data.mtc_log_index);

    {
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(data.landmark_relative_cert, "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      if (data.expect_is_revoked) {
        EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
      } else {
        EXPECT_THAT(error, IsOk());
      }
    }
    {
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(data.standalone_cert, "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      if (data.expect_is_revoked) {
        EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
      } else {
        EXPECT_THAT(error, IsOk());
      }
    }
  }

  // Set revoked_indicies to have a single range that includes all certs.
  mtc_anchor_extra_data.revoked_serials =
      base::flat_map<uint64_t, uint64_t>({{(kLogNumber + 1) << 48, 0}});
  SetMockMTCAnchorData(mtc_anchor_extra_data);
  for (const TestCertData& data : test_cert_data) {
    SCOPED_TRACE(data.mtc_log_index);

    {
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(data.landmark_relative_cert, "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
    }
    {
      CertVerifyResult verify_result;
      NetLogSource verify_net_log_source;
      TestCompletionCallback callback;
      Verify(data.standalone_cert, "www.example.com", /*flags=*/0,
             &verify_result, &verify_net_log_source, callback.callback());

      int error = callback.WaitForResult();
      EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
    }
  }
}

TEST_F(CertVerifyProcBuiltinTest, SignaturelessMtcChromeRootStoreConstraints) {
  constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
  constexpr uint16_t kLogNumber = 1;
  net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
  // TODO(crbug.com/469624806): improve interface for creating MTC cert
  // builders.
  std::unique_ptr<net::CertBuilder> mtc_leaf1 =
      std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
  uint64_t leaf_index = mtc_log.AddEntry(*mtc_leaf1);
  mtc_log.AdvanceLandmark();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  // The CA key isn't actually used by this test, but one is needed to
  // initialize the MTCAnchor object.
  auto ca_key = crypto::keypair::PrivateKey::GenerateEcP256();

  bssl::TrustStoreInMemory trust_store;
  auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
      kMtcCaId, bssl::SignatureAlgorithm::kEcdsaSha256,
      x509_util::CreateCryptoBuffer(ca_key.ToSubjectPublicKeyInfo()),
      mtc_log.GetPerLogLandmarkSubtreeHashes());
  ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
  AddTrustStore(&trust_store);

  auto leaf_der = mtc_log.CreateSignaturelessCertificate(leaf_index);
  ASSERT_TRUE(leaf_der);
  scoped_refptr<X509Certificate> chain =
      X509Certificate::CreateFromBytes(*leaf_der);
  ASSERT_TRUE(chain);

  // With no constraints, verification succeeds
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
  }

  // With constraint that doesn't match, verification fails.
  SetMockChromeRootConstraints({{.permitted_dns_names = {"example.test"}}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }

  // With constraint that does match, verification succeeds.
  SetMockChromeRootConstraints(
      {{.permitted_dns_names = {"example.test", "example.com"}}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
  }
}

TEST_F(CertVerifyProcBuiltinTest,
       SignaturelessMtcChromeRootStoreConstraintsTimeConstraints) {
  const base::Time leaf_time = base::Time::Now() - base::Seconds(1);

  constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
  constexpr uint16_t kLogNumber = 1;
  net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
  // TODO(crbug.com/469624806): improve interface for creating MTC cert
  // builders.
  std::unique_ptr<net::CertBuilder> mtc_leaf1 =
      std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
  mtc_leaf1->SetValidity(leaf_time, leaf_time + base::Days(1));
  uint64_t mtc_leaf_index = mtc_log.AddEntry(*mtc_leaf1);
  mtc_log.AdvanceLandmark();

  auto mtc_leaf_der = mtc_log.CreateSignaturelessCertificate(mtc_leaf_index);
  ASSERT_TRUE(mtc_leaf_der);
  scoped_refptr<X509Certificate> mtc_chain =
      X509Certificate::CreateFromBytes(*mtc_leaf_der);
  ASSERT_TRUE(mtc_chain);

  auto [classic_leaf, classic_root] = CertBuilder::CreateSimpleChain2();
  classic_leaf->SetValidity(leaf_time, leaf_time + base::Days(1));
  scoped_refptr<X509Certificate> classic_chain =
      classic_leaf->GetX509Certificate();
  ASSERT_TRUE(classic_chain.get());
  auto parsed_classic_root_cert = bssl::ParsedCertificate::Create(
      classic_root->DupCertBuffer(), {}, nullptr);
  ASSERT_TRUE(parsed_classic_root_cert);

  struct TestCase {
    StaticChromeRootCertConstraints constraint;
    bool expected_verification_success;
  };
  const TestCase tests[] = {
      // validity_starts_not_after cases:
      {{.validity_starts_not_after = leaf_time + base::Seconds(1)}, true},
      {{.validity_starts_not_after = leaf_time}, true},
      {{.validity_starts_not_after = leaf_time - base::Seconds(1)}, false},
      {{.validity_starts_not_after = leaf_time - base::Seconds(2)}, false},

      // validity_starts_after cases:
      {{.validity_starts_after = leaf_time + base::Seconds(1)}, false},
      {{.validity_starts_after = leaf_time}, false},
      {{.validity_starts_after = leaf_time - base::Seconds(1)}, true},
      {{.validity_starts_after = leaf_time - base::Seconds(2)}, true},

      // Both validity_starts_not_after and validity_starts_after cases:
      {{.validity_starts_not_after = leaf_time + base::Seconds(1),
        .validity_starts_after = leaf_time - base::Seconds(2)},
       true},
      {{.validity_starts_not_after = leaf_time,
        .validity_starts_after = leaf_time - base::Seconds(1)},
       true},
      {{.validity_starts_not_after = leaf_time + base::Seconds(1),
        .validity_starts_after = leaf_time},
       false},
      {{.validity_starts_not_after = leaf_time - base::Seconds(1),
        .validity_starts_after = leaf_time - base::Seconds(2)},
       false},
  };

  // The validity-based constraints shouldn't depend on CT enforcement
  // status, but test with it both enabled and disabled just to confirm.
  for (const bool ct_enabled : {false, true}) {
    InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

    // The CA key isn't actually used by this test, but one is needed to
    // initialize the MTCAnchor object.
    auto ca_key = crypto::keypair::PrivateKey::GenerateEcP256();

    bssl::TrustStoreInMemory trust_store;
    auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
        kMtcCaId, bssl::SignatureAlgorithm::kEcdsaSha256,
        x509_util::CreateCryptoBuffer(ca_key.ToSubjectPublicKeyInfo()),
        mtc_log.GetPerLogLandmarkSubtreeHashes());
    ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
    trust_store.AddTrustAnchor(parsed_classic_root_cert);
    AddTrustStore(&trust_store);

    EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
        .WillRepeatedly(testing::Return(ct_enabled));

    for (const auto& test : tests) {
      SCOPED_TRACE("ct_enabled = " + base::ToString(ct_enabled));
      SCOPED_TRACE("leaf_validity_not_before = " + base::ToString(leaf_time));
      SCOPED_TRACE(
          "validity_starts_not_after = " +
          (test.constraint.validity_starts_not_after
               ? base::ToString(*test.constraint.validity_starts_not_after)
               : "nullopt"));
      SCOPED_TRACE("validity_starts_after = " +
                   (test.constraint.validity_starts_after
                        ? base::ToString(*test.constraint.validity_starts_after)
                        : "nullopt"));

      SetMockChromeRootConstraints(base::span_from_ref(test.constraint));

      // The validity-based constraints should behave the same for both classic
      // and MTC leafs.
      //
      // Test against classic leaf.
      {
        CertVerifyResult verify_result;
        NetLogSource verify_net_log_source;
        TestCompletionCallback callback;
        Verify(classic_chain.get(), "www.example.com",
               /*ocsp_response=*/std::string(),
               /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
               &verify_net_log_source, callback.callback());

        int error = callback.WaitForResult();
        if (test.expected_verification_success) {
          EXPECT_THAT(error, IsOk());
        } else {
          EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
        }
      }

      // Test against MTC leaf.
      {
        CertVerifyResult verify_result;
        NetLogSource verify_net_log_source;
        TestCompletionCallback callback;
        Verify(mtc_chain.get(), "www.example.com",
               /*ocsp_response=*/std::string(),
               /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
               &verify_net_log_source, callback.callback());

        int error = callback.WaitForResult();
        if (test.expected_verification_success) {
          EXPECT_THAT(error, IsOk());
        } else {
          EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
        }
      }
    }
  }
}

TEST_F(CertVerifyProcBuiltinTest,
       SignaturelessMtcChromeRootStoreConstraintsSctConstraints) {
  constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
  constexpr uint16_t kLogNumber = 1;
  net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
  // TODO(crbug.com/469624806): improve interface for creating MTC cert
  // builders.
  std::unique_ptr<net::CertBuilder> mtc_leaf1 =
      std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
  uint64_t leaf_index = mtc_log.AddEntry(*mtc_leaf1);
  mtc_log.AdvanceLandmark();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  // The CA key isn't actually used by this test, but one is needed to
  // initialize the MTCAnchor object.
  auto ca_key = crypto::keypair::PrivateKey::GenerateEcP256();

  bssl::TrustStoreInMemory trust_store;
  auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
      kMtcCaId, bssl::SignatureAlgorithm::kEcdsaSha256,
      x509_util::CreateCryptoBuffer(ca_key.ToSubjectPublicKeyInfo()),
      mtc_log.GetPerLogLandmarkSubtreeHashes());
  ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
  AddTrustStore(&trust_store);

  auto leaf_der = mtc_log.CreateSignaturelessCertificate(leaf_index);
  ASSERT_TRUE(leaf_der);
  scoped_refptr<X509Certificate> chain =
      X509Certificate::CreateFromBytes(*leaf_der);
  ASSERT_TRUE(chain);

  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsLogDataTimely(_))
      .WillRepeatedly(testing::Return(true));

  // With no constraints, verification succeeds
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
  }

  // MTCs don't have SCTs, so sct_not_after will fail even for a cert issued
  // before the time specified time.
  SetMockChromeRootConstraints(
      {{.sct_not_after = base::Time::Now() + base::Days(365)}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }
}

TEST_F(CertVerifyProcBuiltinTest,
       SignaturelessMtcChromeRootStoreConstraintsIndexConstraints) {
  constexpr uint8_t kMtcCaId[] = {0x09, 0x08, 0x07};
  constexpr uint16_t kLogNumber = 1;
  net::MtcLogBuilder mtc_log(kMtcCaId, kLogNumber);
  // TODO(crbug.com/469624806): improve interface for creating MTC cert
  // builders.
  std::unique_ptr<net::CertBuilder> mtc_leaf1 =
      std::move(net::CertBuilder::CreateSimpleChain(1u)[0]);
  mtc_log.AddUnusedEntries(20);
  uint64_t leaf_index = mtc_log.AddEntry(*mtc_leaf1);
  uint64_t leaf_serial = (static_cast<uint64_t>(kLogNumber) << 48) + leaf_index;
  mtc_log.AdvanceLandmark();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  // The CA key isn't actually used by this test, but one is needed to
  // initialize the MTCAnchor object.
  auto ca_key = crypto::keypair::PrivateKey::GenerateEcP256();

  bssl::TrustStoreInMemory trust_store;
  auto mtc_anchor = std::make_shared<const bssl::MTCAnchor>(
      kMtcCaId, bssl::SignatureAlgorithm::kEcdsaSha256,
      x509_util::CreateCryptoBuffer(ca_key.ToSubjectPublicKeyInfo()),
      mtc_log.GetPerLogLandmarkSubtreeHashes());
  ASSERT_TRUE(trust_store.AddMTCTrustAnchor(mtc_anchor));
  AddTrustStore(&trust_store);

  auto leaf_der = mtc_log.CreateSignaturelessCertificate(leaf_index);
  ASSERT_TRUE(leaf_der);
  scoped_refptr<X509Certificate> chain =
      X509Certificate::CreateFromBytes(*leaf_der);
  ASSERT_TRUE(chain);

  struct TestCase {
    StaticChromeRootCertConstraints constraint;
    bool expected_verification_success;
  };
  const TestCase tests[] = {
      // index_not_after cases:
      {{.index_not_after = leaf_serial + 1}, true},
      {{.index_not_after = leaf_serial}, true},
      {{.index_not_after = leaf_serial - 1}, false},
      {{.index_not_after = leaf_serial - 2}, false},
      // index_after cases:
      {{.index_after = leaf_serial + 1}, false},
      {{.index_after = leaf_serial}, false},
      {{.index_after = leaf_serial - 1}, true},
      {{.index_after = leaf_serial - 2}, true},
      // Both index_not_after and index_after cases:
      {{.index_not_after = leaf_serial + 1, .index_after = leaf_serial - 2},
       true},
      {{.index_not_after = leaf_serial, .index_after = leaf_serial - 1}, true},
      {{.index_not_after = leaf_serial + 1, .index_after = leaf_serial}, false},
      {{.index_not_after = leaf_serial - 1, .index_after = leaf_serial - 2},
       false},

      // Invalid combinations of index_not_after and index_after. These are
      // cases in which the constraint can never be valid, ideally there should
      // be something that prevents creating a root store with such a
      // constraint. But test them anyway just to ensure that these do behave
      // as expected.
      //
      // index_not_after == index_after:
      {{.index_not_after = leaf_serial + 1, .index_after = leaf_serial + 1},
       false},
      {{.index_not_after = leaf_serial, .index_after = leaf_serial}, false},
      {{.index_not_after = leaf_serial - 1, .index_after = leaf_serial - 1},
       false},
      {{.index_not_after = leaf_serial - 2, .index_after = leaf_serial - 2},
       false},
      // index_not_after < index_after:
      {{.index_not_after = leaf_serial, .index_after = leaf_serial + 1}, false},
      {{.index_not_after = leaf_serial - 1, .index_after = leaf_serial}, false},
      {{.index_not_after = leaf_serial - 2, .index_after = leaf_serial - 1},
       false},
  };

  for (const auto& test : tests) {
    SCOPED_TRACE("leaf_serial = " + base::NumberToString(leaf_serial));
    SCOPED_TRACE("index_not_after = " +
                 (test.constraint.index_not_after
                      ? base::NumberToString(*test.constraint.index_not_after)
                      : "nullopt"));
    SCOPED_TRACE("index_after = " +
                 (test.constraint.index_after
                      ? base::NumberToString(*test.constraint.index_after)
                      : "nullopt"));

    SetMockChromeRootConstraints(base::span_from_ref(test.constraint));

    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    if (test.expected_verification_success) {
      EXPECT_THAT(error, IsOk());
    } else {
      EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
    }
  }
}
#endif  // BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)

TEST_F(CertVerifyProcBuiltinTest, CallsCtVerifierAndReturnsSctStatus) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  CertVerifyProc::InstanceParams instance_params = CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()});
  InitializeVerifyProc(instance_params);
  net::ScopedTestKnownRoot scoped_known_root(root->GetX509Certificate().get());

  constexpr char kHostname[] = "www.example.com";
  const std::string kOcspResponse = "OCSP response";
  const std::string kSctList = "SCT list";
  const std::string kLogId = "CT log id";
  const ct::SCTVerifyStatus kSctVerifyStatus = ct::SCT_STATUS_LOG_UNKNOWN;

  SignedCertificateTimestampAndStatus sct_and_status;
  sct_and_status.sct = base::MakeRefCounted<ct::SignedCertificateTimestamp>();
  sct_and_status.sct->log_id = kLogId;
  sct_and_status.status = kSctVerifyStatus;
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.push_back(sct_and_status);
  EXPECT_CALL(*mock_ct_verifier(), Verify(_, kOcspResponse, kSctList, _, _, _))
      .WillRepeatedly(testing::SetArgPointee<4>(sct_and_status_list));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  // If a RequireCTDelegate is not supplied, CT is required. Verification
  // should fail.
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), kHostname, kOcspResponse, kSctList, /*flags=*/0,
           &verify_result, &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERTIFICATE_TRANSPARENCY_REQUIRED));
    ASSERT_EQ(verify_result.scts.size(), 1u);
    EXPECT_EQ(verify_result.scts.front().status, kSctVerifyStatus);
    EXPECT_EQ(verify_result.scts.front().sct->log_id, kLogId);
    EXPECT_EQ(verify_result.policy_compliance,
              ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS);
    EXPECT_EQ(verify_result.ct_requirement_status,
              ct::CTRequirementsStatus::CT_REQUIREMENTS_NOT_MET);
  }

  // If a RequireCTDelegate is supplied, it is consulted to check whether the
  // CT result should affect the cert verification result.
  //
  // Use a mock RequireCTDelegate that returns CTRequirementLevel::REQUIRED.
  // Verification should fail.
  auto mock_require_ct_delegate = base::MakeRefCounted<MockRequireCTDelegate>();
  instance_params.require_ct_delegate = mock_require_ct_delegate;
  EXPECT_CALL(*mock_require_ct_delegate, IsCTRequiredForHost(kHostname, _, _))
      .WillRepeatedly(
          testing::Return(RequireCTDelegate::CTRequirementLevel::REQUIRED));
  InitializeVerifyProc(instance_params);
  EXPECT_CALL(*mock_ct_verifier(), Verify(_, kOcspResponse, kSctList, _, _, _))
      .WillRepeatedly(testing::SetArgPointee<4>(sct_and_status_list));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS));
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), kHostname, kOcspResponse, kSctList, /*flags=*/0,
           &verify_result, &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERTIFICATE_TRANSPARENCY_REQUIRED));
    ASSERT_EQ(verify_result.scts.size(), 1u);
    EXPECT_EQ(verify_result.scts.front().status, kSctVerifyStatus);
    EXPECT_EQ(verify_result.scts.front().sct->log_id, kLogId);
    EXPECT_EQ(verify_result.policy_compliance,
              ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS);
    EXPECT_EQ(verify_result.ct_requirement_status,
              ct::CTRequirementsStatus::CT_REQUIREMENTS_NOT_MET);
  }

  // Use a mock RequireCTDelegate that returns CTRequirementLevel::NOT_REQUIRED.
  // Verification should succeed.
  mock_require_ct_delegate = base::MakeRefCounted<MockRequireCTDelegate>();
  instance_params.require_ct_delegate = mock_require_ct_delegate;
  EXPECT_CALL(*mock_require_ct_delegate, IsCTRequiredForHost(kHostname, _, _))
      .WillRepeatedly(
          testing::Return(RequireCTDelegate::CTRequirementLevel::NOT_REQUIRED));
  InitializeVerifyProc(instance_params);
  EXPECT_CALL(*mock_ct_verifier(), Verify(_, kOcspResponse, kSctList, _, _, _))
      .WillRepeatedly(testing::SetArgPointee<4>(sct_and_status_list));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS));
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), kHostname, kOcspResponse, kSctList, /*flags=*/0,
           &verify_result, &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
    ASSERT_EQ(verify_result.scts.size(), 1u);
    EXPECT_EQ(verify_result.scts.front().status, kSctVerifyStatus);
    EXPECT_EQ(verify_result.scts.front().sct->log_id, kLogId);
    EXPECT_EQ(verify_result.policy_compliance,
              ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS);
    EXPECT_EQ(verify_result.ct_requirement_status,
              ct::CTRequirementsStatus::CT_REQUIREMENT_OVERRIDDEN);
  }
}

TEST_F(CertVerifyProcBuiltinTest, CtIsRequiredAndCtVerificationComplies) {
  constexpr char kHostname[] = "www.example.com";
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  CertVerifyProc::InstanceParams instance_params = CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()});
  auto mock_require_ct_delegate = base::MakeRefCounted<MockRequireCTDelegate>();
  instance_params.require_ct_delegate = mock_require_ct_delegate;
  // Since the CT policy complies, the delegate should not be called.
  EXPECT_CALL(*mock_require_ct_delegate, IsCTRequiredForHost(kHostname, _, _))
      .Times(0);
  InitializeVerifyProc(instance_params);
  net::ScopedTestKnownRoot scoped_known_root(root->GetX509Certificate().get());

  const std::string kOcspResponse = "OCSP response";
  const std::string kSctList = "SCT list";
  const std::string kLogId = "CT log id";
  const ct::SCTVerifyStatus kSctVerifyStatus = ct::SCT_STATUS_LOG_UNKNOWN;

  SignedCertificateTimestampAndStatus sct_and_status;
  sct_and_status.sct = base::MakeRefCounted<ct::SignedCertificateTimestamp>();
  sct_and_status.sct->log_id = kLogId;
  sct_and_status.status = kSctVerifyStatus;
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.push_back(sct_and_status);

  InitializeVerifyProc(instance_params);
  EXPECT_CALL(*mock_ct_verifier(), Verify(_, kOcspResponse, kSctList, _, _, _))
      .WillRepeatedly(testing::SetArgPointee<4>(sct_and_status_list));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(leaf->GetX509CertificateChain().get(), kHostname, kOcspResponse,
         kSctList, /*flags=*/0, &verify_result, &verify_net_log_source,
         callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
  ASSERT_EQ(verify_result.scts.size(), 1u);
  EXPECT_EQ(verify_result.scts.front().status, kSctVerifyStatus);
  EXPECT_EQ(verify_result.scts.front().sct->log_id, kLogId);
  EXPECT_EQ(verify_result.policy_compliance,
            ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS);
  EXPECT_EQ(verify_result.ct_requirement_status,
            ct::CTRequirementsStatus::CT_REQUIREMENTS_MET);
}

TEST_F(CertVerifyProcBuiltinTest, DefaultCtComplianceIsNotAvailable) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  const std::string kOcspResponse = "OCSP response";
  const std::string kSctList = "SCT list";
  const std::string kLogId = "CT log id";
  const ct::SCTVerifyStatus kSctVerifyStatus = ct::SCT_STATUS_OK;

  SignedCertificateTimestampAndStatus sct_and_status;
  sct_and_status.sct = base::MakeRefCounted<ct::SignedCertificateTimestamp>();
  sct_and_status.sct->log_id = kLogId;
  sct_and_status.status = kSctVerifyStatus;
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.push_back(sct_and_status);
  EXPECT_CALL(*mock_ct_verifier(), Verify(_, kOcspResponse, kSctList, _, _, _))
      .WillOnce(testing::SetArgPointee<4>(sct_and_status_list));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", kOcspResponse, kSctList, /*flags=*/0,
         &verify_result, &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  ASSERT_EQ(verify_result.scts.size(), 1u);
  EXPECT_EQ(verify_result.scts.front().status, kSctVerifyStatus);
  EXPECT_EQ(verify_result.scts.front().sct->log_id, kLogId);
  // Verification failed, so CT policy compliance isn't checked, and the default
  // value should be COMPLIANCE_DETAILS_NOT_AVAILABLE.
  EXPECT_EQ(verify_result.policy_compliance,
            ct::CTPolicyCompliance::CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE);
}

TEST_F(CertVerifyProcBuiltinTest,
       DefaultCtComplianceIsNotAvailableWhenCtDisabled) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  CertVerifyProc::InstanceParams instance_params = CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()});
  InitializeVerifyProc(instance_params);
  net::ScopedTestKnownRoot scoped_known_root(root->GetX509Certificate().get());

  const std::string kOcspResponse = "OCSP response";
  const std::string kSctList = "SCT list";
  const std::string kLogId = "CT log id";
  const ct::SCTVerifyStatus kSctVerifyStatus = ct::SCT_STATUS_OK;

  SignedCertificateTimestampAndStatus sct_and_status;
  sct_and_status.sct = base::MakeRefCounted<ct::SignedCertificateTimestamp>();
  sct_and_status.sct->log_id = kLogId;
  sct_and_status.status = kSctVerifyStatus;
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.push_back(sct_and_status);
  EXPECT_CALL(*mock_ct_verifier(), Verify(_, kOcspResponse, kSctList, _, _, _))
      .WillOnce(testing::SetArgPointee<4>(sct_and_status_list));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(false));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", kOcspResponse, kSctList, /*flags=*/0,
         &verify_result, &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
  ASSERT_EQ(verify_result.scts.size(), 1u);
  EXPECT_EQ(verify_result.scts.front().status, kSctVerifyStatus);
  EXPECT_EQ(verify_result.scts.front().sct->log_id, kLogId);
  // CT is not enabled, so CT policy compliance isn't checked, and the default
  // value should be COMPLIANCE_DETAILS_NOT_AVAILABLE.
  EXPECT_EQ(verify_result.policy_compliance,
            ct::CTPolicyCompliance::CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE);
  EXPECT_EQ(verify_result.ct_requirement_status,
            ct::CTRequirementsStatus::CT_NOT_REQUIRED);
}

#if defined(PLATFORM_USES_CHROMIUM_EV_METADATA)
TEST_F(CertVerifyProcBuiltinTest, EVCertStatusMaintainedForCompliantCert) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();

  static const char kEVTestCertPolicy[] = "1.2.3.4";
  leaf->SetCertificatePolicies({kEVTestCertPolicy});
  ScopedTestEVPolicy scoped_test_ev_policy(
      EVRootCAMetadata::GetInstance(),
      X509Certificate::CalculateFingerprint256(root->GetCertBuffer()),
      kEVTestCertPolicy);
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  EXPECT_CALL(*mock_ct_verifier(), Verify(_, _, _, _, _, _));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
  EXPECT_EQ(verify_result.policy_compliance,
            ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS);
  EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV);
}
#endif

TEST_F(CertVerifyProcBuiltinTest, DistrustedIntermediate) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()},
      /*additional_trust_anchors_with_enforced_constraints=*/{},
      /*additional_distrusted_certificates=*/
      {intermediate->GetX509Certificate()}));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  EXPECT_EQ(1u, verify_result.verified_cert->intermediate_buffers().size());
}

TEST_F(CertVerifyProcBuiltinTest, AddedRootWithConstraints) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  root->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.org"},
                                   /*excluded_dns_names=*/{});
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{},
      /*additional_trust_anchors_with_enforced_constraints=*/
      {root->GetX509Certificate()},
      /*additional_distrusted_certificates=*/{}));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  // Doesn't chain back to any valid root.
  EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
}

TEST_F(CertVerifyProcBuiltinTest, AddedRootWithConstraintsNotEnforced) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  root->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.org"},
                                   /*excluded_dns_names=*/{});
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()},
      /*additional_trust_anchors_with_enforced_constraints=*/{},
      /*additional_distrusted_certificates=*/{}));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  // Constraint isn't enforced.
  EXPECT_THAT(error, IsOk());
}

TEST_F(CertVerifyProcBuiltinTest, AddedRootWithOutsideDNSConstraints) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  CertVerifyProc::InstanceParams instance_params;

  std::shared_ptr<const bssl::ParsedCertificate> root_cert =
      bssl::ParsedCertificate::Create(
          bssl::UpRef(root->GetX509Certificate()->cert_buffer()),
          net::x509_util::DefaultParseCertificateOptions(), nullptr);
  ASSERT_TRUE(root_cert);
  CertVerifyProc::CertificateWithConstraints cert_with_constraints;
  cert_with_constraints.certificate = std::move(root_cert);
  cert_with_constraints.permitted_dns_names.push_back("example.com");

  instance_params.additional_trust_anchors_with_constraints.push_back(
      cert_with_constraints);

  InitializeVerifyProc(instance_params);

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
}

TEST_F(CertVerifyProcBuiltinTest,
       AddedRootWithOutsideDNSConstraintsNotMatched) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  CertVerifyProc::InstanceParams instance_params;

  std::shared_ptr<const bssl::ParsedCertificate> root_cert =
      bssl::ParsedCertificate::Create(
          bssl::UpRef(root->GetX509Certificate()->cert_buffer()),
          net::x509_util::DefaultParseCertificateOptions(), nullptr);
  ASSERT_TRUE(root_cert);
  CertVerifyProc::CertificateWithConstraints cert_with_constraints;
  cert_with_constraints.certificate = std::move(root_cert);
  cert_with_constraints.permitted_dns_names.push_back("foobar.com");

  instance_params.additional_trust_anchors_with_constraints.push_back(
      cert_with_constraints);

  InitializeVerifyProc(instance_params);

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
}

TEST_F(CertVerifyProcBuiltinTest, AddedRootWithOutsideCIDRConstraints) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  CertVerifyProc::InstanceParams instance_params;

  std::shared_ptr<const bssl::ParsedCertificate> root_cert =
      bssl::ParsedCertificate::Create(
          bssl::UpRef(root->GetX509Certificate()->cert_buffer()),
          net::x509_util::DefaultParseCertificateOptions(), nullptr);
  ASSERT_TRUE(root_cert);
  CertVerifyProc::CertificateWithConstraints cert_with_constraints;
  cert_with_constraints.certificate = std::move(root_cert);
  cert_with_constraints.permitted_cidrs.push_back(
      {net::IPAddress(192, 168, 1, 104), net::IPAddress(255, 255, 255, 0)});

  instance_params.additional_trust_anchors_with_constraints.push_back(
      cert_with_constraints);

  InitializeVerifyProc(instance_params);

  leaf->SetSubjectAltNames(/*dns_names=*/{"www.example.com"},
                           /*ip_addresses=*/{net::IPAddress(192, 168, 1, 254)});
  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
}

TEST_F(CertVerifyProcBuiltinTest,
       AddedRootWithOutsideCIDRConstraintsNotMatched) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  CertVerifyProc::InstanceParams instance_params = CreateParams({});

  std::shared_ptr<const bssl::ParsedCertificate> root_cert =
      bssl::ParsedCertificate::Create(
          bssl::UpRef(root->GetX509Certificate()->cert_buffer()),
          net::x509_util::DefaultParseCertificateOptions(), nullptr);
  ASSERT_TRUE(root_cert);
  CertVerifyProc::CertificateWithConstraints cert_with_constraints;
  cert_with_constraints.certificate = std::move(root_cert);
  cert_with_constraints.permitted_cidrs.push_back(
      {net::IPAddress(192, 168, 1, 1), net::IPAddress(255, 255, 255, 0)});

  instance_params.additional_trust_anchors_with_constraints.push_back(
      cert_with_constraints);

  InitializeVerifyProc(instance_params);

  leaf->SetSubjectAltNames(/*dns_names=*/{"www.example.com"},
                           /*ip_addresses=*/{net::IPAddress(10, 2, 2, 2)});
  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
}

TEST_F(CertVerifyProcBuiltinTest, AddedRootWithBadTime) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  root->SetValidity(/*not_before=*/base::Time::Now() - base::Days(10),
                    /*not_after=*/base::Time::Now() - base::Days(5));
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{},
      /*additional_trust_anchors_with_enforced_constraints=*/
      {root->GetX509Certificate()},
      /*additional_distrusted_certificates=*/{}));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  // Root is valid but expired and we check it.
  EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID));
}

TEST_F(CertVerifyProcBuiltinTest, AddedRootWithBadTimeButNotEnforced) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  root->SetValidity(/*not_before=*/base::Time::Now() - base::Days(10),
                    /*not_after=*/base::Time::Now() - base::Days(5));
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()},
      /*additional_trust_anchors_with_enforced_constraints=*/{},
      /*additional_distrusted_certificates=*/{}));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  // Root is valid but expired, but we don't check it.
  EXPECT_THAT(error, IsOk());
}

TEST_F(CertVerifyProcBuiltinTest, TimeTracker) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  root->SetValidity(/*not_before=*/base::Time::Now() - base::Days(10),
                    /*not_after=*/base::Time::Now() - base::Days(5));
  InitializeVerifyProc(
      CreateParams(
          /*additional_trust_anchors=*/{},
          /*additional_trust_anchors_with_enforced_constraints=*/
          {root->GetX509Certificate()},
          /*additional_distrusted_certificates=*/{}),
      base::Time::Now() - base::Days(7));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  // Root is expired when compared to base::Time::Now, but is valid in the
  // time provided by the time tracker.
  EXPECT_THAT(error, IsOk());
}

TEST_F(CertVerifyProcBuiltinTest, TimeTrackerFailureIsRetriedWithSystemTime) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  root->SetValidity(/*not_before=*/base::Time::Now() - base::Days(10),
                    /*not_after=*/base::Time::Now() + base::Days(10));
  InitializeVerifyProc(
      CreateParams(
          /*additional_trust_anchors=*/{},
          /*additional_trust_anchors_with_enforced_constraints=*/
          {root->GetX509Certificate()},
          /*additional_distrusted_certificates=*/{}),
      base::Time::Now() + base::Days(20));

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*flags=*/0, &verify_result,
         &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  // Root is expired when compared to the time tracker time, but valid when
  // compared to base::Time::Now.
  EXPECT_THAT(error, IsOk());
}

TEST_F(CertVerifyProcBuiltinTest,
       TimeTrackerRevocationFailureIsRetriedWithSystemTime) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  root->SetValidity(/*not_before=*/base::Time::Now() - base::Days(3),
                    /*not_after=*/base::Time::Now() + base::Days(2));
  // The CRL DP sets its this_update time to base::Time::Now() - 1 day. Use two
  // days before now as the current time to cause checks to fail with
  // UNABLE_TO_CHECK_REVOCATION, which then should be retried with the system
  // time and succeed.
  InitializeVerifyProc(
      CreateParams(
          /*additional_trust_anchors=*/{},
          /*additional_trust_anchors_with_enforced_constraints=*/
          {root->GetX509Certificate()},
          /*additional_distrusted_certificates=*/{}),
      base::Time::Now() - base::Days(2));

  EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTP);
  ASSERT_TRUE(test_server.InitializeAndListen());
  // Valid CRL that does not mark the leaf as revoked.
  leaf->SetCrlDistributionPointUrl(
      CreateAndServeCrl(&test_server, root.get(), {1234}));
  test_server.StartAcceptingConnections();

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com",
         CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS,
         &verify_result, &verify_net_log_source, callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
}

TEST_F(CertVerifyProcBuiltinTest, CRLNotCheckedForKnownRoots) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTP);
  ASSERT_TRUE(test_server.InitializeAndListen());

  // CRL that marks leaf as revoked.
  leaf->SetCrlDistributionPointUrl(
      CreateAndServeCrl(&test_server, root.get(), {leaf->GetSerialNumber()}));

  test_server.StartAcceptingConnections();

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  NetLogSource verify_net_log_source;

  {
    CertVerifyResult verify_result;
    TestCompletionCallback verify_callback;
    Verify(chain.get(), "www.example.com",
           CertVerifyProc::VERIFY_REV_CHECKING_ENABLED,
           &verify_result, &verify_net_log_source, verify_callback.callback());

    int error = verify_callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_REVOKED));
    EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
  }

  {
    // Pretend the root is a known root.
    SetMockIsKnownRoot(true);
    CertVerifyResult verify_result;
    TestCompletionCallback verify_callback;
    Verify(chain.get(), "www.example.com",
           CertVerifyProc::VERIFY_REV_CHECKING_ENABLED,
           &verify_result, &verify_net_log_source, verify_callback.callback());

    int error = verify_callback.WaitForResult();
    // CRLs are not checked for chains issued by known roots, so verification
    // should be successful.
    EXPECT_THAT(error, IsOk());
    EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
  }
}

// Tests that if the verification deadline is exceeded during revocation
// checking, additional CRL fetches will not be attempted.
TEST_F(CertVerifyProcBuiltinTest, RevocationCheckDeadlineCRL) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  const base::TimeDelta timeout_increment =
      CertNetFetcherURLRequest::GetDefaultTimeoutForTesting() +
      base::Milliseconds(1);
  const int expected_request_count =
      base::ClampFloor(GetCertVerifyProcBuiltinTimeLimitForTesting() /
                       timeout_increment) +
      1;

  EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTP);
  ASSERT_TRUE(test_server.InitializeAndListen());

  // Set up the test cert to have enough crlDistributionPoint urls that if the
  // first N-1 requests hang the deadline will be exceeded before the Nth
  // request is made.
  std::vector<GURL> crl_urls;
  std::vector<base::RunLoop> runloops(expected_request_count);
  for (int i = 0; i < expected_request_count; ++i) {
    std::string path = base::StringPrintf("/hung/%i", i);
    crl_urls.emplace_back(test_server.GetURL(path));
    test_server.RegisterRequestHandler(
        base::BindRepeating(&test_server::HandlePrefixedRequest, path,
                            base::BindRepeating(&HangRequestAndCallback,
                                                runloops[i].QuitClosure())));
  }
  // Add CRL URLs and handlers that will add test failures if requested.
  for (int i = expected_request_count; i < expected_request_count + 1; ++i) {
    std::string path = base::StringPrintf("/failtest/%i", i);
    crl_urls.emplace_back(test_server.GetURL(path));
    test_server.RegisterRequestHandler(base::BindRepeating(
        &test_server::HandlePrefixedRequest, path,
        base::BindRepeating(FailRequestAndFailTest,
                            "additional request made after deadline exceeded",
                            base::SequencedTaskRunner::GetCurrentDefault())));
  }
  leaf->SetCrlDistributionPointUrls(crl_urls);

  test_server.StartAcceptingConnections();

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback verify_callback;
  Verify(chain.get(), "www.example.com",
         CertVerifyProc::VERIFY_REV_CHECKING_ENABLED,
         &verify_result, &verify_net_log_source, verify_callback.callback());

  for (int i = 0; i < expected_request_count; i++) {
    // Wait for request #|i| to be made.
    runloops[i].Run();
    // Advance virtual time to cause the timeout task to become runnable.
    task_environment().AdvanceClock(timeout_increment);
  }

  // Once |expected_request_count| requests have been made and timed out, the
  // overall deadline should be reached, and no more requests should have been
  // made. (If they were, the test will fail due to the ADD_FAILURE callback in
  // the request handlers.)
  int error = verify_callback.WaitForResult();
  // Soft-fail revocation checking was used, therefore verification result
  // should be OK even though none of the CRLs could be retrieved.
  EXPECT_THAT(error, IsOk());
}

// Tests that if the verification deadline is exceeded during revocation
// checking, additional OCSP fetches will not be attempted.
TEST_F(CertVerifyProcBuiltinTest, RevocationCheckDeadlineOCSP) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  const base::TimeDelta timeout_increment =
      CertNetFetcherURLRequest::GetDefaultTimeoutForTesting() +
      base::Milliseconds(1);
  const int expected_request_count =
      base::ClampFloor(GetCertVerifyProcBuiltinTimeLimitForTesting() /
                       timeout_increment) +
      1;

  EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTP);
  ASSERT_TRUE(test_server.InitializeAndListen());

  // Set up the test cert to have enough OCSP urls that if the
  // first N-1 requests hang the deadline will be exceeded before the Nth
  // request is made.
  std::vector<GURL> ocsp_urls;
  std::vector<base::RunLoop> runloops(expected_request_count);
  for (int i = 0; i < expected_request_count; ++i) {
    std::string path = base::StringPrintf("/hung/%i", i);
    ocsp_urls.emplace_back(test_server.GetURL(path));
    test_server.RegisterRequestHandler(
        base::BindRepeating(&test_server::HandlePrefixedRequest, path,
                            base::BindRepeating(&HangRequestAndCallback,
                                                runloops[i].QuitClosure())));
  }
  // Add OCSP URLs and handlers that will add test failures if requested.
  for (int i = expected_request_count; i < expected_request_count + 1; ++i) {
    std::string path = base::StringPrintf("/failtest/%i", i);
    ocsp_urls.emplace_back(test_server.GetURL(path));
    test_server.RegisterRequestHandler(base::BindRepeating(
        &test_server::HandlePrefixedRequest, path,
        base::BindRepeating(FailRequestAndFailTest,
                            "additional request made after deadline exceeded",
                            base::SequencedTaskRunner::GetCurrentDefault())));
  }
  leaf->SetCaIssuersAndOCSPUrls({}, ocsp_urls);

  test_server.StartAcceptingConnections();

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback verify_callback;
  Verify(chain.get(), "www.example.com",
         CertVerifyProc::VERIFY_REV_CHECKING_ENABLED,
         &verify_result, &verify_net_log_source, verify_callback.callback());

  for (int i = 0; i < expected_request_count; i++) {
    // Wait for request #|i| to be made.
    runloops[i].Run();
    // Advance virtual time to cause the timeout task to become runnable.
    task_environment().AdvanceClock(timeout_increment);
  }

  // Once |expected_request_count| requests have been made and timed out, the
  // overall deadline should be reached, and no more requests should have been
  // made. (If they were, the test will fail due to the ADD_FAILURE callback in
  // the request handlers.)
  int error = verify_callback.WaitForResult();
  // Soft-fail revocation checking was used, therefore verification result
  // should be OK even though none of the OCSP responses could be retrieved.
  EXPECT_THAT(error, IsOk());
}

#if defined(PLATFORM_USES_CHROMIUM_EV_METADATA)
// Tests that if we're doing EV verification, that no OCSP revocation checking
// is done.
TEST_F(CertVerifyProcBuiltinTest, EVNoOCSPRevocationChecks) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  // Add test EV policy to leaf and intermediate.
  static const char kEVTestCertPolicy[] = "1.2.3.4";
  leaf->SetCertificatePolicies({kEVTestCertPolicy});
  intermediate->SetCertificatePolicies({kEVTestCertPolicy});

  EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTP);
  ASSERT_TRUE(test_server.InitializeAndListen());

  // Set up the test intermediate to have an OCSP url that fails the test if
  // called.
  std::vector<GURL> ocsp_urls;
  std::string path = "/failtest";
  ocsp_urls.emplace_back(test_server.GetURL(path));
  test_server.RegisterRequestHandler(base::BindRepeating(
      &test_server::HandlePrefixedRequest, path,
      base::BindRepeating(FailRequestAndFailTest,
                          "no OCSP requests should be sent",
                          base::SequencedTaskRunner::GetCurrentDefault())));
  intermediate->SetCaIssuersAndOCSPUrls({}, ocsp_urls);
  test_server.StartAcceptingConnections();

  // Consider the root of the test chain a valid EV root for the test policy.
  ScopedTestEVPolicy scoped_test_ev_policy(
      EVRootCAMetadata::GetInstance(),
      X509Certificate::CalculateFingerprint256(root->GetCertBuffer()),
      kEVTestCertPolicy);

  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback verify_callback;
  Verify(chain.get(), "www.example.com",
         /*flags=*/0,
         &verify_result, &verify_net_log_source, verify_callback.callback());

  // EV doesn't do revocation checking, therefore verification result
  // should be OK and EV.
  int error = verify_callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
  EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV);
  EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED);

  auto events = net_log_observer.GetEntriesForSource(verify_net_log_source);

  auto event = std::ranges::find(
      events, NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT,
      &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase);
  EXPECT_EQ(true, event->params.FindBool("is_ev_attempt"));

  event = std::ranges::find(++event, events.end(),
                            NetLogEventType::CERT_VERIFY_PROC_PATH_BUILT,
                            &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase);

  event = std::ranges::find(++event, events.end(),
                            NetLogEventType::CERT_VERIFY_PROC_PATH_BUILT,
                            &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::END, event->phase);
  EXPECT_FALSE(event->params.FindString("errors"));

  event = std::ranges::find(
      ++event, events.end(),
      NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT, &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::END, event->phase);
  EXPECT_EQ(true, event->params.FindBool("has_valid_path"));
}
#endif  // defined(PLATFORM_USES_CHROMIUM_EV_METADATA)

#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
TEST_F(CertVerifyProcBuiltinTest, ChromeRootStoreVersionNetLog) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  for (const bool has_crs_ver : {false, true}) {
    SCOPED_TRACE(has_crs_ver);
    for (const bool has_signerset_timestamp : {false, true}) {
      SCOPED_TRACE(has_signerset_timestamp);
      for (const bool has_mtc_metadata_time : {false, true}) {
        SCOPED_TRACE(has_mtc_metadata_time);

        const int64_t expected_crs_ver = has_crs_ver ? 42 : 0;

        SetMockCRSVersion(expected_crs_ver);
        SetMockSignerSetTimestamp(
            has_signerset_timestamp
                ? std::make_optional(
                      base::Time::FromMillisecondsSinceUnixEpoch(1234000))
                : std::nullopt);
        SetMockMtcMetadataUpdateTime(
            has_mtc_metadata_time
                ? std::make_optional(
                      base::Time::FromMillisecondsSinceUnixEpoch(987000))
                : std::nullopt);

        RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
        CertVerifyResult verify_result;
        NetLogSource verify_net_log_source;
        TestCompletionCallback verify_callback;
        Verify(chain.get(), "www.example.com",
               /*flags=*/0, &verify_result, &verify_net_log_source,
               verify_callback.callback());
        EXPECT_THAT(verify_callback.WaitForResult(), IsOk());
        auto events = net_log_observer.GetEntriesWithType(
            NetLogEventType::CERT_VERIFY_PROC_CHROME_ROOT_STORE_VERSION);
        if (!has_crs_ver && !has_signerset_timestamp &&
            !has_mtc_metadata_time) {
          ASSERT_EQ(0U, events.size());
        } else {
          ASSERT_EQ(1U, events.size());
          ASSERT_TRUE(events[0].HasParams());
          EXPECT_EQ(expected_crs_ver,
                    events[0].params.FindInt("version_major"));
          EXPECT_EQ(
              has_signerset_timestamp ? std::make_optional(1234) : std::nullopt,
              events[0].params.FindInt("signer_set_timestamp"));
          EXPECT_EQ(
              has_mtc_metadata_time ? std::make_optional(987) : std::nullopt,
              events[0].params.FindInt("mtc_metadata_update_time"));
        }
      }
    }
  }
}

scoped_refptr<ct::SignedCertificateTimestamp> MakeSct(base::Time t,
                                                      std::string_view log_id) {
  auto sct = base::MakeRefCounted<ct::SignedCertificateTimestamp>();
  sct->timestamp = t;
  sct->log_id = log_id;
  return sct;
}

// Test SCT constraints fail-open if CT is disabled.
TEST_F(CertVerifyProcBuiltinTest,
       ChromeRootStoreConstraintSctConstraintsWithCtDisabled) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(false));
  EXPECT_CALL(*mock_ct_verifier(), Verify(_, _, _, _, _, _)).Times(2);

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  SetMockChromeRootConstraints(
      {{.sct_not_after = base::Time::Now() - base::Days(365)}});

  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
    ASSERT_EQ(verify_result.scts.size(), 0u);
  }

  SetMockChromeRootConstraints(
      {{.sct_all_after = base::Time::Now() + base::Days(365)}});

  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
    ASSERT_EQ(verify_result.scts.size(), 0u);
  }
}

// Test SCT constraints fail-open if CT is enabled but the log list is stale.
TEST_F(CertVerifyProcBuiltinTest,
       ChromeRootStoreConstraintSctConstraintsWithCtListStale) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsLogDataTimely(_))
      .WillRepeatedly(testing::Return(false));
  EXPECT_CALL(*mock_ct_verifier(), Verify(_, _, _, _, _, _)).Times(2);

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  SetMockChromeRootConstraints(
      {{.sct_not_after = base::Time::Now() - base::Days(365)}});

  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
    ASSERT_EQ(verify_result.scts.size(), 0u);
  }

  SetMockChromeRootConstraints(
      {{.sct_all_after = base::Time::Now() + base::Days(365)}});

  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           /*sct_list=*/std::string(), /*flags=*/0, &verify_result,
           &verify_net_log_source, callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
    ASSERT_EQ(verify_result.scts.size(), 0u);
  }
}

// Test SctNotAfter constraint only requires 1 valid SCT that satisfies the
// constraint.
// Set a SctNotAfter constraint at time t1.
// Mock that there are two SCTs, one of which is at t1 and thus satisfies the
// constraint. The second is at t2 and does not satisfy the constraint, but
// this is ok as only one valid SCT that meets the constraint is needed.
TEST_F(CertVerifyProcBuiltinTest, ChromeRootStoreConstraintSctNotAfter) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  const std::string kSctList = "SCT list";
  const std::string kLog1 = "log1";
  const std::string kLog2 = "log2";
  base::Time now = base::Time::Now();
  base::Time t1 = now - base::Days(2);
  base::Time t2 = now - base::Days(1);
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.emplace_back(MakeSct(t1, kLog1), ct::SCT_STATUS_OK);
  sct_and_status_list.emplace_back(MakeSct(t2, kLog2), ct::SCT_STATUS_OK);

  EXPECT_CALL(*mock_ct_verifier(), Verify(_, _, kSctList, _, _, _))
      .WillRepeatedly(testing::SetArgPointee<4>(sct_and_status_list));

  SetMockChromeRootConstraints({{.sct_not_after = t1}});

  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsLogDataTimely(_))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), GetLogDisqualificationTime(kLog1))
      .WillRepeatedly(testing::Return(std::nullopt));
  EXPECT_CALL(*mock_ct_policy_enforcer(), GetLogDisqualificationTime(kLog2))
      .WillRepeatedly(testing::Return(std::nullopt));
  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           kSctList, /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
    ASSERT_EQ(verify_result.scts.size(), 2u);
  }

  // Try again with the SctNotAfter set to before both SCTs. Verification should
  // fail.
  SetMockChromeRootConstraints({{.sct_not_after = t1 - base::Seconds(1)}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           kSctList, /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
    ASSERT_EQ(verify_result.scts.size(), 2u);
  }
}

// Test SctNotAfter constraint is only satisfied by successfully verified SCTs.
// Set a SctNotAfter constraint at time t1.
// Mock that there are two SCTs. One SCT for time t1 but from an unknown log,
// thus should not be usable for the SctNotAfter constraint. The second CT is
// from a known log but is at time t2 which is after t1, so does not satisfy
// the constraint. Therefore the certificate should fail verification.
TEST_F(CertVerifyProcBuiltinTest,
       ChromeRootStoreConstraintSctNotAfterLogUnknown) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  const std::string kSctList = "SCT list";
  const std::string kLog1 = "log1";
  const std::string kLog2 = "log2";
  base::Time now = base::Time::Now();
  base::Time t1 = now - base::Days(2);
  base::Time t2 = now - base::Days(1);
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.emplace_back(MakeSct(t1, kLog1),
                                   ct::SCT_STATUS_LOG_UNKNOWN);
  sct_and_status_list.emplace_back(MakeSct(t2, kLog2), ct::SCT_STATUS_OK);

  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsLogDataTimely(_))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_verifier(), Verify(_, _, kSctList, _, _, _))
      .WillOnce(testing::SetArgPointee<4>(sct_and_status_list));

  SetMockChromeRootConstraints({{.sct_not_after = t1}});

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
         kSctList, /*flags=*/0, &verify_result, &verify_net_log_source,
         callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  ASSERT_EQ(verify_result.scts.size(), 2u);
}

// Test SctNotAfter constraint is not satisfied by a SCT from a disqualified
// log even if the SCT timestamp is before the log was disqualified. Once a log
// is disqualified we assume it can not be trusted and could sign SCTs for any
// timestamp.
// SCT #1 is from a disqualified log and the timestamp is before the log was
// disqualified.
// SCT #2 is from a valid log but is after the SctNotAfter constraint, so does
// not satisfy the constraint.
TEST_F(
    CertVerifyProcBuiltinTest,
    ChromeRootStoreConstraintSctNotAfterFromDisqualifiedLogBeforeDisqualification) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  const std::string kSctList = "SCT list";
  const std::string kLog1 = "log1";
  const std::string kLog2 = "log2";
  base::Time now = base::Time::Now();
  base::Time t1 = now - base::Days(2);
  base::Time t2 = now - base::Days(1);
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.emplace_back(MakeSct(t1, kLog1), ct::SCT_STATUS_OK);
  sct_and_status_list.emplace_back(MakeSct(t2, kLog2), ct::SCT_STATUS_OK);

  EXPECT_CALL(*mock_ct_verifier(), Verify(_, _, kSctList, _, _, _))
      .WillOnce(testing::SetArgPointee<4>(sct_and_status_list));

  SetMockChromeRootConstraints({{.sct_not_after = t1}});

  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsLogDataTimely(_))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), GetLogDisqualificationTime(kLog1))
      .WillRepeatedly(testing::Return(t2));
  EXPECT_CALL(*mock_ct_policy_enforcer(), GetLogDisqualificationTime(kLog2))
      .WillRepeatedly(testing::Return(std::nullopt));

  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
         kSctList, /*flags=*/0, &verify_result, &verify_net_log_source,
         callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
}

// Test SctNotAfter constraint is not satisfied by a SCT from a disqualified
// log if the SCT timestamp is after the log was disqualified.
// SCT #1 is from a disqualified log and the timestamp is after the log was
// disqualified.
// SCT #2 is from a valid log but is after the SctNotAfter constraint, so does
// not satisfy the constraint.
TEST_F(
    CertVerifyProcBuiltinTest,
    ChromeRootStoreConstraintSctNotAfterFromDisqualifiedLogAfterDisqualification) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  const std::string kSctList = "SCT list";
  const std::string kLog1 = "log1";
  const std::string kLog2 = "log2";
  base::Time now = base::Time::Now();
  base::Time t1 = now - base::Days(2);
  base::Time t2 = now - base::Days(1);
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.emplace_back(MakeSct(t1, kLog1), ct::SCT_STATUS_OK);
  sct_and_status_list.emplace_back(MakeSct(t2, kLog2), ct::SCT_STATUS_OK);

  EXPECT_CALL(*mock_ct_verifier(), Verify(_, _, kSctList, _, _, _))
      .WillOnce(testing::SetArgPointee<4>(sct_and_status_list));

  SetMockChromeRootConstraints({{.sct_not_after = t1}});

  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsLogDataTimely(_))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), GetLogDisqualificationTime(kLog1))
      .WillRepeatedly(testing::Return(t1));
  EXPECT_CALL(*mock_ct_policy_enforcer(), GetLogDisqualificationTime(kLog2))
      .WillRepeatedly(testing::Return(std::nullopt));

  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
         kSctList, /*flags=*/0, &verify_result, &verify_net_log_source,
         callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
}

// Test SctNotAfter constraint is satisfied by a SCT from a disqualified
// log if the log disqualification time is in the future.
TEST_F(CertVerifyProcBuiltinTest,
       ChromeRootStoreConstraintSctNotAfterFromFutureDisqualifiedLog) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  const std::string kSctList = "SCT list";
  const std::string kLog1 = "log1";
  const std::string kLog2 = "log2";
  base::Time now = base::Time::Now();
  base::Time t1 = now - base::Days(2);
  base::Time future_t = now + base::Days(1);
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.emplace_back(MakeSct(t1, kLog1), ct::SCT_STATUS_OK);

  EXPECT_CALL(*mock_ct_verifier(), Verify(_, _, kSctList, _, _, _))
      .WillOnce(testing::SetArgPointee<4>(sct_and_status_list));

  SetMockChromeRootConstraints({{.sct_not_after = t1}});

  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsLogDataTimely(_))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), GetLogDisqualificationTime(kLog1))
      .WillRepeatedly(testing::Return(future_t));

  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
         kSctList, /*flags=*/0, &verify_result, &verify_net_log_source,
         callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
}

// Test SctAllAfter constraint requires all valid SCTs to satisfy the
// constraint.
TEST_F(CertVerifyProcBuiltinTest, ChromeRootStoreConstraintSctAllAfter) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  const std::string kSctList = "SCT list";
  const std::string kLog1 = "log1";
  const std::string kLog2 = "log2";
  base::Time now = base::Time::Now();
  base::Time t0 = now - base::Days(3);
  base::Time t1 = now - base::Days(2);
  base::Time t2 = now - base::Days(1);
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.emplace_back(MakeSct(t1, kLog1), ct::SCT_STATUS_OK);
  sct_and_status_list.emplace_back(MakeSct(t2, kLog2), ct::SCT_STATUS_OK);

  EXPECT_CALL(*mock_ct_verifier(), Verify(_, _, kSctList, _, _, _))
      .WillRepeatedly(testing::SetArgPointee<4>(sct_and_status_list));

  // Set a SctAllAfter constraint before the timestamp of either SCT.
  SetMockChromeRootConstraints({{.sct_all_after = t0}});

  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), IsLogDataTimely(_))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_policy_enforcer(), GetLogDisqualificationTime(kLog1))
      .WillRepeatedly(testing::Return(std::nullopt));
  EXPECT_CALL(*mock_ct_policy_enforcer(), GetLogDisqualificationTime(kLog2))
      .WillRepeatedly(testing::Return(std::nullopt));
  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           kSctList, /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
    ASSERT_EQ(verify_result.scts.size(), 2u);
  }

  // Try again with the SctAllAfter set to the same time as one of the SCTs.
  // Verification should now fail.
  SetMockChromeRootConstraints({{.sct_all_after = t1}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
           kSctList, /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
    ASSERT_EQ(verify_result.scts.size(), 2u);
  }
}

std::string CurVersionString() {
  return version_info::GetVersion().GetString();
}
std::string NextVersionString() {
  const std::vector<uint32_t>& components =
      version_info::GetVersion().components();
  return base::Version(
             {components[0], components[1], components[2], components[3] + 1})
      .GetString();
}
std::string PrevVersionString() {
  const std::vector<uint32_t>& components =
      version_info::GetVersion().components();
  if (components[3] > 0) {
    return base::Version(
               {components[0], components[1], components[2], components[3] - 1})
        .GetString();
  } else {
    return base::Version(
               {components[0], components[1], components[2] - 1, UINT32_MAX})
        .GetString();
  }
}

TEST_F(CertVerifyProcBuiltinTest, ChromeRootStoreConstraintMinVersion) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());
  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  SetMockChromeRootConstraints({{.min_version = NextVersionString()}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }

  SetMockChromeRootConstraints({{.min_version = CurVersionString()}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
  }
}

TEST_F(CertVerifyProcBuiltinTest, ChromeRootStoreConstraintMaxVersion) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());
  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  SetMockChromeRootConstraints({{.max_version_exclusive = CurVersionString()}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }

  SetMockChromeRootConstraints(
      {{.max_version_exclusive = NextVersionString()}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
  }
}

TEST_F(CertVerifyProcBuiltinTest, ChromeRootStoreConstraintMinAndMaxVersion) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());
  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  // min_version satisfied, max_version_exclusive not satisfied = not trusted.
  SetMockChromeRootConstraints({{.min_version = PrevVersionString(),
                                 .max_version_exclusive = CurVersionString()}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }

  // min_version not satisfied, max_version_exclusive satisfied = not trusted.
  SetMockChromeRootConstraints(
      {{.min_version = NextVersionString(),
        .max_version_exclusive = NextVersionString()}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }

  // min_version satisfied, max_version_exclusive satisfied = trusted.
  SetMockChromeRootConstraints(
      {{.min_version = CurVersionString(),
        .max_version_exclusive = NextVersionString()}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
  }
}

TEST_F(CertVerifyProcBuiltinTest, ChromeRootStoreConstraintNameConstraints) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  // If the the CRS root has dns name constraints and the cert's names don't
  // match the name constraints, verification should fail.
  {
    std::array<std::string_view, 2> permitted_dns_names = {
        std::string_view("example.org"),
        std::string_view("foo.example.com"),
    };
    SetMockChromeRootConstraints(
        {{.permitted_dns_names = permitted_dns_names}});
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509Certificate(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }

  // If cert's names match the CRS name constraints, verification should
  // succeed.
  {
    std::array<std::string_view, 2> permitted_dns_names = {
        std::string_view("example.org"),
        std::string_view("example.com"),
    };
    SetMockChromeRootConstraints(
        {{.permitted_dns_names = permitted_dns_names}});
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509Certificate(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
  }
}

// Tests multiple constraint objects in the constraints vector. The CRS
// constraints are satisfied if at least one of the constraint objects is
// satisfied.
//
// The first constraint has a SctNotAfter that is before the SCT and thus is
// not satisfied.
// The second constraint has a SctAllAfter set to the same time, which is
// before the certificate SCT, and thus the certificate verification succeeds.
//
// TODO(https://crbug.com/40941039): This test isn't very interesting right
// now. Once more constraint types are added change the test to be more
// realistic of how multiple constraint sets is expected to be used.
TEST_F(CertVerifyProcBuiltinTest,
       ChromeRootStoreConstraintMultipleConstraints) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());

  const std::string kSctList = "SCT list";
  const std::string kLog1 = "log1";
  base::Time now = base::Time::Now();
  base::Time t1 = now - base::Days(2);
  base::Time t2 = now - base::Days(1);
  SignedCertificateTimestampAndStatusList sct_and_status_list;
  sct_and_status_list.emplace_back(MakeSct(t2, kLog1), ct::SCT_STATUS_OK);

  EXPECT_CALL(*mock_ct_policy_enforcer(), IsCtEnabled())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*mock_ct_verifier(), Verify(_, _, kSctList, _, _, _))
      .WillOnce(testing::SetArgPointee<4>(sct_and_status_list));
  EXPECT_CALL(*mock_ct_policy_enforcer(), GetLogDisqualificationTime(kLog1))
      .WillRepeatedly(testing::Return(std::nullopt));
  EXPECT_CALL(*mock_ct_policy_enforcer(), CheckCompliance(_, _, _, _))
      .WillRepeatedly(
          testing::Return(ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS));

  SetMockChromeRootConstraints({{.sct_not_after = t1}, {.sct_all_after = t1}});

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", /*ocsp_response=*/std::string(),
         kSctList, /*flags=*/0, &verify_result, &verify_net_log_source,
         callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
}

TEST_F(CertVerifyProcBuiltinTest,
       ChromeRootStoreConstraintNotEnforcedIfAnchorLocallyTrusted) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  ScopedTestRoot scoped_root(root->GetX509Certificate());
  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  SetMockChromeRootConstraints({{.min_version = NextVersionString()}});
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }

  // If the anchor is trusted locally, the Chrome Root Store constraints should
  // not be enforced.
  SetMockIsLocallyTrustedRoot(true);
  {
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(chain.get(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
  }
}

TEST_F(CertVerifyProcBuiltinTest,
       ChromeRootStoreConstraintNotEnforcedIfAnchorAdditionallyTrusted) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  // The anchor is trusted through additional_trust_anchors, so the Chrome Root
  // Store constraints should not be enforced.
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));
  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  SetMockChromeRootConstraints({{.min_version = NextVersionString()}});

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com",
         /*flags=*/0, &verify_result, &verify_net_log_source,
         callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
}

class CertVerifyProcBuiltin1QwacTest
    : public CertVerifyProcBuiltinTest,
      public testing::WithParamInterface<bool> {
 public:
  CertVerifyProcBuiltin1QwacTest() {
    if (GetParam()) {
      feature_list_.InitAndEnableFeature(features::kVerifyQWACs);
    } else {
      feature_list_.InitAndDisableFeature(features::kVerifyQWACs);
    }
  }

  void ExpectHistogramSample(const base::HistogramTester& histograms,
                             Verify1QwacResult result) {
    if (GetParam()) {
      histograms.ExpectUniqueSample("Net.CertVerifier.Qwac.1Qwac", result, 1u);
    } else {
      histograms.ExpectTotalCount("Net.CertVerifier.Qwac.1Qwac", 0u);
    }
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

TEST_P(CertVerifyProcBuiltin1QwacTest, NotQwac) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{}));
  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509CertificateChain(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
    EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_QWAC);

    // The histogram is not logged if regular verification failed.
    histograms.ExpectTotalCount("Net.CertVerifier.Qwac.1Qwac", 0u);
  }

  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));
  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509CertificateChain(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    EXPECT_THAT(error, IsOk());
    EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_QWAC);

    ExpectHistogramSample(histograms, Verify1QwacResult::kNotQwac);
  }
}

TEST_P(CertVerifyProcBuiltin1QwacTest,
       CanUseEutlCertsAsHintsInNormalPathbuilding) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  // CABF OV, ETSI QNCP-w
  leaf->SetCertificatePolicies({"2.23.140.1.2.2", "0.4.0.194112.1.5"});

  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});

  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509Certificate(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    // The intermediate was not supplied, so verification fails to find a path
    // to the root.
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
    EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_QWAC);
    histograms.ExpectTotalCount("Net.CertVerifier.Qwac.1Qwac", 0u);
  }

  AddMockEutlRoot(intermediate->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509Certificate(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    if (GetParam()) {
      // If the intermediate is on the EUTL, regular path building is able to
      // use it as a hint, so the chain now verifies successfully.
      EXPECT_THAT(error, IsOk());
      EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_QWAC);
      ASSERT_EQ(2u, verify_result.verified_cert->intermediate_buffers().size());
      // The verified chain has the cert chain from the normal TLS verification,
      // not the QWAC verification.
      EXPECT_EQ(intermediate->GetCertBuffer(),
                verify_result.verified_cert->intermediate_buffers()[0].get());
      EXPECT_EQ(root->GetCertBuffer(),
                verify_result.verified_cert->intermediate_buffers()[1].get());
    } else {
      EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
      EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_QWAC);
    }
    ExpectHistogramSample(histograms, Verify1QwacResult::kValid1Qwac);
  }

  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{}));
  AddMockEutlRoot(intermediate->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509Certificate(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    // If the intermediate is an EUTL cert but the root is not trusted,
    // verification should fail. The EUTL certs are only used as hints in
    // the regular path building attempt, but are not trust anchors.
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
    EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_QWAC);
    if (GetParam()) {
      // The path builder should have been able to build the partial path to the
      // hint certificate, but there is no root to build a path to from there.
      ASSERT_EQ(1u, verify_result.verified_cert->intermediate_buffers().size());
      EXPECT_EQ(intermediate->GetCertBuffer(),
                verify_result.verified_cert->intermediate_buffers()[0].get());
    } else {
      ASSERT_EQ(0u, verify_result.verified_cert->intermediate_buffers().size());
    }
    histograms.ExpectTotalCount("Net.CertVerifier.Qwac.1Qwac", 0u);
  }
}

TEST_P(CertVerifyProcBuiltin1QwacTest, OneQwacRequiresEutl) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  // intermediate->SetCertificatePolicies({"2.5.29.32.0"}); // anyPolicy

  // CABF OV, ETSI QNCP-w
  leaf->SetCertificatePolicies({"2.23.140.1.2.2", "0.4.0.194112.1.5"});

  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});

  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509CertificateChain(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    // If the intermediate is not on the EUTL, the certificate verifies
    // successfully but does not have QWAC status set.
    EXPECT_THAT(error, IsOk());
    EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_QWAC);
    ExpectHistogramSample(histograms, Verify1QwacResult::kFailedVerification);
  }

  AddMockEutlRoot(intermediate->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509CertificateChain(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    // If the intermediate is on the EUTL, the same certificate verifies
    // successfully with the QWAC status set.
    EXPECT_THAT(error, IsOk());
    EXPECT_EQ(GetParam(), !!(verify_result.cert_status & CERT_STATUS_IS_QWAC));
    ExpectHistogramSample(histograms, Verify1QwacResult::kValid1Qwac);
  }
}

TEST_P(CertVerifyProcBuiltin1QwacTest, OneQwacRequiresPolicies) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  // CABF OV
  leaf->SetCertificatePolicies({"2.23.140.1.2.2"});

  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});

  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  AddMockEutlRoot(intermediate->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509CertificateChain(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    // If the leaf doesn't have the necessary policies, the certificate
    // verifies successfully but does not have QWAC status set.
    EXPECT_THAT(error, IsOk());
    EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_QWAC);
    ExpectHistogramSample(histograms, Verify1QwacResult::kInconsistentBits);
  }

  // CABF OV, ETSI QNCP-w
  leaf->SetCertificatePolicies({"2.23.140.1.2.2", "0.4.0.194112.1.5"});

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509CertificateChain(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    // If the leaf has the qwac policies, verifies successfully with the QWAC
    // status set.
    EXPECT_THAT(error, IsOk());
    EXPECT_EQ(GetParam(), !!(verify_result.cert_status & CERT_STATUS_IS_QWAC));
    ExpectHistogramSample(histograms, Verify1QwacResult::kValid1Qwac);
  }
}

TEST_P(CertVerifyProcBuiltin1QwacTest, OneQwacRequiresQcStatements) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  // CABF OV, ETSI QNCP-w
  leaf->SetCertificatePolicies({"2.23.140.1.2.2", "0.4.0.194112.1.5"});

  // Initially, set QcStatements with the wrong QcType.
  // id-etsi-qct-eseal OBJECT IDENTIFIER ::= { id-etsi-qcs-QcType 2 }
  constexpr uint8_t kEtsiQctEsealOid[] = {0x04, 0x00, 0x8e, 0x46,
                                          0x01, 0x06, 0x02};
  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctEsealOid)});

  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  AddMockEutlRoot(intermediate->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509CertificateChain(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    // If the leaf doesn't have the necessary QcStatements, the certificate
    // verifies successfully but does not have QWAC status set.
    EXPECT_THAT(error, IsOk());
    EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_QWAC);
    ExpectHistogramSample(histograms, Verify1QwacResult::kInconsistentBits);
  }

  // Try again with the correct QcType.
  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    Verify(leaf->GetX509CertificateChain(), "www.example.com",
           /*flags=*/0, &verify_result, &verify_net_log_source,
           callback.callback());

    int error = callback.WaitForResult();
    // If the leaf has the qwac QcStatements, verifies successfully with the
    // QWAC status set.
    EXPECT_THAT(error, IsOk());
    EXPECT_EQ(GetParam(), !!(verify_result.cert_status & CERT_STATUS_IS_QWAC));
    ExpectHistogramSample(histograms, Verify1QwacResult::kValid1Qwac);
  }
}

TEST_P(CertVerifyProcBuiltin1QwacTest, OneQwacCanBuildAlternatePath) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  // CABF OV, ETSI QNCP-w
  leaf->SetCertificatePolicies({"2.23.140.1.2.2", "0.4.0.194112.1.5"});

  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});

  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  // Create separate intermediate which chains to a different root but has same
  // subject, private key, and SKI so that `leaf` can also be verified with
  // this chain.
  auto [unused, root2] = CertBuilder::CreateSimpleChain2();
  CertBuilder eutl_intermediate(/*orig_cert=*/intermediate->GetCertBuffer(),
                                /*issuer=*/root2.get());
  eutl_intermediate.SetSubjectTLV(
      base::as_byte_span(intermediate->GetSubject()));
  eutl_intermediate.SetKey(bssl::UpRef(intermediate->GetKey()));
  eutl_intermediate.SetSubjectKeyIdentifier(
      intermediate->GetSubjectKeyIdentifier());
  AddMockEutlRoot(eutl_intermediate.GetCertBuffer());

  RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(leaf->GetX509CertificateChain(), "www.example.com",
         /*flags=*/0, &verify_result, &verify_net_log_source,
         callback.callback());

  int error = callback.WaitForResult();
  EXPECT_THAT(error, IsOk());
  EXPECT_EQ(GetParam(), !!(verify_result.cert_status & CERT_STATUS_IS_QWAC));

  ASSERT_EQ(2u, verify_result.verified_cert->intermediate_buffers().size());
  // The verified chain has the cert chain from the normal TLS verification,
  // not the QWAC verification.
  EXPECT_EQ(intermediate->GetCertBuffer(),
            verify_result.verified_cert->intermediate_buffers()[0].get());
  EXPECT_EQ(root->GetCertBuffer(),
            verify_result.verified_cert->intermediate_buffers()[1].get());

  auto events = net_log_observer.GetEntriesForSource(verify_net_log_source);

  auto event = std::ranges::find(
      events, NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT,
      &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase);
  EXPECT_EQ(std::nullopt, event->params.FindBool("is_qwac_attempt"));

  event = std::ranges::find(++event, events.end(),
                            NetLogEventType::CERT_VERIFY_PROC_PATH_BUILT,
                            &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase);

  event = std::ranges::find(++event, events.end(),
                            NetLogEventType::CERT_VERIFY_PROC_PATH_BUILT,
                            &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::END, event->phase);
  EXPECT_EQ(true, event->params.FindBool("is_valid"));
  base::ListValue* pem_certs = event->params.FindList("certificates");
  ASSERT_TRUE(pem_certs);
  // The CERT_VERIFY_PROC_PATH_BUILT netlog for the main verification should
  // contain the TLS cert chain.
  EXPECT_THAT(ParseNetLogCertificatesList(*pem_certs),
              testing::ElementsAre(leaf->GetDER(), intermediate->GetDER(),
                                   root->GetDER()));

  event = std::ranges::find(
      ++event, events.end(),
      NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT, &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::END, event->phase);
  EXPECT_EQ(true, event->params.FindBool("has_valid_path"));

  event = std::ranges::find(
      ++event, events.end(),
      NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT, &NetLogEntry::type);
  if (!GetParam()) {
    // If the feature flag wasn't enabled, there should only be one
    // CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT.
    ASSERT_EQ(event, events.end());
    return;
  }
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase);
  EXPECT_EQ(true, event->params.FindBool("is_qwac_attempt"));

  event = std::ranges::find(++event, events.end(),
                            NetLogEventType::CERT_VERIFY_PROC_PATH_BUILT,
                            &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase);

  event = std::ranges::find(++event, events.end(),
                            NetLogEventType::CERT_VERIFY_PROC_PATH_BUILT,
                            &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::END, event->phase);
  EXPECT_EQ(true, event->params.FindBool("is_valid"));
  pem_certs = event->params.FindList("certificates");
  ASSERT_TRUE(pem_certs);
  // The CERT_VERIFY_PROC_PATH_BUILT netlog for the 1-QWAC verification should
  // contain the QWAC cert chain.
  EXPECT_THAT(ParseNetLogCertificatesList(*pem_certs),
              testing::ElementsAre(leaf->GetDER(), eutl_intermediate.GetDER()));

  event = std::ranges::find(
      ++event, events.end(),
      NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT, &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::END, event->phase);
  EXPECT_EQ(true, event->params.FindBool("has_valid_path"));

  event = std::ranges::find(
      ++event, events.end(),
      NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT, &NetLogEntry::type);
  ASSERT_EQ(event, events.end());
}

INSTANTIATE_TEST_SUITE_P(, CertVerifyProcBuiltin1QwacTest, testing::Bool());

class CertVerifyProcBuiltin2QwacTest : public CertVerifyProcBuiltinTest {
 public:
  void ExpectHistogramSample(const base::HistogramTester& histograms,
                             Verify2QwacBindingResult result) {
    histograms.ExpectUniqueSample("Net.CertVerifier.Qwac.2QwacBinding", result,
                                  1u);
  }
  void ExpectNoHistogramSample(const base::HistogramTester& histograms) {
    histograms.ExpectTotalCount("Net.CertVerifier.Qwac.2QwacBinding", 0);
  }
};

TEST_F(CertVerifyProcBuiltin2QwacTest, InvalidCertificate) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  intermediate->SetCertificatePolicies({"2.5.29.32.0"});  // anyPolicy

  leaf->SetCertificatePolicies({"0.4.0.194112.1.6"});  // QNCP-w-gen
  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});
  leaf->SetExtendedKeyUsages({bssl::der::Input(kIdKpTlsBinding)});
  leaf->SetExtension(bssl::der::Input(bssl::kBasicConstraintsOid),
                     "invalid extension value", /*critical=*/true);

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(root->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
    EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
    ExpectHistogramSample(histograms,
                          Verify2QwacBindingResult::kCertLeafParsingError);
  }
}

TEST_F(CertVerifyProcBuiltin2QwacTest, TwoQwacRequiresEutl) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  intermediate->SetCertificatePolicies({"2.5.29.32.0"});  // anyPolicy

  leaf->SetCertificatePolicies({"0.4.0.194112.1.6"});  // QNCP-w-gen
  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});
  leaf->SetExtendedKeyUsages({bssl::der::Input(kIdKpTlsBinding)});

  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    // If the root is not on the EUTL, a valid path cannot be found, even if
    // it's a normal root.
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
    EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID);
    ExpectHistogramSample(histograms,
                          Verify2QwacBindingResult::kCertAuthorityInvalid);

    // The path builder should have found the intermediate, but no root.
    EXPECT_EQ(leaf->GetCertBuffer(),
              verify_result.verified_cert->cert_buffer());
    ASSERT_EQ(1u, verify_result.verified_cert->intermediate_buffers().size());
    EXPECT_EQ(intermediate->GetCertBuffer(),
              verify_result.verified_cert->intermediate_buffers()[0].get());
  }

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(root->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    // If the root is on the EUTL, the certificate verifies successfully with
    // the QWAC status set.
    EXPECT_THAT(error, IsOk());
    EXPECT_FALSE(IsCertStatusError(verify_result.cert_status));
    ExpectNoHistogramSample(histograms);

    // The verified chain has the full cert chain.
    EXPECT_EQ(leaf->GetCertBuffer(),
              verify_result.verified_cert->cert_buffer());
    ASSERT_EQ(2u, verify_result.verified_cert->intermediate_buffers().size());
    EXPECT_EQ(intermediate->GetCertBuffer(),
              verify_result.verified_cert->intermediate_buffers()[0].get());
    EXPECT_EQ(root->GetCertBuffer(),
              verify_result.verified_cert->intermediate_buffers()[1].get());
  }
}

TEST_F(CertVerifyProcBuiltin2QwacTest, TwoQwacRequiresPolicies) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  intermediate->SetCertificatePolicies({"2.5.29.32.0"});  // anyPolicy

  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});
  leaf->SetExtendedKeyUsages({bssl::der::Input(kIdKpTlsBinding)});

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(root->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
    EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
    ExpectHistogramSample(histograms,
                          Verify2QwacBindingResult::kCertInconsistentBits);
  }

  leaf->SetCertificatePolicies({"0.4.0.194112.1.6"});  // QNCP-w-gen

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsOk());
    EXPECT_FALSE(IsCertStatusError(verify_result.cert_status));
    ExpectNoHistogramSample(histograms);
  }
}

TEST_F(CertVerifyProcBuiltin2QwacTest, TwoQwacRequiresQcStatements) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  intermediate->SetCertificatePolicies({"2.5.29.32.0"});  // anyPolicy

  leaf->SetCertificatePolicies({"0.4.0.194112.1.6"});  // QNCP-w-gen
  leaf->SetExtendedKeyUsages({bssl::der::Input(kIdKpTlsBinding)});

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(root->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
    EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
    ExpectHistogramSample(histograms,
                          Verify2QwacBindingResult::kCertInconsistentBits);
  }

  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsOk());
    EXPECT_FALSE(IsCertStatusError(verify_result.cert_status));
    ExpectNoHistogramSample(histograms);
  }
}

TEST_F(CertVerifyProcBuiltin2QwacTest, TwoQwacRequiresEku) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  intermediate->SetCertificatePolicies({"2.5.29.32.0"});  // anyPolicy

  leaf->SetCertificatePolicies({"0.4.0.194112.1.6"});  // QNCP-w-gen
  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(root->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
    EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
    ExpectHistogramSample(histograms,
                          Verify2QwacBindingResult::kCertInconsistentBits);
  }

  leaf->SetExtendedKeyUsages({bssl::der::Input(kIdKpTlsBinding)});

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsOk());
    EXPECT_FALSE(IsCertStatusError(verify_result.cert_status));
    ExpectNoHistogramSample(histograms);
  }
}

TEST_F(CertVerifyProcBuiltin2QwacTest, TwoQwacVerifiesName) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  intermediate->SetCertificatePolicies({"2.5.29.32.0"});  // anyPolicy

  leaf->SetCertificatePolicies({"0.4.0.194112.1.6"});  // QNCP-w-gen
  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});
  leaf->SetExtendedKeyUsages({bssl::der::Input(kIdKpTlsBinding)});

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(root->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    TestCompletionCallback callback;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.wrong.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsError(ERR_CERT_COMMON_NAME_INVALID));
    EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_COMMON_NAME_INVALID);
    ExpectHistogramSample(histograms,
                          Verify2QwacBindingResult::kCertNameInvalid);
  }

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsOk());
    EXPECT_FALSE(IsCertStatusError(verify_result.cert_status));
    ExpectNoHistogramSample(histograms);
  }
}

TEST_F(CertVerifyProcBuiltin2QwacTest, TwoQwacVerifiesValidityDate) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();

  intermediate->SetCertificatePolicies({"2.5.29.32.0"});  // anyPolicy

  leaf->SetCertificatePolicies({"0.4.0.194112.1.6"});  // QNCP-w-gen
  leaf->SetQwacQcStatements({bssl::der::Input(kEtsiQctWebOid)});
  leaf->SetExtendedKeyUsages({bssl::der::Input(kIdKpTlsBinding)});
  leaf->SetValidity(base::Time::Now() - base::Days(2),
                    base::Time::Now() - base::Days(1));

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(root->GetCertBuffer());

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID));
    EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID);
    ExpectHistogramSample(histograms,
                          Verify2QwacBindingResult::kCertDateInvalid);
  }

  // 2-QWACs are not bound by BR lifetime limits, so we don't enforce any
  // validity too long errors.
  leaf->SetValidity(base::Time::Now() - base::Days(2),
                    base::Time::Now() + base::Days(3650));

  {
    base::HistogramTester histograms;
    CertVerifyResult verify_result;
    NetLogSource verify_net_log_source;
    int error = Verify2Qwac(leaf->GetX509CertificateChain(), "www.example.com",
                            &verify_result, &verify_net_log_source);

    EXPECT_THAT(error, IsOk());
    EXPECT_FALSE(IsCertStatusError(verify_result.cert_status));
    ExpectNoHistogramSample(histograms);
  }
}

class CertVerifyProcBuiltin2QwacBindingTest : public CertVerifyProcBuiltinTest {
 public:
  void ExpectHistogramSample(const base::HistogramTester& histograms,
                             Verify2QwacBindingResult result) {
    histograms.ExpectUniqueSample("Net.CertVerifier.Qwac.2QwacBinding", result,
                                  1u);
  }
};

TEST_F(CertVerifyProcBuiltin2QwacBindingTest, TestValidBinding) {
  auto [tls_leaf, tls_root] = CertBuilder::CreateSimpleChain2();

  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetBoundCerts({tls_leaf->GetDER()});
  std::string jws = binding_builder.GetJWS();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(binding_builder.GetRootBuilder()->GetCertBuffer());

  base::HistogramTester histograms;
  RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
  NetLogSource verify_net_log_source;
  scoped_refptr<X509Certificate> verified_2qwac = Verify2QwacBinding(
      jws, "www.example.com", base::as_byte_span(tls_leaf->GetDER()),
      &verify_net_log_source);
  ASSERT_TRUE(verified_2qwac);
  EXPECT_TRUE(verified_2qwac->EqualsIncludingChain(
      binding_builder.GetLeafBuilder()->GetX509CertificateFullChain().get()));
  ExpectHistogramSample(histograms,
                        Verify2QwacBindingResult::kValid2QwacBinding);

  auto events = net_log_observer.GetEntriesForSource(verify_net_log_source);
  auto event =
      std::ranges::find(events, NetLogEventType::CERT_VERIFY_PROC_2QWAC_BINDING,
                        &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase);
  EXPECT_EQ(jws, base::optional_ref(event->params.FindString("binding")));
  EXPECT_EQ("www.example.com",
            base::optional_ref(event->params.FindString("host")));
  EXPECT_EQ(base::as_byte_span(tls_leaf->GetDER()),
            ParsePemCertificate(event->params.FindString("tls_certificate")));

  event = std::ranges::find(++event, events.end(),
                            NetLogEventType::CERT_VERIFY_PROC_2QWAC,
                            &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase);

  event = std::ranges::find(++event, events.end(),
                            NetLogEventType::CERT_VERIFY_PROC_2QWAC,
                            &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::END, event->phase);

  EXPECT_FALSE(event->params.Find("net_error"));
  base::DictValue* pem_verified_certs = event->params.FindDict("verified_cert");
  ASSERT_TRUE(pem_verified_certs);
  EXPECT_THAT(ParseNetLogCertificatesDict(*pem_verified_certs),
              testing::ElementsAre(binding_builder.GetLeafBuilder()->GetDER(),
                                   binding_builder.GetRootBuilder()->GetDER()));

  event = std::ranges::find(++event, events.end(),
                            NetLogEventType::CERT_VERIFY_PROC_2QWAC_BINDING,
                            &NetLogEntry::type);
  ASSERT_NE(event, events.end());
  EXPECT_EQ(net::NetLogEventPhase::END, event->phase);
  EXPECT_FALSE(event->params.Find("net_error"));
  EXPECT_EQ(true, event->params.FindBool("is_valid_2qwac_binding"));
}

TEST_F(CertVerifyProcBuiltin2QwacBindingTest, TestBindingFailsParsing) {
  auto [tls_leaf, tls_root] = CertBuilder::CreateSimpleChain2();

  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetBoundCerts({tls_leaf->GetDER()});
  std::string jws = "invalid:" + binding_builder.GetJWS();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(binding_builder.GetRootBuilder()->GetCertBuffer());

  RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
  base::HistogramTester histograms;
  NetLogSource verify_net_log_source;
  EXPECT_FALSE(Verify2QwacBinding(jws, "www.example.com",
                                  base::as_byte_span(tls_leaf->GetDER()),
                                  &verify_net_log_source));
  ExpectHistogramSample(histograms,
                        Verify2QwacBindingResult::kBindingParsingError);

  auto end_events = net_log_observer.GetEntriesForSourceWithType(
      verify_net_log_source, NetLogEventType::CERT_VERIFY_PROC_2QWAC_BINDING,
      net::NetLogEventPhase::END);
  ASSERT_EQ(1U, end_events.size());
  auto& event = end_events[0];
  EXPECT_EQ(ERR_FAILED, event.params.FindInt("net_error"));
  EXPECT_EQ("binding parsing error: base64 decoding header error",
            base::optional_ref(event.params.FindString("error_description")));
}

TEST_F(CertVerifyProcBuiltin2QwacBindingTest, TestBindingInvalidSignature) {
  auto [tls_leaf, tls_root] = CertBuilder::CreateSimpleChain2();

  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetBoundCerts({tls_leaf->GetDER()});
  std::string jws = binding_builder.GetJWSWithInvalidSignature();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(binding_builder.GetRootBuilder()->GetCertBuffer());

  RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
  base::HistogramTester histograms;
  NetLogSource verify_net_log_source;
  EXPECT_FALSE(Verify2QwacBinding(jws, "www.example.com",
                                  base::as_byte_span(tls_leaf->GetDER()),
                                  &verify_net_log_source));
  ExpectHistogramSample(histograms,
                        Verify2QwacBindingResult::kBindingSignatureInvalid);

  auto end_events = net_log_observer.GetEntriesForSourceWithType(
      verify_net_log_source, NetLogEventType::CERT_VERIFY_PROC_2QWAC_BINDING,
      net::NetLogEventPhase::END);
  ASSERT_EQ(1U, end_events.size());
  auto& event = end_events[0];
  EXPECT_EQ(ERR_FAILED, event.params.FindInt("net_error"));
  EXPECT_EQ("binding signature invalid",
            base::optional_ref(event.params.FindString("error_description")));
}

TEST_F(CertVerifyProcBuiltin2QwacBindingTest,
       TestBinding2QwacFailsVerification) {
  auto [tls_leaf, tls_root] = CertBuilder::CreateSimpleChain2();

  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetBoundCerts({tls_leaf->GetDER()});
  std::string jws = binding_builder.GetJWS();

  // The qwac root is not added to the EUTL, so cert verification of the 2-QWAC
  // certificate should fail.
  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));

  RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
  base::HistogramTester histograms;
  NetLogSource verify_net_log_source;
  EXPECT_FALSE(Verify2QwacBinding(jws, "www.example.com",
                                  base::as_byte_span(tls_leaf->GetDER()),
                                  &verify_net_log_source));
  ExpectHistogramSample(histograms,
                        Verify2QwacBindingResult::kCertAuthorityInvalid);

  auto end_events = net_log_observer.GetEntriesForSourceWithType(
      verify_net_log_source, NetLogEventType::CERT_VERIFY_PROC_2QWAC_BINDING,
      net::NetLogEventPhase::END);
  ASSERT_EQ(1U, end_events.size());
  auto& event = end_events[0];
  EXPECT_EQ(ERR_FAILED, event.params.FindInt("net_error"));
  EXPECT_EQ("2-QWAC cert verify failed",
            base::optional_ref(event.params.FindString("error_description")));
}

TEST_F(CertVerifyProcBuiltin2QwacBindingTest, TestTlsCertIsNotBound) {
  auto [bound_leaf, bound_root] = CertBuilder::CreateSimpleChain2();
  auto [tls_leaf, tls_root] = CertBuilder::CreateSimpleChain2();

  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetBoundCerts({bound_leaf->GetDER()});
  std::string jws = binding_builder.GetJWS();

  InitializeVerifyProc(CreateParams(/*additional_trust_anchors=*/{}));
  AddMockEutlRoot(binding_builder.GetRootBuilder()->GetCertBuffer());

  RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
  base::HistogramTester histograms;
  NetLogSource verify_net_log_source;
  EXPECT_FALSE(Verify2QwacBinding(jws, "www.example.com",
                                  base::as_byte_span(tls_leaf->GetDER()),
                                  &verify_net_log_source));
  ExpectHistogramSample(histograms, Verify2QwacBindingResult::kTlsCertNotBound);

  auto end_events = net_log_observer.GetEntriesForSourceWithType(
      verify_net_log_source, NetLogEventType::CERT_VERIFY_PROC_2QWAC_BINDING,
      net::NetLogEventPhase::END);
  ASSERT_EQ(1U, end_events.size());
  auto& event = end_events[0];
  EXPECT_EQ(ERR_FAILED, event.params.FindInt("net_error"));
  EXPECT_EQ("TLS cert not bound",
            base::optional_ref(event.params.FindString("error_description")));
}

#endif  // BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)

TEST_F(CertVerifyProcBuiltinTest, DeadlineExceededDuringSyncGetIssuers) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  InitializeVerifyProc(CreateParams(
      /*additional_trust_anchors=*/{root->GetX509Certificate()}));

  BlockingTrustStore trust_store;
  AddTrustStore(&trust_store);

  auto intermediate_parsed_cert = bssl::ParsedCertificate::Create(
      intermediate->DupCertBuffer(), {}, nullptr);
  ASSERT_TRUE(intermediate_parsed_cert);
  trust_store.backing_trust_store_.AddCertificateWithUnspecifiedTrust(
      intermediate_parsed_cert);

  scoped_refptr<X509Certificate> chain = leaf->GetX509Certificate();
  ASSERT_TRUE(chain.get());

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback verify_callback;
  Verify(chain.get(), "www.example.com",
         /*flags=*/0,
         &verify_result, &verify_net_log_source, verify_callback.callback());

  // Wait for trust_store.SyncGetIssuersOf to be called.
  trust_store.sync_get_issuer_started_event_.Wait();

  // Advance the clock past the verifier deadline.
  const base::TimeDelta timeout_increment =
      GetCertVerifyProcBuiltinTimeLimitForTesting() + base::Milliseconds(1);
  task_environment().AdvanceClock(timeout_increment);

  // Signal trust_store.SyncGetIssuersOf to finish.
  trust_store.sync_get_issuer_ok_to_finish_event_.Signal();

  int error = verify_callback.WaitForResult();
  // Because the deadline was reached while retrieving the intermediate, path
  // building should have stopped there and not found the root. The partial
  // path built up to that point should be returned, and the error should be
  // CERT_AUTHORITY_INVALID.
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  ASSERT_EQ(1u, verify_result.verified_cert->intermediate_buffers().size());
  EXPECT_EQ(intermediate->GetCertBuffer(),
            verify_result.verified_cert->intermediate_buffers()[0].get());
}

namespace {

// Returns a TLV to use as an unknown signature algorithm when building a cert.
// The specific contents are as follows (the OID is from
// https://davidben.net/oid):
//
// SEQUENCE {
//   OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.0 }
//   NULL {}
// }
std::string UnknownSignatureAlgorithmTLV() {
  const uint8_t kInvalidSignatureAlgorithmTLV[] = {
      0x30, 0x10, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
      0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x00, 0x05, 0x00};
  return std::string(std::begin(kInvalidSignatureAlgorithmTLV),
                     std::end(kInvalidSignatureAlgorithmTLV));
}

// Returns a TLV to use as an invalid signature algorithm when building a cert.
// This is a SEQUENCE so that it will pass the bssl::ParseCertificate code
// and fail inside bssl::ParseSignatureAlgorithm.
// SEQUENCE {
//   INTEGER { 42 }
// }
std::string InvalidSignatureAlgorithmTLV() {
  const uint8_t kInvalidSignatureAlgorithmTLV[] = {0x30, 0x03, 0x02, 0x01,
                                                   0x2a};
  return std::string(std::begin(kInvalidSignatureAlgorithmTLV),
                     std::end(kInvalidSignatureAlgorithmTLV));
}

}  // namespace

TEST_F(CertVerifyProcBuiltinTest, UnknownSignatureAlgorithmTarget) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  leaf->SetSignatureAlgorithmTLV(UnknownSignatureAlgorithmTLV());

  // Trust the root and build a chain to verify that includes the intermediate.
  ScopedTestRoot scoped_root(root->GetX509Certificate());
  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  int flags = 0;
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", flags, &verify_result,
         &verify_net_log_source, callback.callback());
  int error = callback.WaitForResult();
  // Unknown signature algorithm in the leaf cert should result in the cert
  // being invalid.
  EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
  EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
}

TEST_F(CertVerifyProcBuiltinTest,
       UnparsableMismatchedTBSSignatureAlgorithmTarget) {
  auto [leaf, root] = CertBuilder::CreateSimpleChain2();
  // Set only the tbsCertificate signature to an invalid value.
  leaf->SetTBSSignatureAlgorithmTLV(InvalidSignatureAlgorithmTLV());

  // Trust the root and build a chain to verify.
  ScopedTestRoot scoped_root(root->GetX509Certificate());
  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  int flags = 0;
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", flags, &verify_result,
         &verify_net_log_source, callback.callback());
  int error = callback.WaitForResult();
  // Invalid signature algorithm in the leaf cert should result in the
  // cert being invalid.
  EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
  EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
}

TEST_F(CertVerifyProcBuiltinTest, UnknownSignatureAlgorithmIntermediate) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  intermediate->SetSignatureAlgorithmTLV(UnknownSignatureAlgorithmTLV());

  // Trust the root and build a chain to verify that includes the intermediate.
  ScopedTestRoot scoped_root(root->GetX509Certificate());
  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  int flags = 0;
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", flags, &verify_result,
         &verify_net_log_source, callback.callback());
  int error = callback.WaitForResult();
  // Unknown signature algorithm in the intermediate cert should result in the
  // cert being invalid.
  EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
  EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
}

TEST_F(CertVerifyProcBuiltinTest,
       UnparsableMismatchedTBSSignatureAlgorithmIntermediate) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  // Set only the tbsCertificate signature to an invalid value.
  intermediate->SetTBSSignatureAlgorithmTLV(InvalidSignatureAlgorithmTLV());

  // Trust the root and build a chain to verify that includes the intermediate.
  ScopedTestRoot scoped_root(root->GetX509Certificate());
  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());
  ASSERT_EQ(chain->intermediate_buffers().size(), 1U);

  int flags = 0;
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", flags, &verify_result,
         &verify_net_log_source, callback.callback());
  int error = callback.WaitForResult();
  // Invalid signature algorithm in the intermediate cert should result in the
  // cert being invalid.
  EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
  EXPECT_THAT(error, IsError(ERR_CERT_INVALID));
}

TEST_F(CertVerifyProcBuiltinTest, UnknownSignatureAlgorithmRoot) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  root->SetSignatureAlgorithmTLV(UnknownSignatureAlgorithmTLV());

  // Trust the root and build a chain to verify that includes the intermediate.
  ScopedTestRoot scoped_root(root->GetX509Certificate());
  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  int flags = 0;
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", flags, &verify_result,
         &verify_net_log_source, callback.callback());
  int error = callback.WaitForResult();
  // Unknown signature algorithm in the root cert should have no effect on
  // verification.
  EXPECT_THAT(error, IsOk());
}

// This test is disabled on Android as adding the invalid root through
// ScopedTestRoot causes it to be parsed by the Java X509 code which barfs. We
// could re-enable if Chrome on Android has fully switched to the
// builtin-verifier and ScopedTestRoot no longer has Android-specific code.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_UnparsableMismatchedTBSSignatureAlgorithmRoot \
  DISABLED_UnparsableMismatchedTBSSignatureAlgorithmRoot
#else
#define MAYBE_UnparsableMismatchedTBSSignatureAlgorithmRoot \
  UnparsableMismatchedTBSSignatureAlgorithmRoot
#endif
TEST_F(CertVerifyProcBuiltinTest,
       MAYBE_UnparsableMismatchedTBSSignatureAlgorithmRoot) {
  auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3();
  // Set only the tbsCertificate signature to an invalid value.
  root->SetTBSSignatureAlgorithmTLV(InvalidSignatureAlgorithmTLV());

  // Trust the root and build a chain to verify that includes the intermediate.
  ScopedTestRoot scoped_root(root->GetX509Certificate());
  scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain();
  ASSERT_TRUE(chain.get());

  int flags = 0;
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", flags, &verify_result,
         &verify_net_log_source, callback.callback());
  int error = callback.WaitForResult();
  // Invalid signature algorithm in the root cert should have no effect on
  // verification.
  EXPECT_THAT(error, IsOk());
}

TEST_F(CertVerifyProcBuiltinTest, IterationLimit) {
  // Create a chain which will require many iterations in the path builder.
  std::vector<std::unique_ptr<CertBuilder>> builders =
      CertBuilder::CreateSimpleChain(6);

  base::Time not_before = base::Time::Now() - base::Days(1);
  base::Time not_after = base::Time::Now() + base::Days(1);
  for (auto& builder : builders) {
    builder->SetValidity(not_before, not_after);
  }

  // Generate certificates, making two versions of each intermediate.
  std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
  for (size_t i = 1; i < builders.size(); i++) {
    intermediates.push_back(builders[i]->DupCertBuffer());
    builders[i]->SetValidity(not_before, not_after + base::Seconds(1));
    intermediates.push_back(builders[i]->DupCertBuffer());
  }

  // The above alone is enough to make the path builder explore many paths, but
  // it will always return the best path it has found, so the error will be the
  // same. Instead, arrange for all those paths to be invalid (untrusted root),
  // and add a separate chain that is valid.
  CertBuilder root_ok(/*orig_cert=*/builders[2]->GetCertBuffer(),
                      /*issuer=*/nullptr);
  CertBuilder intermediate_ok(/*orig_cert=*/builders[1]->GetCertBuffer(),
                              /*issuer=*/&root_ok);
  // Using the old intermediate as a template does not preserve the subject,
  // SKID, or key.
  intermediate_ok.SetSubjectTLV(base::as_byte_span(builders[1]->GetSubject()));
  intermediate_ok.SetKey(bssl::UpRef(builders[1]->GetKey()));
  intermediate_ok.SetSubjectKeyIdentifier(
      builders[1]->GetSubjectKeyIdentifier());
  // Make the valid intermediate older than the invalid ones, so that it is
  // explored last.
  intermediate_ok.SetValidity(not_before - base::Seconds(10),
                              not_after - base::Seconds(10));
  intermediates.push_back(intermediate_ok.DupCertBuffer());

  // Verify the chain.
  ScopedTestRoot scoped_root(root_ok.GetX509Certificate().get());
  scoped_refptr<X509Certificate> chain = X509Certificate::CreateFromBuffer(
      builders[0]->DupCertBuffer(), std::move(intermediates));
  ASSERT_TRUE(chain.get());

  RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
  int flags = 0;
  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(chain.get(), "www.example.com", flags, &verify_result,
         &verify_net_log_source, callback.callback());
  int error = callback.WaitForResult();

  auto events = net_log_observer.GetEntriesForSource(verify_net_log_source);
  auto event = std::ranges::find_if(events, [](const NetLogEntry& e) {
    return e.type == NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT &&
           e.phase == NetLogEventPhase::END;
  });
  ASSERT_NE(event, events.end());

  // The path builder gives up before it finishes all the invalid paths.
  EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID);
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  EXPECT_EQ(true, event->params.FindBool("exceeded_iteration_limit"));
}

class CertVerifyProcBuiltinSelfSignedTest
    : public CertVerifyProcBuiltinTest,
      public testing::WithParamInterface<bool> {
 public:
  CertVerifyProcBuiltinSelfSignedTest() {
    if (GetParam()) {
      feature_list_.InitAndEnableFeature(
          features::kSelfSignedLocalNetworkInterstitial);
    } else {
      feature_list_.InitAndDisableFeature(
          features::kSelfSignedLocalNetworkInterstitial);
    }
  }

  scoped_refptr<X509Certificate> CreateSelfSigned(
      std::string_view subject_dns_name) {
    // Create a chain of size 1, which will result in a self-signed certificate
    std::vector<std::unique_ptr<CertBuilder>> builders =
        CertBuilder::CreateSimpleChain(1);
    base::Time not_before = base::Time::Now() - base::Days(1);
    base::Time not_after = base::Time::Now() + base::Days(1);
    builders[0]->SetValidity(not_before, not_after);
    builders[0]->SetSubjectAltName(subject_dns_name);
    return builders[0]->GetX509Certificate();
  }

  scoped_refptr<X509Certificate> CreateSelfSignedIPSubject(
      std::string_view ip_address) {
    // Create a chain of size 1, which will result in a self-signed certificate
    std::vector<std::unique_ptr<CertBuilder>> builders =
        CertBuilder::CreateSimpleChain(1);
    base::Time not_before = base::Time::Now() - base::Days(1);
    base::Time not_after = base::Time::Now() + base::Days(1);
    builders[0]->SetValidity(not_before, not_after);
    IPAddress ip;
    if (!ParseURLHostnameToAddress(ip_address, &ip)) {
      ADD_FAILURE() << "Failed to parse IP address";
    }

    builders[0]->SetSubjectAltNames({}, {ip});
    return builders[0]->GetX509Certificate();
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

TEST_P(CertVerifyProcBuiltinSelfSignedTest,
       SelfSignedCertOnLocalNetworkHostname) {
  scoped_refptr<X509Certificate> cert = CreateSelfSigned("testurl.local");

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(cert, "testurl.local", 0, &verify_result, &verify_net_log_source,
         callback.callback());
  int error = callback.WaitForResult();

  if (GetParam()) {
    EXPECT_TRUE(verify_result.cert_status &
                CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
    EXPECT_THAT(error, IsError(ERR_CERT_SELF_SIGNED_LOCAL_NETWORK));
  } else {
    EXPECT_FALSE(verify_result.cert_status &
                 CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }
}

TEST_P(CertVerifyProcBuiltinSelfSignedTest, SelfSignedCertOnLocalNetworkIP) {
  scoped_refptr<X509Certificate> cert =
      CreateSelfSignedIPSubject("192.168.0.1");

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(cert, "192.168.0.1", 0, &verify_result, &verify_net_log_source,
         callback.callback());
  int error = callback.WaitForResult();

  if (GetParam()) {
    EXPECT_TRUE(verify_result.cert_status &
                CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
    EXPECT_THAT(error, IsError(ERR_CERT_SELF_SIGNED_LOCAL_NETWORK));
  } else {
    EXPECT_FALSE(verify_result.cert_status &
                 CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }
}

TEST_P(CertVerifyProcBuiltinSelfSignedTest, SelfSignedCertOnLocalNetworkIPv6) {
  scoped_refptr<X509Certificate> cert =
      CreateSelfSignedIPSubject("[fc00:0:0:0:0:0:0:0]");

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(cert, "fc00:0:0:0:0:0:0:0", 0, &verify_result, &verify_net_log_source,
         callback.callback());
  int error = callback.WaitForResult();

  if (GetParam()) {
    EXPECT_TRUE(verify_result.cert_status &
                CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
    EXPECT_THAT(error, IsError(ERR_CERT_SELF_SIGNED_LOCAL_NETWORK));
  } else {
    EXPECT_FALSE(verify_result.cert_status &
                 CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }
}

TEST_P(CertVerifyProcBuiltinSelfSignedTest, NonSelfSignedCertOnLocalNetwork) {
  std::vector<std::unique_ptr<CertBuilder>> builders =
      CertBuilder::CreateSimpleChain(2);

  base::Time not_before = base::Time::Now() - base::Days(2);
  base::Time not_after = base::Time::Now() - base::Days(2);
  builders[0]->SetValidity(not_before, not_after);
  builders[0]->SetSubjectAltName("testurl.local");

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(builders[0]->GetX509CertificateChain(), "testurl.local", 0,
         &verify_result, &verify_net_log_source, callback.callback());
  int error = callback.WaitForResult();

  EXPECT_FALSE(verify_result.cert_status &
               CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
}

TEST_P(CertVerifyProcBuiltinSelfSignedTest,
       SelfSignedCertNotLocalNetworkHostname) {
  scoped_refptr<X509Certificate> cert = CreateSelfSigned("www.example.com");

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(cert, "www.example.com", 0, &verify_result, &verify_net_log_source,
         callback.callback());
  int error = callback.WaitForResult();

  EXPECT_FALSE(verify_result.cert_status &
               CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
}

TEST_P(CertVerifyProcBuiltinSelfSignedTest, SelfSignedCertNotLocalNetworkIP) {
  scoped_refptr<X509Certificate> cert = CreateSelfSignedIPSubject("8.8.8.8");

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(cert, "8.8.8.8", 0, &verify_result, &verify_net_log_source,
         callback.callback());
  int error = callback.WaitForResult();

  EXPECT_FALSE(verify_result.cert_status &
               CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
}

TEST_P(CertVerifyProcBuiltinSelfSignedTest, SelfSignedCertNotLocalNetworkIPv6) {
  scoped_refptr<X509Certificate> cert =
      CreateSelfSignedIPSubject("[2001:4860:4860::8888]");

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(cert, "2001:4860:4860::8888", 0, &verify_result,
         &verify_net_log_source, callback.callback());
  int error = callback.WaitForResult();

  EXPECT_FALSE(verify_result.cert_status &
               CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
  EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
}

TEST_P(CertVerifyProcBuiltinSelfSignedTest,
       SelfSignedCertOnLocalNetworkHostnameNameMismatchTakesPrecedence) {
  scoped_refptr<X509Certificate> cert = CreateSelfSigned("nottesturl.local");

  CertVerifyResult verify_result;
  NetLogSource verify_net_log_source;
  TestCompletionCallback callback;
  Verify(cert, "testurl.local", 0, &verify_result, &verify_net_log_source,
         callback.callback());
  int error = callback.WaitForResult();

  if (GetParam()) {
    EXPECT_TRUE(verify_result.cert_status &
                CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
    EXPECT_THAT(error, IsError(ERR_CERT_COMMON_NAME_INVALID));
  } else {
    EXPECT_FALSE(verify_result.cert_status &
                 CERT_STATUS_SELF_SIGNED_LOCAL_NETWORK);
    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
  }
}

INSTANTIATE_TEST_SUITE_P(SelfSignedInterstitial,
                         CertVerifyProcBuiltinSelfSignedTest,
                         testing::Bool());

}  // namespace net
