Rewrite X509's parser with CBS/CBB

In doing so, embed a whole host of types into X509. With all this, we
can *finally* add extra parameters to the parser, like lists of
supported algorithms or flag-protecting future changes. In the previous
state, it went through a very messy reflection-based framework.

Since we now control the parser entrypoint, this also means we can
handle the cached encoding differently. Just to make the
X509_parse_from_buffer and d2i_X509 flows more uniform, we now always
save a CRYPTO_BUFFER containing the whole certificate, and then
TBSCertificate marshalling finds the TBSCertificate from there. (Though
it is still possible to create X509 objects without CRYPTO_BUFFERs.)

That in turn means the generic ASN1_ENCODING path doesn't need to store
a CRYPTO_BUFFER anymore, so all that is now unwound. If we rewrite the
CSR and CRL parsers, then ASN1_ENCODING can be removed entirely.

As a bonus, this new parser is significantly faster!
Before:
Did 1037634 Parse X.509 certificate operations in 5000690us (207498.2 ops/sec)
After:
Did 1318068 Parse X.509 certificate operations in 5000462us (263589.2 ops/sec)

(The X509_NAME parser is still using tasn_dec, so there's still some
overhead there. This also does not remove the overhead from X509's
in-memory representation where lots of parts of the certificate get
copied separate allocations.)

Update-Note: This CL is not expected to change the external behavior of
the X.509 parser, but it's a lot of code that had to get shuffled
around, so if something funny happens around X.509, this is a likely
culprit.

Bug: 42290417
Change-Id: Ia4142f5e8d31b041176a545379c7df30e946a670
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/81781
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/build.json b/build.json
index 53551d8..39ba40e 100644
--- a/build.json
+++ b/build.json
@@ -404,7 +404,6 @@
             "crypto/x509/x_req.cc",
             "crypto/x509/x_sig.cc",
             "crypto/x509/x_spki.cc",
-            "crypto/x509/x_val.cc",
             "crypto/x509/x_x509.cc",
             "crypto/x509/x_x509a.cc",
             "crypto/xwing/xwing.cc"
diff --git a/crypto/asn1/a_time.cc b/crypto/asn1/a_time.cc
index 46785f6..d77a99a 100644
--- a/crypto/asn1/a_time.cc
+++ b/crypto/asn1/a_time.cc
@@ -222,3 +222,19 @@
   }
   return OPENSSL_tm_to_posix(&tm, out_time);
 }
+
+int asn1_parse_time(CBS *cbs, ASN1_TIME *out, int allow_utc_timezone_offset) {
+  if (CBS_peek_asn1_tag(cbs, CBS_ASN1_UTCTIME)) {
+    return asn1_parse_utc_time(cbs, out, /*tag=*/0, allow_utc_timezone_offset);
+  }
+  return asn1_parse_generalized_time(cbs, out, /*tag=*/0);
+}
+
+int asn1_marshal_time(CBB *cbb, const ASN1_TIME *in) {
+  if (in->type != V_ASN1_UTCTIME && in->type != V_ASN1_GENERALIZEDTIME) {
+    OPENSSL_PUT_ERROR(ASN1, ASN1_R_WRONG_TYPE);
+    return 0;
+  }
+  return asn1_marshal_octet_string(cbb, in,
+                                   static_cast<CBS_ASN1_TAG>(in->type));
+}
diff --git a/crypto/asn1/internal.h b/crypto/asn1/internal.h
index a828957..9be2fe3 100644
--- a/crypto/asn1/internal.h
+++ b/crypto/asn1/internal.h
@@ -159,6 +159,9 @@
 OPENSSL_EXPORT int asn1_generalizedtime_to_tm(struct tm *tm,
                                               const ASN1_GENERALIZEDTIME *d);
 
+int asn1_parse_time(CBS *cbs, ASN1_TIME *out, int allow_utc_timezone_offset);
+int asn1_marshal_time(CBB *cbb, const ASN1_TIME *in);
+
 
 // The ASN.1 ANY type.
 
@@ -201,9 +204,6 @@
   uint8_t *enc;
   // len is the length of |enc|. If zero, there is no saved encoding.
   size_t len;
-  // buf, if non-NULL, is the |CRYPTO_BUFFER| that |enc| points into. If NULL,
-  // |enc| must be released with |OPENSSL_free|.
-  CRYPTO_BUFFER *buf;
 } ASN1_ENCODING;
 
 int ASN1_item_ex_new(ASN1_VALUE **pval, const ASN1_ITEM *it);
@@ -214,14 +214,12 @@
 // ASN1_item_ex_d2i parses |len| bytes from |*in| as a structure of type |it|
 // and writes the result to |*pval|. If |tag| is non-negative, |it| is
 // implicitly tagged with the tag specified by |tag| and |aclass|. If |opt| is
-// non-zero, the value is optional. If |buf| is non-NULL, |*in| must point into
-// |buf|.
+// non-zero, the value is optional.
 //
 // This function returns one and advances |*in| if an object was successfully
 // parsed, -1 if an optional value was successfully skipped, and zero on error.
 int ASN1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in, long len,
-                     const ASN1_ITEM *it, int tag, int aclass, char opt,
-                     CRYPTO_BUFFER *buf);
+                     const ASN1_ITEM *it, int tag, int aclass, char opt);
 
 // ASN1_item_ex_i2d encodes |*pval| as a value of type |it| to |out| under the
 // i2d output convention. It returns a non-zero length on success and -1 on
@@ -267,7 +265,7 @@
 // returns one on success and zero on error. If |buf| is non-NULL, |in| must
 // point into |buf|.
 int asn1_enc_save(ASN1_VALUE **pval, const uint8_t *in, size_t inlen,
-                  const ASN1_ITEM *it, CRYPTO_BUFFER *buf);
+                  const ASN1_ITEM *it);
 
 // asn1_encoding_clear clears the cached encoding in |enc|.
 void asn1_encoding_clear(ASN1_ENCODING *enc);
diff --git a/crypto/asn1/tasn_dec.cc b/crypto/asn1/tasn_dec.cc
index 2f53a79..c749d88 100644
--- a/crypto/asn1/tasn_dec.cc
+++ b/crypto/asn1/tasn_dec.cc
@@ -39,16 +39,16 @@
 
 static int asn1_template_ex_d2i(ASN1_VALUE **pval, const unsigned char **in,
                                 long len, const ASN1_TEMPLATE *tt, char opt,
-                                CRYPTO_BUFFER *buf, int depth);
+                                int depth);
 static int asn1_template_noexp_d2i(ASN1_VALUE **val, const unsigned char **in,
                                    long len, const ASN1_TEMPLATE *tt, char opt,
-                                   CRYPTO_BUFFER *buf, int depth);
+                                   int depth);
 static int asn1_d2i_ex_primitive(ASN1_VALUE **pval, const unsigned char **in,
                                  long len, const ASN1_ITEM *it, int tag,
                                  int aclass, char opt);
 static int asn1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in,
                             long len, const ASN1_ITEM *it, int tag, int aclass,
-                            char opt, CRYPTO_BUFFER *buf, int depth);
+                            char opt, int depth);
 
 unsigned long ASN1_tag2bit(int tag) {
   switch (tag) {
@@ -98,7 +98,7 @@
                           const ASN1_ITEM *it) {
   ASN1_VALUE *ret = NULL;
   if (asn1_item_ex_d2i(&ret, in, len, it, /*tag=*/-1, /*aclass=*/0, /*opt=*/0,
-                       /*buf=*/NULL, /*depth=*/0) <= 0) {
+                       /*depth=*/0) <= 0) {
     // Clean up, in case the caller left a partial object.
     //
     // TODO(davidben): I don't think it can leave one, but the codepaths below
@@ -128,7 +128,7 @@
 
 static int asn1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in,
                             long len, const ASN1_ITEM *it, int tag, int aclass,
-                            char opt, CRYPTO_BUFFER *buf, int depth) {
+                            char opt, int depth) {
   const ASN1_TEMPLATE *tt, *errtt = NULL;
   const unsigned char *p = NULL, *q;
   unsigned char oclass;
@@ -145,11 +145,6 @@
     goto err;
   }
 
-  if (buf != NULL) {
-    assert(CRYPTO_BUFFER_data(buf) <= *in &&
-           *in + len <= CRYPTO_BUFFER_data(buf) + CRYPTO_BUFFER_len(buf));
-  }
-
   // Bound |len| to comfortably fit in an int. Lengths in this module often
   // switch between int and long without overflow checks.
   if (len > INT_MAX / 2) {
@@ -172,8 +167,7 @@
           OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_OPTIONS_ON_ITEM_TEMPLATE);
           goto err;
         }
-        return asn1_template_ex_d2i(pval, in, len, it->templates, opt, buf,
-                                    depth);
+        return asn1_template_ex_d2i(pval, in, len, it->templates, opt, depth);
       }
       return asn1_d2i_ex_primitive(pval, in, len, it, tag, aclass, opt);
 
@@ -267,7 +261,7 @@
       for (i = 0, tt = it->templates; i < it->tcount; i++, tt++) {
         pchptr = asn1_get_field_ptr(pval, tt);
         // We mark field as OPTIONAL so its absence can be recognised.
-        ret = asn1_template_ex_d2i(pchptr, &p, len, tt, 1, buf, depth);
+        ret = asn1_template_ex_d2i(pchptr, &p, len, tt, 1, depth);
         // If field not present, try the next one
         if (ret == -1) {
           continue;
@@ -373,7 +367,7 @@
         }
         // attempt to read in field, allowing each to be OPTIONAL
 
-        ret = asn1_template_ex_d2i(pseqval, &p, len, seqtt, isopt, buf, depth);
+        ret = asn1_template_ex_d2i(pseqval, &p, len, seqtt, isopt, depth);
         if (!ret) {
           errtt = seqtt;
           goto err;
@@ -412,7 +406,7 @@
         }
       }
       // Save encoding
-      if (!asn1_enc_save(pval, *in, p - *in, it, buf)) {
+      if (!asn1_enc_save(pval, *in, p - *in, it)) {
         goto auxerr;
       }
       if (asn1_cb && !asn1_cb(ASN1_OP_D2I_POST, pval, it, NULL)) {
@@ -438,10 +432,8 @@
 }
 
 int ASN1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in, long len,
-                     const ASN1_ITEM *it, int tag, int aclass, char opt,
-                     CRYPTO_BUFFER *buf) {
-  return asn1_item_ex_d2i(pval, in, len, it, tag, aclass, opt, buf,
-                          /*depth=*/0);
+                     const ASN1_ITEM *it, int tag, int aclass, char opt) {
+  return asn1_item_ex_d2i(pval, in, len, it, tag, aclass, opt, /*depth=*/0);
 }
 
 // Templates are handled with two separate functions. One handles any
@@ -449,7 +441,7 @@
 
 static int asn1_template_ex_d2i(ASN1_VALUE **val, const unsigned char **in,
                                 long inlen, const ASN1_TEMPLATE *tt, char opt,
-                                CRYPTO_BUFFER *buf, int depth) {
+                                int depth) {
   int aclass;
   int ret;
   long len;
@@ -481,7 +473,7 @@
       return 0;
     }
     // We've found the field so it can't be OPTIONAL now
