Add verify.cc and verify.h as top level public API.

Not the end of a public API, but rather a beginning
as this one has successful consumers.

This is an adaptation of google3 verify.cc with the
primary differences being:

- No custom error code conversions are provided. when
  google3 is switched to use this the conversions for
  absl, sts, and trawler will be re-done and kept in
  third_party/chromium_certificate verifier.
- No CRL or certificate revocation functionality is
  yet provided. (This comes via the delegate, we can
  consider exposing a way to to do this later)

Bug: 660, b:323560158
Change-Id: Id5f36f792ca0f6d61298e6bb239bf51b1b6b40da
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/64847
Commit-Queue: Bob Beck <bbe@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/build.json b/build.json
index efd7d77..f2c1ac9 100644
--- a/build.json
+++ b/build.json
@@ -603,6 +603,7 @@
             "pki/trust_store.cc",
             "pki/verify_certificate_chain.cc",
             "pki/verify_error.cc",
+            "pki/verify.cc",
             "pki/verify_name_match.cc",
             "pki/verify_signed_data.cc"
         ],
@@ -925,7 +926,8 @@
             "pki/verify_certificate_chain_pkits_unittest.cc",
             "pki/verify_certificate_chain_unittest.cc",
             "pki/verify_name_match_unittest.cc",
-            "pki/verify_signed_data_unittest.cc"
+            "pki/verify_signed_data_unittest.cc",
+            "pki/verify_unittest.cc"
         ],
         "data": [
             "pki/testdata/cert_issuer_source_static_unittest/*.pem",
@@ -944,7 +946,7 @@
             "pki/testdata/verify_certificate_chain_unittest/pkits_errors/*.txt",
             "pki/testdata/verify_name_match_unittest/names/*.pem",
             "pki/testdata/verify_signed_data_unittest/*.pem",
-            "pki/testdata/verify_unittest/google-leaf.der",
+            "pki/testdata/verify_unittest/*.der",
             "pki/testdata/verify_unittest/self-issued.pem"
         ]
     },
diff --git a/gen/sources.bzl b/gen/sources.bzl
index bb42d2c..86f2cbe 100644
--- a/gen/sources.bzl
+++ b/gen/sources.bzl
@@ -1060,6 +1060,7 @@
   "pki/trust_store.cc",
   "pki/trust_store_collection.cc",
   "pki/trust_store_in_memory.cc",
+  "pki/verify.cc",
   "pki/verify_certificate_chain.cc",
   "pki/verify_error.cc",
   "pki/verify_name_match.cc",
@@ -1149,6 +1150,7 @@
   "pki/verify_certificate_chain_unittest.cc",
   "pki/verify_name_match_unittest.cc",
   "pki/verify_signed_data_unittest.cc",
+  "pki/verify_unittest.cc",
 ]
 
 pki_test_data = [
@@ -2559,7 +2561,15 @@
   "pki/testdata/verify_signed_data_unittest/rsa-pss-sha256.pem",
   "pki/testdata/verify_signed_data_unittest/rsa-using-ec-key.pem",
   "pki/testdata/verify_signed_data_unittest/rsa2048-pkcs1-sha512.pem",
+  "pki/testdata/verify_unittest/google-intermediate1.der",
+  "pki/testdata/verify_unittest/google-intermediate2.der",
   "pki/testdata/verify_unittest/google-leaf.der",
+  "pki/testdata/verify_unittest/lencr-intermediate-r3.der",
+  "pki/testdata/verify_unittest/lencr-leaf.der",
+  "pki/testdata/verify_unittest/lencr-root-dst-x3.der",
+  "pki/testdata/verify_unittest/lencr-root-x1-cross-signed.der",
+  "pki/testdata/verify_unittest/lencr-root-x1.der",
+  "pki/testdata/verify_unittest/mozilla_roots.der",
   "pki/testdata/verify_unittest/self-issued.pem",
 ]
 
diff --git a/gen/sources.cmake b/gen/sources.cmake
index 643cb18..9d15ef0 100644
--- a/gen/sources.cmake
+++ b/gen/sources.cmake
@@ -1094,6 +1094,7 @@
   pki/trust_store.cc
   pki/trust_store_collection.cc
   pki/trust_store_in_memory.cc
+  pki/verify.cc
   pki/verify_certificate_chain.cc
   pki/verify_error.cc
   pki/verify_name_match.cc
@@ -1189,6 +1190,7 @@
   pki/verify_certificate_chain_unittest.cc
   pki/verify_name_match_unittest.cc
   pki/verify_signed_data_unittest.cc
