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