-    ret = asn1_template_noexp_d2i(val, &p, len, tt, /*opt=*/0, buf, depth);
+    ret = asn1_template_noexp_d2i(val, &p, len, tt, /*opt=*/0, depth);
     if (!ret) {
       OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
       return 0;
@@ -494,7 +486,7 @@
       goto err;
     }
   } else {
-    return asn1_template_noexp_d2i(val, in, inlen, tt, opt, buf, depth);
+    return asn1_template_noexp_d2i(val, in, inlen, tt, opt, depth);
   }
 
   *in = p;
@@ -507,7 +499,7 @@
 
 static int asn1_template_noexp_d2i(ASN1_VALUE **val, const unsigned char **in,
                                    long len, const ASN1_TEMPLATE *tt, char opt,
-                                   CRYPTO_BUFFER *buf, int depth) {
+                                   int depth) {
   int aclass;
   int ret;
   const unsigned char *p;
@@ -565,7 +557,7 @@
       const unsigned char *q = p;
       skfield = NULL;
       if (!asn1_item_ex_d2i(&skfield, &p, len, ASN1_ITEM_ptr(tt->item),
-                            /*tag=*/-1, /*aclass=*/0, /*opt=*/0, buf, depth)) {
+                            /*tag=*/-1, /*aclass=*/0, /*opt=*/0, depth)) {
         ASN1_item_ex_free(&skfield, ASN1_ITEM_ptr(tt->item));
         OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
         goto err;
@@ -579,7 +571,7 @@
   } else if (flags & ASN1_TFLG_IMPTAG) {
     // IMPLICIT tagging
     ret = asn1_item_ex_d2i(val, &p, len, ASN1_ITEM_ptr(tt->item), tt->tag,
-                           aclass, opt, buf, depth);
+                           aclass, opt, depth);
     if (!ret) {
       OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
       goto err;
@@ -589,7 +581,7 @@
   } else {
     // Nothing special
     ret = asn1_item_ex_d2i(val, &p, len, ASN1_ITEM_ptr(tt->item), /*tag=*/-1,
-                           /*aclass=*/0, opt, buf, depth);
+                           /*aclass=*/0, opt, depth);
     if (!ret) {
       OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
       goto err;
@@ -771,9 +763,9 @@
     case V_ASN1_UTF8STRING:
       return asn1_parse_utf8_string(cbs, str, cbs_tag);
     case V_ASN1_UTCTIME:
-      // TODO(crbug.com/42290417): Once |X509|'s parser is written by hand, we
-      // won't have any known compatibility constraints forcing an invalid
-      // parser here. At that point, we can make the general case strict.
+      // TODO(crbug.com/42290221): Reject timezone offsets. We need to parse
+      // invalid timestamps in |X509| objects, but that parser no longer uses
+      // this code.
       return asn1_parse_utc_time(cbs, str, cbs_tag,
                                  /*allow_timezone_offset=*/1);
     case V_ASN1_GENERALIZEDTIME:
diff --git a/crypto/asn1/tasn_utl.cc b/crypto/asn1/tasn_utl.cc
index 3b1dbfc..f7233f5 100644
--- a/crypto/asn1/tasn_utl.cc
+++ b/crypto/asn1/tasn_utl.cc
@@ -94,7 +94,6 @@
   if (enc) {
     enc->enc = NULL;
     enc->len = 0;
-    enc->buf = NULL;
   }
 }
 
