diff --git a/crypto/curve25519/internal.h b/crypto/curve25519/internal.h
index 0cd1a12..a40f109 100644
--- a/crypto/curve25519/internal.h
+++ b/crypto/curve25519/internal.h
@@ -32,7 +32,7 @@
 #endif
 
 #if !defined(OPENSSL_NO_ASM) && !defined(OPENSSL_SMALL) && \
-    defined(__GNUC__) && defined(__x86_64__)
+    defined(__GNUC__) && defined(__x86_64__) && !defined(OPENSSL_WINDOWS)
 #define BORINGSSL_FE25519_ADX
 
 // fiat_curve25519_adx_mul is defined in
diff --git a/crypto/x509/x509_lu.c b/crypto/x509/x509_lu.c
index bda1a3e..c79c558 100644
--- a/crypto/x509/x509_lu.c
+++ b/crypto/x509/x509_lu.c
@@ -230,13 +230,11 @@
     }
   }
 
-  // if (ret->data.ptr != NULL) X509_OBJECT_free_contents(ret);
-
+  // TODO(crbug.com/boringssl/685): This should call
+  // |X509_OBJECT_free_contents|.
   ret->type = tmp->type;
-  ret->data.ptr = tmp->data.ptr;
-
+  ret->data = tmp->data;
   X509_OBJECT_up_ref_count(ret);
-
   return 1;
 }
 
@@ -391,8 +389,27 @@
   return sk_X509_OBJECT_value(h, idx);
 }
 
-STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(X509_STORE *st) {
-  return st->objs;
+static X509_OBJECT *x509_object_dup(const X509_OBJECT *obj) {
+  X509_OBJECT *ret = X509_OBJECT_new();
+  if (ret == NULL) {
+    return NULL;
+  }
+  ret->type = obj->type;
+  ret->data = obj->data;
+  X509_OBJECT_up_ref_count(ret);
+  return ret;
+}
+
+STACK_OF(X509_OBJECT) *X509_STORE_get1_objects(X509_STORE *store) {
+  CRYPTO_MUTEX_lock_read(&store->objs_lock);
+  STACK_OF(X509_OBJECT) *ret =
+      sk_X509_OBJECT_deep_copy(store->objs, x509_object_dup, X509_OBJECT_free);
+  CRYPTO_MUTEX_unlock_read(&store->objs_lock);
+  return ret;
+}
+
+STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(X509_STORE *store) {
+  return store->objs;
 }
 
 STACK_OF(X509) *X509_STORE_CTX_get1_certs(X509_STORE_CTX *ctx, X509_NAME *nm) {
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index b40eb99..a3616dc 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -1382,6 +1382,11 @@
     threads.emplace_back([&] {
       ASSERT_TRUE(X509_STORE_add_cert(store.get(), other2.get()));
     });
+    threads.emplace_back([&] {
+      bssl::UniquePtr<STACK_OF(X509_OBJECT)> objs(
+          X509_STORE_get1_objects(store.get()));
+      ASSERT_TRUE(objs);
+    });
   }
   for (auto &thread : threads) {
     thread.join();
diff --git a/include/openssl/asn1.h b/include/openssl/asn1.h
index c9f265a..b7afbbb 100644
--- a/include/openssl/asn1.h
+++ b/include/openssl/asn1.h
@@ -1636,18 +1636,18 @@
 
 // ASN1_STRFLGS_ESC_2253 causes characters to be escaped as in RFC 2253, section
 // 2.4.
-#define ASN1_STRFLGS_ESC_2253 1
+#define ASN1_STRFLGS_ESC_2253 1ul
 
 // ASN1_STRFLGS_ESC_CTRL causes all control characters to be escaped.
-#define ASN1_STRFLGS_ESC_CTRL 2
+#define ASN1_STRFLGS_ESC_CTRL 2ul
 
 // ASN1_STRFLGS_ESC_MSB causes all characters above 127 to be escaped.
-#define ASN1_STRFLGS_ESC_MSB 4
+#define ASN1_STRFLGS_ESC_MSB 4ul
 
 // ASN1_STRFLGS_ESC_QUOTE causes the string to be surrounded by quotes, rather
 // than using backslashes, when characters are escaped. Fewer characters will
 // require escapes in this case.
-#define ASN1_STRFLGS_ESC_QUOTE 8
+#define ASN1_STRFLGS_ESC_QUOTE 8ul
 
 // ASN1_STRFLGS_UTF8_CONVERT causes the string to be encoded as UTF-8, with each
 // byte in the UTF-8 encoding treated as an individual character for purposes of
@@ -1655,29 +1655,29 @@
 // as a character, with wide characters escaped as "\Uxxxx" or "\Wxxxxxxxx".
 // Note this can be ambiguous if |ASN1_STRFLGS_ESC_*| are all unset. In that
 // case, backslashes are not escaped, but wide characters are.
-#define ASN1_STRFLGS_UTF8_CONVERT 0x10
+#define ASN1_STRFLGS_UTF8_CONVERT 0x10ul
 
 // ASN1_STRFLGS_IGNORE_TYPE causes the string type to be ignored. The
 // |ASN1_STRING| in-memory representation will be printed directly.
-#define ASN1_STRFLGS_IGNORE_TYPE 0x20
+#define ASN1_STRFLGS_IGNORE_TYPE 0x20ul
 
 // ASN1_STRFLGS_SHOW_TYPE causes the string type to be included in the output.
-#define ASN1_STRFLGS_SHOW_TYPE 0x40
+#define ASN1_STRFLGS_SHOW_TYPE 0x40ul
 
 // ASN1_STRFLGS_DUMP_ALL causes all strings to be printed as a hexdump, using
 // RFC 2253 hexstring notation, such as "#0123456789ABCDEF".
-#define ASN1_STRFLGS_DUMP_ALL 0x80
+#define ASN1_STRFLGS_DUMP_ALL 0x80ul
 
 // ASN1_STRFLGS_DUMP_UNKNOWN behaves like |ASN1_STRFLGS_DUMP_ALL| but only
 // applies to values of unknown type. If unset, unknown values will print
 // their contents as single-byte characters with escape sequences.
-#define ASN1_STRFLGS_DUMP_UNKNOWN 0x100
+#define ASN1_STRFLGS_DUMP_UNKNOWN 0x100ul
 
 // ASN1_STRFLGS_DUMP_DER causes hexdumped strings (as determined by
 // |ASN1_STRFLGS_DUMP_ALL| or |ASN1_STRFLGS_DUMP_UNKNOWN|) to print the entire
 // DER element as in RFC 2253, rather than only the contents of the
 // |ASN1_STRING|.
-#define ASN1_STRFLGS_DUMP_DER 0x200
+#define ASN1_STRFLGS_DUMP_DER 0x200ul
 
 // ASN1_STRFLGS_RFC2253 causes the string to be escaped as in RFC 2253,
 // additionally escaping control characters.
diff --git a/include/openssl/base.h b/include/openssl/base.h
index 9a47321..98de503 100644
--- a/include/openssl/base.h
+++ b/include/openssl/base.h
@@ -109,7 +109,7 @@
 // A consumer may use this symbol in the preprocessor to temporarily build
 // against multiple revisions of BoringSSL at the same time. It is not
 // recommended to do so for longer than is necessary.
-#define BORINGSSL_API_VERSION 29
+#define BORINGSSL_API_VERSION 30
 
 #if defined(BORINGSSL_SHARED_LIBRARY)
 
diff --git a/include/openssl/pki/certificate.h b/include/openssl/pki/certificate.h
new file mode 100644
index 0000000..3adaa03
--- /dev/null
+++ b/include/openssl/pki/certificate.h
@@ -0,0 +1,83 @@
+/* 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. */
+
+#ifndef OPENSSL_HEADER_BSSL_PKI_CERTIFICATE_H_
+#define OPENSSL_HEADER_BSSL_PKI_CERTIFICATE_H_
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include <openssl/base.h>
+#include <openssl/span.h>
+
+namespace bssl {
+
+struct CertificateInternals;
+
+// Certificate represents a parsed X.509 certificate. It includes accessors for
+// the various things that one might want to extract from a certificate,
+class OPENSSL_EXPORT Certificate {
+ public:
+  Certificate(Certificate&& other);
+  Certificate(const Certificate& other) = delete;
+  ~Certificate();
+  Certificate& operator=(const Certificate& other) = delete;
+
+  // FromDER returns a certificate from an DER-encoded X.509 object in |der|.
+  // In the event of a failure, it will return no value, and |out_diagnostic|
+  // may be set to a string of human readable debugging information if
+  // information abou the failure is available.
+  static std::unique_ptr<Certificate> FromDER(
+      bssl::Span<const uint8_t> der, std::string *out_diagnostic);
+
+  // FromPEM returns a certificate from the first CERTIFICATE PEM block in
+  // |pem|. In the event of a failure, it will return no value, and
+  // |out_diagnostic| may be set to a string of human readable debugging
+  // informtion if informaiton about the failuew is available.
+  static std::unique_ptr<Certificate> FromPEM(
+      std::string_view pem, std::string *out_diagnostic);
+
+  // IsSelfIssued returns true if the certificate is "self-issued" per RFC 5280
+  // section 6.1. I.e. that the subject and issuer names are equal after
+  // canonicalization (and no other checks).
+  //
+  // Other contexts may have a different notion such as "self signed" which
+  // may or may not be this, and may check other properties of the certificate.
+  bool IsSelfIssued() const;
+
+  // Validity specifies the temporal validity of a cerificate, expressed in
+  // POSIX time values of seconds since the POSIX epoch. The certificate is
+  // valid at POSIX time t in second granularity, where not_before <= t <=
+  // not_after.
+  struct Validity {
+    int64_t not_before;
+    int64_t not_after;
+  };
+
+  Validity GetValidity() const;
+
+  // The binary, big-endian, DER representation of the certificate serial
+  // number. It may include a leading 00 byte.
+  bssl::Span<const uint8_t> GetSerialNumber() const;
+
+ private:
+  explicit Certificate(std::unique_ptr<CertificateInternals> internals);
+
+  std::unique_ptr<CertificateInternals> internals_;
+};
+
+}  // namespace bssl
+
+#endif  // OPENSSL_HEADER_BSSL_PKI_CERTIFICATE_H_
diff --git a/pki/signature_verify_cache.h b/include/openssl/pki/signature_verify_cache.h
similarity index 100%
rename from pki/signature_verify_cache.h
rename to include/openssl/pki/signature_verify_cache.h
diff --git a/include/openssl/x509.h b/include/openssl/x509.h
index 59356ed..c7e6919 100644
--- a/include/openssl/x509.h
+++ b/include/openssl/x509.h
@@ -2215,6 +2215,9 @@
 // functions which take a non-const pointer may not. Callers that wish to modify
 // verification parameters in a shared |X509_STORE| should instead modify
 // |X509_STORE_CTX|s individually.
+//
+// Objects in an |X509_STORE| are represented as an |X509_OBJECT|. Some
+// functions in this library return values with this type.
 
 // X509_STORE_new returns a newly-allocated |X509_STORE|, or NULL on error.
 OPENSSL_EXPORT X509_STORE *X509_STORE_new(void);
@@ -2303,6 +2306,41 @@
 // |X509_VERIFY_PARAM_set_trust| for details.
 OPENSSL_EXPORT int X509_STORE_set_trust(X509_STORE *store, int trust);
 
+// The following constants indicate the type of an |X509_OBJECT|.
+#define X509_LU_NONE 0
+#define X509_LU_X509 1
+#define X509_LU_CRL 2
+#define X509_LU_PKEY 3
+
+DEFINE_STACK_OF(X509_OBJECT)
+
+// X509_OBJECT_new returns a newly-allocated, empty |X509_OBJECT| or NULL on
+// error.
+OPENSSL_EXPORT X509_OBJECT *X509_OBJECT_new(void);
+
+// X509_OBJECT_free releases memory associated with |obj|.
+OPENSSL_EXPORT void X509_OBJECT_free(X509_OBJECT *obj);
+
+// X509_OBJECT_get_type returns the type of |obj|, which will be one of the
+// |X509_LU_*| constants.
+OPENSSL_EXPORT int X509_OBJECT_get_type(const X509_OBJECT *obj);
+
+// X509_OBJECT_get0_X509 returns |obj| as a certificate, or NULL if |obj| is not
+// a certificate.
+OPENSSL_EXPORT X509 *X509_OBJECT_get0_X509(const X509_OBJECT *obj);
+
+// X509_STORE_get1_objects returns a newly-allocated stack containing the
+// contents of |store|, or NULL on error. The caller must release the result
+// with |sk_X509_OBJECT_pop_free| and |X509_OBJECT_free| when done.
+//
+// The result will include all certificates and CRLs added via
+// |X509_STORE_add_cert| and |X509_STORE_add_crl|, as well as any cached objects
+// added by |X509_LOOKUP_hash_dir|. The last of these may change over time, as
+// different objects are loaded from the filesystem. Callers should not depend
+// on this caching behavior. The objects are returned in no particular order.
+OPENSSL_EXPORT STACK_OF(X509_OBJECT) *X509_STORE_get1_objects(
+    X509_STORE *store);
+
 
 // Certificate verification.
 //
@@ -3187,42 +3225,42 @@
 
 // XN_FLAG_COMPAT prints with |X509_NAME_print|'s format and return value
 // convention.
-#define XN_FLAG_COMPAT 0
+#define XN_FLAG_COMPAT 0ul
 
 // XN_FLAG_SEP_MASK determines the separators to use between attributes.
-#define XN_FLAG_SEP_MASK (0xf << 16)
+#define XN_FLAG_SEP_MASK (0xful << 16)
 
 // XN_FLAG_SEP_COMMA_PLUS separates RDNs with "," and attributes within an RDN
 // with "+", as in RFC 2253.
-#define XN_FLAG_SEP_COMMA_PLUS (1 << 16)
+#define XN_FLAG_SEP_COMMA_PLUS (1ul << 16)
 
 // XN_FLAG_SEP_CPLUS_SPC behaves like |XN_FLAG_SEP_COMMA_PLUS| but adds spaces
 // between the separators.
-#define XN_FLAG_SEP_CPLUS_SPC (2 << 16)
+#define XN_FLAG_SEP_CPLUS_SPC (2ul << 16)
 
 // XN_FLAG_SEP_SPLUS_SPC separates RDNs with "; " and attributes within an RDN
 // with " + ".
-#define XN_FLAG_SEP_SPLUS_SPC (3 << 16)
+#define XN_FLAG_SEP_SPLUS_SPC (3ul << 16)
 
 // XN_FLAG_SEP_MULTILINE prints each attribute on one line.
-#define XN_FLAG_SEP_MULTILINE (4 << 16)
+#define XN_FLAG_SEP_MULTILINE (4ul << 16)
 
 // XN_FLAG_DN_REV prints RDNs in reverse, from least significant to most
 // significant, as RFC 2253.
-#define XN_FLAG_DN_REV (1 << 20)
+#define XN_FLAG_DN_REV (1ul << 20)
 
 // XN_FLAG_FN_MASK determines how attribute types are displayed.
-#define XN_FLAG_FN_MASK (0x3 << 21)
+#define XN_FLAG_FN_MASK (0x3ul << 21)
 
 // XN_FLAG_FN_SN uses the attribute type's short name, when available.
-#define XN_FLAG_FN_SN 0
+#define XN_FLAG_FN_SN 0ul
 
 // XN_FLAG_SPC_EQ wraps the "=" operator with spaces when printing attributes.
-#define XN_FLAG_SPC_EQ (1 << 23)
+#define XN_FLAG_SPC_EQ (1ul << 23)
 
 // XN_FLAG_DUMP_UNKNOWN_FIELDS causes unknown attribute types to be printed in
 // hex, as in RFC 2253.
-#define XN_FLAG_DUMP_UNKNOWN_FIELDS (1 << 24)
+#define XN_FLAG_DUMP_UNKNOWN_FIELDS (1ul << 24)
 
 // XN_FLAG_RFC2253 prints like RFC 2253.
 #define XN_FLAG_RFC2253                                             \
@@ -3787,6 +3825,43 @@
 // |flags| should be zero and is ignored.
 OPENSSL_EXPORT int X509_check_trust(X509 *x509, int id, int flags);
 
+// X509_STORE_CTX_get1_certs returns a newly-allocated stack containing all
+// trusted certificates in |ctx|'s |X509_STORE| whose subject matches |name|, or
+// NULL on error. The caller must release the result with |sk_X509_pop_free| and
+// |X509_free| when done.
+//
+// TODO(crbug.com/boringssl/407): |name| should be const.
+OPENSSL_EXPORT STACK_OF(X509) *X509_STORE_CTX_get1_certs(X509_STORE_CTX *ctx,
+                                                         X509_NAME *name);
+
+// X509_STORE_CTX_get1_crls returns a newly-allocated stack containing all
+// CRLs in |ctx|'s |X509_STORE| whose subject matches |name|, or NULL on error.
+// The caller must release the result with |sk_X509_CRL_pop_free| and
+// |X509_CRL_free| when done.
+//
+// TODO(crbug.com/boringssl/407): |name| should be const.
+OPENSSL_EXPORT STACK_OF(X509_CRL) *X509_STORE_CTX_get1_crls(X509_STORE_CTX *ctx,
+                                                            X509_NAME *name);
+
+// X509_STORE_CTX_get_by_subject looks up an object of type |type| in |ctx|'s
+// |X509_STORE| that matches |name|. |type| should be one of the |X509_LU_*|
+// constants to indicate the type of object. If a match was found, it stores the
+// result in |ret| and returns one. Otherwise, it returns zero. If multiple
+// objects match, this function outputs an arbitray one.
+//
+// WARNING: |ret| must be in the empty state, as returned by |X509_OBJECT_new|.
+// Otherwise, the object currently in |ret| will be leaked when overwritten.
+// https://crbug.com/boringssl/685 tracks fixing this.
+//
+// WARNING: Multiple trusted certificates or CRLs may share a name. In this
+// case, this function returns an arbitrary match. Use
+// |X509_STORE_CTX_get1_certs| or |X509_STORE_CTX_get1_crls| instead.
+//
+// TODO(crbug.com/boringssl/407): |name| should be const.
+OPENSSL_EXPORT int X509_STORE_CTX_get_by_subject(X509_STORE_CTX *ctx, int type,
+                                                 X509_NAME *name,
+                                                 X509_OBJECT *ret);
+
 
 // X.509 information.
 //
@@ -4224,6 +4299,20 @@
 // always enabled.
 #define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0
 
+// X509_STORE_get0_objects returns a non-owning pointer of |store|'s internal
+// object list. Although this function is not const, callers must not modify
+// the result of this function.
+//
+// WARNING: This function is not thread-safe. If |store| is shared across
+// multiple threads, callers cannot safely inspect the result of this function,
+// because another thread may have concurrently added to it. In particular,
+// |X509_LOOKUP_hash_dir| treats this list as a cache and may add to it in the
+// course of certificate verification. This API additionally prevents fixing
+// some quadratic worst-case behavior in |X509_STORE| and may be removed in the
+// future. Use |X509_STORE_get1_objects| instead.
+OPENSSL_EXPORT STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(
+    X509_STORE *store);
+
 
 // Private structures.
 
@@ -4324,13 +4413,6 @@
 certificate chain.
 */
 
-#define X509_LU_NONE 0
-#define X509_LU_X509 1
-#define X509_LU_CRL 2
-#define X509_LU_PKEY 3
-
-DEFINE_STACK_OF(X509_OBJECT)
-
 #define X509_STORE_CTX_set_app_data(ctx, data) \
   X509_STORE_CTX_set_ex_data(ctx, 0, data)
 #define X509_STORE_CTX_get_app_data(ctx) X509_STORE_CTX_get_ex_data(ctx, 0)
@@ -4407,37 +4489,12 @@
 // verification.
 #define X509_V_FLAG_NO_CHECK_TIME 0x200000
 
-// X509_OBJECT_new returns a newly-allocated, empty |X509_OBJECT| or NULL on
-// error.
-OPENSSL_EXPORT X509_OBJECT *X509_OBJECT_new(void);
-
-// X509_OBJECT_free releases memory associated with |obj|.
-OPENSSL_EXPORT void X509_OBJECT_free(X509_OBJECT *obj);
-
-// X509_OBJECT_get_type returns the type of |obj|, which will be one of the
-// |X509_LU_*| constants.
-OPENSSL_EXPORT int X509_OBJECT_get_type(const X509_OBJECT *obj);
-
-// X509_OBJECT_get0_X509 returns |obj| as a certificate, or NULL if |obj| is not
-// a certificate.
-OPENSSL_EXPORT X509 *X509_OBJECT_get0_X509(const X509_OBJECT *obj);
-
-OPENSSL_EXPORT STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(X509_STORE *st);
-OPENSSL_EXPORT STACK_OF(X509) *X509_STORE_CTX_get1_certs(X509_STORE_CTX *st,
-                                                         X509_NAME *nm);
-OPENSSL_EXPORT STACK_OF(X509_CRL) *X509_STORE_CTX_get1_crls(X509_STORE_CTX *st,
-                                                            X509_NAME *nm);
-
 OPENSSL_EXPORT X509_LOOKUP *X509_STORE_add_lookup(X509_STORE *v,
                                                   const X509_LOOKUP_METHOD *m);
 
 OPENSSL_EXPORT const X509_LOOKUP_METHOD *X509_LOOKUP_hash_dir(void);
 OPENSSL_EXPORT const X509_LOOKUP_METHOD *X509_LOOKUP_file(void);
 
-OPENSSL_EXPORT int X509_STORE_CTX_get_by_subject(X509_STORE_CTX *vs, int type,
-                                                 X509_NAME *name,
-                                                 X509_OBJECT *ret);
-
 OPENSSL_EXPORT int X509_LOOKUP_ctrl(X509_LOOKUP *ctx, int cmd, const char *argc,
                                     long argl, char **ret);
 
@@ -4885,6 +4942,7 @@
 BORINGSSL_MAKE_DELETER(X509_LOOKUP, X509_LOOKUP_free)
 BORINGSSL_MAKE_DELETER(X509_NAME, X509_NAME_free)
 BORINGSSL_MAKE_DELETER(X509_NAME_ENTRY, X509_NAME_ENTRY_free)
+BORINGSSL_MAKE_DELETER(X509_OBJECT, X509_OBJECT_free)
 BORINGSSL_MAKE_DELETER(X509_PUBKEY, X509_PUBKEY_free)
 BORINGSSL_MAKE_DELETER(X509_REQ, X509_REQ_free)
 BORINGSSL_MAKE_DELETER(X509_REVOKED, X509_REVOKED_free)
diff --git a/pki/cert_errors.cc b/pki/cert_errors.cc
index da95b49..3f5a77f 100644
--- a/pki/cert_errors.cc
+++ b/pki/cert_errors.cc
@@ -92,15 +92,20 @@
   return result;
 }
 
-bool CertErrors::ContainsError(CertErrorId id) const {
+bool CertErrors::ContainsErrorWithSeverity(CertErrorId id,
+                                           CertError::Severity severity) const {
   for (const CertError &node : nodes_) {
-    if (node.id == id) {
+    if (node.id == id && node.severity == severity) {
       return true;
     }
   }
   return false;
 }
 
+bool CertErrors::ContainsError(CertErrorId id) const {
+  return ContainsErrorWithSeverity(id, CertError::SEVERITY_HIGH);
+}
+
 bool CertErrors::ContainsAnyErrorWithSeverity(
     CertError::Severity severity) const {
   for (const CertError &node : nodes_) {
diff --git a/pki/cert_errors.h b/pki/cert_errors.h
index 8f16e11..da35060 100644
--- a/pki/cert_errors.h
+++ b/pki/cert_errors.h
@@ -104,8 +104,13 @@
   // Dumps a textual representation of the errors for debugging purposes.
   std::string ToDebugString() const;
 
-  // Returns true if the error |id| was added to this CertErrors (of any
-  // severity).
+  // Returns true if the error |id| was added to this CertErrors at
+  // severity |severity|
+  bool ContainsErrorWithSeverity(CertErrorId id,
+                                 CertError::Severity severity) const;
+
+  // Returns true if the error |id| was added to this CertErrors at
+  // high serverity.
   bool ContainsError(CertErrorId id) const;
 
   // Returns true if this contains any errors of the given severity level.
diff --git a/pki/certificate.cc b/pki/certificate.cc
new file mode 100644
index 0000000..db1c627
--- /dev/null
+++ b/pki/certificate.cc
@@ -0,0 +1,117 @@
+/* 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 <optional>
+#include <string_view>
+
+#include <openssl/pki/certificate.h>
+#include <openssl/pool.h>
+
+#include "cert_errors.h"
+#include "encode_values.h"
+#include "parsed_certificate.h"
+#include "pem.h"
+#include "parse_values.h"
+
+namespace bssl {
+
+namespace {
+
+std::shared_ptr<const bssl::ParsedCertificate> ParseCertificateFromDer(
+    bssl::Span<const uint8_t>cert, std::string *out_diagnostic) {
+  bssl::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.  davidben explains
+  // why:
+  //
+  // 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;
+
+  bssl::UniquePtr<CRYPTO_BUFFER> buffer(
+      CRYPTO_BUFFER_new(cert.data(), cert.size(), nullptr));
+  bssl::CertErrors errors;
+  std::shared_ptr<const bssl::ParsedCertificate> parsed_cert(
+      bssl::ParsedCertificate::Create(std::move(buffer), default_options, &errors));
+  if (!parsed_cert) {
+    *out_diagnostic = errors.ToDebugString();
+    return nullptr;
+  }
+  return parsed_cert;
+}
+
+} // namespace
+
+struct CertificateInternals {
+  std::shared_ptr<const bssl::ParsedCertificate> cert;
+};
+
+Certificate::Certificate(std::unique_ptr<CertificateInternals> internals)
+    : internals_(std::move(internals)) {}
+Certificate::~Certificate() = default;
+Certificate::Certificate(Certificate&& other) = default;
+
+std::unique_ptr<Certificate> Certificate::FromDER(bssl::Span<const uint8_t> der,
+                                                  std::string *out_diagnostic) {
+  std::shared_ptr<const bssl::ParsedCertificate> result =
+      ParseCertificateFromDer(der, out_diagnostic);
+  if (result == nullptr) {
+    return nullptr;
+  }
+
+  auto internals = std::make_unique<CertificateInternals>();
+  internals->cert = std::move(result);
+  std::unique_ptr<Certificate> ret(new Certificate(std::move(internals)));
+  return ret;
+}
+
+std::unique_ptr<Certificate> Certificate::FromPEM(std::string_view pem,
+                                                  std::string *out_diagnostic) {
+  bssl::PEMTokenizer tokenizer(pem, {"CERTIFICATE"});
+  if (!tokenizer.GetNext()) {
+    return nullptr;
+  }
+  return FromDER(StringAsBytes(tokenizer.data()), out_diagnostic);
+}
+
+bool Certificate::IsSelfIssued() const {
+  return internals_->cert->normalized_subject() ==
+         internals_->cert->normalized_issuer();
+}
+
+Certificate::Validity Certificate::GetValidity() const {
+  Certificate::Validity validity;
+
+  // As this is a previously parsed certificate, we know the not_before
+  // and not after are valid, so these conversions can not fail.
+  (void) GeneralizedTimeToPosixTime(
+      internals_->cert->tbs().validity_not_before, &validity.not_before);
+  (void) GeneralizedTimeToPosixTime(
+      internals_->cert->tbs().validity_not_after, &validity.not_after);
+  return validity;
+}
+
+bssl::Span<const uint8_t> Certificate::GetSerialNumber() const {
+  return internals_->cert->tbs().serial_number;
+}
+
+}  // namespace boringssl
diff --git a/pki/certificate_unittest.cc b/pki/certificate_unittest.cc
new file mode 100644
index 0000000..57a51fa
--- /dev/null
+++ b/pki/certificate_unittest.cc
@@ -0,0 +1,76 @@
+/* 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 <optional>
+#include <string>
+#include <string_view>
+
+#include <openssl/pki/certificate.h>
+#include <gmock/gmock.h>
+
+#include "string_util.h"
+#include "test_helpers.h"
+
+TEST(CertificateTest, FromPEM) {
+  std::string diagnostic;
+  std::unique_ptr<bssl::Certificate> cert(
+      bssl::Certificate::FromPEM("nonsense", &diagnostic));
+  EXPECT_FALSE(cert);
+
+  cert = bssl::Certificate::FromPEM(bssl::ReadTestFileToString(
+      "testdata/verify_unittest/self-issued.pem"), &diagnostic);
+  EXPECT_TRUE(cert);
+}
+
+TEST(CertificateTest, IsSelfIssued) {
+  std::string diagnostic;
+  const std::string leaf =
+      bssl::ReadTestFileToString("testdata/verify_unittest/google-leaf.der");
+  std::unique_ptr<bssl::Certificate> leaf_cert(
+      bssl::Certificate::FromDER(bssl::StringAsBytes(leaf), &diagnostic));
+  EXPECT_TRUE(leaf_cert);
+  EXPECT_FALSE(leaf_cert->IsSelfIssued());
+
+  const std::string self_issued =
+      bssl::ReadTestFileToString("testdata/verify_unittest/self-issued.pem");
+  std::unique_ptr<bssl::Certificate> self_issued_cert(
+      bssl::Certificate::FromPEM(self_issued, &diagnostic));
+  EXPECT_TRUE(self_issued_cert);
+  EXPECT_TRUE(self_issued_cert->IsSelfIssued());
+}
+
+TEST(CertificateTest, Validity) {
+  std::string diagnostic;
+  const std::string leaf =
+      bssl::ReadTestFileToString("testdata/verify_unittest/google-leaf.der");
+  std::unique_ptr<bssl::Certificate> cert(
+      bssl::Certificate::FromDER(bssl::StringAsBytes(leaf), &diagnostic));
+  EXPECT_TRUE(cert);
+
+  bssl::Certificate::Validity validity = cert->GetValidity();
+  EXPECT_EQ(validity.not_before, 1498644466);
+  EXPECT_EQ(validity.not_after, 1505899620);
+}
+
+TEST(CertificateTest, SerialNumber) {
+  std::string diagnostic;
+  const std::string leaf =
+      bssl::ReadTestFileToString("testdata/verify_unittest/google-leaf.der");
+  std::unique_ptr<bssl::Certificate> cert(
+      bssl::Certificate::FromDER(bssl::StringAsBytes(leaf), &diagnostic));
+  EXPECT_TRUE(cert);
+
+  EXPECT_EQ(bssl::string_util::HexEncode(cert->GetSerialNumber()),
+            "0118F044A8F31892");
+}
diff --git a/pki/mock_signature_verify_cache.h b/pki/mock_signature_verify_cache.h
index 80c4fa3..f380f61 100644
--- a/pki/mock_signature_verify_cache.h
+++ b/pki/mock_signature_verify_cache.h
@@ -11,8 +11,7 @@
 #include <string_view>
 #include <unordered_map>
 
-
-#include "signature_verify_cache.h"
+#include <openssl/pki/signature_verify_cache.h>
 
 namespace bssl {
 
diff --git a/pki/parse_certificate.h b/pki/parse_certificate.h
index cd38c65..81e267f 100644
--- a/pki/parse_certificate.h
+++ b/pki/parse_certificate.h
@@ -415,6 +415,14 @@
 // In dotted notation: 2.5.29.31
 inline constexpr uint8_t kCrlDistributionPointsOid[] = {0x55, 0x1d, 0x1f};
 
+// From RFC 6962:
+//
+// critical poison extension.
+//
+// In dotted notation 1.3.6.1.4.1.11129.2.4.3
+inline constexpr uint8_t kCtPoisonOid[] = {0x2B, 0x06, 0x01, 0x04, 0x01,
+                                           0xD6, 0x79, 0x02, 0x04, 0x03};
+
 // From
 // https://learn.microsoft.com/en-us/windows/win32/seccertenroll/supported-extensions#msapplicationpolicies
 //
diff --git a/pki/path_builder_unittest.cc b/pki/path_builder_unittest.cc
index 7747c34..624c0bc 100644
--- a/pki/path_builder_unittest.cc
+++ b/pki/path_builder_unittest.cc
@@ -54,9 +54,18 @@
 
   MockSignatureVerifyCache *GetMockVerifyCache() { return &cache_; }
 
- private:
+  void AllowPrecert() { allow_precertificate_ = true; }
+
+  void DisallowPrecert() { allow_precertificate_ = false; }
+
+  bool AcceptPreCertificates() override {
+    return allow_precertificate_;
+  }
+
+private:
   bool deadline_is_expired_ = false;
   bool use_signature_cache_ = false;
+  bool allow_precertificate_ = false;
   MockSignatureVerifyCache cache_;
 };
 
@@ -907,6 +916,46 @@
   EXPECT_EQ(c_by_d_, valid_path->certs[2]);
 }
 
+TEST_F(PathBuilderMultiRootTest, TestPreCertificate) {
+
+  std::string test_dir =
+      "testdata/path_builder_unittest/precertificate/";
+  std::shared_ptr<const ParsedCertificate> root1 =
+      ReadCertFromFile(test_dir + "root.pem");
+  ASSERT_TRUE(root1);
+  std::shared_ptr<const ParsedCertificate> target =
+      ReadCertFromFile(test_dir + "precertificate.pem");
+  ASSERT_TRUE(target);
+
+  der::GeneralizedTime precert_time = {2023, 10, 1, 0, 0, 0};
+
+  TrustStoreInMemory trust_store;
+  trust_store.AddTrustAnchor(root1);
+
+  // PreCertificate should be rejected by default.
+  EXPECT_FALSE(delegate_.AcceptPreCertificates());
+  CertPathBuilder path_builder(
+      target, &trust_store, &delegate_, precert_time, KeyPurpose::ANY_EKU,
+      initial_explicit_policy_, user_initial_policy_set_,
+      initial_policy_mapping_inhibit_, initial_any_policy_inhibit_);
+  auto result = path_builder.Run();
+  ASSERT_EQ(1U, result.paths.size());
+  ASSERT_FALSE(result.paths[0]->IsValid())
+      << result.paths[0]->errors.ToDebugString(result.paths[0]->certs);
+
+  // PreCertificate should be accepted if configured.
+  delegate_.AllowPrecert();
+  EXPECT_TRUE(delegate_.AcceptPreCertificates());
+  CertPathBuilder path_builder2(
+      target, &trust_store, &delegate_, precert_time, KeyPurpose::ANY_EKU,
+      initial_explicit_policy_, user_initial_policy_set_,
+      initial_policy_mapping_inhibit_, initial_any_policy_inhibit_);
+  auto result2 = path_builder2.Run();
+  ASSERT_EQ(1U, result2.paths.size());
+  ASSERT_TRUE(result2.paths[0]->IsValid())
+      << result2.paths[0]->errors.ToDebugString(result.paths[0]->certs);
+}
+
 class PathBuilderKeyRolloverTest : public ::testing::Test {
  public:
   PathBuilderKeyRolloverTest()
@@ -2010,7 +2059,8 @@
   const CertErrors *cert1_errors =
       result.GetBestValidPath()->errors.GetErrorsForCert(1);
   ASSERT_TRUE(cert1_errors);
-  EXPECT_TRUE(cert1_errors->ContainsError(kWarningFromDelegate));
+  EXPECT_TRUE(cert1_errors->ContainsErrorWithSeverity(
+      kWarningFromDelegate, CertError::SEVERITY_WARNING));
 }
 
 DEFINE_CERT_ERROR_ID(kErrorFromDelegate, "Error from delegate");
diff --git a/pki/simple_path_builder_delegate.cc b/pki/simple_path_builder_delegate.cc
index a168513..822019b 100644
--- a/pki/simple_path_builder_delegate.cc
+++ b/pki/simple_path_builder_delegate.cc
@@ -11,11 +11,12 @@
 #include <openssl/ec_key.h>
 #include <openssl/evp.h>
 #include <openssl/nid.h>
+#include <openssl/pki/signature_verify_cache.h>
 #include <openssl/rsa.h>
+
 #include "cert_error_params.h"
 #include "cert_errors.h"
 #include "signature_algorithm.h"
-#include "signature_verify_cache.h"
 #include "verify_signed_data.h"
 
 namespace bssl {
@@ -55,6 +56,8 @@
 
 bool SimplePathBuilderDelegate::IsDebugLogEnabled() { return false; }
 
+bool SimplePathBuilderDelegate::AcceptPreCertificates() { return false; }
+
 void SimplePathBuilderDelegate::DebugLog(std::string_view msg) {}
 
 SignatureVerifyCache *SimplePathBuilderDelegate::GetVerifyCache() {
diff --git a/pki/simple_path_builder_delegate.h b/pki/simple_path_builder_delegate.h
index 4c36b09..6d95b0d 100644
--- a/pki/simple_path_builder_delegate.h
+++ b/pki/simple_path_builder_delegate.h
@@ -8,10 +8,10 @@
 #include <stddef.h>
 
 #include <openssl/base.h>
+#include <openssl/pki/signature_verify_cache.h>
 
 #include "path_builder.h"
 #include "signature_algorithm.h"
-#include "signature_verify_cache.h"
 
 namespace bssl {
 
@@ -70,6 +70,9 @@
   // No-op implementation.
   void DebugLog(std::string_view msg) override;
 
+  // No-op implementation.
+  bool AcceptPreCertificates() override;
+
  private:
   const size_t min_rsa_modulus_length_bits_;
   const DigestPolicy digest_policy_;
diff --git a/pki/testdata/path_builder_unittest/precertificate/precertificate.pem b/pki/testdata/path_builder_unittest/precertificate/precertificate.pem
new file mode 100644
index 0000000..23b9900
--- /dev/null
+++ b/pki/testdata/path_builder_unittest/precertificate/precertificate.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEfzCCA2egAwIBAgIQDIMDs7tv6W4RteOR0LnF9DANBgkqhkiG9w0BAQsFADBG
+MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
+QzETMBEGA1UEAxMKR1RTIENBIDFQNTAeFw0yMzA5MTQwMDU5NTFaFw0yMzEyMTMw
+MDU5NTBaMBkxFzAVBgNVBAMTDnJmYy1lZGl0b3Iub3JnMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA2jNKh7UQ/v36Qz/KV+QLBYwxI9+kzv3t6363d63V
+3EkhIPTLZF8wQ7kfztmFKUuMcLcvEpY1jwxyBioGJN1b9QaxlLCsK8WvEyxnR+nq
+pR1h+j+zceZIEZoDVcS2GIr7LoFtFUeGidQRYBRf9Wu6bRMaT2fHaI6FcZECH7Bn
+TSe2e62BFejah3pA91+oVlnI9EjKtMQOGEaoyKjrKswVaWcFiUWKcvGnjygkYWRb
+6jBUhGINamkCgrtAblHrwB0ym9VENj06fpnWbkxFtU6GmmRplHag5npRwWI439+9
+QNtqj6PgMsSMBLj7ljq8GlY81Y6TZTZ2F06b0NLkRdjetwIDAQABo4IBlDCCAZAw
+DgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQC
+MAAwHQYDVR0OBBYEFGMbD3hNq55udW3jdGHpx+v2rzbWMB8GA1UdIwQYMBaAFNX8
+ng3fHsrdCJeXbivFX8Ur9ey4MHgGCCsGAQUFBwEBBGwwajA1BggrBgEFBQcwAYYp
+aHR0cDovL29jc3AucGtpLmdvb2cvcy9ndHMxcDUvZDJ5N2JUcElndEkwMQYIKwYB
+BQUHMAKGJWh0dHA6Ly9wa2kuZ29vZy9yZXBvL2NlcnRzL2d0czFwNS5kZXIwKwYD
+VR0RBCQwIoIOcmZjLWVkaXRvci5vcmeCECoucmZjLWVkaXRvci5vcmcwIQYDVR0g
+BBowGDAIBgZngQwBAgEwDAYKKwYBBAHWeQIFAzA8BgNVHR8ENTAzMDGgL6Athito
+dHRwOi8vY3Jscy5wa2kuZ29vZy9ndHMxcDUvZWV4a0MyUEp4YXcuY3JsMBMGCisG
+AQQB1nkCBAMBAf8EAgUAMA0GCSqGSIb3DQEBCwUAA4IBAQAP3c64wNkNq6Nq7HrV
+OzaygYS0dZ9gC8EuJsBe591AkPO8/B8y2kyPFRSHaG7IfRDDSyb9KqgtbknBDbHh
+GWDF7CS6sid8ulT1kwDd8HKBVCfd37EODrHmzfJhtxMfEcB7FGjxHjcNZ7g5A4K1
+ph0AXHym+hPB0Jz/0MzDMeFpFp7llib3vAYfXz6bS4xYcBMqPmJV+okpHYjF4UXZ
+0JIeMMSlypw5FgoMcsMydFW5X1KzoeKwSDhsVet03AM14QqlKv9p9u4MPzzVxYuW
+syhj72JMl1YA01MZ06Org8SaLwUtKu0/kq4mysCNA3GYkKwUQIod/ilW65l6dZoS
++lRn
+-----END CERTIFICATE-----
diff --git a/pki/testdata/path_builder_unittest/precertificate/root.pem b/pki/testdata/path_builder_unittest/precertificate/root.pem
new file mode 100644
index 0000000..ab573a0
--- /dev/null
+++ b/pki/testdata/path_builder_unittest/precertificate/root.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFjDCCA3SgAwIBAgINAgO8UKMnU/CRgCLt8TANBgkqhkiG9w0BAQsFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjAwODEzMDAwMDQyWhcNMjcwOTMwMDAw
+MDQyWjBGMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzETMBEGA1UEAxMKR1RTIENBIDFQNTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBALOC8CSMvy2Hr7LZp676yrpE1ls+/rL3smUW3N4Q6E8tEFha
+KIaHoe5qs6DZdU9/oVIBi1WoSlsGSMg2EiWrifnyI1+dYGX5XNq+OuhcbX2c0IQY
+hTDNTpvsPNiz4ZbU88ULZduPsHTL9h7zePGslcXdc8MxiIGvdKpv/QzjBZXwxRBP
+ZWP6oK/GGD3Fod+XedcFibMwsHSuPZIQa4wVd90LBFf7gQPd6iI01eVWsvDEjUGx
+wwLbYuyA0P921IbkBBq2tgwrYnF92a/Z8V76wB7KoBlcVfCA0SoMB4aQnzXjKCtb
+7yPIox2kozru/oPcgkwlsE3FUa2em9NbhMIaWukCAwEAAaOCAXYwggFyMA4GA1Ud
+DwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0T
+AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU1fyeDd8eyt0Il5duK8VfxSv17LgwHwYD
+VR0jBBgwFoAU5K8rJnEaK0gnhS9SZizv8IkTcT4waAYIKwYBBQUHAQEEXDBaMCYG
+CCsGAQUFBzABhhpodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHNyMTAwBggrBgEFBQcw
+AoYkaHR0cDovL3BraS5nb29nL3JlcG8vY2VydHMvZ3RzcjEuZGVyMDQGA1UdHwQt
+MCswKaAnoCWGI2h0dHA6Ly9jcmwucGtpLmdvb2cvZ3RzcjEvZ3RzcjEuY3JsME0G
+A1UdIARGMEQwOAYKKwYBBAHWeQIFAzAqMCgGCCsGAQUFBwIBFhxodHRwczovL3Br
+aS5nb29nL3JlcG9zaXRvcnkvMAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAgEA
+bGMn7iPf5VJoTYFmkYXffWXlWzcxCCayB12avrHKAbmtv5139lEd15jFC0mhe6HX
+02jlRA+LujbdQoJ30o3d9T/768gHmJPuWtC1Pd5LHC2MTex+jHv+TkD98LSzWQIQ
+UVzjwCv9twZIUX4JXj8P3Kf+l+d5xQ5EiXjFaVkpoJo6SDYpppSTVS24R7XplrWf
+B82mqz4yisCGg8XBQcifLzWODcAHeuGsyWW1y4qn3XHYYWU5hKwyPvd6NvFWn1ep
+QW1akKfbOup1gAxjC2l0bwdMFfM3KKUZpG719iDNY7J+xCsJdYna0Twuck82GqGe
+RNDNm6YjCD+XoaeeWqX3CZStXXZdKFbRGmZRUQd73j2wyO8weiQtvrizhvZL9/C1
+T//Oxvn2PyonCA8JPiNax+NCLXo25D2YlmA5mOrR22Mq63gJsU4hs463zj6S8ZVc
+pDnQwCvIUxX10i+CzQZ0Z5mQdzcKly3FHB700FvpFePqAgnIE9cTcGW/+4ibWiW+
+dwnhp2pOEXW5Hk3xABtqZnmOw27YbaIiom0F+yzy8VDloNHYnzV9/HCrWSoC8b6w
+0/H4zRK5aiWQW+OFIOb12stAHBk0IANhd7p/SA9JCynr52Fkx2PRR+sc4e6URu85
+c8zuTyuN3PtYp7NlIJmVuftVb9eWbpQ99HqSjmMd320=
+-----END CERTIFICATE-----
diff --git a/pki/testdata/verify_unittest/google-leaf.der b/pki/testdata/verify_unittest/google-leaf.der
new file mode 100644
index 0000000..01eea35
--- /dev/null
+++ b/pki/testdata/verify_unittest/google-leaf.der
Binary files differ
diff --git a/pki/testdata/verify_unittest/self-issued.pem b/pki/testdata/verify_unittest/self-issued.pem
new file mode 100644
index 0000000..e219b6d
--- /dev/null
+++ b/pki/testdata/verify_unittest/self-issued.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDrTCCApWgAwIBAgIUcu/NYcYTwG79IqzpuKoOf0Jyu1UwDQYJKoZIhvcNAQEL
+BQAwZjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEfMB0GA1UEAwwWSXNTZWxmSXNzdWVk
+IHRlc3QgY2VydDAeFw0yMjA0MDcyMzIyMzRaFw0yMzA0MDcyMzIyMzRaMGYxCzAJ
+BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGQxHzAdBgNVBAMMFklzU2VsZklzc3VlZCB0ZXN0IGNl
+cnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv6ueRHng0bJsKdDSg
+YP1wfpao+dGZ6iY0wfDaLGY2SUgHn7HCvGeS1btZ7lCdCBdRTd6OjrMJkaoPXOCG
+rqUVv9/WBPyYgpA+Zlm3F8Uf8vuYUZ96ZOdvHBeAO8Ac9wFoZhC3VNwVF0mynlVb
+AhMu1T33Pi387jaE2OMMAle2w4zApcEMbMvlgEk1UdlpL0PBT4nT6XIoCbO04tEG
+KLMnGutdHUmVW3L+77hjy/H4LhfsZb7PD3xqFkWpspP3OgfWJU0NUiAX/RIbgKTe
+pYNVUElAGgchziGQyDJ8Si7rXslCIx6m/qMD9+JX4hZa+ANPf9psEAVjt8TKhozi
+TCBHAgMBAAGjUzBRMB0GA1UdDgQWBBQludvSp1hD9JPiK6JyzuROz9S95zAfBgNV
+HSMEGDAWgBQludvSp1hD9JPiK6JyzuROz9S95zAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBCwUAA4IBAQA3a4h15aX4xbinxNUNDNRoqrHL5J/nwFgZxTKvzvC+
+I+evxJr3b2vWip70nepiDYW9XyzDD3rr18P/XlacxaBWy2L6iwAc0SDHHUwPJtid
+7av3VjC3Qz8kk3gIuLb9SMJ9eQweKzox7UF6ZJhoEhSMDkyvbHmkMYmQGjIwuVvG
+GZm0Df1XYiBQyqj6jqsTkZeqlWyFkpuFMOOSvFwWV5rOt0WrIYB/iIbHqXtePGU1
+aE2CZqTTTCldn4gvMjtAAJ5D/JXY788a0apaEG5FjU1XzVHzWbKm0bz7y1cEPBhQ
+iEgWK2iloE3NzPABUZIcqdQF5NUiP45i396ccRGuNxY5
+-----END CERTIFICATE-----
diff --git a/pki/verify_certificate_chain.cc b/pki/verify_certificate_chain.cc
index 1d3d0c0..c42f757 100644
--- a/pki/verify_certificate_chain.cc
+++ b/pki/verify_certificate_chain.cc
@@ -84,9 +84,13 @@
 // Adds errors to |errors| if the certificate contains unconsumed _critical_
 // extensions.
 void VerifyNoUnconsumedCriticalExtensions(const ParsedCertificate &cert,
-                                          CertErrors *errors) {
+                                          CertErrors *errors,
+                                          bool allow_precertificate) {
   for (const auto &it : cert.extensions()) {
     const ParsedExtension &extension = it.second;
+    if (allow_precertificate && extension.oid == der::Input(kCtPoisonOid)) {
+      continue;
+    }
     if (extension.critical && !IsHandledCriticalExtension(extension, cert)) {
       errors->AddError(cert_errors::kUnconsumedCriticalExtension,
                        CreateCertErrorParams2Der("oid", extension.oid, "value",
@@ -659,7 +663,7 @@
   // Procedure". It does processing for the final certificate (the target cert).
   void WrapUp(const ParsedCertificate &cert, KeyPurpose required_key_purpose,
               const std::set<der::Input> &user_initial_policy_set,
-              CertErrors *errors);
+              bool allow_precertificate, CertErrors *errors);
 
   // Enforces trust anchor constraints compatibile with RFC 5937.
   //
@@ -1165,7 +1169,8 @@
   //    the certificate.  Process any other recognized non-critical
   //    extension present in the certificate that is relevant to path
   //    processing.
-  VerifyNoUnconsumedCriticalExtensions(cert, errors);
+  VerifyNoUnconsumedCriticalExtensions(cert, errors,
+                                       delegate_->AcceptPreCertificates());
 }
 
 // Checks if the target certificate has the CA bit set. If it does, add
@@ -1196,7 +1201,8 @@
 void PathVerifier::WrapUp(const ParsedCertificate &cert,
                           KeyPurpose required_key_purpose,
                           const std::set<der::Input> &user_initial_policy_set,
-                          CertErrors *errors) {
+                          bool allow_precertificate,
+                          CertErrors * errors) {
   // From RFC 5280 section 6.1.5:
   //      (a)  If explicit_policy is not 0, decrement explicit_policy by 1.
   if (explicit_policy_ > 0) {
@@ -1224,7 +1230,7 @@
   //
   // Note that this is duplicated by PrepareForNextCertificate() so as to
   // directly match the procedures in RFC 5280's section 6.1.
-  VerifyNoUnconsumedCriticalExtensions(cert, errors);
+  VerifyNoUnconsumedCriticalExtensions(cert, errors, allow_precertificate);
 
   // This calculates the intersection from RFC 5280 section 6.1.5 step g, as
   // well as applying the deferred recursive node that were skipped earlier in
@@ -1325,7 +1331,8 @@
   //    Extensions may be marked critical or not critical.  When trust anchor
   //    constraints are enforced, clients MUST reject certification paths
   //    containing a trust anchor with unrecognized critical extensions.
-  VerifyNoUnconsumedCriticalExtensions(cert, errors);
+  VerifyNoUnconsumedCriticalExtensions(cert, errors,
+                                       /*allow_precertificate=*/false);
 }
 
 void PathVerifier::ProcessRootCertificate(const ParsedCertificate &cert,
@@ -1427,7 +1434,8 @@
 
   // Checking for unknown critical extensions matches Windows, but is stricter
   // than the Mac verifier.
-  VerifyNoUnconsumedCriticalExtensions(cert, errors);
+  VerifyNoUnconsumedCriticalExtensions(cert, errors,
+                                       /*allow_precertificate=*/false);
 }
 
 bssl::UniquePtr<EVP_PKEY> PathVerifier::ParseAndCheckPublicKey(
@@ -1572,7 +1580,8 @@
     if (!is_target_cert) {
       PrepareForNextCertificate(cert, cert_errors);
     } else {
-      WrapUp(cert, required_key_purpose, user_initial_policy_set, cert_errors);
+      WrapUp(cert, required_key_purpose, user_initial_policy_set,
+             delegate->AcceptPreCertificates(), cert_errors);
     }
   }
 
diff --git a/pki/verify_certificate_chain.h b/pki/verify_certificate_chain.h
index 718eceb..6c4cccf 100644
--- a/pki/verify_certificate_chain.h
+++ b/pki/verify_certificate_chain.h
@@ -9,11 +9,11 @@
 
 #include <openssl/base.h>
 #include <openssl/evp.h>
+#include <openssl/pki/signature_verify_cache.h>
 
 #include "cert_errors.h"
 #include "input.h"
 #include "parsed_certificate.h"
-#include "signature_verify_cache.h"
 
 namespace bssl {
 
@@ -74,6 +74,13 @@
   // is no verification cache.
   virtual SignatureVerifyCache *GetVerifyCache() = 0;
 
+  // This is called to determine if PreCertificates should be accepted, for the
+  // purpose of validating issued PreCertificates in a path. Most callers should
+  // return false here. This should never return true for TLS certificate
+  // validation. If this function returns true the CT precertificate poison
+  // extension will not prevent the certificate from being validated.
+  virtual bool AcceptPreCertificates() = 0;
+
   virtual ~VerifyCertificateChainDelegate();
 };
 
diff --git a/pki/verify_signed_data.cc b/pki/verify_signed_data.cc
index a30f9d6..4502e2d 100644
--- a/pki/verify_signed_data.cc
+++ b/pki/verify_signed_data.cc
@@ -8,14 +8,15 @@
 #include <openssl/digest.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
+#include <openssl/pki/signature_verify_cache.h>
 #include <openssl/rsa.h>
 #include <openssl/sha.h>
+
 #include "cert_errors.h"
 #include "input.h"
 #include "parse_values.h"
 #include "parser.h"
 #include "signature_algorithm.h"
-#include "signature_verify_cache.h"
 
 namespace bssl {
 
diff --git a/pki/verify_signed_data.h b/pki/verify_signed_data.h
index e67c919..5ecfcea 100644
--- a/pki/verify_signed_data.h
+++ b/pki/verify_signed_data.h
@@ -7,9 +7,9 @@
 
 #include <openssl/base.h>
 #include <openssl/evp.h>
+#include <openssl/pki/signature_verify_cache.h>
 
 #include "signature_algorithm.h"
-#include "signature_verify_cache.h"
 
 namespace bssl {
 
diff --git a/sources.cmake b/sources.cmake
index c65afb9..febdb31 100644
--- a/sources.cmake
+++ b/sources.cmake
@@ -359,6 +359,7 @@
   pki/cert_error_params.cc
   pki/cert_errors.cc
   pki/cert_issuer_source_static.cc
+  pki/certificate.cc
   pki/certificate_policies.cc
   pki/common_cert_errors.cc
   pki/crl.cc
@@ -395,6 +396,7 @@
 
   crypto/test/gtest_main.cc
   pki/cert_issuer_source_static_unittest.cc
+  pki/certificate_unittest.cc
   pki/certificate_policies_unittest.cc
   pki/crl_unittest.cc
   pki/encode_values_unittest.cc
@@ -1430,6 +1432,8 @@
   pki/testdata/path_builder_unittest/multi-root-D-by-D.pem
   pki/testdata/path_builder_unittest/multi-root-E-by-E.pem
   pki/testdata/path_builder_unittest/multi-root-F-by-E.pem
+  pki/testdata/path_builder_unittest/precertificate/precertificate.pem
+  pki/testdata/path_builder_unittest/precertificate/root.pem
   pki/testdata/path_builder_unittest/self_issued_prioritization/generate-certs.py
   pki/testdata/path_builder_unittest/self_issued_prioritization/keys/Root1.key
   pki/testdata/path_builder_unittest/self_issued_prioritization/keys/Root2.key