+  pki/verify_unittest.cc
 )
 
 set(
@@ -2601,7 +2603,15 @@
   pki/testdata/verify_signed_data_unittest/rsa-pss-sha256.pem
   pki/testdata/verify_signed_data_unittest/rsa-using-ec-key.pem
   pki/testdata/verify_signed_data_unittest/rsa2048-pkcs1-sha512.pem
+  pki/testdata/verify_unittest/google-intermediate1.der
+  pki/testdata/verify_unittest/google-intermediate2.der
   pki/testdata/verify_unittest/google-leaf.der
+  pki/testdata/verify_unittest/lencr-intermediate-r3.der
+  pki/testdata/verify_unittest/lencr-leaf.der
+  pki/testdata/verify_unittest/lencr-root-dst-x3.der
+  pki/testdata/verify_unittest/lencr-root-x1-cross-signed.der
+  pki/testdata/verify_unittest/lencr-root-x1.der
+  pki/testdata/verify_unittest/mozilla_roots.der
   pki/testdata/verify_unittest/self-issued.pem
 )
 
diff --git a/gen/sources.json b/gen/sources.json
index c4e542f..86da01d 100644
--- a/gen/sources.json
+++ b/gen/sources.json
@@ -1042,6 +1042,7 @@
       "pki/trust_store.cc",
       "pki/trust_store_collection.cc",
       "pki/trust_store_in_memory.cc",
+      "pki/verify.cc",
       "pki/verify_certificate_chain.cc",
       "pki/verify_error.cc",
       "pki/verify_name_match.cc",
@@ -1129,7 +1130,8 @@
       "pki/verify_certificate_chain_pkits_unittest.cc",
       "pki/verify_certificate_chain_unittest.cc",
       "pki/verify_name_match_unittest.cc",
-      "pki/verify_signed_data_unittest.cc"
+      "pki/verify_signed_data_unittest.cc",
+      "pki/verify_unittest.cc"
     ],
     "data": [
       "pki/testdata/cert_issuer_source_static_unittest/c1.pem",
@@ -2539,7 +2541,15 @@
       "pki/testdata/verify_signed_data_unittest/rsa-pss-sha256.pem",
       "pki/testdata/verify_signed_data_unittest/rsa-using-ec-key.pem",
       "pki/testdata/verify_signed_data_unittest/rsa2048-pkcs1-sha512.pem",
+      "pki/testdata/verify_unittest/google-intermediate1.der",
+      "pki/testdata/verify_unittest/google-intermediate2.der",
       "pki/testdata/verify_unittest/google-leaf.der",
+      "pki/testdata/verify_unittest/lencr-intermediate-r3.der",
+      "pki/testdata/verify_unittest/lencr-leaf.der",
+      "pki/testdata/verify_unittest/lencr-root-dst-x3.der",
+      "pki/testdata/verify_unittest/lencr-root-x1-cross-signed.der",
+      "pki/testdata/verify_unittest/lencr-root-x1.der",
+      "pki/testdata/verify_unittest/mozilla_roots.der",
       "pki/testdata/verify_unittest/self-issued.pem"
     ]
   },