@@ -106,7 +105,7 @@
 }
 
 int asn1_enc_save(ASN1_VALUE **pval, const uint8_t *in, size_t in_len,
-                  const ASN1_ITEM *it, CRYPTO_BUFFER *buf) {
+                  const ASN1_ITEM *it) {
   ASN1_ENCODING *enc;
   enc = asn1_get_enc_ptr(pval, it);
   if (!enc) {
@@ -114,17 +113,9 @@
   }
 
   asn1_encoding_clear(enc);
-  if (buf != NULL) {
-    assert(CRYPTO_BUFFER_data(buf) <= in &&
-           in + in_len <= CRYPTO_BUFFER_data(buf) + CRYPTO_BUFFER_len(buf));
-    CRYPTO_BUFFER_up_ref(buf);
-    enc->buf = buf;
-    enc->enc = (uint8_t *)in;
-  } else {
-    enc->enc = reinterpret_cast<uint8_t *>(OPENSSL_memdup(in, in_len));
-    if (!enc->enc) {
-      return 0;
-    }
+  enc->enc = reinterpret_cast<uint8_t *>(OPENSSL_memdup(in, in_len));
+  if (!enc->enc) {
+    return 0;
   }
 
   enc->len = in_len;
@@ -132,14 +123,9 @@
 }
 
 void asn1_encoding_clear(ASN1_ENCODING *enc) {
-  if (enc->buf != NULL) {
-    CRYPTO_BUFFER_free(enc->buf);
-  } else {
-    OPENSSL_free(enc->enc);
-  }
+  OPENSSL_free(enc->enc);
   enc->enc = NULL;
   enc->len = 0;
-  enc->buf = NULL;
 }
 
 int asn1_enc_restore(int *len, unsigned char **out, ASN1_VALUE **pval,
diff --git a/crypto/internal.h b/crypto/internal.h
index 95d60e1..82ba64d 100644
--- a/crypto/internal.h
+++ b/crypto/internal.h
@@ -23,6 +23,9 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <utility>
+#include <type_traits>
+
 #if defined(BORINGSSL_CONSTANT_TIME_VALIDATION)
 #include <valgrind/memcheck.h>
 #endif
@@ -1604,4 +1607,28 @@
 #endif
 
 
+BSSL_NAMESPACE_BEGIN
+// Cleanup implements a custom scope guard, when the cleanup logic does not fit
+// in a destructor. Usage:
+//
+//     bssl::Cleanup cleanup = [&] { SomeCleanupWork(local_var); };
+template <typename F>
+class Cleanup {
+ public:
+  static_assert(std::is_invocable_v<F>);
+  static_assert(std::is_same_v<std::invoke_result_t<F>, void>);
+
+  Cleanup(F func) : func_(func) {}
+  Cleanup(const Cleanup &) = delete;
+  Cleanup &operator=(const Cleanup &) = delete;
+  ~Cleanup() { func_(); }
+
+ private:
+  F func_;
+};
+template <typename F>
+Cleanup(F func) -> Cleanup<F>;
+BSSL_NAMESPACE_END
+
+
 #endif  // OPENSSL_HEADER_CRYPTO_INTERNAL_H
diff --git a/crypto/x509/a_sign.cc b/crypto/x509/a_sign.cc
index 018a31b..90b8784 100644
--- a/crypto/x509/a_sign.cc
+++ b/crypto/x509/a_sign.cc
@@ -18,12 +18,16 @@
 #include <openssl/evp.h>
 #include <openssl/mem.h>
 #include <openssl/obj.h>
+#include <openssl/span.h>
 #include <openssl/x509.h>
 
 #include <limits.h>
 
+#include "../internal.h"
+#include "../mem_internal.h"
 #include "internal.h"
 
+
 int ASN1_item_sign(const ASN1_ITEM *it, X509_ALGOR *algor1, X509_ALGOR *algor2,
                    ASN1_BIT_STRING *signature, void *asn, EVP_PKEY *pkey,
                    const EVP_MD *type) {
@@ -41,55 +45,56 @@
 int ASN1_item_sign_ctx(const ASN1_ITEM *it, X509_ALGOR *algor1,
                        X509_ALGOR *algor2, ASN1_BIT_STRING *signature,
                        void *asn, EVP_MD_CTX *ctx) {
-  int ret = 0;
-  uint8_t *in = NULL, *out = NULL;
+  // Historically, this function called |EVP_MD_CTX_cleanup| on return. Some
+  // callers rely on this to avoid memory leaks.
+  bssl::Cleanup cleanup = [&] { EVP_MD_CTX_cleanup(ctx); };
 
-  {
-    if (signature->type != V_ASN1_BIT_STRING) {
-      OPENSSL_PUT_ERROR(ASN1, ASN1_R_WRONG_TYPE);
-      goto err;
-    }
-
-    // Write out the requested copies of the AlgorithmIdentifier.
-    if (algor1 && !x509_digest_sign_algorithm(ctx, algor1)) {
-      goto err;
-    }
-    if (algor2 && !x509_digest_sign_algorithm(ctx, algor2)) {
-      goto err;
-    }
-
-    int in_len = ASN1_item_i2d(reinterpret_cast<ASN1_VALUE *>(asn), &in, it);
-    if (in_len < 0) {
-      goto err;
-    }
-
-    EVP_PKEY *pkey = EVP_PKEY_CTX_get0_pkey(ctx->pctx);
-    size_t out_len = EVP_PKEY_size(pkey);
-    if (out_len > INT_MAX) {
-      OPENSSL_PUT_ERROR(X509, ERR_R_OVERFLOW);
-      goto err;
-    }
-
-    out = reinterpret_cast<uint8_t *>(OPENSSL_malloc(out_len));
-    if (out == NULL) {
-      goto err;
-    }
-
-    if (!EVP_DigestSign(ctx, out, &out_len, in, in_len)) {
-      OPENSSL_PUT_ERROR(X509, ERR_R_EVP_LIB);
-      goto err;
-    }
-
-    ASN1_STRING_set0(signature, out, (int)out_len);
-    out = NULL;
-    signature->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07);
-    signature->flags |= ASN1_STRING_FLAG_BITS_LEFT;
-    ret = (int)out_len;
+  // Write out the requested copies of the AlgorithmIdentifier. This may modify
+  // |asn|, so we must do it first.
+  if ((algor1 != nullptr && !x509_digest_sign_algorithm(ctx, algor1)) ||
+      (algor2 != nullptr && !x509_digest_sign_algorithm(ctx, algor2))) {
+    return 0;
   }
 
-err:
-  EVP_MD_CTX_cleanup(ctx);
-  OPENSSL_free(in);
-  OPENSSL_free(out);
-  return ret;
+  uint8_t *in = nullptr;
+  int in_len = ASN1_item_i2d(reinterpret_cast<ASN1_VALUE *>(asn), &in, it);
+  if (in_len < 0) {
+    return 0;
+  }
+  bssl::UniquePtr<uint8_t> free_in(in);
+
+  return x509_sign_to_bit_string(ctx, signature, bssl::Span(in, in_len));
+}
+
+int x509_sign_to_bit_string(EVP_MD_CTX *ctx, ASN1_BIT_STRING *out,
+                            bssl::Span<const uint8_t> in) {
+  if (out->type != V_ASN1_BIT_STRING) {
+    OPENSSL_PUT_ERROR(ASN1, ASN1_R_WRONG_TYPE);
+    return 0;
+  }
+
+  EVP_PKEY *pkey = EVP_PKEY_CTX_get0_pkey(ctx->pctx);
+  size_t sig_len = EVP_PKEY_size(pkey);
+  if (sig_len > INT_MAX) {
+    // Ensure the signature will fit in |out|.
+    OPENSSL_PUT_ERROR(X509, ERR_R_OVERFLOW);
+    return 0;
+  }
+  bssl::Array<uint8_t> sig;
+  if (!sig.Init(sig_len)) {
+    return 0;
+  }
+
+  if (!EVP_DigestSign(ctx, sig.data(), &sig_len, in.data(), in.size())) {
+    OPENSSL_PUT_ERROR(X509, ERR_R_EVP_LIB);
+    return 0;
+  }
+  sig.Shrink(sig_len);
+
+  uint8_t *sig_data;
+  sig.Release(&sig_data, &sig_len);
+  ASN1_STRING_set0(out, sig_data, static_cast<int>(sig_len));
+  out->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07);
+  out->flags |= ASN1_STRING_FLAG_BITS_LEFT;
+  return static_cast<int>(sig_len);
 }
diff --git a/crypto/x509/a_verify.cc b/crypto/x509/a_verify.cc
index 58085ed..9a1483e 100644
--- a/crypto/x509/a_verify.cc
+++ b/crypto/x509/a_verify.cc
@@ -23,12 +23,14 @@
 #include <openssl/evp.h>
 #include <openssl/mem.h>
 #include <openssl/obj.h>
+#include <openssl/span.h>
 
 #include "internal.h"
 
-int ASN1_item_verify(const ASN1_ITEM *it, const X509_ALGOR *a,
-                     const ASN1_BIT_STRING *signature, void *asn,
-                     EVP_PKEY *pkey) {
+
+int x509_verify_signature(const X509_ALGOR *sigalg,
+                          const ASN1_BIT_STRING *signature,
+                          bssl::Span<const uint8_t> in, EVP_PKEY *pkey) {
   if (!pkey) {
     OPENSSL_PUT_ERROR(X509, ERR_R_PASSED_NULL_PARAMETER);
     return 0;
@@ -41,34 +43,29 @@
       return 0;
     }
   } else {
-    sig_len = (size_t)ASN1_STRING_length(signature);
+    sig_len = static_cast<size_t>(ASN1_STRING_length(signature));
   }
 
-  EVP_MD_CTX ctx;
-  uint8_t *buf_in = NULL;
-  int ret = 0, inl = 0;
-  EVP_MD_CTX_init(&ctx);
-
-  if (!x509_digest_verify_init(&ctx, a, pkey)) {
-    goto err;
+  bssl::ScopedEVP_MD_CTX ctx;
+  if (!x509_digest_verify_init(ctx.get(), sigalg, pkey)) {
+    return 0;
   }
-
-  inl = ASN1_item_i2d(reinterpret_cast<ASN1_VALUE *>(asn), &buf_in, it);
-
-  if (buf_in == NULL) {
-    goto err;
-  }
-
-  if (!EVP_DigestVerify(&ctx, ASN1_STRING_get0_data(signature), sig_len, buf_in,
-                        inl)) {
+  if (!EVP_DigestVerify(ctx.get(), ASN1_STRING_get0_data(signature), sig_len,
+                        in.data(), in.size())) {
     OPENSSL_PUT_ERROR(X509, ERR_R_EVP_LIB);
-    goto err;
+    return 0;
   }
+  return 1;
+}
 
-  ret = 1;
-
-err:
-  OPENSSL_free(buf_in);
-  EVP_MD_CTX_cleanup(&ctx);
-  return ret;
+int ASN1_item_verify(const ASN1_ITEM *it, const X509_ALGOR *sigalg,
+                     const ASN1_BIT_STRING *signature, void *asn,
+                     EVP_PKEY *pkey) {
+  uint8_t *in = nullptr;
+  int in_len = ASN1_item_i2d(reinterpret_cast<ASN1_VALUE *>(asn), &in, it);
+  if (in_len < 0) {
+    return 0;
+  }
+  bssl::UniquePtr<uint8_t> free_in(in);
+  return x509_verify_signature(sigalg, signature, bssl::Span(in, in_len), pkey);
 }
diff --git a/crypto/x509/internal.h b/crypto/x509/internal.h
index dcff849..b4d48ca 100644
--- a/crypto/x509/internal.h
+++ b/crypto/x509/internal.h
@@ -30,13 +30,6 @@
 
 // Internal structures.
 
-typedef struct X509_val_st {
-  ASN1_TIME *notBefore;
-  ASN1_TIME *notAfter;
-} X509_VAL;
-
-DECLARE_ASN1_FUNCTIONS_const(X509_VAL)
-
 struct X509_pubkey_st {
   X509_ALGOR algor;
   ASN1_BIT_STRING public_key;
@@ -49,6 +42,7 @@
 int x509_parse_public_key(CBS *cbs, X509_PUBKEY *out,
                           bssl::Span<const EVP_PKEY_ALG *const> algs);
 int x509_marshal_public_key(CBB *cbb, const X509_PUBKEY *in);
+int x509_pubkey_set1(X509_PUBKEY *key, EVP_PKEY *pkey);
 
 // X509_PUBKEY is an |ASN1_ITEM| whose ASN.1 type is SubjectPublicKeyInfo and C
 // type is |X509_PUBKEY*|.
@@ -107,28 +101,31 @@
 // (RFC 5280) and C type is |STACK_OF(X509_EXTENSION)*|.
 DECLARE_ASN1_ITEM(X509_EXTENSIONS)
 
-typedef struct {
-  ASN1_INTEGER *version;  // [ 0 ] default of v1
-  ASN1_INTEGER *serialNumber;
-  X509_ALGOR *tbs_sig_alg;
+struct x509_st {
+  // TBSCertificate fields:
+  uint8_t version;  // One of the |X509_VERSION_*| constants.
+  ASN1_INTEGER serialNumber;
+  X509_ALGOR tbs_sig_alg;
+  // TODO(crbug.com/42290417): When |X509_NAME| no longer uses the macro system,
+  // try to embed this struct.
   X509_NAME *issuer;
-  X509_VAL *validity;
+  ASN1_TIME notBefore;
+  ASN1_TIME notAfter;
+  // TODO(crbug.com/42290417): When |X509_NAME| no longer uses the macro system,
+  // try to embed this struct.
   X509_NAME *subject;
-  X509_PUBKEY *key;
+  X509_PUBKEY key;
   ASN1_BIT_STRING *issuerUID;            // [ 1 ] optional in v2
   ASN1_BIT_STRING *subjectUID;           // [ 2 ] optional in v2
   STACK_OF(X509_EXTENSION) *extensions;  // [ 3 ] optional in v3
-  ASN1_ENCODING enc;
-} X509_CINF;
-
-// TODO(https://crbug.com/boringssl/407): This is not const because it contains
-// an |X509_NAME|.
-DECLARE_ASN1_FUNCTIONS(X509_CINF)
-
-struct x509_st {
-  X509_CINF *cert_info;
+  // Certificate fields:
   X509_ALGOR sig_alg;
   ASN1_BIT_STRING signature;
+  // Other state:
+  // buf, if not nullptr, contains a copy of the serialized Certificate.
+  // TODO(davidben): Now every parsed |X509| has an underlying |CRYPTO_BUFFER|,
+  // but |X509|s created peacemeal do not. Can we make this more uniform?
+  CRYPTO_BUFFER *buf;
   CRYPTO_refcount_t references;
   CRYPTO_EX_DATA ex_data;
   // These contain copies of various extension values
@@ -146,6 +143,8 @@
   CRYPTO_MUTEX lock;
 } /* X509 */;
 
+int x509_marshal_tbs_cert(CBB *cbb, X509 *x509);
+
 // X509 is an |ASN1_ITEM| whose ASN.1 type is X.509 Certificate (RFC 5280) and C
 // type is |X509*|.
 DECLARE_ASN1_ITEM(X509)
@@ -395,6 +394,17 @@
 int x509_digest_verify_init(EVP_MD_CTX *ctx, const X509_ALGOR *sigalg,
                             EVP_PKEY *pkey);
 
+// x509_verify_signature verifies a |signature| using |sigalg| and |pkey| over
+// |in|. It returns one if the signature is valid and zero on error.
+int x509_verify_signature(const X509_ALGOR *sigalg,
+                          const ASN1_BIT_STRING *signature,
+                          bssl::Span<const uint8_t> in, EVP_PKEY *pkey);
+
+// x509_sign_to_bit_string signs |in| using |ctx| and saves the result in |out|.
+// It returns the length of the signature on success and zero on error.
+int x509_sign_to_bit_string(EVP_MD_CTX *ctx, ASN1_BIT_STRING *out,
+                            bssl::Span<const uint8_t> in);
+
 
 // Path-building functions.
 
diff --git a/crypto/x509/t_x509.cc b/crypto/x509/t_x509.cc
index 1264cb0..94d64f7 100644
--- a/crypto/x509/t_x509.cc
+++ b/crypto/x509/t_x509.cc
@@ -59,7 +59,6 @@
     nmindent = 16;
   }
 
-  const X509_CINF *ci = x->cert_info;
   if (!(cflag & X509_FLAG_NO_HEADER)) {
     if (BIO_write(bp, "Certificate:\n", 13) <= 0) {
       return 0;
@@ -107,7 +106,7 @@
   }
 
   if (!(cflag & X509_FLAG_NO_SIGNAME)) {
-    if (X509_signature_print(bp, ci->tbs_sig_alg, NULL) <= 0) {
+    if (X509_signature_print(bp, &x->tbs_sig_alg, NULL) <= 0) {
       return 0;
     }
   }
@@ -163,7 +162,7 @@
     if (BIO_printf(bp, "%12sPublic Key Algorithm: ", "") <= 0) {
       return 0;
     }
-    if (i2a_ASN1_OBJECT(bp, ci->key->algor.algorithm) <= 0) {
+    if (i2a_ASN1_OBJECT(bp, x->key.algor.algorithm) <= 0) {
       return 0;
     }
     if (BIO_puts(bp, "\n") <= 0) {
@@ -180,26 +179,26 @@
   }
 
   if (!(cflag & X509_FLAG_NO_IDS)) {
-    if (ci->issuerUID) {
+    if (x->issuerUID) {
       if (BIO_printf(bp, "%8sIssuer Unique ID: ", "") <= 0) {
         return 0;
       }
-      if (!X509_signature_dump(bp, ci->issuerUID, 12)) {
+      if (!X509_signature_dump(bp, x->issuerUID, 12)) {
         return 0;
       }
     }
-    if (ci->subjectUID) {
+    if (x->subjectUID) {
       if (BIO_printf(bp, "%8sSubject Unique ID: ", "") <= 0) {
         return 0;
       }
-      if (!X509_signature_dump(bp, ci->subjectUID, 12)) {
+      if (!X509_signature_dump(bp, x->subjectUID, 12)) {
         return 0;
       }
     }
   }
 
   if (!(cflag & X509_FLAG_NO_EXTENSIONS)) {
-    X509V3_extensions_print(bp, "X509v3 extensions", ci->extensions, cflag, 8);
+    X509V3_extensions_print(bp, "X509v3 extensions", x->extensions, cflag, 8);
   }
 
   if (!(cflag & X509_FLAG_NO_SIGDUMP)) {
diff --git a/crypto/x509/v3_conf.cc b/crypto/x509/v3_conf.cc
index f48c128..e4cb758 100644
--- a/crypto/x509/v3_conf.cc
+++ b/crypto/x509/v3_conf.cc
@@ -308,7 +308,7 @@
                          const char *section, X509 *cert) {
   STACK_OF(X509_EXTENSION) **sk = NULL;
   if (cert) {
-    sk = &cert->cert_info->extensions;
+    sk = &cert->extensions;
   }
   return X509V3_EXT_add_nconf_sk(conf, ctx, section, sk);
 }
diff --git a/crypto/x509/v3_skey.cc b/crypto/x509/v3_skey.cc
index 8cf754a..bec89ce 100644
--- a/crypto/x509/v3_skey.cc
+++ b/crypto/x509/v3_skey.cc
@@ -89,7 +89,7 @@
   if (ctx->subject_req) {
     pk = &ctx->subject_req->req_info->pubkey->public_key;
   } else {
-    pk = &ctx->subject_cert->cert_info->key->public_key;
+    pk = &ctx->subject_cert->key.public_key;
   }
 
   if (!EVP_Digest(pk->data, pk->length, pkey_dig, &diglen, EVP_sha1(), NULL)) {
diff --git a/crypto/x509/x509_cmp.cc b/crypto/x509/x509_cmp.cc
index 4085d12..be08ec8 100644
--- a/crypto/x509/x509_cmp.cc
+++ b/crypto/x509/x509_cmp.cc
@@ -29,15 +29,15 @@
 
 
 int X509_issuer_name_cmp(const X509 *a, const X509 *b) {
-  return (X509_NAME_cmp(a->cert_info->issuer, b->cert_info->issuer));
+  return X509_NAME_cmp(a->issuer, b->issuer);
 }
 
 int X509_subject_name_cmp(const X509 *a, const X509 *b) {
-  return (X509_NAME_cmp(a->cert_info->subject, b->cert_info->subject));
+  return X509_NAME_cmp(a->subject, b->subject);
 }
 
 int X509_CRL_cmp(const X509_CRL *a, const X509_CRL *b) {
-  return (X509_NAME_cmp(a->crl->issuer, b->crl->issuer));
+  return X509_NAME_cmp(a->crl->issuer, b->crl->issuer);
 }
 
 int X509_CRL_match(const X509_CRL *a, const X509_CRL *b) {
@@ -45,35 +45,31 @@
 }
 
 X509_NAME *X509_get_issuer_name(const X509 *a) {
-  return a->cert_info->issuer;
+  // This function is not const-correct for OpenSSL compatibility.
+  return a->issuer;
 }
 
-uint32_t X509_issuer_name_hash(X509 *x) {
-  return X509_NAME_hash(x->cert_info->issuer);
-}
+uint32_t X509_issuer_name_hash(X509 *x) { return X509_NAME_hash(x->issuer); }
 
 uint32_t X509_issuer_name_hash_old(X509 *x) {
-  return (X509_NAME_hash_old(x->cert_info->issuer));
+  return X509_NAME_hash_old(x->issuer);
 }
 
 X509_NAME *X509_get_subject_name(const X509 *a) {
-  return a->cert_info->subject;
+  // This function is not const-correct for OpenSSL compatibility.
+  return a->subject;
 }
 
-ASN1_INTEGER *X509_get_serialNumber(X509 *a) {
-  return a->cert_info->serialNumber;
-}
+ASN1_INTEGER *X509_get_serialNumber(X509 *a) { return &a->serialNumber; }
 
 const ASN1_INTEGER *X509_get0_serialNumber(const X509 *x509) {
-  return x509->cert_info->serialNumber;
+  return &x509->serialNumber;
 }
 
-uint32_t X509_subject_name_hash(X509 *x) {
-  return X509_NAME_hash(x->cert_info->subject);
-}
+uint32_t X509_subject_name_hash(X509 *x) { return X509_NAME_hash(x->subject); }
 
 uint32_t X509_subject_name_hash_old(X509 *x) {
-  return X509_NAME_hash_old(x->cert_info->subject);
+  return X509_NAME_hash_old(x->subject);
 }
 
 // Compare two certificates: they must be identical for this to work. NB:
@@ -180,21 +176,22 @@
   if (x == NULL) {
     return NULL;
   }
-  return X509_PUBKEY_get0(x->cert_info->key);
+  return X509_PUBKEY_get0(&x->key);
 }
 
 EVP_PKEY *X509_get_pubkey(const X509 *x) {
   if (x == NULL) {
     return NULL;
   }
-  return X509_PUBKEY_get(x->cert_info->key);
+  return X509_PUBKEY_get(&x->key);
 }
 
 ASN1_BIT_STRING *X509_get0_pubkey_bitstr(const X509 *x) {
   if (!x) {
     return NULL;
   }
-  return &x->cert_info->key->public_key;
+  // This function is not const-correct for OpenSSL compatibility.
+  return const_cast<ASN1_BIT_STRING*>(&x->key.public_key);
 }
 
 int X509_check_private_key(const X509 *x, const EVP_PKEY *k) {
diff --git a/crypto/x509/x509_ext.cc b/crypto/x509/x509_ext.cc
index baddf5a..bb896dc 100644
--- a/crypto/x509/x509_ext.cc
+++ b/crypto/x509/x509_ext.cc
@@ -70,42 +70,41 @@
 }
 
 int X509_get_ext_count(const X509 *x) {
-  return X509v3_get_ext_count(x->cert_info->extensions);
+  return X509v3_get_ext_count(x->extensions);
 }
 
 int X509_get_ext_by_NID(const X509 *x, int nid, int lastpos) {
-  return X509v3_get_ext_by_NID(x->cert_info->extensions, nid, lastpos);
+  return X509v3_get_ext_by_NID(x->extensions, nid, lastpos);
 }
 
 int X509_get_ext_by_OBJ(const X509 *x, const ASN1_OBJECT *obj, int lastpos) {
-  return X509v3_get_ext_by_OBJ(x->cert_info->extensions, obj, lastpos);
+  return X509v3_get_ext_by_OBJ(x->extensions, obj, lastpos);
 }
 
 int X509_get_ext_by_critical(const X509 *x, int crit, int lastpos) {
-  return X509v3_get_ext_by_critical(x->cert_info->extensions, crit, lastpos);
+  return X509v3_get_ext_by_critical(x->extensions, crit, lastpos);
 }
 
 X509_EXTENSION *X509_get_ext(const X509 *x, int loc) {
-  return X509v3_get_ext(x->cert_info->extensions, loc);
+  return X509v3_get_ext(x->extensions, loc);
 }
 
 X509_EXTENSION *X509_delete_ext(X509 *x, int loc) {
-  return delete_ext(&x->cert_info->extensions, loc);
+  return delete_ext(&x->extensions, loc);
 }
 
 int X509_add_ext(X509 *x, const X509_EXTENSION *ex, int loc) {
-  return X509v3_add_ext(&x->cert_info->extensions, ex, loc) != NULL;
+  return X509v3_add_ext(&x->extensions, ex, loc) != NULL;
 }
 
 void *X509_get_ext_d2i(const X509 *x509, int nid, int *out_critical,
                        int *out_idx) {
-  return X509V3_get_d2i(x509->cert_info->extensions, nid, out_critical,
-                        out_idx);
+  return X509V3_get_d2i(x509->extensions, nid, out_critical, out_idx);
 }
 
 int X509_add1_ext_i2d(X509 *x, int nid, void *value, int crit,
                       unsigned long flags) {
-  return X509V3_add1_i2d(&x->cert_info->extensions, nid, value, crit, flags);
+  return X509V3_add1_i2d(&x->extensions, nid, value, crit, flags);
 }
 
 int X509_REVOKED_get_ext_count(const X509_REVOKED *x) {
diff --git a/crypto/x509/x509_lu.cc b/crypto/x509/x509_lu.cc
index eb9624a..6646f4d 100644
--- a/crypto/x509/x509_lu.cc
+++ b/crypto/x509/x509_lu.cc
@@ -287,7 +287,6 @@
                                X509_NAME *name, int *pnmatch) {
   X509_OBJECT stmp;
   X509 x509_s;
-  X509_CINF cinf_s;
   X509_CRL crl_s;
   X509_CRL_INFO crl_info_s;
 
@@ -295,8 +294,7 @@
   switch (type) {
     case X509_LU_X509:
       stmp.data.x509 = &x509_s;
-      x509_s.cert_info = &cinf_s;
-      cinf_s.subject = name;
+      x509_s.subject = name;
       break;
     case X509_LU_CRL:
       stmp.data.crl = &crl_s;
diff --git a/crypto/x509/x509_set.cc b/crypto/x509/x509_set.cc
index 76c86d3..98ab04f 100644
--- a/crypto/x509/x509_set.cc
+++ b/crypto/x509/x509_set.cc
@@ -21,13 +21,7 @@
 #include "internal.h"
 
 
-long X509_get_version(const X509 *x509) {
-  // The default version is v1(0).
-  if (x509->cert_info->version == NULL) {
-    return X509_VERSION_1;
-  }
-  return ASN1_INTEGER_get(x509->cert_info->version);
-}
+long X509_get_version(const X509 *x509) { return x509->version; }
 
 int X509_set_version(X509 *x, long version) {
   if (x == NULL) {
@@ -39,20 +33,8 @@
     return 0;
   }
 
-  // v1(0) is default and is represented by omitting the version.
-  if (version == X509_VERSION_1) {
-    ASN1_INTEGER_free(x->cert_info->version);
-    x->cert_info->version = NULL;
-    return 1;
-  }
-
-  if (x->cert_info->version == NULL) {
-    x->cert_info->version = ASN1_INTEGER_new();
-    if (x->cert_info->version == NULL) {
-      return 0;
-    }
-  }
-  return ASN1_INTEGER_set_int64(x->cert_info->version, version);
+  x->version = static_cast<uint8_t>(version);
+  return 1;
 }
 
 int X509_set_serialNumber(X509 *x, const ASN1_INTEGER *serial) {
@@ -61,138 +43,99 @@
     return 0;
   }
 
-  ASN1_INTEGER *in;
-  if (x == NULL) {
-    return 0;
-  }
-  in = x->cert_info->serialNumber;
-  if (in != serial) {
-    in = ASN1_INTEGER_dup(serial);
-    if (in != NULL) {
-      ASN1_INTEGER_free(x->cert_info->serialNumber);
-      x->cert_info->serialNumber = in;
-    }
-  }
-  return in != NULL;
+  return ASN1_STRING_copy(&x->serialNumber, serial);
 }
 
 int X509_set_issuer_name(X509 *x, X509_NAME *name) {
-  if ((x == NULL) || (x->cert_info == NULL)) {
+  if (x == NULL) {
     return 0;
   }
-  return (X509_NAME_set(&x->cert_info->issuer, name));
+  return (X509_NAME_set(&x->issuer, name));
 }
 
 int X509_set_subject_name(X509 *x, X509_NAME *name) {
-  if ((x == NULL) || (x->cert_info == NULL)) {
+  if (x == NULL) {
     return 0;
   }
-  return (X509_NAME_set(&x->cert_info->subject, name));
+  return (X509_NAME_set(&x->subject, name));
 }
 
 int X509_set1_notBefore(X509 *x, const ASN1_TIME *tm) {
-  ASN1_TIME *in;
-
-  if ((x == NULL) || (x->cert_info->validity == NULL)) {
-    return 0;
-  }
-  in = x->cert_info->validity->notBefore;
-  if (in != tm) {
-    in = ASN1_STRING_dup(tm);
-    if (in != NULL) {
-      ASN1_TIME_free(x->cert_info->validity->notBefore);
-      x->cert_info->validity->notBefore = in;
-    }
-  }
-  return in != NULL;
+  // TODO(crbug.com/42290309): Check that |tm->type| is correct.
+  return ASN1_STRING_copy(&x->notBefore, tm);
 }
 
 int X509_set_notBefore(X509 *x, const ASN1_TIME *tm) {
   return X509_set1_notBefore(x, tm);
 }
 
-const ASN1_TIME *X509_get0_notBefore(const X509 *x) {
-  return x->cert_info->validity->notBefore;
-}
+const ASN1_TIME *X509_get0_notBefore(const X509 *x) { return &x->notBefore; }
 
 ASN1_TIME *X509_getm_notBefore(X509 *x) {
   // Note this function takes a const |X509| pointer in OpenSSL. We require
   // non-const as this allows mutating |x|. If it comes up for compatibility,
   // we can relax this.
-  return x->cert_info->validity->notBefore;
+  return &x->notBefore;
 }
 
 ASN1_TIME *X509_get_notBefore(const X509 *x509) {
   // In OpenSSL, this function is an alias for |X509_getm_notBefore|, but our
   // |X509_getm_notBefore| is const-correct. |X509_get_notBefore| was
   // originally a macro, so it needs to capture both get0 and getm use cases.
-  return x509->cert_info->validity->notBefore;
+  return const_cast<ASN1_TIME *>(&x509->notBefore);
 }
 
 int X509_set1_notAfter(X509 *x, const ASN1_TIME *tm) {
-  ASN1_TIME *in;
-
-  if ((x == NULL) || (x->cert_info->validity == NULL)) {
-    return 0;
-  }
-  in = x->cert_info->validity->notAfter;
-  if (in != tm) {
-    in = ASN1_STRING_dup(tm);
-    if (in != NULL) {
-      ASN1_TIME_free(x->cert_info->validity->notAfter);
-      x->cert_info->validity->notAfter = in;
-    }
-  }
-  return in != NULL;
+  // TODO(crbug.com/42290309): Check that |tm->type| is correct.
+  return ASN1_STRING_copy(&x->notAfter, tm);
 }
 
 int X509_set_notAfter(X509 *x, const ASN1_TIME *tm) {
   return X509_set1_notAfter(x, tm);
 }
 
-const ASN1_TIME *X509_get0_notAfter(const X509 *x) {
-  return x->cert_info->validity->notAfter;
-}
+const ASN1_TIME *X509_get0_notAfter(const X509 *x) { return &x->notAfter; }
 
 ASN1_TIME *X509_getm_notAfter(X509 *x) {
   // Note this function takes a const |X509| pointer in OpenSSL. We require
   // non-const as this allows mutating |x|. If it comes up for compatibility,
   // we can relax this.
-  return x->cert_info->validity->notAfter;
+  return &x->notAfter;
 }
 
 ASN1_TIME *X509_get_notAfter(const X509 *x509) {
   // In OpenSSL, this function is an alias for |X509_getm_notAfter|, but our
   // |X509_getm_notAfter| is const-correct. |X509_get_notAfter| was
   // originally a macro, so it needs to capture both get0 and getm use cases.
-  return x509->cert_info->validity->notAfter;
-}
+  return const_cast<ASN1_TIME *>(&x509->notAfter);
+  }
 
 void X509_get0_uids(const X509 *x509, const ASN1_BIT_STRING **out_issuer_uid,
                     const ASN1_BIT_STRING **out_subject_uid) {
   if (out_issuer_uid != NULL) {
-    *out_issuer_uid = x509->cert_info->issuerUID;
+    *out_issuer_uid = x509->issuerUID;
   }
   if (out_subject_uid != NULL) {
-    *out_subject_uid = x509->cert_info->subjectUID;
+    *out_subject_uid = x509->subjectUID;
   }
 }
 
 int X509_set_pubkey(X509 *x, EVP_PKEY *pkey) {
-  if ((x == NULL) || (x->cert_info == NULL)) {
+  if (x == nullptr) {
     return 0;
   }
-  return (X509_PUBKEY_set(&(x->cert_info->key), pkey));
+  return x509_pubkey_set1(&x->key, pkey);
 }
 
 const STACK_OF(X509_EXTENSION) *X509_get0_extensions(const X509 *x) {
-  return x->cert_info->extensions;
+  return x->extensions;
 }
 
 const X509_ALGOR *X509_get0_tbs_sigalg(const X509 *x) {
-  return x->cert_info->tbs_sig_alg;
+  return &x->tbs_sig_alg;
 }
 
 X509_PUBKEY *X509_get_X509_PUBKEY(const X509 *x509) {
-  return x509->cert_info->key;
+  // This function is not const-correct for OpenSSL compatibility.
+  return const_cast<X509_PUBKEY *>(&x509->key);
 }
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 94aeb4e..c2eb764 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -2663,15 +2663,12 @@
   bssl::UniquePtr<X509> root(X509_parse_from_buffer(buf.get()));
   ASSERT_TRUE(root);
 
-  const uint8_t *enc_pointer = root->cert_info->enc.enc;
-  const uint8_t *buf_pointer = CRYPTO_BUFFER_data(buf.get());
-  ASSERT_GE(enc_pointer, buf_pointer);
-  ASSERT_LT(enc_pointer, buf_pointer + CRYPTO_BUFFER_len(buf.get()));
+  EXPECT_EQ(buf.get(), root->buf);
   buf.reset();
 
-  /* This ensures the X509 took a reference to |buf|, otherwise this will be a
-   * reference to free memory and ASAN should notice. */
-  ASSERT_EQ(0x30, enc_pointer[0]);
+  // This ensures the X509 took a reference to |buf|, otherwise this will be a
+  // reference to free memory and ASAN should notice.
+  CRYPTO_BUFFER_len(root->buf);
 }
 
 TEST(X509Test, TestFromBufferWithTrailingData) {
@@ -2730,28 +2727,23 @@
   size_t data2_len;
   bssl::UniquePtr<uint8_t> data2;
   ASSERT_TRUE(PEMToDER(&data2, &data2_len, kLeafPEM));
-  EXPECT_TRUE(buffers_alias(root->cert_info->enc.enc, root->cert_info->enc.len,
-                            CRYPTO_BUFFER_data(buf.get()),
-                            CRYPTO_BUFFER_len(buf.get())));
+  EXPECT_EQ(root->buf, buf.get());
 
   // Historically, this function tested the interaction betweeen
   // |X509_parse_from_buffer| and object reuse. We no longer support object
   // reuse, so |d2i_X509| will replace |raw| with a new object. However, we
   // retain this test to verify that releasing objects from |d2i_X509| works
-  // correctly.
+  // correctly and doesn't keep the old buffer.
   X509 *raw = root.release();
   const uint8_t *inp = data2.get();
   X509 *ret = d2i_X509(&raw, &inp, data2_len);
   root.reset(raw);
 
   ASSERT_EQ(root.get(), ret);
-  ASSERT_EQ(nullptr, root->cert_info->enc.buf);
-  EXPECT_FALSE(buffers_alias(root->cert_info->enc.enc, root->cert_info->enc.len,
-                             CRYPTO_BUFFER_data(buf.get()),
-                             CRYPTO_BUFFER_len(buf.get())));
+  ASSERT_NE(buf.get(), root->buf);
 
-  // Free |data2| and ensure that |root| took its own copy. Otherwise the
-  // following will trigger a use-after-free.
+  // Free |data2| and ensure that |root| took its own copy. Otherwise
+  // serializing |root|, below, will trigger a use-after-free.
   data2.reset();
 
   uint8_t *i2d = nullptr;
@@ -2760,10 +2752,7 @@
   bssl::UniquePtr<uint8_t> i2d_storage(i2d);
 
   ASSERT_TRUE(PEMToDER(&data2, &data2_len, kLeafPEM));
-
-  ASSERT_EQ(static_cast<long>(data2_len), i2d_len);
-  ASSERT_EQ(0, OPENSSL_memcmp(data2.get(), i2d, i2d_len));
-  ASSERT_EQ(nullptr, root->cert_info->enc.buf);
+  EXPECT_EQ(Bytes(i2d, i2d_len), Bytes(data2.get(), data2_len));
 }
 
 TEST(X509Test, TestFailedParseFromBuffer) {
diff --git a/crypto/x509/x_all.cc b/crypto/x509/x_all.cc
index 73f620a..e98d046 100644
--- a/crypto/x509/x_all.cc
+++ b/crypto/x509/x_all.cc
@@ -17,24 +17,33 @@
 #include <limits.h>
 
 #include <openssl/asn1.h>
+#include <openssl/bytestring.h>
 #include <openssl/digest.h>
 #include <openssl/dsa.h>
 #include <openssl/evp.h>
 #include <openssl/mem.h>
 #include <openssl/rsa.h>
+#include <openssl/span.h>
 #include <openssl/stack.h>
 
 #include "../asn1/internal.h"
+#include "../internal.h"
 #include "internal.h"
 
 
 int X509_verify(X509 *x509, EVP_PKEY *pkey) {
-  if (X509_ALGOR_cmp(&x509->sig_alg, x509->cert_info->tbs_sig_alg)) {
+  if (X509_ALGOR_cmp(&x509->sig_alg, &x509->tbs_sig_alg)) {
     OPENSSL_PUT_ERROR(X509, X509_R_SIGNATURE_ALGORITHM_MISMATCH);
     return 0;
   }
-  return ASN1_item_verify(ASN1_ITEM_rptr(X509_CINF), &x509->sig_alg,
-                          &x509->signature, x509->cert_info, pkey);
+  // This uses the cached TBSCertificate encoding, if any.
+  bssl::ScopedCBB cbb;
+  if (!CBB_init(cbb.get(), 128) || !x509_marshal_tbs_cert(cbb.get(), x509)) {
+    return 0;
+  }
+  return x509_verify_signature(
+      &x509->sig_alg, &x509->signature,
+      bssl::Span(CBB_data(cbb.get()), CBB_len(cbb.get())), pkey);
 }
 
 int X509_REQ_verify(X509_REQ *req, EVP_PKEY *pkey) {
@@ -43,21 +52,41 @@
 }
 
 int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md) {
-  asn1_encoding_clear(&x->cert_info->enc);
-  return (ASN1_item_sign(ASN1_ITEM_rptr(X509_CINF), x->cert_info->tbs_sig_alg,
-                         &x->sig_alg, &x->signature, x->cert_info, pkey, md));
+  bssl::ScopedEVP_MD_CTX ctx;
+  if (!EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey)) {
+    return 0;
+  }
+  return X509_sign_ctx(x, ctx.get());
 }
 
 int X509_sign_ctx(X509 *x, EVP_MD_CTX *ctx) {
-  asn1_encoding_clear(&x->cert_info->enc);
-  return ASN1_item_sign_ctx(ASN1_ITEM_rptr(X509_CINF), x->cert_info->tbs_sig_alg,
-                            &x->sig_alg, &x->signature, x->cert_info, ctx);
+  // Historically, this function called |EVP_MD_CTX_cleanup| on return. Some
+  // callers rely on this to avoid memory leaks.
+  bssl::Cleanup cleanup = [&] { EVP_MD_CTX_cleanup(ctx); };
+
+  // Fill in the two copies of AlgorithmIdentifier. Note one of these modifies
+  // the TBSCertificate.
+  if (!x509_digest_sign_algorithm(ctx, &x->tbs_sig_alg) ||
+      !x509_digest_sign_algorithm(ctx, &x->sig_alg)) {
+    return 0;
+  }
+
+  // Discard the cached encoding. (We just modified it.)
+  CRYPTO_BUFFER_free(x->buf);
+  x->buf = nullptr;
+
+  bssl::ScopedCBB cbb;
+  if (!CBB_init(cbb.get(), 128) || !x509_marshal_tbs_cert(cbb.get(), x)) {
+    return 0;
+  }
+  return x509_sign_to_bit_string(
+      ctx, &x->signature, bssl::Span(CBB_data(cbb.get()), CBB_len(cbb.get())));
 }
 
 int X509_REQ_sign(X509_REQ *x, EVP_PKEY *pkey, const EVP_MD *md) {
   asn1_encoding_clear(&x->req_info->enc);
-  return (ASN1_item_sign(ASN1_ITEM_rptr(X509_REQ_INFO), x->sig_alg, NULL,
-                         x->signature, x->req_info, pkey, md));
+  return ASN1_item_sign(ASN1_ITEM_rptr(X509_REQ_INFO), x->sig_alg, NULL,
+                        x->signature, x->req_info, pkey, md);
 }
 
 int X509_REQ_sign_ctx(X509_REQ *x, EVP_MD_CTX *ctx) {
@@ -68,8 +97,8 @@
 
 int X509_CRL_sign(X509_CRL *x, EVP_PKEY *pkey, const EVP_MD *md) {
   asn1_encoding_clear(&x->crl->enc);
-  return (ASN1_item_sign(ASN1_ITEM_rptr(X509_CRL_INFO), x->crl->sig_alg,
-                         x->sig_alg, x->signature, x->crl, pkey, md));
+  return ASN1_item_sign(ASN1_ITEM_rptr(X509_CRL_INFO), x->crl->sig_alg,
+                        x->sig_alg, x->signature, x->crl, pkey, md);
 }
 
 int X509_CRL_sign_ctx(X509_CRL *x, EVP_MD_CTX *ctx) {
@@ -79,13 +108,13 @@
 }
 
 int NETSCAPE_SPKI_sign(NETSCAPE_SPKI *x, EVP_PKEY *pkey, const EVP_MD *md) {
-  return (ASN1_item_sign(ASN1_ITEM_rptr(NETSCAPE_SPKAC), x->sig_algor, NULL,
-                         x->signature, x->spkac, pkey, md));
+  return ASN1_item_sign(ASN1_ITEM_rptr(NETSCAPE_SPKAC), x->sig_algor, NULL,
+                        x->signature, x->spkac, pkey, md);
 }
 
 int NETSCAPE_SPKI_verify(NETSCAPE_SPKI *spki, EVP_PKEY *pkey) {
-  return (ASN1_item_verify(ASN1_ITEM_rptr(NETSCAPE_SPKAC), spki->sig_algor,
-                           spki->signature, spki->spkac, pkey));
+  return ASN1_item_verify(ASN1_ITEM_rptr(NETSCAPE_SPKAC), spki->sig_algor,
+                          spki->signature, spki->spkac, pkey);
 }
 
 X509_CRL *d2i_X509_CRL_fp(FILE *fp, X509_CRL **crl) {
diff --git a/crypto/x509/x_name.cc b/crypto/x509/x_name.cc
index 6dad6a2..7ec83f6 100644
--- a/crypto/x509/x_name.cc
+++ b/crypto/x509/x_name.cc
@@ -152,7 +152,7 @@
   ASN1_VALUE *intname_val = NULL;
   if (ASN1_item_ex_d2i(&intname_val, &p, len,
                        ASN1_ITEM_rptr(X509_NAME_INTERNAL), /*tag=*/-1,
-                       /*aclass=*/0, /*opt=*/0, /*buf=*/NULL) <= 0) {
+                       /*aclass=*/0, /*opt=*/0) <= 0) {
     return 0;
   }
   intname = (STACK_OF(STACK_OF_X509_NAME_ENTRY) *)intname_val;
diff --git a/crypto/x509/x_pubkey.cc b/crypto/x509/x_pubkey.cc
index be6c17f..8b74f65 100644
--- a/crypto/x509/x_pubkey.cc
+++ b/crypto/x509/x_pubkey.cc
@@ -132,42 +132,30 @@
 IMPLEMENT_EXTERN_ASN1_SIMPLE(X509_PUBKEY, X509_PUBKEY_new, X509_PUBKEY_free,
                              x509_parse_public_key_default, i2d_X509_PUBKEY)
 
-int X509_PUBKEY_set(X509_PUBKEY **x, EVP_PKEY *pkey) {
-  X509_PUBKEY *pk = NULL;
-  uint8_t *spki = NULL;
-  size_t spki_len;
-
-  if (x == NULL) {
+int x509_pubkey_set1(X509_PUBKEY *key, EVP_PKEY *pkey) {
+  bssl::ScopedCBB cbb;
+  if (!CBB_init(cbb.get(), 64) ||
+      !EVP_marshal_public_key(cbb.get(), pkey)) {
+    OPENSSL_PUT_ERROR(X509, X509_R_PUBLIC_KEY_ENCODE_ERROR);
     return 0;
   }
 
-  CBB cbb;
-  const uint8_t *p;
-  if (!CBB_init(&cbb, 0) ||  //
-      !EVP_marshal_public_key(&cbb, pkey) ||
-      !CBB_finish(&cbb, &spki, &spki_len) ||  //
-      spki_len > LONG_MAX) {
-    CBB_cleanup(&cbb);
-    OPENSSL_PUT_ERROR(X509, X509_R_PUBLIC_KEY_ENCODE_ERROR);
-    goto error;
-  }
+  CBS cbs;
+  CBS_init(&cbs, CBB_data(cbb.get()), CBB_len(cbb.get()));
+  // TODO(crbug.com/42290364): Use an |EVP_PKEY_ALG| derived from |pkey|.
+  // |X509_PUBKEY_get0| does not currently work when setting, say, an
+  // |EVP_PKEY_RSA_PSS| key.
+  return x509_parse_public_key(&cbs, key, bssl::GetDefaultEVPAlgorithms());
+}
 
-  p = spki;
-  pk = d2i_X509_PUBKEY(NULL, &p, (long)spki_len);
-  if (pk == NULL || p != spki + spki_len) {
-    OPENSSL_PUT_ERROR(X509, X509_R_PUBLIC_KEY_DECODE_ERROR);
-    goto error;
+int X509_PUBKEY_set(X509_PUBKEY **x, EVP_PKEY *pkey) {
+  bssl::UniquePtr<X509_PUBKEY> new_key(X509_PUBKEY_new());
+  if (new_key == nullptr || !x509_pubkey_set1(new_key.get(), pkey)) {
+    return 0;
   }
-
-  OPENSSL_free(spki);
   X509_PUBKEY_free(*x);
-  *x = pk;
-
+  *x = new_key.release();
   return 1;
-error:
-  X509_PUBKEY_free(pk);
-  OPENSSL_free(spki);
-  return 0;
 }
 
 EVP_PKEY *X509_PUBKEY_get0(const X509_PUBKEY *key) {
diff --git a/crypto/x509/x_val.cc b/crypto/x509/x_val.cc
deleted file mode 100644
index c9bc0a0..0000000
--- a/crypto/x509/x_val.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-
-#include <openssl/asn1t.h>
-#include <openssl/x509.h>
-
-#include "internal.h"
-
-
-ASN1_SEQUENCE(X509_VAL) = {
-    ASN1_SIMPLE(X509_VAL, notBefore, ASN1_TIME),
-    ASN1_SIMPLE(X509_VAL, notAfter, ASN1_TIME),
-} ASN1_SEQUENCE_END(X509_VAL)
-
-IMPLEMENT_ASN1_FUNCTIONS_const(X509_VAL)
diff --git a/crypto/x509/x_x509.cc b/crypto/x509/x_x509.cc
index f4793d4..520ae63 100644
--- a/crypto/x509/x_x509.cc
+++ b/crypto/x509/x_x509.cc
@@ -27,36 +27,36 @@
 
 #include "../asn1/internal.h"
 #include "../bytestring/internal.h"
+#include "../evp/internal.h"
 #include "../internal.h"
 #include "internal.h"
 
 static CRYPTO_EX_DATA_CLASS g_ex_data_class = CRYPTO_EX_DATA_CLASS_INIT;
 
-ASN1_SEQUENCE_enc(X509_CINF, enc, 0) = {
-    ASN1_EXP_OPT(X509_CINF, version, ASN1_INTEGER, 0),
-    ASN1_SIMPLE(X509_CINF, serialNumber, ASN1_INTEGER),
-    ASN1_SIMPLE(X509_CINF, tbs_sig_alg, X509_ALGOR),
-    ASN1_SIMPLE(X509_CINF, issuer, X509_NAME),
-    ASN1_SIMPLE(X509_CINF, validity, X509_VAL),
-    ASN1_SIMPLE(X509_CINF, subject, X509_NAME),
-    ASN1_SIMPLE(X509_CINF, key, X509_PUBKEY),
-    ASN1_IMP_OPT(X509_CINF, issuerUID, ASN1_BIT_STRING, 1),
-    ASN1_IMP_OPT(X509_CINF, subjectUID, ASN1_BIT_STRING, 2),
-    ASN1_EXP_SEQUENCE_OF_OPT(X509_CINF, extensions, X509_EXTENSION, 3),
-} ASN1_SEQUENCE_END_enc(X509_CINF, X509_CINF)
+static constexpr CBS_ASN1_TAG kVersionTag =
+    CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0;
+static constexpr CBS_ASN1_TAG kIssuerUIDTag = CBS_ASN1_CONTEXT_SPECIFIC | 1;
+static constexpr CBS_ASN1_TAG kSubjectUIDTag = CBS_ASN1_CONTEXT_SPECIFIC | 2;
+static constexpr CBS_ASN1_TAG kExtensionsTag =
+    CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 3;
 
-IMPLEMENT_ASN1_FUNCTIONS(X509_CINF)
-
-// x509_new_null returns a new |X509| object where the |cert_info|, |sig_alg|,
-// and |signature| fields are not yet filled in.
-static X509 *x509_new_null(void) {
-  X509 *ret = reinterpret_cast<X509 *>(OPENSSL_zalloc(sizeof(X509)));
-  if (ret == NULL) {
-    return NULL;
+// x509_new_null returns a new |X509| object where the |issuer| and |subject|
+// fields are not yet filled in.
+static bssl::UniquePtr<X509> x509_new_null(void) {
+  bssl::UniquePtr<X509> ret(
+      reinterpret_cast<X509 *>(OPENSSL_zalloc(sizeof(X509))));
+  if (ret == nullptr) {
+    return nullptr;
   }
 
   ret->references = 1;
   ret->ex_pathlen = -1;
+  ret->version = X509_VERSION_1;
+  asn1_string_init(&ret->serialNumber, V_ASN1_INTEGER);
+  x509_algor_init(&ret->tbs_sig_alg);
+  asn1_string_init(&ret->notBefore, -1);
+  asn1_string_init(&ret->notAfter, -1);
+  x509_pubkey_init(&ret->key);
   x509_algor_init(&ret->sig_alg);
   asn1_string_init(&ret->signature, V_ASN1_BIT_STRING);
   CRYPTO_new_ex_data(&ret->ex_data);
@@ -65,18 +65,19 @@
 }
 
 X509 *X509_new(void) {
-  X509 *ret = x509_new_null();
-  if (ret == NULL) {
-    return NULL;
+  bssl::UniquePtr<X509> ret = x509_new_null();
+  if (ret == nullptr) {
+    return nullptr;
   }
-
-  ret->cert_info = X509_CINF_new();
-  if (ret->cert_info == NULL) {
-    X509_free(ret);
-    return NULL;
+  // TODO(crbug.com/42290417): When the |X509_NAME| parser is CBS-based and
+  // writes into a pre-existing |X509_NAME|, we will no longer need the
+  // |X509_new| and |x509_new_null| split.
+  ret->issuer = X509_NAME_new();
+  ret->subject = X509_NAME_new();
+  if (ret->issuer == nullptr || ret->subject == nullptr) {
+    return nullptr;
   }
-
-  return ret;
+  return ret.release();
 }
 
 void X509_free(X509 *x509) {
@@ -86,9 +87,19 @@
 
   CRYPTO_free_ex_data(&g_ex_data_class, &x509->ex_data);
 
-  X509_CINF_free(x509->cert_info);
+  asn1_string_cleanup(&x509->serialNumber);
+  x509_algor_cleanup(&x509->tbs_sig_alg);
+  X509_NAME_free(x509->issuer);
+  asn1_string_cleanup(&x509->notBefore);
+  asn1_string_cleanup(&x509->notAfter);
+  X509_NAME_free(x509->subject);
+  x509_pubkey_cleanup(&x509->key);
+  ASN1_BIT_STRING_free(x509->issuerUID);
+  ASN1_BIT_STRING_free(x509->subjectUID);
+  sk_X509_EXTENSION_pop_free(x509->extensions, X509_EXTENSION_free);
   x509_algor_cleanup(&x509->sig_alg);
   asn1_string_cleanup(&x509->signature);
+  CRYPTO_BUFFER_free(x509->buf);
   ASN1_OCTET_STRING_free(x509->skid);
   AUTHORITY_KEYID_free(x509->akid);
   CRL_DIST_POINTS_free(x509->crldp);
@@ -100,18 +111,38 @@
   OPENSSL_free(x509);
 }
 
-static X509 *x509_parse(CBS *cbs, CRYPTO_BUFFER *buf) {
+static int parse_name(CBS *cbs, X509_NAME **out) {
+  // TODO(crbug.com/42290417): Make the |X509_NAME| parser CBS-based and avoid
+  // this awkward conversion.
+  const uint8_t *p = CBS_data(cbs);
+  X509_NAME_free(*out);
+  *out = d2i_X509_NAME(nullptr, &p, CBS_len(cbs));
+  if (*out == nullptr) {
+    return 0;
+  }
+  BSSL_CHECK(CBS_skip(cbs, p - CBS_data(cbs)));
+  return 1;
+}
+
+
+X509 *X509_parse_from_buffer(CRYPTO_BUFFER *buf) {
   bssl::UniquePtr<X509> ret(x509_new_null());
   if (ret == nullptr) {
     return nullptr;
   }
 
-  CBS cert, tbs;
-  if (!CBS_get_asn1(cbs, &cert, CBS_ASN1_SEQUENCE) ||
+  // Save the buffer to cache the original encoding.
+  ret->buf = bssl::UpRef(buf).release();
+
+  // Parse the Certificate.
+  CBS cbs, cert, tbs;
+  CRYPTO_BUFFER_init_CBS(buf, &cbs);
+  if (!CBS_get_asn1(&cbs, &cert, CBS_ASN1_SEQUENCE) ||  //
+      CBS_len(&cbs) != 0 ||
       // Bound the length to comfortably fit in an int. Lengths in this
       // module often omit overflow checks.
       CBS_len(&cert) > INT_MAX / 2 ||
-      !CBS_get_asn1_element(&cert, &tbs, CBS_ASN1_SEQUENCE) ||
+      !CBS_get_asn1(&cert, &tbs, CBS_ASN1_SEQUENCE) ||
       !x509_parse_algorithm(&cert, &ret->sig_alg) ||
       // For just the signature field, we accept non-minimal BER lengths, though
       // not indefinite-length encoding. See b/18228011.
@@ -124,49 +155,168 @@
     return nullptr;
   }
 
-  // TODO(crbug.com/boringssl/443): When the rest of the library is decoupled
-  // from the tasn_*.c implementation, replace this with |CBS|-based
-  // functions.
-  const uint8_t *inp = CBS_data(&tbs);
-  if (ASN1_item_ex_d2i((ASN1_VALUE **)&ret->cert_info, &inp, CBS_len(&tbs),
-                       ASN1_ITEM_rptr(X509_CINF), /*tag=*/-1,
-                       /*aclass=*/0, /*opt=*/0, buf) <= 0 ||
-      inp != CBS_data(&tbs) + CBS_len(&tbs)) {
-    return nullptr;
-  }
-
-  // The version must be one of v1(0), v2(1), or v3(2).
-  long version = X509_VERSION_1;
-  if (ret->cert_info->version != nullptr) {
-    version = ASN1_INTEGER_get(ret->cert_info->version);
-    // TODO(https://crbug.com/boringssl/364): |X509_VERSION_1| should
-    // also be rejected here. This means an explicitly-encoded X.509v1
-    // version. v1 is DEFAULT, so DER requires it be omitted.
-    if (version < X509_VERSION_1 || version > X509_VERSION_3) {
+  // Parse the TBSCertificate.
+  if (CBS_peek_asn1_tag(&tbs, kVersionTag)) {
+    CBS wrapper;
+    uint64_t version;
+    if (!CBS_get_asn1(&tbs, &wrapper, kVersionTag) ||
+        !CBS_get_asn1_uint64(&wrapper, &version) ||  //
+        CBS_len(&wrapper) != 0) {
+      OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
+      return nullptr;
+    }
+    // The version must be one of v1(0), v2(1), or v3(2).
+    // TODO(https://crbug.com/42290225): Also reject |X509_VERSION_1|. v1 is
+    // DEFAULT, so DER requires it be omitted.
+    if (version != X509_VERSION_1 && version != X509_VERSION_2 &&
+        version != X509_VERSION_3) {
       OPENSSL_PUT_ERROR(X509, X509_R_INVALID_VERSION);
       return nullptr;
     }
+    ret->version = static_cast<uint8_t>(version);
+  } else {
+    ret->version = X509_VERSION_1;
   }
-
-  // Per RFC 5280, section 4.1.2.8, these fields require v2 or v3.
-  if (version == X509_VERSION_1 && (ret->cert_info->issuerUID != nullptr ||
-                                    ret->cert_info->subjectUID != nullptr)) {
-    OPENSSL_PUT_ERROR(X509, X509_R_INVALID_FIELD_FOR_VERSION);
+  CBS validity;
+  if (!asn1_parse_integer(&tbs, &ret->serialNumber, /*tag=*/0) ||
+      !x509_parse_algorithm(&tbs, &ret->tbs_sig_alg) ||
+      !parse_name(&tbs, &ret->issuer) ||
+      !CBS_get_asn1(&tbs, &validity, CBS_ASN1_SEQUENCE) ||
+      !asn1_parse_time(&validity, &ret->notBefore,
+                       /*allow_utc_timezone_offset=*/1) ||
+      !asn1_parse_time(&validity, &ret->notAfter,
+                       /*allow_utc_timezone_offset=*/1) ||
+      CBS_len(&validity) != 0 ||  //
+      !parse_name(&tbs, &ret->subject) ||
+      // TODO(crbug.com/42290364): Expose an API to use different algorithms.
+      !x509_parse_public_key(&tbs, &ret->key,
+                             bssl::GetDefaultEVPAlgorithms())) {
+    OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
     return nullptr;
   }
-
-  // Per RFC 5280, section 4.1.2.9, extensions require v3.
-  if (version != X509_VERSION_3 && ret->cert_info->extensions != nullptr) {
-    OPENSSL_PUT_ERROR(X509, X509_R_INVALID_FIELD_FOR_VERSION);
+  // Per RFC 5280, section 4.1.2.8, these fields require v2 or v3:
+  if (ret->version >= X509_VERSION_2 &&
+      CBS_peek_asn1_tag(&tbs, kIssuerUIDTag)) {
+    ret->issuerUID = ASN1_BIT_STRING_new();
+    if (ret->issuerUID == nullptr ||
+        !asn1_parse_bit_string(&tbs, ret->issuerUID, kIssuerUIDTag)) {
+      return nullptr;
+    }
+  }
+  if (ret->version >= X509_VERSION_2 &&
+      CBS_peek_asn1_tag(&tbs, kSubjectUIDTag)) {
+    ret->subjectUID = ASN1_BIT_STRING_new();
+    if (ret->subjectUID == nullptr ||
+        !asn1_parse_bit_string(&tbs, ret->subjectUID, kSubjectUIDTag)) {
+      return nullptr;
+    }
+  }
+  // Per RFC 5280, section 4.1.2.9, extensions require v3:
+  if (ret->version >= X509_VERSION_3 &&
+      CBS_peek_asn1_tag(&tbs, kExtensionsTag)) {
+    CBS wrapper;
+    if (!CBS_get_asn1(&tbs, &wrapper, kExtensionsTag)) {
+      OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
+      return nullptr;
+    }
+    // TODO(crbug.com/42290219): Empty extension lists should be rejected. An
+    // empty extensions list is encoded by omitting the field altogether. libpki
+    // already rejects this.
+    const uint8_t *p = CBS_data(&wrapper);
+    ret->extensions = d2i_X509_EXTENSIONS(nullptr, &p, CBS_len(&wrapper));
+    if (ret->extensions == nullptr ||
+        p != CBS_data(&wrapper) + CBS_len(&wrapper)) {
+      OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
+      return nullptr;
+    }
+  }
+  if (CBS_len(&tbs) != 0) {
+    OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
     return nullptr;
   }
 
   return ret.release();
 }
 
+static bssl::UniquePtr<X509> x509_parse(CBS *cbs) {
+  CBS cert;
+  if (!CBS_get_asn1_element(cbs, &cert, CBS_ASN1_SEQUENCE)) {
+    OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
+    return nullptr;
+  }
+
+  bssl::UniquePtr<CRYPTO_BUFFER> buf(CRYPTO_BUFFER_new_from_CBS(&cert, nullptr));
+  if (buf == nullptr) {
+    return nullptr;
+  }
+  return bssl::UniquePtr<X509>(X509_parse_from_buffer(buf.get()));
+}
+
+int x509_marshal_tbs_cert(CBB *cbb, X509 *x509) {
+  if (x509->buf != nullptr) {
+    // Replay the saved TBSCertificate from the |CRYPTO_BUFFER|, to verify
+    // exactly what we parsed. The |CRYPTO_BUFFER| contains the full
+    // Certificate, so we need to find the TBSCertificate portion.
+    CBS cbs, cert, tbs;
+    CRYPTO_BUFFER_init_CBS(x509->buf, &cbs);
+    if (!CBS_get_asn1(&cbs, &cert, CBS_ASN1_SEQUENCE) ||
+        !CBS_get_asn1_element(&cert, &tbs, CBS_ASN1_SEQUENCE)) {
+      // This should be impossible.
+      OPENSSL_PUT_ERROR(X509, ERR_R_INTERNAL_ERROR);
+      return 0;
+    }
+    return CBB_add_bytes(cbb, CBS_data(&tbs), CBS_len(&tbs));
+  }
+
+  // No saved TBSCertificate encoding. Encode it anew.
+  CBB tbs, version, validity, extensions;
+  if (!CBB_add_asn1(cbb, &tbs, CBS_ASN1_SEQUENCE)) {
+    return 0;
+  }
+  if (x509->version != X509_VERSION_1) {
+    if (!CBB_add_asn1(&tbs, &version, kVersionTag) ||
+        !CBB_add_asn1_uint64(&version, x509->version)) {
+      return 0;
+    }
+  }
+  if (!asn1_marshal_integer(&tbs, &x509->serialNumber, /*tag=*/0) ||
+      !x509_marshal_algorithm(&tbs, &x509->tbs_sig_alg) ||
+      !x509_marshal_name(&tbs, x509->issuer) ||
+      !CBB_add_asn1(&tbs, &validity, CBS_ASN1_SEQUENCE) ||
+      !asn1_marshal_time(&validity, &x509->notBefore) ||
+      !asn1_marshal_time(&validity, &x509->notAfter) ||
+      !x509_marshal_name(&tbs, x509->subject) ||
+      !x509_marshal_public_key(&tbs, &x509->key) ||
+      (x509->issuerUID != nullptr &&
+       !asn1_marshal_bit_string(&tbs, x509->issuerUID, kIssuerUIDTag)) ||
+      (x509->subjectUID != nullptr &&
+       !asn1_marshal_bit_string(&tbs, x509->subjectUID, kSubjectUIDTag))) {
+    return 0;
+  }
+  if (x509->extensions != nullptr) {
+    int len = i2d_X509_EXTENSIONS(x509->extensions, nullptr);
+    uint8_t *out;
+    if (len <= 0 ||  //
+        !CBB_add_asn1(&tbs, &extensions, kExtensionsTag) ||
+        !CBB_add_space(&extensions, &out, len) ||
+        i2d_X509_EXTENSIONS(x509->extensions, &out) != len) {
+      return 0;
+    }
+  }
+  return CBB_flush(cbb);
+}
+
+static int x509_marshal(CBB *cbb, X509 *x509) {
+  CBB cert;
+  return CBB_add_asn1(cbb, &cert, CBS_ASN1_SEQUENCE) &&
+         x509_marshal_tbs_cert(&cert, x509) &&
+         x509_marshal_algorithm(&cert, &x509->sig_alg) &&
+         asn1_marshal_bit_string(&cert, &x509->signature, /*tag=*/0) &&
+         CBB_flush(cbb);
+}
+
 X509 *d2i_X509(X509 **out, const uint8_t **inp, long len) {
-  return bssl::D2IFromCBS(out, inp, len,
-                          [](CBS *cbs) { return x509_parse(cbs, nullptr); });
+  return bssl::D2IFromCBS(out, inp, len, x509_parse);
 }
 
 int i2d_X509(X509 *x509, uint8_t **outp) {
@@ -176,27 +326,8 @@
   }
 
   return bssl::I2DFromCBB(
-      /*initial_capacity=*/64, outp, [&](CBB *cbb) -> bool {
-        CBB cert;
-        if (!CBB_add_asn1(cbb, &cert, CBS_ASN1_SEQUENCE)) {
-          return false;
-        }
-
-        // TODO(crbug.com/boringssl/443): When the rest of the library is
-        // decoupled from the tasn_*.c implementation, replace this with
-        // |CBB|-based functions.
-        uint8_t *out;
-        int len = i2d_X509_CINF(x509->cert_info, NULL);
-        if (len < 0 ||  //
-            !CBB_add_space(&cert, &out, static_cast<size_t>(len)) ||
-            i2d_X509_CINF(x509->cert_info, &out) != len ||
-            !x509_marshal_algorithm(&cert, &x509->sig_alg) ||
-            !asn1_marshal_bit_string(&cert, &x509->signature, /*tag=*/0) ||
-            !CBB_flush(cbb)) {
-          return false;
-        }
-        return true;
-      });
+      /*initial_capacity=*/256, outp,
+      [&](CBB *cbb) -> bool { return x509_marshal(cbb, x509); });
 }
 
 static int x509_new_cb(ASN1_VALUE **pval, const ASN1_ITEM *it) {
@@ -215,13 +346,13 @@
     return 1;
   }
 
-  X509 *ret = x509_parse(cbs, nullptr);
+  bssl::UniquePtr<X509> ret = x509_parse(cbs);
   if (ret == nullptr) {
     return 0;
   }
 
   X509_free((X509 *)*pval);
-  *pval = (ASN1_VALUE *)ret;
+  *pval = (ASN1_VALUE *)ret.release();
   return 1;
 }
 
@@ -247,18 +378,6 @@
   return ret;
 }
 
-X509 *X509_parse_from_buffer(CRYPTO_BUFFER *buf) {
-  CBS cbs;
-  CBS_init(&cbs, CRYPTO_BUFFER_data(buf), CRYPTO_BUFFER_len(buf));
-  X509 *ret = x509_parse(&cbs, buf);
-  if (ret == NULL || CBS_len(&cbs) != 0) {
-    X509_free(ret);
-    return NULL;
-  }
-
-  return ret;
-}
-
 int X509_up_ref(X509 *x) {
   CRYPTO_refcount_inc(&x->references);
   return 1;
@@ -383,17 +502,20 @@
 }
 
 int i2d_re_X509_tbs(X509 *x509, unsigned char **outp) {
-  asn1_encoding_clear(&x509->cert_info->enc);
-  return i2d_X509_CINF(x509->cert_info, outp);
+  CRYPTO_BUFFER_free(x509->buf);
+  x509->buf = nullptr;
+  return i2d_X509_tbs(x509, outp);
 }
 
 int i2d_X509_tbs(X509 *x509, unsigned char **outp) {
-  return i2d_X509_CINF(x509->cert_info, outp);
+  return bssl::I2DFromCBB(/*initial_capacity=*/128, outp, [&](CBB *cbb) -> bool {
+    return x509_marshal_tbs_cert(cbb, x509);
+  });
 }
 
 int X509_set1_signature_algo(X509 *x509, const X509_ALGOR *algo) {
   return X509_ALGOR_copy(&x509->sig_alg, algo) &&
-         X509_ALGOR_copy(x509->cert_info->tbs_sig_alg, algo);
+         X509_ALGOR_copy(&x509->tbs_sig_alg, algo);
 }
 
 int X509_set1_signature_value(X509 *x509, const uint8_t *sig, size_t sig_len) {
diff --git a/gen/sources.bzl b/gen/sources.bzl
index 72d897b..0bfbe31 100644
--- a/gen/sources.bzl
+++ b/gen/sources.bzl
@@ -503,7 +503,6 @@
     "crypto/x509/x_req.cc",
     "crypto/x509/x_sig.cc",
     "crypto/x509/x_spki.cc",
-    "crypto/x509/x_val.cc",
     "crypto/x509/x_x509.cc",
     "crypto/x509/x_x509a.cc",
     "crypto/xwing/xwing.cc",
diff --git a/gen/sources.cmake b/gen/sources.cmake
index be6608b..6f24612 100644
--- a/gen/sources.cmake
+++ b/gen/sources.cmake
@@ -517,7 +517,6 @@
   crypto/x509/x_req.cc
   crypto/x509/x_sig.cc
   crypto/x509/x_spki.cc
-  crypto/x509/x_val.cc
   crypto/x509/x_x509.cc
   crypto/x509/x_x509a.cc
   crypto/xwing/xwing.cc
diff --git a/gen/sources.gni b/gen/sources.gni
index 83740e0..14dce85 100644
--- a/gen/sources.gni
+++ b/gen/sources.gni
@@ -503,7 +503,6 @@
   "crypto/x509/x_req.cc",
   "crypto/x509/x_sig.cc",
   "crypto/x509/x_spki.cc",
-  "crypto/x509/x_val.cc",
   "crypto/x509/x_x509.cc",
   "crypto/x509/x_x509a.cc",
   "crypto/xwing/xwing.cc",
diff --git a/gen/sources.json b/gen/sources.json
index 6334c44..a3a0569 100644
--- a/gen/sources.json
+++ b/gen/sources.json
@@ -487,7 +487,6 @@
       "crypto/x509/x_req.cc",
       "crypto/x509/x_sig.cc",
       "crypto/x509/x_spki.cc",
-      "crypto/x509/x_val.cc",
       "crypto/x509/x_x509.cc",
       "crypto/x509/x_x509a.cc",
       "crypto/xwing/xwing.cc",
diff --git a/gen/sources.mk b/gen/sources.mk
index 7de81ef..5c2c045 100644
--- a/gen/sources.mk
+++ b/gen/sources.mk
@@ -497,7 +497,6 @@
   crypto/x509/x_req.cc \
   crypto/x509/x_sig.cc \
   crypto/x509/x_spki.cc \
-  crypto/x509/x_val.cc \
   crypto/x509/x_x509.cc \
   crypto/x509/x_x509a.cc \
   crypto/xwing/xwing.cc \