diff --git a/include/openssl/pki/verify.h b/include/openssl/pki/verify.h
new file mode 100644
index 0000000..0c1b551
--- /dev/null
+++ b/include/openssl/pki/verify.h
@@ -0,0 +1,170 @@
+#ifndef BSSL_VERIFY_H_
+#define BSSL_VERIFY_H_
+
+#include <chrono>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <openssl/pki/signature_verify_cache.h>
+#include <openssl/pki/verify_error.h>
+
+namespace bssl {
+class CertIssuerSourceStatic;
+class TrustStoreInMemory;
+class CertificateVerifyOptions;
+class CertificateVerifyStatus;
+
+class OPENSSL_EXPORT VerifyTrustStore {
+ public:
+  std::unique_ptr<TrustStoreInMemory> trust_store;
+
+  ~VerifyTrustStore();
+
+  // FromDER returns a |TrustStore| derived from interpreting the |der_certs| as
+  // a bunch of DER-encoded certs, concatenated. In the event of a failure nullptr
+  // e is returned and a diagnostic string is placed in |out_diagnostic|
+  static std::unique_ptr<VerifyTrustStore> FromDER(
+      std::string_view der_certs, std::string *out_diagnostic);
+
+  // FromDER returns a |TrustStore| consisting of the supplied DER-encoded
+  // certs in |der_certs|. In the event of a failure nullptr is returned and a
+  // diagnostic string is placed in |out_diagnostic|
+  static std::unique_ptr<VerifyTrustStore> FromDER(
+      const std::vector<std::string_view> &der_certs,
+      std::string *out_diagnostic);
+};
+
+class OPENSSL_EXPORT CertPool {
+ public:
+  CertPool();
+  CertPool(const CertPool &) = delete;
+  CertPool &operator=(const CertPool &) = delete;
+  virtual ~CertPool();
+
+  // FromCerts returns a |CertPool| consisting of the supplied DER-encoded
+  // certs in |der_certs|. In the event of a failure nullptr is returned and a
+  // diagnostic string is placed in |out_diagnostic|
+  static std::unique_ptr<CertPool> FromCerts(
+      const std::vector<std::string_view> &der_certs,
+      std::string *out_diagnostic);
+
+ private:
+  friend std::optional<std::vector<std::vector<std::string>>>
+  CertificateVerifyInternal(const CertificateVerifyOptions &opts,
+                            VerifyError *out_error,
+                            CertificateVerifyStatus *out_status,
+                            bool all_paths);
+  std::unique_ptr<CertIssuerSourceStatic> impl_;
+};
+
+// CertificateVerifyOptions contains all the options for a certificate verification.
+class OPENSSL_EXPORT CertificateVerifyOptions {
+ public:
+  // The key purpose (extended key usage) to check for during verification.
+  enum class KeyPurpose {
+    ANY_EKU,
+    SERVER_AUTH,
+    CLIENT_AUTH,
+    SERVER_AUTH_STRICT,
+    CLIENT_AUTH_STRICT,
+    SERVER_AUTH_STRICT_LEAF,
+    CLIENT_AUTH_STRICT_LEAF,
+  };
+
+  CertificateVerifyOptions();
+  CertificateVerifyOptions(const CertificateVerifyOptions &) = delete;
+  CertificateVerifyOptions &operator=(const CertificateVerifyOptions &) =
+      delete;
+
+  KeyPurpose key_purpose = KeyPurpose::SERVER_AUTH;
+  std::string_view leaf_cert;
+  std::vector<std::string_view> intermediates;
+
+  // extra_intermediates optionally points to a pool of common intermediates.
+  const CertPool *extra_intermediates = nullptr;
+  // trust_store points to the set of root certificates to trust.
+  const VerifyTrustStore *trust_store = nullptr;
+  // min_rsa_modulus_length is the minimum acceptable RSA key size in a chain.
+  size_t min_rsa_modulus_length = 1024;
+  // time is the time in POSIX seconds since the POSIX epoch at which to
+  // validate the chain. It defaults to the current time if not set.
+  std::optional<int64_t> time;
+  // insecurely_allow_sha1 allows verification of signatures that use SHA-1
+  // message digests.  This option is insecure and should not be used.
+  bool insecurely_allow_sha1 = false;
+
+  // max_iteration_count, if not zero, limits the number of times path building
+  // will try to append an intermediate to a potential path. This bounds the
+  // amount of time that a verification attempt can take, at the risk of
+  // rejecting cases that would be solved if only more effort were used.
+  uint32_t max_iteration_count = 0;
+
+  // Sets an optional deadline for completing path building. It defaults
+  // to std::chrono::time_point::max() if it not set. If |deadline| has a
+  // value that has passed based on comparison to
+  // std::chrono::steady_clock::now(), and path building has not completed,
+  // path building will stop. Note that this is not a hard limit, there is no
+  // guarantee how far past |deadline| time will be when path building is
+  // aborted.
+  std::optional<std::chrono::time_point<std::chrono::steady_clock>> deadline;
+
+  // max_path_building_depth, if not zero, limits the depth of the path that the
+  // path building algorithm attempts to build between leafs and roots. Using
+  // this comes at the risk of rejecting cases that would be solved if only one
+  // more certificate is added to the path.
+  uint32_t max_path_building_depth = 0;
+
+  // signature_verify_cache, if not nullptr, points to an object implementing a
+  // signature verification cache derived from
+  // <openssl/pki/signature_verify_cache.h>
+  SignatureVerifyCache *signature_verify_cache = nullptr;
+};
+
+// CertificateVerifyStatus describes the status of a certificate verification
+// attempt.
+class OPENSSL_EXPORT CertificateVerifyStatus {
+ public:
+  CertificateVerifyStatus();
+
+  // IterationCount returns the total number of attempted certificate additions
+  // to any potential path while performing path building for verification. It
+  // is the same value which may be bound by max_iteration_count in
+  // CertificateVerifyOptions.
+  size_t IterationCount() const;
+
+  // MaxDepthSeen returns the maximum path depth seen during path building.
+  size_t MaxDepthSeen() const;
+
+ private:
+  friend std::optional<std::vector<std::vector<std::string>>>
+  CertificateVerifyInternal(const CertificateVerifyOptions &opts,
+                            VerifyError *out_error,
+                            CertificateVerifyStatus *out_status,
+                            bool all_paths);
+  size_t iteration_count_ = 0;
+  size_t max_depth_seen_ = 0;
+};
+
+// Verify verifies |opts.leaf_cert| using the other values in |opts|. It
+// returns either an error, or else a validated chain from leaf to root.
+//
+// In the event of an error return, |out_error| will be updated with information
+// about the error.  It may be |nullptr|.
+//
+// Status information about the verification will be returned in |out_status|.
+// It may be |nullptr|.
+OPENSSL_EXPORT std::optional<std::vector<std::string>> CertificateVerify(
+    const CertificateVerifyOptions &opts, VerifyError *out_error = nullptr,
+    CertificateVerifyStatus *out_status = nullptr);
+
+// VerifyAllPaths verifies |opts.leaf_cert| using the other values in |opts|,
+// and returns all possible valid chains from the leaf to a root. If no chains
+// exist, it returns an error.
+OPENSSL_EXPORT std::optional<std::vector<std::vector<std::string>>>
+CertificateVerifyAllPaths(const CertificateVerifyOptions &opts);
+
+}  // namespace bssl
+
+#endif  // BSSL_VERIFY_H_
diff --git a/pki/testdata/verify_unittest/google-intermediate1.der b/pki/testdata/verify_unittest/google-intermediate1.der
new file mode 100644
index 0000000..d3c07ef
--- /dev/null
+++ b/pki/testdata/verify_unittest/google-intermediate1.der
Binary files differ
diff --git a/pki/testdata/verify_unittest/google-intermediate2.der b/pki/testdata/verify_unittest/google-intermediate2.der
new file mode 100644
index 0000000..82e5004
--- /dev/null
+++ b/pki/testdata/verify_unittest/google-intermediate2.der
Binary files differ
diff --git a/pki/testdata/verify_unittest/lencr-intermediate-r3.der b/pki/testdata/verify_unittest/lencr-intermediate-r3.der
new file mode 100644
index 0000000..2d66ea7
--- /dev/null
+++ b/pki/testdata/verify_unittest/lencr-intermediate-r3.der
Binary files differ
diff --git a/pki/testdata/verify_unittest/lencr-leaf.der b/pki/testdata/verify_unittest/lencr-leaf.der
new file mode 100644
index 0000000..4dbed56
--- /dev/null
+++ b/pki/testdata/verify_unittest/lencr-leaf.der
Binary files differ
diff --git a/pki/testdata/verify_unittest/lencr-root-dst-x3.der b/pki/testdata/verify_unittest/lencr-root-dst-x3.der
new file mode 100644
index 0000000..95500f6
--- /dev/null
+++ b/pki/testdata/verify_unittest/lencr-root-dst-x3.der
Binary files differ
diff --git a/pki/testdata/verify_unittest/lencr-root-x1-cross-signed.der b/pki/testdata/verify_unittest/lencr-root-x1-cross-signed.der
new file mode 100644
index 0000000..79a33ba
--- /dev/null
+++ b/pki/testdata/verify_unittest/lencr-root-x1-cross-signed.der
Binary files differ
diff --git a/pki/testdata/verify_unittest/lencr-root-x1.der b/pki/testdata/verify_unittest/lencr-root-x1.der
new file mode 100644
index 0000000..9d2132e
--- /dev/null
+++ b/pki/testdata/verify_unittest/lencr-root-x1.der
Binary files differ
diff --git a/pki/testdata/verify_unittest/mozilla_roots.der b/pki/testdata/verify_unittest/mozilla_roots.der
new file mode 100644
index 0000000..f89eec4
--- /dev/null
+++ b/pki/testdata/verify_unittest/mozilla_roots.der
Binary files differ
diff --git a/pki/verify.cc b/pki/verify.cc
new file mode 100644
index 0000000..e7e4980
--- /dev/null
+++ b/pki/verify.cc
@@ -0,0 +1,355 @@
+/* Copyright (c) 2023, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <openssl/pki/verify.h>
+
+#include <assert.h>
+
+#include <chrono>
+#include <optional>
+#include <string_view>
+
+#include <openssl/base.h>
+#include <openssl/bytestring.h>
+#include <openssl/pool.h>
+
+#include <openssl/pki/signature_verify_cache.h>
+
+#include "cert_errors.h"
+#include "cert_issuer_source_static.h"
+#include "certificate_policies.h"
+#include "common_cert_errors.h"
+#include "encode_values.h"
+#include "input.h"
+#include "parse_certificate.h"
+#include "parse_values.h"
+#include "parsed_certificate.h"
+#include "path_builder.h"
+#include "simple_path_builder_delegate.h"
+#include "trust_store.h"
+#include "trust_store_in_memory.h"
+#include "verify_certificate_chain.h"
+
+namespace bssl {
+
+namespace {
+
+std::optional<std::shared_ptr<const ParsedCertificate>>
+InternalParseCertificate(Span<const uint8_t> cert, std::string *out_diagnostic) {
+  ParseCertificateOptions default_options{};
+  // We follow Chromium in setting |allow_invalid_serial_numbers| in order to
+  // not choke on 21-byte serial numbers, which are common.
+  //
+  // The reason for the discrepancy is that unsigned numbers with the high bit
+  // otherwise set get an extra 0 byte in front to keep them positive. So if you
+  // do:
+  //    var num [20]byte
+  //    fillWithRandom(num[:])
+  //    serialNumber := new(big.Int).SetBytes(num[:])
+  //    encodeASN1Integer(serialNumber)
+  //
+  // Then half of your serial numbers will be encoded with 21 bytes. (And
+  // 1/512th will have 19 bytes instead of 20.)
+  default_options.allow_invalid_serial_numbers = true;
+
+  UniquePtr<CRYPTO_BUFFER> buffer(
+      CRYPTO_BUFFER_new(cert.data(), cert.size(), nullptr));
+  CertErrors errors;
+  std::shared_ptr<const ParsedCertificate> parsed_cert(
+      ParsedCertificate::Create(std::move(buffer), default_options, &errors));
+  if (!parsed_cert) {
+    *out_diagnostic = errors.ToDebugString();
+    return {};
+  }
+  return parsed_cert;
+}
+}  // namespace
+
+
+CertPool::CertPool() {}
+
+CertificateVerifyOptions::CertificateVerifyOptions() {}
+
+static std::unique_ptr<VerifyTrustStore> WrapTrustStore(
+    std::unique_ptr<TrustStoreInMemory> trust_store) {
+  std::unique_ptr<VerifyTrustStore> ret(new VerifyTrustStore);
+  ret->trust_store = std::move(trust_store);
+  return ret;
+}
+
+VerifyTrustStore::~VerifyTrustStore() {}
+
+std::unique_ptr<VerifyTrustStore> VerifyTrustStore::FromDER(
+    std::string_view der_certs, std::string *out_diagnostic) {
+  auto store = std::make_unique<TrustStoreInMemory>();
+  CBS cbs = StringAsBytes(der_certs);
+
+  for (size_t cert_num = 1; CBS_len(&cbs) != 0; cert_num++) {
+    CBS cert;
+    if (!CBS_get_asn1_element(&cbs, &cert, CBS_ASN1_SEQUENCE)) {
+      *out_diagnostic = "failed to get ASN.1 SEQUENCE from input at cert " +
+                        std::to_string(cert_num);
+      return {};
+    }
+
+    auto parsed_cert = InternalParseCertificate(
+        Span(CBS_data(&cert), CBS_len(&cert)), out_diagnostic);
+    if (!parsed_cert.has_value()) {
+      return {};
+    }
+    store->AddTrustAnchor(parsed_cert.value());
+  }
+
+  return WrapTrustStore(std::move(store));
+}
+
+std::unique_ptr<VerifyTrustStore> VerifyTrustStore::FromDER(
+    const std::vector<std::string_view> &der_roots,
+    std::string *out_diagnostic) {
+  auto store = std::make_unique<TrustStoreInMemory>();
+
+  for (const std::string_view &cert : der_roots) {
+    auto parsed_cert = InternalParseCertificate(StringAsBytes(cert), out_diagnostic);
+    if (!parsed_cert.has_value()) {
+      return {};
+    }
+    store->AddTrustAnchor(parsed_cert.value());
+  }
+
+  return WrapTrustStore(std::move(store));
+}
+
+CertPool::~CertPool() {}
+
+
+std::unique_ptr<CertPool> CertPool::FromCerts(
+    const std::vector<std::string_view> &der_certs,
+    std::string *out_diagnostic) {
+  auto pool = std::make_unique<CertPool>();
+  pool->impl_ = std::make_unique<CertIssuerSourceStatic>();
+
+  for (const std::string_view &cert : der_certs) {
+    auto parsed_cert =
+        InternalParseCertificate(StringAsBytes(cert), out_diagnostic);
+    if (!parsed_cert.has_value()) {
+      return {};
+    }
+    pool->impl_->AddCert(std::move(parsed_cert.value()));
+  }
+
+  return pool;
+}
+
+CertificateVerifyStatus::CertificateVerifyStatus() {}
+
+size_t CertificateVerifyStatus::IterationCount() const {
+  return iteration_count_;
+}
+
+size_t CertificateVerifyStatus::MaxDepthSeen() const { return max_depth_seen_; }
+
+// PathBuilderDelegateImpl implements a deadline and allows for the
+// use of a SignatureVerifyCache if an implementation is provided.
+class PathBuilderDelegateImpl : public SimplePathBuilderDelegate {
+ public:
+  PathBuilderDelegateImpl(
+      size_t min_rsa_modulus_length_bits, DigestPolicy digest_policy,
+      std::chrono::time_point<std::chrono::steady_clock> deadline,
+      SignatureVerifyCache *cache)
+      : SimplePathBuilderDelegate(min_rsa_modulus_length_bits, digest_policy),
+        deadline_(deadline),
+        cache_(cache) {}
+
+  bool IsDeadlineExpired() override {
+    return (std::chrono::steady_clock::now() > deadline_);
+  }
+
+  SignatureVerifyCache *GetVerifyCache() override { return cache_; }
+
+ private:
+  const std::chrono::time_point<std::chrono::steady_clock> deadline_;
+  SignatureVerifyCache *cache_;
+};
+
+std::optional<std::vector<std::vector<std::string>>> CertificateVerifyInternal(
+    const CertificateVerifyOptions &opts, VerifyError *out_error,
+    CertificateVerifyStatus *out_status, bool all_paths) {
+  VerifyError dummy;
+  if (!out_error) {
+    out_error = &dummy;
+  }
+  if (out_status != nullptr) {
+    out_status->iteration_count_ = 0;
+    out_status->max_depth_seen_ = 0;
+  }
+
+  std::string diagnostic;
+  std::optional<std::shared_ptr<const ParsedCertificate>> maybe_leaf =
+      InternalParseCertificate(StringAsBytes(opts.leaf_cert), &diagnostic);
+
+  if (!maybe_leaf.has_value()) {
+    *out_error = {VerifyError::StatusCode::CERTIFICATE_INVALID, 0, diagnostic};
+    return {};
+  }
+  std::shared_ptr<const ParsedCertificate> leaf_cert = maybe_leaf.value();
+
+  int64_t now;
+  if (opts.time.has_value()) {
+    now = opts.time.value();
+  } else {
+    now = time(NULL);
+  }
+
+  der::GeneralizedTime verification_time;
+  if (!der::EncodePosixTimeAsGeneralizedTime(now, &verification_time)) {
+    *out_error = {VerifyError::StatusCode::VERIFICATION_FAILURE, -1,
+                  "\nCould not encode verification time\n"};
+    return {};
+  }
+
+  TrustStore *trust_store = nullptr;
+  if (opts.trust_store) {
+    trust_store = opts.trust_store->trust_store.get();
+  }
+
+  auto digest_policy = SimplePathBuilderDelegate::DigestPolicy::kStrong;
+  // TODO(b/111551631): remove this
+  if (opts.insecurely_allow_sha1) {
+    digest_policy = SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1;
+  }
+
+  std::chrono::time_point<std::chrono::steady_clock> deadline =
+      std::chrono::time_point<std::chrono::steady_clock>::max();
+  if (opts.deadline.has_value()) {
+    deadline = opts.deadline.value();
+  }
+
+  PathBuilderDelegateImpl path_builder_delegate(
+      opts.min_rsa_modulus_length, digest_policy, deadline,
+      opts.signature_verify_cache);
+
+  KeyPurpose key_purpose = KeyPurpose::SERVER_AUTH;
+  switch (opts.key_purpose) {
+    case CertificateVerifyOptions::KeyPurpose::ANY_EKU:
+      key_purpose = KeyPurpose::ANY_EKU;
+      break;
+    case CertificateVerifyOptions::KeyPurpose::SERVER_AUTH:
+      key_purpose = KeyPurpose::SERVER_AUTH;
+      break;
+    case CertificateVerifyOptions::KeyPurpose::CLIENT_AUTH:
+      key_purpose = KeyPurpose::CLIENT_AUTH;
+      break;
+    case CertificateVerifyOptions::KeyPurpose::SERVER_AUTH_STRICT:
+      key_purpose = KeyPurpose::SERVER_AUTH_STRICT;
+      break;
+    case CertificateVerifyOptions::KeyPurpose::CLIENT_AUTH_STRICT:
+      key_purpose = KeyPurpose::CLIENT_AUTH_STRICT;
+      break;
+    case CertificateVerifyOptions::KeyPurpose::SERVER_AUTH_STRICT_LEAF:
+      key_purpose = KeyPurpose::SERVER_AUTH_STRICT_LEAF;
+      break;
+    case CertificateVerifyOptions::KeyPurpose::CLIENT_AUTH_STRICT_LEAF:
+      key_purpose = KeyPurpose::CLIENT_AUTH_STRICT_LEAF;
+      break;
+  }
+  CertPathBuilder path_builder(leaf_cert, trust_store, &path_builder_delegate,
+                               verification_time, key_purpose,
+                               InitialExplicitPolicy::kFalse,
+                               /* user_initial_policy_set= */
+                               {der::Input(kAnyPolicyOid)},
+                               InitialPolicyMappingInhibit::kFalse,
+                               InitialAnyPolicyInhibit::kFalse);
+
+  CertIssuerSourceStatic intermediates;
+  for (const std::string_view &cert : opts.intermediates) {
+    std::string diag_string;
+    std::optional<std::shared_ptr<const ParsedCertificate>> parsed =
+        InternalParseCertificate(StringAsBytes(cert), &diag_string);
+    if (!parsed.has_value()) {
+      if (path_builder_delegate.IsDebugLogEnabled()) {
+        path_builder_delegate.DebugLog("skipping bad intermediate: " +
+                                       diag_string);
+      }
+      continue;
+    }
+    intermediates.AddCert(std::move(parsed.value()));
+  }
+  path_builder.AddCertIssuerSource(&intermediates);
+
+  if (opts.extra_intermediates != nullptr) {
+    path_builder.AddCertIssuerSource(opts.extra_intermediates->impl_.get());
+  }
+
+  if (opts.max_iteration_count > 0) {
+    path_builder.SetIterationLimit(opts.max_iteration_count);
+  }
+
+  if (opts.max_path_building_depth > 0) {
+    path_builder.SetDepthLimit(opts.max_path_building_depth);
+  }
+
+  path_builder.SetExploreAllPaths(all_paths);
+
+  CertPathBuilder::Result result = path_builder.Run();
+
+  if (out_status != nullptr) {
+    out_status->iteration_count_ = result.iteration_count;
+    out_status->max_depth_seen_ = result.max_depth_seen;
+  }
+
+  *out_error = result.GetBestPathVerifyError();
+
+  if (result.HasValidPath()) {
+    std::vector<std::vector<std::string>> ret;
+    if (!all_paths) {
+      auto best_path = result.GetBestValidPath();
+      ret.push_back(std::vector<std::string>());
+      for (size_t i = 0; i < best_path->certs.size(); i++) {
+        ret[0].emplace_back(BytesAsStringView(best_path->certs[i]->der_cert()));
+      }
+      return ret;
+    }
+    for (const auto &path : result.paths) {
+      if (!path->IsValid()) {
+        continue;
+      }
+      std::vector<std::string> ret_path;
+      for (const auto &cert : path->certs) {
+        ret_path.emplace_back(BytesAsStringView(cert->der_cert()));
+      }
+      ret.push_back(ret_path);
+    }
+    return ret;
+  }
+
+  return {};
+}
+
+std::optional<std::vector<std::string>> CertificateVerify(
+    const CertificateVerifyOptions &opts, VerifyError *out_error,
+    CertificateVerifyStatus *out_status) {
+  auto single_path = CertificateVerifyInternal(opts, out_error, out_status,
+                                               /*all_paths=*/false);
+  if (!single_path.has_value()) {
+    return {};
+  }
+  return single_path.value()[0];
+}
+
+std::optional<std::vector<std::vector<std::string>>> CertificateVerifyAllPaths(
+    const CertificateVerifyOptions &opts) {
+  return CertificateVerifyInternal(opts, nullptr, nullptr, /*all_paths=*/true);
+}
+
+}  // namespace bssl
diff --git a/pki/verify_unittest.cc b/pki/verify_unittest.cc
new file mode 100644
index 0000000..b724a98
--- /dev/null
+++ b/pki/verify_unittest.cc
@@ -0,0 +1,140 @@
+/* Copyright (c) 2023, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <string.h>
+
+#include <optional>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <openssl/pki/verify.h>
+#include <openssl/pki/verify_error.h>
+#include <openssl/sha.h>
+
+#include "test_helpers.h"
+
+namespace bssl {
+
+static std::unique_ptr<VerifyTrustStore> MozillaRootStore() {
+  std::string diagnostic;
+  return VerifyTrustStore::FromDER(
+             bssl::ReadTestFileToString(
+                 "testdata/verify_unittest/mozilla_roots.der"),
+             &diagnostic);
+}
+
+using ::testing::UnorderedElementsAre;
+
+static std::string GetTestdata(std::string_view filename) {
+  return bssl::ReadTestFileToString("testdata/verify_unittest/" +
+                                    std::string(filename));
+}
+
+TEST(VerifyTest, GoogleChain) {
+  const std::string leaf = GetTestdata("google-leaf.der");
+  const std::string intermediate1 = GetTestdata("google-intermediate1.der");
+  const std::string intermediate2 = GetTestdata("google-intermediate2.der");
+  CertificateVerifyOptions opts;
+  opts.leaf_cert = leaf;
+  opts.intermediates = {intermediate1, intermediate2};
+  opts.time = 1499727444;
+  std::unique_ptr<VerifyTrustStore> roots = MozillaRootStore();
+  opts.trust_store = roots.get();
+
+  VerifyError error;
+  ASSERT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString();
+
+  opts.intermediates = {};
+  EXPECT_FALSE(CertificateVerify(opts, &error));
+  ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND)
+      << error.DiagnosticString();
+}
+
+
+TEST(VerifyTest, ExtraIntermediates) {
+  const std::string leaf = GetTestdata("google-leaf.der");
+  const std::string intermediate1 = GetTestdata("google-intermediate1.der");
+  const std::string intermediate2 = GetTestdata("google-intermediate2.der");
+
+  CertificateVerifyOptions opts;
+  opts.leaf_cert = leaf;
+  std::string diagnostic;
+  const auto cert_pool_status = CertPool::FromCerts(
+      {
+          intermediate1,
+          intermediate2,
+      },
+      &diagnostic);
+  ASSERT_TRUE(cert_pool_status) << diagnostic;
+  opts.extra_intermediates = cert_pool_status.get();
+  opts.time = 1499727444;
+  std::unique_ptr<VerifyTrustStore> roots = MozillaRootStore();
+  opts.trust_store = roots.get();
+
+  VerifyError error;
+  ASSERT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString();
+}
+
+TEST(VerifyTest, AllPaths) {
+  const std::string leaf = GetTestdata("lencr-leaf.der");
+  const std::string intermediate1 = GetTestdata("lencr-intermediate-r3.der");
+  const std::string intermediate2 =
+      GetTestdata("lencr-root-x1-cross-signed.der");
+  const std::string root1 = GetTestdata("lencr-root-x1.der");
+  const std::string root2 = GetTestdata("lencr-root-dst-x3.der");
+
+  std::vector<std::string> expected_path1 = {leaf, intermediate1, root1};
+  std::vector<std::string> expected_path2 = {leaf, intermediate1, intermediate2,
+                                             root2};
+
+  CertificateVerifyOptions opts;
+  opts.leaf_cert = leaf;
+  opts.intermediates = {intermediate1, intermediate2};
+  opts.time = 1699404611;
+  std::unique_ptr<VerifyTrustStore> roots = MozillaRootStore();
+  opts.trust_store = roots.get();
+
+  auto paths = CertificateVerifyAllPaths(opts);
+  ASSERT_TRUE(paths);
+  EXPECT_EQ(2U, paths.value().size());
+  EXPECT_THAT(paths.value(),
+              UnorderedElementsAre(expected_path1, expected_path2));
+}
+
+TEST(VerifyTest, DepthLimit) {
+  const std::string leaf = GetTestdata("google-leaf.der");
+  const std::string intermediate1 = GetTestdata("google-intermediate1.der");
+  const std::string intermediate2 = GetTestdata("google-intermediate2.der");
+  CertificateVerifyOptions opts;
+  opts.leaf_cert = leaf;
+  opts.intermediates = {intermediate1, intermediate2};
+  opts.time = 1499727444;
+  // Set the |max_path_building_depth| explicitly to test the non-default case.
+  // Depth of 5 is enough to successfully find a path.
+  opts.max_path_building_depth = 5;
+  std::unique_ptr<VerifyTrustStore> roots = MozillaRootStore();
+  opts.trust_store = roots.get();
+
+  VerifyError error;
+  ASSERT_TRUE(CertificateVerify(opts, &error)) << error.DiagnosticString();
+
+  // Depth of 2 is not enough to find a path.
+  opts.max_path_building_depth = 2;
+  EXPECT_FALSE(CertificateVerify(opts, &error));
+  ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_DEPTH_LIMIT_REACHED)
+      << error.DiagnosticString();
+}
+
+}  // namespace bssl