diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5933f02..77ba873 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -59,14 +59,6 @@
   endif()
 endif()
 
-if(USE_CUSTOM_LIBCXX)
-  set(BORINGSSL_ALLOW_CXX_RUNTIME 1)
-endif()
-
-if(BORINGSSL_ALLOW_CXX_RUNTIME)
-  add_definitions(-DBORINGSSL_ALLOW_CXX_RUNTIME)
-endif()
-
 string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
 if(NOT FIPS)
   if(CMAKE_BUILD_TYPE_LOWER STREQUAL "relwithassert" OR
@@ -177,10 +169,6 @@
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_CXX_FLAGS} -Wmissing-prototypes -Wold-style-definition -Wstrict-prototypes")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${C_CXX_FLAGS} -Wmissing-declarations")
 
-  if(NOT MSVC AND NOT BORINGSSL_ALLOW_CXX_RUNTIME)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
-  endif()
-
   # In GCC, -Wmissing-declarations is the C++ spelling of -Wmissing-prototypes
   # and using the wrong one is an error. In Clang, -Wmissing-prototypes is the
   # spelling for both and -Wmissing-declarations is some other warning.
diff --git a/crypto/x509/internal.h b/crypto/x509/internal.h
index 422e51c..1cdefaf 100644
--- a/crypto/x509/internal.h
+++ b/crypto/x509/internal.h
@@ -588,6 +588,13 @@
 // |X509_NAME| issue is resolved.
 int X509_check_akid(X509 *issuer, const AUTHORITY_KEYID *akid);
 
+int X509_is_valid_trust_id(int trust);
+
+int X509_PURPOSE_get_trust(const X509_PURPOSE *xp);
+
+// TODO(https://crbug.com/boringssl/695): Remove this.
+int DIST_POINT_set_dpname(DIST_POINT_NAME *dpn, X509_NAME *iname);
+
 
 #if defined(__cplusplus)
 }  // extern C
diff --git a/crypto/x509/v3_purp.c b/crypto/x509/v3_purp.c
index 5419f06..78681c5 100644
--- a/crypto/x509/v3_purp.c
+++ b/crypto/x509/v3_purp.c
@@ -54,8 +54,6 @@
  * (eay@cryptsoft.com).  This product includes software written by Tim
  * Hudson (tjh@cryptsoft.com). */
 
-#include <assert.h>
-#include <limits.h>
 #include <string.h>
 
 #include <openssl/digest.h>
@@ -68,6 +66,14 @@
 #include "../internal.h"
 #include "internal.h"
 
+
+struct x509_purpose_st {
+  int purpose;
+  int trust;  // Default trust ID
+  int (*check_purpose)(const struct x509_purpose_st *, const X509 *, int);
+  const char *sname;
+} /* X509_PURPOSE */;
+
 #define V1_ROOT (EXFLAG_V1 | EXFLAG_SS)
 #define ku_reject(x, usage) \
   (((x)->ex_flags & EXFLAG_KUSAGE) && !((x)->ex_kusage & (usage)))
@@ -91,30 +97,30 @@
                                         int ca);
 static int no_check(const X509_PURPOSE *xp, const X509 *x, int ca);
 
+// X509_TRUST_NONE is not a valid |X509_TRUST_*| constant. It is used by
+// |X509_PURPOSE_ANY| to indicate that it has no corresponding trust type and
+// cannot be used with |X509_STORE_CTX_set_purpose|.
+#define X509_TRUST_NONE (-1)
+
 static const X509_PURPOSE xstandard[] = {
-    {X509_PURPOSE_SSL_CLIENT, X509_TRUST_SSL_CLIENT, 0,
-     check_purpose_ssl_client, (char *)"SSL client", (char *)"sslclient", NULL},
-    {X509_PURPOSE_SSL_SERVER, X509_TRUST_SSL_SERVER, 0,
-     check_purpose_ssl_server, (char *)"SSL server", (char *)"sslserver", NULL},
-    {X509_PURPOSE_NS_SSL_SERVER, X509_TRUST_SSL_SERVER, 0,
-     check_purpose_ns_ssl_server, (char *)"Netscape SSL server",
-     (char *)"nssslserver", NULL},
-    {X509_PURPOSE_SMIME_SIGN, X509_TRUST_EMAIL, 0, check_purpose_smime_sign,
-     (char *)"S/MIME signing", (char *)"smimesign", NULL},
-    {X509_PURPOSE_SMIME_ENCRYPT, X509_TRUST_EMAIL, 0,
-     check_purpose_smime_encrypt, (char *)"S/MIME encryption",
-     (char *)"smimeencrypt", NULL},
-    {X509_PURPOSE_CRL_SIGN, X509_TRUST_COMPAT, 0, check_purpose_crl_sign,
-     (char *)"CRL signing", (char *)"crlsign", NULL},
-    {X509_PURPOSE_ANY, X509_TRUST_DEFAULT, 0, no_check, (char *)"Any Purpose",
-     (char *)"any", NULL},
+    {X509_PURPOSE_SSL_CLIENT, X509_TRUST_SSL_CLIENT, check_purpose_ssl_client,
+     "sslclient"},
+    {X509_PURPOSE_SSL_SERVER, X509_TRUST_SSL_SERVER, check_purpose_ssl_server,
+     "sslserver"},
+    {X509_PURPOSE_NS_SSL_SERVER, X509_TRUST_SSL_SERVER,
+     check_purpose_ns_ssl_server, "nssslserver"},
+    {X509_PURPOSE_SMIME_SIGN, X509_TRUST_EMAIL, check_purpose_smime_sign,
+     "smimesign"},
+    {X509_PURPOSE_SMIME_ENCRYPT, X509_TRUST_EMAIL, check_purpose_smime_encrypt,
+     "smimeencrypt"},
+    {X509_PURPOSE_CRL_SIGN, X509_TRUST_COMPAT, check_purpose_crl_sign,
+     "crlsign"},
+    {X509_PURPOSE_ANY, X509_TRUST_NONE, no_check, "any"},
     // |X509_PURPOSE_OCSP_HELPER| performs no actual checks. OpenSSL's OCSP
     // implementation relied on the caller performing EKU and KU checks.
-    {X509_PURPOSE_OCSP_HELPER, X509_TRUST_COMPAT, 0, no_check,
-     (char *)"OCSP helper", (char *)"ocsphelper", NULL},
-    {X509_PURPOSE_TIMESTAMP_SIGN, X509_TRUST_TSA, 0,
-     check_purpose_timestamp_sign, (char *)"Time Stamp signing",
-     (char *)"timestampsign", NULL},
+    {X509_PURPOSE_OCSP_HELPER, X509_TRUST_COMPAT, no_check, "ocsphelper"},
+    {X509_PURPOSE_TIMESTAMP_SIGN, X509_TRUST_TSA, check_purpose_timestamp_sign,
+     "timestampsign"},
 };
 
 int X509_check_purpose(X509 *x, int id, int ca) {
@@ -127,8 +133,8 @@
   if (id == -1) {
     return 1;
   }
-  int idx = X509_PURPOSE_get_by_id(id);
-  if (idx == -1) {
+  const X509_PURPOSE *pt = X509_PURPOSE_get0(id);
+  if (pt == NULL) {
     return 0;
   }
   // Historically, |check_purpose| implementations other than |X509_PURPOSE_ANY|
@@ -138,45 +144,22 @@
   if (ca && id != X509_PURPOSE_ANY && !check_ca(x)) {
     return 0;
   }
-  const X509_PURPOSE *pt = X509_PURPOSE_get0(idx);
   return pt->check_purpose(pt, x, ca);
 }
 
-int X509_PURPOSE_set(int *p, int purpose) {
-  if (X509_PURPOSE_get_by_id(purpose) == -1) {
-    OPENSSL_PUT_ERROR(X509V3, X509V3_R_INVALID_PURPOSE);
-    return 0;
+const X509_PURPOSE *X509_PURPOSE_get0(int id) {
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(xstandard); i++) {
+    if (xstandard[i].purpose == id) {
+      return &xstandard[i];
+    }
   }
-  *p = purpose;
-  return 1;
-}
-
-int X509_PURPOSE_get_count(void) { return OPENSSL_ARRAY_SIZE(xstandard); }
-
-const X509_PURPOSE *X509_PURPOSE_get0(int idx) {
-  if (idx < 0 || (size_t)idx >= OPENSSL_ARRAY_SIZE(xstandard)) {
-    return NULL;
-  }
-  return xstandard + idx;
+  return NULL;
 }
 
 int X509_PURPOSE_get_by_sname(const char *sname) {
-  const X509_PURPOSE *xptmp;
-  for (int i = 0; i < X509_PURPOSE_get_count(); i++) {
-    xptmp = X509_PURPOSE_get0(i);
-    if (!strcmp(xptmp->sname, sname)) {
-      return i;
-    }
-  }
-  return -1;
-}
-
-int X509_PURPOSE_get_by_id(int purpose) {
-  for (size_t i = 0; i <OPENSSL_ARRAY_SIZE(xstandard); i++) {
-    if (xstandard[i].purpose == purpose) {
-      static_assert(OPENSSL_ARRAY_SIZE(xstandard) <= INT_MAX,
-                    "indices must fit in int");
-      return (int)i;
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(xstandard); i++) {
+    if (strcmp(xstandard[i].sname, sname) == 0) {
+      return xstandard[i].purpose;
     }
   }
   return -1;
@@ -184,10 +167,6 @@
 
 int X509_PURPOSE_get_id(const X509_PURPOSE *xp) { return xp->purpose; }
 
-char *X509_PURPOSE_get0_name(const X509_PURPOSE *xp) { return xp->name; }
-
-char *X509_PURPOSE_get0_sname(const X509_PURPOSE *xp) { return xp->sname; }
-
 int X509_PURPOSE_get_trust(const X509_PURPOSE *xp) { return xp->trust; }
 
 int X509_supported_extension(const X509_EXTENSION *ex) {
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index a3616dc..6b06b7c 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -4329,6 +4329,17 @@
                                     {}, {}, flags));
     }
   }
+
+  // X509_V_FLAG_USE_CHECK_TIME is an internal flag, but one caller relies on
+  // being able to clear it to restore the system time. Using the system time,
+  // all certificates in this test should read as expired.
+  EXPECT_EQ(X509_V_ERR_CERT_HAS_EXPIRED,
+            Verify(leaf.valid.get(), {root.valid.get()},
+                   {intermediate.valid.get()}, {}, 0, [](X509_STORE_CTX *ctx) {
+                     X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
+                     X509_VERIFY_PARAM_clear_flags(param,
+                                                   X509_V_FLAG_USE_CHECK_TIME);
+                   }));
 }
 
 TEST(X509Test, SignatureVerification) {
@@ -7719,6 +7730,36 @@
       X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT,
       Verify(leaf.normal.get(), {root.trusted_any.get()},
              {intermediate.normal.get()}, {}, /*flags=*/0, set_server_trust));
+
+  // Trust settings on a certificate are ignored if the leaf did not come from
+  // |X509_STORE|. This is important because trust settings may be serialized
+  // via |d2i_X509_AUX|. It is often not obvious which functions may trigger
+  // this, so callers may inadvertently run with attacker-supplied trust
+  // settings on untrusted certificates.
+  EXPECT_EQ(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+            Verify(leaf.trusted_server.get(), /*roots=*/{},
+                   /*intermediates=*/{intermediate.trusted_server.get()}, {},
+                   /*flags=*/0, set_server_trust));
+  EXPECT_EQ(
+      X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
+      Verify(leaf.trusted_server.get(), /*roots=*/{},
+             /*intermediates=*/
+             {intermediate.trusted_server.get(), root.trusted_server.get()}, {},
+             /*flags=*/0, set_server_trust));
+
+  // Likewise, distrusts only take effect from |X509_STORE|.
+  EXPECT_EQ(X509_V_OK, Verify(leaf.distrusted_server.get(), {root.normal.get()},
+                              {intermediate.normal.get()}, {},
+                              /*flags=*/0, set_server_trust));
+}
+
+// Test some APIs that rust-openssl uses to look up purposes by name.
+TEST(X509Test, PurposeByShortName) {
+  int idx = X509_PURPOSE_get_by_sname("sslserver");
+  ASSERT_NE(idx, -1);
+  const X509_PURPOSE *purpose = X509_PURPOSE_get0(idx);
+  ASSERT_TRUE(purpose);
+  EXPECT_EQ(X509_PURPOSE_get_id(purpose), X509_PURPOSE_SSL_SERVER);
 }
 
 TEST(X509Test, CriticalExtension) {
@@ -7745,4 +7786,6 @@
 
   EXPECT_EQ(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION,
             Verify(leaf.get(), {root.get()}, {}, {}));
+  EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()}, {}, {},
+                              X509_V_FLAG_IGNORE_CRITICAL));
 }
diff --git a/crypto/x509/x509_trs.c b/crypto/x509/x509_trs.c
index 907e492..87137b1 100644
--- a/crypto/x509/x509_trs.c
+++ b/crypto/x509/x509_trs.c
@@ -54,9 +54,6 @@
  * (eay@cryptsoft.com).  This product includes software written by Tim
  * Hudson (tjh@cryptsoft.com). */
 
-#include <assert.h>
-#include <limits.h>
-
 #include <openssl/err.h>
 #include <openssl/mem.h>
 #include <openssl/obj.h>
@@ -66,23 +63,35 @@
 #include "internal.h"
 
 
+typedef struct x509_trust_st X509_TRUST;
+
+struct x509_trust_st {
+  int trust;
+  int (*check_trust)(const X509_TRUST *, X509 *, int);
+  int nid;
+} /* X509_TRUST */;
+
 static int trust_1oidany(const X509_TRUST *trust, X509 *x, int flags);
 static int trust_compat(const X509_TRUST *trust, X509 *x, int flags);
 
 static int obj_trust(int id, X509 *x, int flags);
 
 static const X509_TRUST trstandard[] = {
-    {X509_TRUST_COMPAT, 0, trust_compat, (char *)"compatible", 0, NULL},
-    {X509_TRUST_SSL_CLIENT, 0, trust_1oidany, (char *)"SSL Client",
-     NID_client_auth, NULL},
-    {X509_TRUST_SSL_SERVER, 0, trust_1oidany, (char *)"SSL Server",
-     NID_server_auth, NULL},
-    {X509_TRUST_EMAIL, 0, trust_1oidany, (char *)"S/MIME email",
-     NID_email_protect, NULL},
-    {X509_TRUST_OBJECT_SIGN, 0, trust_1oidany, (char *)"Object Signer",
-     NID_code_sign, NULL},
-    {X509_TRUST_TSA, 0, trust_1oidany, (char *)"TSA server", NID_time_stamp,
-     NULL}};
+    {X509_TRUST_COMPAT, trust_compat, 0},
+    {X509_TRUST_SSL_CLIENT, trust_1oidany, NID_client_auth},
+    {X509_TRUST_SSL_SERVER, trust_1oidany, NID_server_auth},
+    {X509_TRUST_EMAIL, trust_1oidany, NID_email_protect},
+    {X509_TRUST_OBJECT_SIGN, trust_1oidany, NID_code_sign},
+    {X509_TRUST_TSA, trust_1oidany, NID_time_stamp}};
+
+static const X509_TRUST *X509_TRUST_get0(int id) {
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(trstandard); i++) {
+    if (trstandard[i].trust == id) {
+      return &trstandard[i];
+    }
+  }
+  return NULL;
+}
 
 int X509_check_trust(X509 *x, int id, int flags) {
   if (id == -1) {
@@ -96,52 +105,23 @@
     }
     return trust_compat(NULL, x, 0);
   }
-  int idx = X509_TRUST_get_by_id(id);
-  if (idx == -1) {
+  const X509_TRUST *pt = X509_TRUST_get0(id);
+  if (pt == NULL) {
+    // Unknown trust IDs are silently reintrepreted as NIDs. This is unreachable
+    // from the certificate verifier itself, but wpa_supplicant relies on it.
+    // Note this relies on commonly-used NIDs and trust IDs not colliding.
     return obj_trust(id, x, flags);
   }
-  const X509_TRUST *pt = X509_TRUST_get0(idx);
   return pt->check_trust(pt, x, flags);
 }
 
-int X509_TRUST_get_count(void) { return OPENSSL_ARRAY_SIZE(trstandard); }
-
-const X509_TRUST *X509_TRUST_get0(int idx) {
-  if (idx < 0 || (size_t)idx >= OPENSSL_ARRAY_SIZE(trstandard)) {
-    return NULL;
-  }
-  return trstandard + idx;
+int X509_is_valid_trust_id(int trust) {
+  return X509_TRUST_get0(trust) != NULL;
 }
 
-int X509_TRUST_get_by_id(int id) {
-  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(trstandard); i++) {
-    if (trstandard[i].trust == id) {
-      static_assert(OPENSSL_ARRAY_SIZE(trstandard) <= INT_MAX,
-                    "indices must fit in int");
-      return (int)i;
-    }
-  }
-  return -1;
-}
-
-int X509_TRUST_set(int *t, int trust) {
-  if (X509_TRUST_get_by_id(trust) == -1) {
-    OPENSSL_PUT_ERROR(X509, X509_R_INVALID_TRUST);
-    return 0;
-  }
-  *t = trust;
-  return 1;
-}
-
-int X509_TRUST_get_flags(const X509_TRUST *xp) { return xp->flags; }
-
-char *X509_TRUST_get0_name(const X509_TRUST *xp) { return xp->name; }
-
-int X509_TRUST_get_trust(const X509_TRUST *xp) { return xp->trust; }
-
 static int trust_1oidany(const X509_TRUST *trust, X509 *x, int flags) {
   if (x->aux && (x->aux->trust || x->aux->reject)) {
-    return obj_trust(trust->arg1, x, flags);
+    return obj_trust(trust->nid, x, flags);
   }
   // we don't have any trust settings: for compatibility we return trusted
   // if it is self signed
@@ -160,24 +140,21 @@
 }
 
 static int obj_trust(int id, X509 *x, int flags) {
-  ASN1_OBJECT *obj;
-  size_t i;
-  X509_CERT_AUX *ax;
-  ax = x->aux;
+  X509_CERT_AUX *ax = x->aux;
   if (!ax) {
     return X509_TRUST_UNTRUSTED;
   }
   if (ax->reject) {
-    for (i = 0; i < sk_ASN1_OBJECT_num(ax->reject); i++) {
-      obj = sk_ASN1_OBJECT_value(ax->reject, i);
+    for (size_t i = 0; i < sk_ASN1_OBJECT_num(ax->reject); i++) {
+      const ASN1_OBJECT *obj = sk_ASN1_OBJECT_value(ax->reject, i);
       if (OBJ_obj2nid(obj) == id) {
         return X509_TRUST_REJECTED;
       }
     }
   }
   if (ax->trust) {
-    for (i = 0; i < sk_ASN1_OBJECT_num(ax->trust); i++) {
-      obj = sk_ASN1_OBJECT_value(ax->trust, i);
+    for (size_t i = 0; i < sk_ASN1_OBJECT_num(ax->trust); i++) {
+      const ASN1_OBJECT *obj = sk_ASN1_OBJECT_value(ax->trust, i);
       if (OBJ_obj2nid(obj) == id) {
         return X509_TRUST_TRUSTED;
       }
diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c
index a6f6215..81e413e 100644
--- a/crypto/x509/x509_vfy.c
+++ b/crypto/x509/x509_vfy.c
@@ -1485,13 +1485,13 @@
     return 1;
   }
 
-  int idx = X509_PURPOSE_get_by_id(purpose);
-  if (idx == -1) {
+  const X509_PURPOSE *pobj = X509_PURPOSE_get0(purpose);
+  if (pobj == NULL) {
     OPENSSL_PUT_ERROR(X509, X509_R_UNKNOWN_PURPOSE_ID);
     return 0;
   }
 
-  int trust = X509_PURPOSE_get_trust(X509_PURPOSE_get0(idx));
+  int trust = X509_PURPOSE_get_trust(pobj);
   if (!X509_STORE_CTX_set_trust(ctx, trust)) {
     return 0;
   }
@@ -1508,7 +1508,7 @@
     return 1;
   }
 
-  if (X509_TRUST_get_by_id(trust) == -1) {
+  if (!X509_is_valid_trust_id(trust)) {
     OPENSSL_PUT_ERROR(X509, X509_R_UNKNOWN_TRUST_ID);
     return 0;
   }
diff --git a/crypto/x509/x509_vpm.c b/crypto/x509/x509_vpm.c
index 7314c44..0640fd0 100644
--- a/crypto/x509/x509_vpm.c
+++ b/crypto/x509/x509_vpm.c
@@ -269,11 +269,22 @@
 }
 
 int X509_VERIFY_PARAM_set_purpose(X509_VERIFY_PARAM *param, int purpose) {
-  return X509_PURPOSE_set(&param->purpose, purpose);
+  if (X509_PURPOSE_get0(purpose) == NULL) {
+    OPENSSL_PUT_ERROR(X509V3, X509V3_R_INVALID_PURPOSE);
+    return 0;
+  }
+  param->purpose = purpose;
+  return 1;
 }
 
 int X509_VERIFY_PARAM_set_trust(X509_VERIFY_PARAM *param, int trust) {
-  return X509_TRUST_set(&param->trust, trust);
+  if (!X509_is_valid_trust_id(trust)) {
+    OPENSSL_PUT_ERROR(X509, X509_R_UNKNOWN_TRUST_ID);
+    return 0;
+  }
+
+  param->trust = trust;
+  return 1;
 }
 
 void X509_VERIFY_PARAM_set_depth(X509_VERIFY_PARAM *param, int depth) {
diff --git a/include/openssl/base.h b/include/openssl/base.h
index 98de503..9b72e99 100644
--- a/include/openssl/base.h
+++ b/include/openssl/base.h
@@ -378,11 +378,11 @@
 typedef struct x509_lookup_st X509_LOOKUP;
 typedef struct x509_lookup_method_st X509_LOOKUP_METHOD;
 typedef struct x509_object_st X509_OBJECT;
+typedef struct x509_purpose_st X509_PURPOSE;
 typedef struct x509_revoked_st X509_REVOKED;
 typedef struct x509_st X509;
 typedef struct x509_store_ctx_st X509_STORE_CTX;
 typedef struct x509_store_st X509_STORE;
-typedef struct x509_trust_st X509_TRUST;
 
 typedef void *OPENSSL_BLOCK;
 
diff --git a/include/openssl/crypto.h b/include/openssl/crypto.h
index 171ac43..c5c21bb 100644
--- a/include/openssl/crypto.h
+++ b/include/openssl/crypto.h
@@ -162,6 +162,7 @@
 #define OPENSSL_INIT_NO_ADD_ALL_DIGESTS 0
 #define OPENSSL_INIT_LOAD_CONFIG 0
 #define OPENSSL_INIT_NO_LOAD_CONFIG 0
+#define OPENSSL_INIT_NO_ATEXIT 0
 
 // OPENSSL_init_crypto calls |CRYPTO_library_init| and returns one.
 OPENSSL_EXPORT int OPENSSL_init_crypto(uint64_t opts,
diff --git a/include/openssl/pem.h b/include/openssl/pem.h
index 351e3f6..b67417f 100644
--- a/include/openssl/pem.h
+++ b/include/openssl/pem.h
@@ -358,6 +358,11 @@
 // If |sk| is non-NULL, it appends the results to |sk| instead and returns |sk|
 // on success. In this case, the caller retains ownership of |sk| in both
 // success and failure.
+//
+// WARNING: If the input contains "TRUSTED CERTIFICATE" PEM blocks, this
+// function parses auxiliary properties as in |d2i_X509_AUX|. Passing untrusted
+// input to this function allows an attacker to influence those properties. See
+// |d2i_X509_AUX| for details.
 OPENSSL_EXPORT STACK_OF(X509_INFO) *PEM_X509_INFO_read_bio(
     BIO *bp, STACK_OF(X509_INFO) *sk, pem_password_cb *cb, void *u);
 
@@ -390,6 +395,8 @@
 
 DECLARE_PEM_rw(X509, X509)
 
+// TODO(crbug.com/boringssl/426): When documenting these, copy the warning
+// about auxiliary properties from |PEM_X509_INFO_read_bio|.
 DECLARE_PEM_rw(X509_AUX, X509)
 
 DECLARE_PEM_rw(X509_REQ, X509_REQ)
diff --git a/include/openssl/pki/certificate.h b/include/openssl/pki/certificate.h
index 3adaa03..782a70a 100644
--- a/include/openssl/pki/certificate.h
+++ b/include/openssl/pki/certificate.h
@@ -12,7 +12,7 @@
  * 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_
+#if !defined(OPENSSL_HEADER_BSSL_PKI_CERTIFICATE_H_) && defined(__cplusplus)
 #define OPENSSL_HEADER_BSSL_PKI_CERTIFICATE_H_
 
 #include <memory>
@@ -80,4 +80,4 @@
 
 }  // namespace bssl
 
-#endif  // OPENSSL_HEADER_BSSL_PKI_CERTIFICATE_H_
+#endif  // OPENSSL_HEADER_BSSL_PKI_CERTIFICATE_H_ && __cplusplus
diff --git a/include/openssl/pki/signature_verify_cache.h b/include/openssl/pki/signature_verify_cache.h
index 640132e..ae73765 100644
--- a/include/openssl/pki/signature_verify_cache.h
+++ b/include/openssl/pki/signature_verify_cache.h
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef BSSL_PKI_SIGNATURE_VERIFY_CACHE_H_
+#if !defined(BSSL_PKI_SIGNATURE_VERIFY_CACHE_H_) && defined(__cplusplus)
 #define BSSL_PKI_SIGNATURE_VERIFY_CACHE_H_
 
 #include <openssl/base.h>
@@ -38,4 +38,4 @@
 
 }  // namespace bssl
 
-#endif  // BSSL_PKI_SIGNATURE_VERIFY_CACHE_H_
+#endif  // BSSL_PKI_SIGNATURE_VERIFY_CACHE_H_ && __cplusplus
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 2f1264a..839cf2f 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -1229,6 +1229,11 @@
 // reads the contents of |file| as a PEM-encoded leaf certificate followed
 // optionally by the certificate chain to send to the peer. It returns one on
 // success and zero on failure.
+//
+// WARNING: If the input contains "TRUSTED CERTIFICATE" PEM blocks, this
+// function parses auxiliary properties as in |d2i_X509_AUX|. Passing untrusted
+// input to this function allows an attacker to influence those properties. See
+// |d2i_X509_AUX| for details.
 OPENSSL_EXPORT int SSL_CTX_use_certificate_chain_file(SSL_CTX *ctx,
                                                       const char *file);
 
diff --git a/include/openssl/x509.h b/include/openssl/x509.h
index c7e6919..008c0cb 100644
--- a/include/openssl/x509.h
+++ b/include/openssl/x509.h
@@ -102,10 +102,6 @@
 //
 // In the future, a replacement library will be available. Meanwhile, minimize
 // dependencies on this header where possible.
-//
-// TODO(https://crbug.com/boringssl/426): Documentation for this library is
-// still in progress. Some functions have not yet been documented, and some
-// functions have not yet been grouped into sections.
 
 
 // Certificates.
@@ -608,8 +604,11 @@
 // Certificate (RFC 5280), followed optionally by a separate, OpenSSL-specific
 // structure with auxiliary properties. It behaves as described in |d2i_SAMPLE|.
 //
-// Some auxiliary properties affect trust decisions, so this function should not
-// be used with untrusted input.
+// WARNING: Passing untrusted input to this function allows an attacker to
+// control auxiliary properties. This can allow unexpected influence over the
+// application if the certificate is used in a context that reads auxiliary
+// properties. This includes PKCS#12 serialization, trusted certificates in
+// |X509_STORE|, and callers of |X509_alias_get0| or |X509_keyid_get0|.
 //
 // Unlike similarly-named functions, this function does not parse a single
 // ASN.1 element. Trying to parse data directly embedded in a larger ASN.1
@@ -619,7 +618,9 @@
 
 // X509_alias_set1 sets |x509|'s alias to |len| bytes from |name|. If |name| is
 // NULL, the alias is cleared instead. Aliases are not part of the certificate
-// itself and will not be serialized by |i2d_X509|.
+// itself and will not be serialized by |i2d_X509|. If |x509| is serialized in
+// a PKCS#12 structure, the friendlyName attribute (RFC 2985) will contain this
+// alias.
 OPENSSL_EXPORT int X509_alias_set1(X509 *x509, const uint8_t *name,
                                    ossl_ssize_t len);
 
@@ -655,16 +656,20 @@
 
 // X509_add1_trust_object configures |x509| as a valid trust anchor for |obj|.
 // It returns one on success and zero on error. |obj| should be a certificate
-// usage OID associated with an |X509_TRUST| object.
+// usage OID associated with an |X509_TRUST_*| constant.
 //
 // See |X509_VERIFY_PARAM_set_trust| for details on how this value is evaluated.
+// Note this only takes effect if |x509| was configured as a trusted certificate
+// via |X509_STORE|.
 OPENSSL_EXPORT int X509_add1_trust_object(X509 *x509, const ASN1_OBJECT *obj);
 
 // X509_add1_reject_object configures |x509| as distrusted for |obj|. It returns
 // one on success and zero on error. |obj| should be a certificate usage OID
-// associated with an |X509_TRUST| object.
+// associated with an |X509_TRUST_*| constant.
 //
 // See |X509_VERIFY_PARAM_set_trust| for details on how this value is evaluated.
+// Note this only takes effect if |x509| was configured as a trusted certificate
+// via |X509_STORE|.
 OPENSSL_EXPORT int X509_add1_reject_object(X509 *x509, const ASN1_OBJECT *obj);
 
 // X509_trust_clear clears the list of OIDs for which |x509| is trusted. See
@@ -2647,6 +2652,70 @@
 OPENSSL_EXPORT int X509_VERIFY_PARAM_set1(X509_VERIFY_PARAM *to,
                                           const X509_VERIFY_PARAM *from);
 
+// X509_V_FLAG_* are flags for |X509_VERIFY_PARAM_set_flags| and
+// |X509_VERIFY_PARAM_clear_flags|.
+
+// X509_V_FLAG_CB_ISSUER_CHECK causes the deprecated verify callback (see
+// |X509_STORE_CTX_set_verify_cb|) to be called for errors while matching
+// subject and issuer certificates.
+#define X509_V_FLAG_CB_ISSUER_CHECK 0x1
+// X509_V_FLAG_USE_CHECK_TIME is an internal flag used to track whether
+// |X509_STORE_CTX_set_time| has been used. If cleared, the system time is
+// restored.
+#define X509_V_FLAG_USE_CHECK_TIME 0x2
+// X509_V_FLAG_CRL_CHECK enables CRL lookup and checking for the leaf.
+#define X509_V_FLAG_CRL_CHECK 0x4
+// X509_V_FLAG_CRL_CHECK_ALL enables CRL lookup and checking for the entire
+// certificate chain. |X509_V_FLAG_CRL_CHECK| must be set for this flag to take
+// effect.
+#define X509_V_FLAG_CRL_CHECK_ALL 0x8
+// X509_V_FLAG_IGNORE_CRITICAL ignores unhandled critical extensions. Do not use
+// this option. Critical extensions ensure the verifier does not bypass
+// unrecognized security restrictions in certificates.
+#define X509_V_FLAG_IGNORE_CRITICAL 0x10
+// X509_V_FLAG_X509_STRICT does nothing. Its functionality has been enabled by
+// default.
+#define X509_V_FLAG_X509_STRICT 0x00
+// X509_V_FLAG_ALLOW_PROXY_CERTS does nothing. Proxy certificate support has
+// been removed.
+#define X509_V_FLAG_ALLOW_PROXY_CERTS 0x40
+// X509_V_FLAG_POLICY_CHECK does nothing. Policy checking is always enabled.
+#define X509_V_FLAG_POLICY_CHECK 0x80
+// X509_V_FLAG_EXPLICIT_POLICY requires some policy OID to be asserted by the
+// final certificate chain. See initial-explicit-policy from RFC 5280,
+// section 6.1.1.
+#define X509_V_FLAG_EXPLICIT_POLICY 0x100
+// X509_V_FLAG_INHIBIT_ANY inhibits the anyPolicy OID. See
+// initial-any-policy-inhibit from RFC 5280, section 6.1.1.
+#define X509_V_FLAG_INHIBIT_ANY 0x200
+// X509_V_FLAG_INHIBIT_MAP inhibits policy mapping. See
+// initial-policy-mapping-inhibit from RFC 5280, section 6.1.1.
+#define X509_V_FLAG_INHIBIT_MAP 0x400
+// X509_V_FLAG_NOTIFY_POLICY does nothing. Its functionality has been removed.
+#define X509_V_FLAG_NOTIFY_POLICY 0x800
+// X509_V_FLAG_EXTENDED_CRL_SUPPORT causes all verifications to fail. Extended
+// CRL features have been removed.
+#define X509_V_FLAG_EXTENDED_CRL_SUPPORT 0x1000
+// X509_V_FLAG_USE_DELTAS causes all verifications to fail. Delta CRL support
+// has been removed.
+#define X509_V_FLAG_USE_DELTAS 0x2000
+// X509_V_FLAG_CHECK_SS_SIGNATURE checks the redundant signature on self-signed
+// trust anchors. This check provides no security benefit and only wastes CPU.
+#define X509_V_FLAG_CHECK_SS_SIGNATURE 0x4000
+// X509_V_FLAG_TRUSTED_FIRST, during path-building, checks for a match in the
+// trust store before considering an untrusted intermediate. This flag is
+// enabled by default.
+#define X509_V_FLAG_TRUSTED_FIRST 0x8000
+// X509_V_FLAG_PARTIAL_CHAIN treats all trusted certificates as trust anchors,
+// independent of the |X509_VERIFY_PARAM_set_trust| setting.
+#define X509_V_FLAG_PARTIAL_CHAIN 0x80000
+// X509_V_FLAG_NO_ALT_CHAINS disables building alternative chains if the initial
+// one was rejected.
+#define X509_V_FLAG_NO_ALT_CHAINS 0x100000
+// X509_V_FLAG_NO_CHECK_TIME disables all time checks in certificate
+// verification.
+#define X509_V_FLAG_NO_CHECK_TIME 0x200000
+
 // X509_VERIFY_PARAM_set_flags enables all values in |flags| in |param|'s
 // verification flags and returns one. |flags| should be a combination of
 // |X509_V_FLAG_*| constants.
@@ -2868,6 +2937,10 @@
 // it is trusted if self-signed instead. Note this slightly differs from the
 // above.
 //
+// If the |X509_V_FLAG_PARTIAL_CHAIN| is set, every certificate from
+// |X509_STORE| is a trust anchor, unless it was explicitly distrusted for the
+// OID.
+//
 // It is currently not possible to configure custom trust OIDs. Contact the
 // BoringSSL maintainers if your application needs to do so. OpenSSL had an
 // |X509_TRUST_add| API, but it was not thread-safe and relied on global mutable
@@ -4313,6 +4386,31 @@
 OPENSSL_EXPORT STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(
     X509_STORE *store);
 
+// X509_PURPOSE_get_by_sname returns the |X509_PURPOSE_*| constant corresponding
+// a short name |sname|, or -1 if |sname| was not recognized.
+//
+// Use |X509_PURPOSE_*| constants directly instead. The short names used by this
+// function look like "sslserver" or "smimeencrypt", so they do not make
+// especially good APIs.
+//
+// This function differs from OpenSSL, which returns an "index" to be passed to
+// |X509_PURPOSE_get0|, followed by |X509_PURPOSE_get_id|, to finally obtain an
+// |X509_PURPOSE_*| value suitable for use with |X509_VERIFY_PARAM_set_purpose|.
+OPENSSL_EXPORT int X509_PURPOSE_get_by_sname(const char *sname);
+
+// X509_PURPOSE_get0 returns the |X509_PURPOSE| object corresponding to |id|,
+// which should be one of the |X509_PURPOSE_*| constants, or NULL if none
+// exists.
+//
+// This function differs from OpenSSL, which takes an "index", returned from
+// |X509_PURPOSE_get_by_sname|. In BoringSSL, indices and |X509_PURPOSE_*| IDs
+// are the same.
+OPENSSL_EXPORT const X509_PURPOSE *X509_PURPOSE_get0(int id);
+
+// X509_PURPOSE_get_id returns |purpose|'s ID. This will be one of the
+// |X509_PURPOSE_*| constants.
+OPENSSL_EXPORT int X509_PURPOSE_get_id(const X509_PURPOSE *purpose);
+
 
 // Private structures.
 
@@ -4322,7 +4420,10 @@
 } /* X509_ALGOR */;
 
 
-// Functions below this point have not yet been organized into sections.
+// Underdocumented functions.
+//
+// TODO(https://crbug.com/boringssl/426): Functions below this point have not
+// yet been documented or organized into sections.
 
 // This stuff is certificate "auxiliary info"
 // it contains details which are useful in certificate
@@ -4331,23 +4432,6 @@
 
 DECLARE_STACK_OF(DIST_POINT)
 
-// This is used for a table of trust checking functions
-
-struct x509_trust_st {
-  int trust;
-  int flags;
-  int (*check_trust)(const X509_TRUST *, X509 *, int);
-  char *name;
-  int arg1;
-  void *arg2;
-} /* X509_TRUST */;
-
-DEFINE_STACK_OF(X509_TRUST)
-
-// standard trust ids
-
-#define X509_TRUST_DEFAULT (-1)  // Only valid in purpose settings
-
 OPENSSL_EXPORT const char *X509_get_default_cert_area(void);
 OPENSSL_EXPORT const char *X509_get_default_cert_dir(void);
 OPENSSL_EXPORT const char *X509_get_default_cert_file(void);
@@ -4356,8 +4440,6 @@
 OPENSSL_EXPORT const char *X509_get_default_private_dir(void);
 
 
-OPENSSL_EXPORT int X509_TRUST_set(int *t, int trust);
-
 OPENSSL_EXPORT int X509_cmp(const X509 *a, const X509 *b);
 
 // X509_NAME_hash returns a hash of |name|, or zero on error. This is the new
@@ -4388,13 +4470,6 @@
 
 OPENSSL_EXPORT int X509_CRL_match(const X509_CRL *a, const X509_CRL *b);
 
-OPENSSL_EXPORT int X509_TRUST_get_count(void);
-OPENSSL_EXPORT const X509_TRUST *X509_TRUST_get0(int idx);
-OPENSSL_EXPORT int X509_TRUST_get_by_id(int id);
-OPENSSL_EXPORT int X509_TRUST_get_flags(const X509_TRUST *xp);
-OPENSSL_EXPORT char *X509_TRUST_get0_name(const X509_TRUST *xp);
-OPENSSL_EXPORT int X509_TRUST_get_trust(const X509_TRUST *xp);
-
 
 /*
 SSL_CTX -> X509_STORE
@@ -4442,53 +4517,6 @@
 OPENSSL_EXPORT int X509_LOOKUP_add_dir(X509_LOOKUP *lookup, const char *path,
                                        int type);
 
-// Certificate verify flags
-
-// Send issuer+subject checks to verify_cb
-#define X509_V_FLAG_CB_ISSUER_CHECK 0x1
-// Use check time instead of current time
-#define X509_V_FLAG_USE_CHECK_TIME 0x2
-// Lookup CRLs
-#define X509_V_FLAG_CRL_CHECK 0x4
-// Lookup CRLs for whole chain
-#define X509_V_FLAG_CRL_CHECK_ALL 0x8
-// Ignore unhandled critical extensions
-#define X509_V_FLAG_IGNORE_CRITICAL 0x10
-// Does nothing as its functionality has been enabled by default.
-#define X509_V_FLAG_X509_STRICT 0x00
-// This flag does nothing as proxy certificate support has been removed.
-#define X509_V_FLAG_ALLOW_PROXY_CERTS 0x40
-// Does nothing as its functionality has been enabled by default.
-#define X509_V_FLAG_POLICY_CHECK 0x80
-// Policy variable require-explicit-policy
-#define X509_V_FLAG_EXPLICIT_POLICY 0x100
-// Policy variable inhibit-any-policy
-#define X509_V_FLAG_INHIBIT_ANY 0x200
-// Policy variable inhibit-policy-mapping
-#define X509_V_FLAG_INHIBIT_MAP 0x400
-// Does nothing
-#define X509_V_FLAG_NOTIFY_POLICY 0x800
-// Causes all verifications to fail. Extended CRL features have been removed.
-#define X509_V_FLAG_EXTENDED_CRL_SUPPORT 0x1000
-// Causes all verifications to fail. Delta CRL support has been removed.
-#define X509_V_FLAG_USE_DELTAS 0x2000
-// Check selfsigned CA signature
-#define X509_V_FLAG_CHECK_SS_SIGNATURE 0x4000
-// Use trusted store first
-#define X509_V_FLAG_TRUSTED_FIRST 0x8000
-
-// Allow partial chains if at least one certificate is in trusted store
-#define X509_V_FLAG_PARTIAL_CHAIN 0x80000
-
-// If the initial chain is not trusted, do not attempt to build an alternative
-// chain. Alternate chain checking was introduced in 1.0.2b. Setting this flag
-// will force the behaviour to match that of previous versions.
-#define X509_V_FLAG_NO_ALT_CHAINS 0x100000
-
-// X509_V_FLAG_NO_CHECK_TIME disables all time checks in certificate
-// verification.
-#define X509_V_FLAG_NO_CHECK_TIME 0x200000
-
 OPENSSL_EXPORT X509_LOOKUP *X509_STORE_add_lookup(X509_STORE *v,
                                                   const X509_LOOKUP_METHOD *m);
 
@@ -4561,7 +4589,6 @@
 
 DEFINE_STACK_OF(X509V3_EXT_METHOD)
 
-// ext_flags values
 #define X509V3_EXT_CTX_DEP 0x2
 #define X509V3_EXT_MULTILINE 0x4
 
@@ -4590,8 +4617,6 @@
   // If relativename then this contains the full distribution point name
   X509_NAME *dpname;
 } DIST_POINT_NAME;
-// All existing reasons
-#define CRLDP_ALL_REASONS 0x807f
 
 struct DIST_POINT_st {
   DIST_POINT_NAME *distpoint;
@@ -4686,18 +4711,6 @@
 #define NS_OBJSIGN_CA 0x01
 #define NS_ANY_CA (NS_SSL_CA | NS_SMIME_CA | NS_OBJSIGN_CA)
 
-typedef struct x509_purpose_st {
-  int purpose;
-  int trust;  // Default trust ID
-  int flags;
-  int (*check_purpose)(const struct x509_purpose_st *, const X509 *, int);
-  char *name;
-  char *sname;
-  void *usr_data;
-} X509_PURPOSE;
-
-DEFINE_STACK_OF(X509_PURPOSE)
-
 DECLARE_ASN1_FUNCTIONS_const(BASIC_CONSTRAINTS)
 
 // TODO(https://crbug.com/boringssl/407): This is not const because it contains
@@ -4725,9 +4738,6 @@
 // an |X509_NAME|.
 DECLARE_ASN1_FUNCTIONS(ISSUING_DIST_POINT)
 
-OPENSSL_EXPORT int DIST_POINT_set_dpname(DIST_POINT_NAME *dpn,
-                                         X509_NAME *iname);
-
 // TODO(https://crbug.com/boringssl/407): This is not const because it contains
 // an |X509_NAME|.
 DECLARE_ASN1_FUNCTIONS(ACCESS_DESCRIPTION)
@@ -4863,8 +4873,8 @@
 // append if it is not present.
 #define X509V3_ADD_REPLACE 2L
 
-// X509V3_ADD_REPLACE causes the function to replace the existing extension and
-// fail if it is not present.
+// X509V3_ADD_REPLACE_EXISTING causes the function to replace the existing
+// extension and fail if it is not present.
 #define X509V3_ADD_REPLACE_EXISTING 3L
 
 // X509V3_ADD_KEEP_EXISTING causes the function to succeed without replacing the
@@ -4897,17 +4907,6 @@
 OPENSSL_EXPORT int X509V3_add1_i2d(STACK_OF(X509_EXTENSION) **x, int nid,
                                    void *value, int crit, unsigned long flags);
 
-OPENSSL_EXPORT int X509_PURPOSE_set(int *p, int purpose);
-
-OPENSSL_EXPORT int X509_PURPOSE_get_count(void);
-OPENSSL_EXPORT const X509_PURPOSE *X509_PURPOSE_get0(int idx);
-OPENSSL_EXPORT int X509_PURPOSE_get_by_sname(const char *sname);
-OPENSSL_EXPORT int X509_PURPOSE_get_by_id(int id);
-OPENSSL_EXPORT char *X509_PURPOSE_get0_name(const X509_PURPOSE *xp);
-OPENSSL_EXPORT char *X509_PURPOSE_get0_sname(const X509_PURPOSE *xp);
-OPENSSL_EXPORT int X509_PURPOSE_get_trust(const X509_PURPOSE *xp);
-OPENSSL_EXPORT int X509_PURPOSE_get_id(const X509_PURPOSE *);
-
 
 #if defined(__cplusplus)
 }  // extern C
diff --git a/pki/certificate_policies.cc b/pki/certificate_policies.cc
index 5a6df34..198e348 100644
--- a/pki/certificate_policies.cc
+++ b/pki/certificate_policies.cc
@@ -12,7 +12,6 @@
 #include "input.h"
 #include "parse_values.h"
 #include "parser.h"
-#include "tag.h"
 
 namespace bssl {
 
@@ -64,7 +63,7 @@
     }
     //      policyQualifierId  PolicyQualifierId,
     der::Input qualifier_oid;
-    if (!policy_information_parser.ReadTag(der::kOid, &qualifier_oid)) {
+    if (!policy_information_parser.ReadTag(CBS_ASN1_OBJECT, &qualifier_oid)) {
       return false;
     }
     if (restrict_to_known_qualifiers &&
@@ -164,7 +163,7 @@
     }
     //      policyIdentifier   CertPolicyId,
     der::Input policy_oid;
-    if (!policy_information_parser.ReadTag(der::kOid, &policy_oid)) {
+    if (!policy_information_parser.ReadTag(CBS_ASN1_OBJECT, &policy_oid)) {
       return false;
     }
 
@@ -273,7 +272,7 @@
   }
 
   std::optional<der::Input> require_value;
-  if (!sequence_parser.ReadOptionalTag(der::ContextSpecificPrimitive(0),
+  if (!sequence_parser.ReadOptionalTag(CBS_ASN1_CONTEXT_SPECIFIC | 0,
                                        &require_value)) {
     return false;
   }
@@ -289,7 +288,7 @@
   }
 
   std::optional<der::Input> inhibit_value;
-  if (!sequence_parser.ReadOptionalTag(der::ContextSpecificPrimitive(1),
+  if (!sequence_parser.ReadOptionalTag(CBS_ASN1_CONTEXT_SPECIFIC | 1,
                                        &inhibit_value)) {
     return false;
   }
@@ -364,10 +363,12 @@
     }
 
     ParsedPolicyMapping mapping;
-    if (!mapping_parser.ReadTag(der::kOid, &mapping.issuer_domain_policy)) {
+    if (!mapping_parser.ReadTag(CBS_ASN1_OBJECT,
+                                &mapping.issuer_domain_policy)) {
       return false;
     }
-    if (!mapping_parser.ReadTag(der::kOid, &mapping.subject_domain_policy)) {
+    if (!mapping_parser.ReadTag(CBS_ASN1_OBJECT,
+                                &mapping.subject_domain_policy)) {
       return false;
     }
 
diff --git a/pki/crl.cc b/pki/crl.cc
index cc9c878..63e1cc6 100644
--- a/pki/crl.cc
+++ b/pki/crl.cc
@@ -6,6 +6,7 @@
 #include <iterator>
 
 #include <openssl/base.h>
+#include <openssl/bytestring.h>
 
 #include "cert_errors.h"
 #include "crl.h"
@@ -14,7 +15,6 @@
 #include "parser.h"
 #include "revocation_util.h"
 #include "signature_algorithm.h"
-#include "tag.h"
 #include "verify_name_match.h"
 #include "verify_signed_data.h"
 
@@ -31,7 +31,7 @@
   der::Parser parser(name_tlv);
   der::Input name_rdn;
   bssl::CertErrors unused_errors;
-  return parser.ReadTag(der::kSequence, &name_rdn) &&
+  return parser.ReadTag(CBS_ASN1_SEQUENCE, &name_rdn) &&
          NormalizeName(name_rdn, out_normalized_name, &unused_errors) &&
          !parser.HasMore();
 }
@@ -104,7 +104,7 @@
   //         version                 Version OPTIONAL,
   //                                      -- if present, MUST be v2
   std::optional<der::Input> version_der;
-  if (!tbs_parser.ReadOptionalTag(der::kInteger, &version_der)) {
+  if (!tbs_parser.ReadOptionalTag(CBS_ASN1_INTEGER, &version_der)) {
     return false;
   }
   if (version_der.has_value()) {
@@ -139,12 +139,12 @@
   }
 
   //         nextUpdate              Time OPTIONAL,
-  der::Tag maybe_next_update_tag;
+  CBS_ASN1_TAG maybe_next_update_tag;
   der::Input unused_next_update_input;
   if (tbs_parser.PeekTagAndValue(&maybe_next_update_tag,
                                  &unused_next_update_input) &&
-      (maybe_next_update_tag == der::kUtcTime ||
-       maybe_next_update_tag == der::kGeneralizedTime)) {
+      (maybe_next_update_tag == CBS_ASN1_UTCTIME ||
+       maybe_next_update_tag == CBS_ASN1_GENERALIZEDTIME)) {
     der::GeneralizedTime next_update_time;
     if (!ReadUTCOrGeneralizedTime(&tbs_parser, &next_update_time)) {
       return false;
@@ -156,10 +156,10 @@
 
   //         revokedCertificates     SEQUENCE OF SEQUENCE  { ... } OPTIONAL,
   der::Input unused_revoked_certificates;
-  der::Tag maybe_revoked_certifigates_tag;
+  CBS_ASN1_TAG maybe_revoked_certifigates_tag;
   if (tbs_parser.PeekTagAndValue(&maybe_revoked_certifigates_tag,
                                  &unused_revoked_certificates) &&
-      maybe_revoked_certifigates_tag == der::kSequence) {
+      maybe_revoked_certifigates_tag == CBS_ASN1_SEQUENCE) {
     der::Input revoked_certificates_tlv;
     if (!tbs_parser.ReadRawTLV(&revoked_certificates_tlv)) {
       return false;
@@ -171,8 +171,9 @@
 
   //         crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
   //                                       -- if present, version MUST be v2
-  if (!tbs_parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
-                                  &out->crl_extensions_tlv)) {
+  if (!tbs_parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0,
+          &out->crl_extensions_tlv)) {
     return false;
   }
   if (out->crl_extensions_tlv.has_value()) {
@@ -216,7 +217,7 @@
   //  distributionPoint          [0] DistributionPointName OPTIONAL,
   std::optional<der::Input> distribution_point;
   if (!idp_parser.ReadOptionalTag(
-          der::kTagContextSpecific | der::kTagConstructed | 0,
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0,
           &distribution_point)) {
     return false;
   }
@@ -228,7 +229,7 @@
     //        nameRelativeToCRLIssuer [1]     RelativeDistinguishedName }
     std::optional<der::Input> der_full_name;
     if (!dp_name_parser.ReadOptionalTag(
-            der::kTagContextSpecific | der::kTagConstructed | 0,
+            CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0,
             &der_full_name)) {
       return false;
     }
@@ -253,7 +254,7 @@
 
   //  onlyContainsUserCerts      [1] BOOLEAN DEFAULT FALSE,
   std::optional<der::Input> only_contains_user_certs;
-  if (!idp_parser.ReadOptionalTag(der::kTagContextSpecific | 1,
+  if (!idp_parser.ReadOptionalTag(CBS_ASN1_CONTEXT_SPECIFIC | 1,
                                   &only_contains_user_certs)) {
     return false;
   }
@@ -270,7 +271,7 @@
 
   //  onlyContainsCACerts        [2] BOOLEAN DEFAULT FALSE,
   std::optional<der::Input> only_contains_ca_certs;
-  if (!idp_parser.ReadOptionalTag(der::kTagContextSpecific | 2,
+  if (!idp_parser.ReadOptionalTag(CBS_ASN1_CONTEXT_SPECIFIC | 2,
                                   &only_contains_ca_certs)) {
     return false;
   }
@@ -343,7 +344,8 @@
 
     der::Input revoked_cert_serial_number;
     //              userCertificate         CertificateSerialNumber,
-    if (!crl_entry_parser.ReadTag(der::kInteger, &revoked_cert_serial_number)) {
+    if (!crl_entry_parser.ReadTag(CBS_ASN1_INTEGER,
+                                  &revoked_cert_serial_number)) {
       return CRLRevocationStatus::UNKNOWN;
     }
 
diff --git a/pki/extended_key_usage.cc b/pki/extended_key_usage.cc
index a5d007f..1aeeb6a 100644
--- a/pki/extended_key_usage.cc
+++ b/pki/extended_key_usage.cc
@@ -4,9 +4,10 @@
 
 #include "extended_key_usage.h"
 
+#include <openssl/bytestring.h>
+
 #include "input.h"
 #include "parser.h"
-#include "tag.h"
 
 namespace bssl {
 
@@ -27,7 +28,7 @@
   }
   while (sequence_parser.HasMore()) {
     der::Input eku_oid;
-    if (!sequence_parser.ReadTag(der::kOid, &eku_oid)) {
+    if (!sequence_parser.ReadTag(CBS_ASN1_OBJECT, &eku_oid)) {
       // The SEQUENCE OF must contain only KeyPurposeIds (OIDs).
       return false;
     }
diff --git a/pki/general_names.cc b/pki/general_names.cc
index 74874ba..c446287 100644
--- a/pki/general_names.cc
+++ b/pki/general_names.cc
@@ -5,6 +5,7 @@
 #include "general_names.h"
 
 #include <openssl/base.h>
+#include <openssl/bytestring.h>
 
 #include <climits>
 #include <cstring>
@@ -15,7 +16,6 @@
 #include "ip_util.h"
 #include "parser.h"
 #include "string_util.h"
-#include "tag.h"
 
 namespace bssl {
 
@@ -52,7 +52,7 @@
   // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
   der::Parser parser(general_names_tlv);
   der::Input sequence_value;
-  if (!parser.ReadTag(der::kSequence, &sequence_value)) {
+  if (!parser.ReadTag(CBS_ASN1_SEQUENCE, &sequence_value)) {
     errors->AddError(kFailedReadingGeneralNames);
     return nullptr;
   }
@@ -101,17 +101,17 @@
     GeneralNames *subtrees, CertErrors *errors) {
   BSSL_CHECK(errors);
   der::Parser parser(input);
-  der::Tag tag;
+  CBS_ASN1_TAG tag;
   der::Input value;
   if (!parser.ReadTagAndValue(&tag, &value)) {
     return false;
   }
   GeneralNameTypes name_type = GENERAL_NAME_NONE;
-  if (tag == der::ContextSpecificConstructed(0)) {
+  if (tag == (CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)) {
     // otherName                       [0]     OtherName,
     name_type = GENERAL_NAME_OTHER_NAME;
     subtrees->other_names.push_back(value);
-  } else if (tag == der::ContextSpecificPrimitive(1)) {
+  } else if (tag == (CBS_ASN1_CONTEXT_SPECIFIC | 1)) {
     // rfc822Name                      [1]     IA5String,
     name_type = GENERAL_NAME_RFC822_NAME;
     const std::string_view s = BytesAsStringView(value);
@@ -120,7 +120,7 @@
       return false;
     }
     subtrees->rfc822_names.push_back(s);
-  } else if (tag == der::ContextSpecificPrimitive(2)) {
+  } else if (tag == (CBS_ASN1_CONTEXT_SPECIFIC | 2)) {
     // dNSName                         [2]     IA5String,
     name_type = GENERAL_NAME_DNS_NAME;
     const std::string_view s = BytesAsStringView(value);
@@ -129,11 +129,11 @@
       return false;
     }
     subtrees->dns_names.push_back(s);
-  } else if (tag == der::ContextSpecificConstructed(3)) {
+  } else if (tag == (CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 3)) {
     // x400Address                     [3]     ORAddress,
     name_type = GENERAL_NAME_X400_ADDRESS;
     subtrees->x400_addresses.push_back(value);
-  } else if (tag == der::ContextSpecificConstructed(4)) {
+  } else if (tag == (CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 4)) {
     // directoryName                   [4]     Name,
     name_type = GENERAL_NAME_DIRECTORY_NAME;
     // Name is a CHOICE { rdnSequence  RDNSequence }, therefore the SEQUENCE
@@ -141,15 +141,16 @@
     // only the value portion.
     der::Parser name_parser(value);
     der::Input name_value;
-    if (!name_parser.ReadTag(der::kSequence, &name_value) || parser.HasMore()) {
+    if (!name_parser.ReadTag(CBS_ASN1_SEQUENCE, &name_value) ||
+        parser.HasMore()) {
       return false;
     }
     subtrees->directory_names.push_back(name_value);
-  } else if (tag == der::ContextSpecificConstructed(5)) {
+  } else if (tag == (CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 5)) {
     // ediPartyName                    [5]     EDIPartyName,
     name_type = GENERAL_NAME_EDI_PARTY_NAME;
     subtrees->edi_party_names.push_back(value);
-  } else if (tag == der::ContextSpecificPrimitive(6)) {
+  } else if (tag == (CBS_ASN1_CONTEXT_SPECIFIC | 6)) {
     // uniformResourceIdentifier       [6]     IA5String,
     name_type = GENERAL_NAME_UNIFORM_RESOURCE_IDENTIFIER;
     const std::string_view s = BytesAsStringView(value);
@@ -158,7 +159,7 @@
       return false;
     }
     subtrees->uniform_resource_identifiers.push_back(s);
-  } else if (tag == der::ContextSpecificPrimitive(7)) {
+  } else if (tag == (CBS_ASN1_CONTEXT_SPECIFIC | 7)) {
     // iPAddress                       [7]     OCTET STRING,
     name_type = GENERAL_NAME_IP_ADDRESS;
     if (ip_address_type == GeneralNames::IP_ADDRESS_ONLY) {
@@ -201,7 +202,7 @@
       }
       subtrees->ip_address_ranges.emplace_back(addr, mask);
     }
-  } else if (tag == der::ContextSpecificPrimitive(8)) {
+  } else if (tag == (CBS_ASN1_CONTEXT_SPECIFIC | 8)) {
     // registeredID                    [8]     OBJECT IDENTIFIER }
     name_type = GENERAL_NAME_REGISTERED_ID;
     subtrees->registered_ids.push_back(value);
diff --git a/pki/name_constraints.cc b/pki/name_constraints.cc
index 73d87aa..1ce9a8f 100644
--- a/pki/name_constraints.cc
+++ b/pki/name_constraints.cc
@@ -7,9 +7,11 @@
 #include <limits.h>
 
 #include <memory>
+#include <optional>
 
 #include <openssl/base.h>
-#include <optional>
+#include <openssl/bytestring.h>
+
 #include "cert_errors.h"
 #include "common_cert_errors.h"
 #include "general_names.h"
@@ -17,7 +19,6 @@
 #include "ip_util.h"
 #include "parser.h"
 #include "string_util.h"
-#include "tag.h"
 #include "verify_name_match.h"
 
 namespace bssl {
@@ -317,8 +318,9 @@
   }
 
   std::optional<der::Input> permitted_subtrees_value;
-  if (!sequence_parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
-                                       &permitted_subtrees_value)) {
+  if (!sequence_parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0,
+          &permitted_subtrees_value)) {
     return false;
   }
   if (permitted_subtrees_value &&
@@ -331,8 +333,9 @@
       (is_critical ? GENERAL_NAME_ALL_TYPES : kSupportedNameTypes);
 
   std::optional<der::Input> excluded_subtrees_value;
-  if (!sequence_parser.ReadOptionalTag(der::ContextSpecificConstructed(1),
-                                       &excluded_subtrees_value)) {
+  if (!sequence_parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1,
+          &excluded_subtrees_value)) {
     return false;
   }
   if (excluded_subtrees_value &&
diff --git a/pki/ocsp.cc b/pki/ocsp.cc
index 49076e9..03d6c21 100644
--- a/pki/ocsp.cc
+++ b/pki/ocsp.cc
@@ -51,16 +51,16 @@
   if (!parser.ReadRawTLV(&sigalg_tlv)) {
     return false;
   }
-  if (!ParseHashAlgorithm(sigalg_tlv, &(out->hash_algorithm))) {
+  if (!ParseHashAlgorithm(sigalg_tlv, &out->hash_algorithm)) {
     return false;
   }
-  if (!parser.ReadTag(der::kOctetString, &(out->issuer_name_hash))) {
+  if (!parser.ReadTag(CBS_ASN1_OCTETSTRING, &out->issuer_name_hash)) {
     return false;
   }
-  if (!parser.ReadTag(der::kOctetString, &(out->issuer_key_hash))) {
+  if (!parser.ReadTag(CBS_ASN1_OCTETSTRING, &out->issuer_key_hash)) {
     return false;
   }
-  if (!parser.ReadTag(der::kInteger, &(out->serial_number))) {
+  if (!parser.ReadTag(CBS_ASN1_INTEGER, &out->serial_number)) {
     return false;
   }
   CertErrors errors;
@@ -89,15 +89,16 @@
   }
 
   der::Input reason_input;
-  if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), &reason_input,
-                              &(out->has_reason))) {
+  if (!parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0, &reason_input,
+          &(out->has_reason))) {
     return false;
   }
   if (out->has_reason) {
     der::Parser reason_parser(reason_input);
     der::Input reason_value_input;
     uint8_t reason_value;
-    if (!reason_parser.ReadTag(der::kEnumerated, &reason_value_input)) {
+    if (!reason_parser.ReadTag(CBS_ASN1_ENUMERATED, &reason_value_input)) {
       return false;
     }
     if (!der::ParseUint8(reason_value_input, &reason_value)) {
@@ -132,21 +133,22 @@
 // UnknownInfo ::= NULL
 bool ParseCertStatus(der::Input raw_tlv, OCSPCertStatus *out) {
   der::Parser parser(raw_tlv);
-  der::Tag status_tag;
+  CBS_ASN1_TAG status_tag;
   der::Input status;
   if (!parser.ReadTagAndValue(&status_tag, &status)) {
     return false;
   }
 
   out->has_reason = false;
-  if (status_tag == der::ContextSpecificPrimitive(0)) {
+  if (status_tag == (CBS_ASN1_CONTEXT_SPECIFIC | 0)) {
     out->status = OCSPRevocationStatus::GOOD;
-  } else if (status_tag == der::ContextSpecificConstructed(1)) {
+  } else if (status_tag ==
+             (CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1)) {
     out->status = OCSPRevocationStatus::REVOKED;
     if (!ParseRevokedInfo(status, out)) {
       return false;
     }
-  } else if (status_tag == der::ContextSpecificPrimitive(2)) {
+  } else if (status_tag == (CBS_ASN1_CONTEXT_SPECIFIC | 2)) {
     out->status = OCSPRevocationStatus::UNKNOWN;
   } else {
     return false;
@@ -203,8 +205,9 @@
   }
 
   der::Input next_update_input;
-  if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
-                              &next_update_input, &(out->has_next_update))) {
+  if (!parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0,
+          &next_update_input, &(out->has_next_update))) {
     return false;
   }
   if (out->has_next_update) {
@@ -217,8 +220,9 @@
     }
   }
 
-  if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(1),
-                              &(out->extensions), &(out->has_extensions))) {
+  if (!parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1,
+          &(out->extensions), &(out->has_extensions))) {
     return false;
   }
 
@@ -236,19 +240,19 @@
 // }
 bool ParseResponderID(der::Input raw_tlv, OCSPResponseData::ResponderID *out) {
   der::Parser parser(raw_tlv);
-  der::Tag id_tag;
+  CBS_ASN1_TAG id_tag;
   der::Input id_input;
   if (!parser.ReadTagAndValue(&id_tag, &id_input)) {
     return false;
   }
 
-  if (id_tag == der::ContextSpecificConstructed(1)) {
+  if (id_tag == (CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1)) {
     out->type = OCSPResponseData::ResponderType::NAME;
     out->name = id_input;
-  } else if (id_tag == der::ContextSpecificConstructed(2)) {
+  } else if (id_tag == (CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 2)) {
     der::Parser key_parser(id_input);
     der::Input key_hash;
-    if (!key_parser.ReadTag(der::kOctetString, &key_hash)) {
+    if (!key_parser.ReadTag(CBS_ASN1_OCTETSTRING, &key_hash)) {
       return false;
     }
     if (key_parser.HasMore()) {
@@ -287,8 +291,9 @@
 
   der::Input version_input;
   bool version_present;
-  if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
-                              &version_input, &version_present)) {
+  if (!parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0, &version_input,
+          &version_present)) {
     return false;
   }
 
@@ -335,8 +340,9 @@
     out->responses.push_back(single_response);
   }
 
-  if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(1),
-                              &(out->extensions), &(out->has_extensions))) {
+  if (!parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1,
+          &(out->extensions), &(out->has_extensions))) {
     return false;
   }
 
@@ -385,8 +391,9 @@
   }
   out->signature = signature.value();
   der::Input certs_input;
-  if (!parser.ReadOptionalTag(der::ContextSpecificConstructed(0), &certs_input,
-                              &(out->has_certs))) {
+  if (!parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0, &certs_input,
+          &(out->has_certs))) {
     return false;
   }
 
@@ -435,7 +442,7 @@
 
   der::Input response_status_input;
   uint8_t response_status;
-  if (!parser.ReadTag(der::kEnumerated, &response_status_input)) {
+  if (!parser.ReadTag(CBS_ASN1_ENUMERATED, &response_status_input)) {
     return false;
   }
   if (!der::ParseUint8(response_status_input, &response_status)) {
@@ -453,8 +460,9 @@
   if (out->status == OCSPResponse::ResponseStatus::SUCCESSFUL) {
     der::Parser outer_bytes_parser;
     der::Parser bytes_parser;
-    if (!parser.ReadConstructed(der::ContextSpecificConstructed(0),
-                                &outer_bytes_parser)) {
+    if (!parser.ReadConstructed(
+            CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0,
+            &outer_bytes_parser)) {
       return false;
     }
     if (!outer_bytes_parser.ReadSequence(&bytes_parser)) {
@@ -465,7 +473,7 @@
     }
 
     der::Input type_oid;
-    if (!bytes_parser.ReadTag(der::kOid, &type_oid)) {
+    if (!bytes_parser.ReadTag(CBS_ASN1_OBJECT, &type_oid)) {
       return false;
     }
     if (type_oid != der::Input(kBasicOCSPResponseOid)) {
@@ -475,7 +483,7 @@
     // As per RFC 6960 Section 4.2.1, the value of |response| SHALL be the DER
     // encoding of BasicOCSPResponse.
     der::Input response;
-    if (!bytes_parser.ReadTag(der::kOctetString, &response)) {
+    if (!bytes_parser.ReadTag(CBS_ASN1_OCTETSTRING, &response)) {
       return false;
     }
     if (!ParseBasicOCSPResponse(response, out)) {
@@ -607,9 +615,9 @@
     case OCSPResponseData::ResponderType::NAME: {
       der::Input name_rdn;
       der::Input cert_rdn;
-      if (!der::Parser(id.name).ReadTag(der::kSequence, &name_rdn) ||
+      if (!der::Parser(id.name).ReadTag(CBS_ASN1_SEQUENCE, &name_rdn) ||
           !der::Parser(cert->tbs().subject_tlv)
-               .ReadTag(der::kSequence, &cert_rdn)) {
+               .ReadTag(CBS_ASN1_SEQUENCE, &cert_rdn)) {
         return false;
       }
       return VerifyNameMatch(name_rdn, cert_rdn);
diff --git a/pki/parse_certificate.cc b/pki/parse_certificate.cc
index de910d3..5e2ff88 100644
--- a/pki/parse_certificate.cc
+++ b/pki/parse_certificate.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include <openssl/base.h>
+#include <openssl/bytestring.h>
 
 #include "cert_error_params.h"
 #include "cert_errors.h"
@@ -152,7 +153,7 @@
   der::Parser parser(dp_name);
   std::optional<der::Input> der_full_name;
   if (!parser.ReadOptionalTag(
-          der::kTagContextSpecific | der::kTagConstructed | 0,
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0,
           &der_full_name)) {
     return false;
   }
@@ -168,7 +169,7 @@
   }
 
   if (!parser.ReadOptionalTag(
-          der::kTagContextSpecific | der::kTagConstructed | 1,
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1,
           &distribution_point
                ->distribution_point_name_relative_to_crl_issuer)) {
     return false;
@@ -201,7 +202,7 @@
   //  distributionPoint       [0]     DistributionPointName OPTIONAL,
   std::optional<der::Input> distribution_point_name;
   if (!distrib_point_parser.ReadOptionalTag(
-          der::kTagContextSpecific | der::kTagConstructed | 0,
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0,
           &distribution_point_name)) {
     return false;
   }
@@ -213,14 +214,14 @@
   }
 
   //  reasons                 [1]     ReasonFlags OPTIONAL,
-  if (!distrib_point_parser.ReadOptionalTag(der::kTagContextSpecific | 1,
+  if (!distrib_point_parser.ReadOptionalTag(CBS_ASN1_CONTEXT_SPECIFIC | 1,
                                             &distribution_point.reasons)) {
     return false;
   }
 
   //  cRLIssuer               [2]     GeneralNames OPTIONAL }
   if (!distrib_point_parser.ReadOptionalTag(
-          der::kTagContextSpecific | der::kTagConstructed | 2,
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 2,
           &distribution_point.crl_issuer)) {
     return false;
   }
@@ -290,17 +291,17 @@
 
 bool ReadUTCOrGeneralizedTime(der::Parser *parser, der::GeneralizedTime *out) {
   der::Input value;
-  der::Tag tag;
+  CBS_ASN1_TAG tag;
 
   if (!parser->ReadTagAndValue(&tag, &value)) {
     return false;
   }
 
-  if (tag == der::kUtcTime) {
+  if (tag == CBS_ASN1_UTCTIME) {
     return der::ParseUTCTime(value, out);
   }
 
-  if (tag == der::kGeneralizedTime) {
+  if (tag == CBS_ASN1_GENERALIZEDTIME) {
     return der::ParseGeneralizedTime(value, out);
   }
 
@@ -443,8 +444,8 @@
 
   //        version         [0]  EXPLICIT Version DEFAULT v1,
   std::optional<der::Input> version;
-  if (!tbs_parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
-                                  &version)) {
+  if (!tbs_parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0, &version)) {
     errors->AddError(kFailedReadingVersion);
     return false;
   }
@@ -464,7 +465,7 @@
   }
 
   //        serialNumber         CertificateSerialNumber,
-  if (!tbs_parser.ReadTag(der::kInteger, &out->serial_number)) {
+  if (!tbs_parser.ReadTag(CBS_ASN1_INTEGER, &out->serial_number)) {
     errors->AddError(kFailedReadingSerialNumber);
     return false;
   }
@@ -516,7 +517,7 @@
   //        issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
   //                             -- If present, version MUST be v2 or v3
   std::optional<der::Input> issuer_unique_id;
-  if (!tbs_parser.ReadOptionalTag(der::ContextSpecificPrimitive(1),
+  if (!tbs_parser.ReadOptionalTag(CBS_ASN1_CONTEXT_SPECIFIC | 1,
                                   &issuer_unique_id)) {
     errors->AddError(kFailedReadingIssuerUniqueId);
     return false;
@@ -537,7 +538,7 @@
   //        subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
   //                             -- If present, version MUST be v2 or v3
   std::optional<der::Input> subject_unique_id;
-  if (!tbs_parser.ReadOptionalTag(der::ContextSpecificPrimitive(2),
+  if (!tbs_parser.ReadOptionalTag(CBS_ASN1_CONTEXT_SPECIFIC | 2,
                                   &subject_unique_id)) {
     errors->AddError(kFailedReadingSubjectUniqueId);
     return false;
@@ -557,8 +558,9 @@
 
   //        extensions      [3]  EXPLICIT Extensions OPTIONAL
   //                             -- If present, version MUST be v3
-  if (!tbs_parser.ReadOptionalTag(der::ContextSpecificConstructed(3),
-                                  &out->extensions_tlv)) {
+  if (!tbs_parser.ReadOptionalTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 3,
+          &out->extensions_tlv)) {
     errors->AddError(kFailedReadingExtensions);
     return false;
   }
@@ -616,7 +618,7 @@
   }
 
   //            extnID      OBJECT IDENTIFIER,
-  if (!extension_parser.ReadTag(der::kOid, &out->oid)) {
+  if (!extension_parser.ReadTag(CBS_ASN1_OBJECT, &out->oid)) {
     return false;
   }
 
@@ -624,7 +626,8 @@
   out->critical = false;
   bool has_critical;
   der::Input critical;
-  if (!extension_parser.ReadOptionalTag(der::kBool, &critical, &has_critical)) {
+  if (!extension_parser.ReadOptionalTag(CBS_ASN1_BOOLEAN, &critical,
+                                        &has_critical)) {
     return false;
   }
   if (has_critical) {
@@ -637,7 +640,7 @@
   }
 
   //            extnValue   OCTET STRING
-  if (!extension_parser.ReadTag(der::kOctetString, &out->value)) {
+  if (!extension_parser.ReadTag(CBS_ASN1_OCTETSTRING, &out->value)) {
     return false;
   }
 
@@ -733,7 +736,7 @@
   out->is_ca = false;
   bool has_ca;
   der::Input ca;
-  if (!sequence_parser.ReadOptionalTag(der::kBool, &ca, &has_ca)) {
+  if (!sequence_parser.ReadOptionalTag(CBS_ASN1_BOOLEAN, &ca, &has_ca)) {
     return false;
   }
   if (has_ca) {
@@ -748,7 +751,7 @@
 
   //         pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
   der::Input encoded_path_len;
-  if (!sequence_parser.ReadOptionalTag(der::kInteger, &encoded_path_len,
+  if (!sequence_parser.ReadOptionalTag(CBS_ASN1_INTEGER, &encoded_path_len,
                                        &out->has_path_len)) {
     return false;
   }
@@ -830,7 +833,7 @@
 
     //            accessMethod          OBJECT IDENTIFIER,
     if (!access_description_sequence_parser.ReadTag(
-            der::kOid, &access_description.access_method_oid)) {
+            CBS_ASN1_OBJECT, &access_description.access_method_oid)) {
       return false;
     }
 
@@ -862,7 +865,7 @@
 
   for (const auto &access_description : access_descriptions) {
     der::Parser access_location_parser(access_description.access_location);
-    der::Tag access_location_tag;
+    CBS_ASN1_TAG access_location_tag;
     der::Input access_location_value;
     if (!access_location_parser.ReadTagAndValue(&access_location_tag,
                                                 &access_location_value)) {
@@ -870,7 +873,7 @@
     }
 
     // GeneralName ::= CHOICE {
-    if (access_location_tag == der::ContextSpecificPrimitive(6)) {
+    if (access_location_tag == (CBS_ASN1_CONTEXT_SPECIFIC | 6)) {
       // uniformResourceIdentifier       [6]     IA5String,
       std::string_view uri = BytesAsStringView(access_location_value);
       if (!bssl::string_util::IsAscii(uri)) {
@@ -956,21 +959,21 @@
   // error? RFC 5280 doesn't explicitly say it.
 
   //       keyIdentifier             [0] KeyIdentifier           OPTIONAL,
-  if (!aki_parser.ReadOptionalTag(der::ContextSpecificPrimitive(0),
+  if (!aki_parser.ReadOptionalTag(CBS_ASN1_CONTEXT_SPECIFIC | 0,
                                   &authority_key_identifier->key_identifier)) {
     return false;
   }
 
   //       authorityCertIssuer       [1] GeneralNames            OPTIONAL,
   if (!aki_parser.ReadOptionalTag(
-          der::ContextSpecificConstructed(1),
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1,
           &authority_key_identifier->authority_cert_issuer)) {
     return false;
   }
 
   //       authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL  }
   if (!aki_parser.ReadOptionalTag(
-          der::ContextSpecificPrimitive(2),
+          CBS_ASN1_CONTEXT_SPECIFIC | 2,
           &authority_key_identifier->authority_cert_serial_number)) {
     return false;
   }
@@ -997,7 +1000,7 @@
   //
   //    KeyIdentifier ::= OCTET STRING
   der::Parser extension_value_parser(extension_value);
-  if (!extension_value_parser.ReadTag(der::kOctetString,
+  if (!extension_value_parser.ReadTag(CBS_ASN1_OCTETSTRING,
                                       subject_key_identifier)) {
     return false;
   }
diff --git a/pki/parse_name.cc b/pki/parse_name.cc
index dca76b4..f1b3a91 100644
--- a/pki/parse_name.cc
+++ b/pki/parse_name.cc
@@ -8,6 +8,7 @@
 
 #include <openssl/bytestring.h>
 #include <openssl/mem.h>
+
 #include "parse_values.h"
 #include "string_util.h"
 
@@ -31,18 +32,18 @@
 
 bool X509NameAttribute::ValueAsString(std::string *out) const {
   switch (value_tag) {
-    case der::kTeletexString:
+    case CBS_ASN1_T61STRING:
       return der::ParseTeletexStringAsLatin1(value, out);
-    case der::kIA5String:
+    case CBS_ASN1_IA5STRING:
       return der::ParseIA5String(value, out);
-    case der::kPrintableString:
+    case CBS_ASN1_PRINTABLESTRING:
       return der::ParsePrintableString(value, out);
-    case der::kUtf8String:
+    case CBS_ASN1_UTF8STRING:
       *out = BytesAsStringView(value);
       return true;
-    case der::kUniversalString:
+    case CBS_ASN1_UNIVERSALSTRING:
       return der::ParseUniversalString(value, out);
-    case der::kBmpString:
+    case CBS_ASN1_BMPSTRING:
       return der::ParseBmpString(value, out);
     default:
       return false;
@@ -52,7 +53,7 @@
 bool X509NameAttribute::ValueAsStringWithUnsafeOptions(
     PrintableStringHandling printable_string_handling, std::string *out) const {
   if (printable_string_handling == PrintableStringHandling::kAsUTF8Hack &&
-      value_tag == der::kPrintableString) {
+      value_tag == CBS_ASN1_PRINTABLESTRING) {
     *out = BytesAsStringView(value);
     return true;
   }
@@ -61,15 +62,15 @@
 
 bool X509NameAttribute::ValueAsStringUnsafe(std::string *out) const {
   switch (value_tag) {
-    case der::kIA5String:
-    case der::kPrintableString:
-    case der::kTeletexString:
-    case der::kUtf8String:
+    case CBS_ASN1_IA5STRING:
+    case CBS_ASN1_PRINTABLESTRING:
+    case CBS_ASN1_T61STRING:
+    case CBS_ASN1_UTF8STRING:
       *out = BytesAsStringView(value);
       return true;
-    case der::kUniversalString:
+    case CBS_ASN1_UNIVERSALSTRING:
       return der::ParseUniversalString(value, out);
-    case der::kBmpString:
+    case CBS_ASN1_BMPSTRING:
       return der::ParseBmpString(value, out);
     default:
       assert(0);  // NOTREACHED
@@ -137,7 +138,7 @@
 
     // If we have non-printable characters in a TeletexString, we hex encode
     // since we don't handle Teletex control codes.
-    if (nonprintable && value_tag == der::kTeletexString) {
+    if (nonprintable && value_tag == CBS_ASN1_T61STRING) {
       value_string = "#" + bssl::string_util::HexEncode(value);
     }
   }
@@ -154,12 +155,12 @@
     }
     // Read the attribute type, which must be an OBJECT IDENTIFIER.
     der::Input type;
-    if (!attr_type_and_value.ReadTag(der::kOid, &type)) {
+    if (!attr_type_and_value.ReadTag(CBS_ASN1_OBJECT, &type)) {
       return false;
     }
 
     // Read the attribute value.
-    der::Tag tag;
+    CBS_ASN1_TAG tag;
     der::Input value;
     if (!attr_type_and_value.ReadTagAndValue(&tag, &value)) {
       return false;
@@ -182,7 +183,7 @@
 bool ParseName(der::Input name_tlv, RDNSequence *out) {
   der::Parser name_parser(name_tlv);
   der::Input name_value;
-  if (!name_parser.ReadTag(der::kSequence, &name_value)) {
+  if (!name_parser.ReadTag(CBS_ASN1_SEQUENCE, &name_value)) {
     return false;
   }
   return ParseNameValue(name_value, out);
@@ -192,7 +193,7 @@
   der::Parser rdn_sequence_parser(name_value);
   while (rdn_sequence_parser.HasMore()) {
     der::Parser rdn_parser;
-    if (!rdn_sequence_parser.ReadConstructed(der::kSet, &rdn_parser)) {
+    if (!rdn_sequence_parser.ReadConstructed(CBS_ASN1_SET, &rdn_parser)) {
       return false;
     }
     RelativeDistinguishedName type_and_values;
diff --git a/pki/parse_name.h b/pki/parse_name.h
index 837f326..be5c53e 100644
--- a/pki/parse_name.h
+++ b/pki/parse_name.h
@@ -8,10 +8,10 @@
 #include <vector>
 
 #include <openssl/base.h>
+#include <openssl/bytestring.h>
 
 #include "input.h"
 #include "parser.h"
-#include "tag.h"
 
 namespace bssl {
 
@@ -65,7 +65,7 @@
 //     value AttributeValue
 // }
 struct OPENSSL_EXPORT X509NameAttribute {
-  X509NameAttribute(der::Input in_type, der::Tag in_value_tag,
+  X509NameAttribute(der::Input in_type, CBS_ASN1_TAG in_value_tag,
                     der::Input in_value)
       : type(in_type), value_tag(in_value_tag), value(in_value) {}
 
@@ -106,7 +106,7 @@
   [[nodiscard]] bool AsRFC2253String(std::string *out) const;
 
   der::Input type;
-  der::Tag value_tag;
+  CBS_ASN1_TAG value_tag;
   der::Input value;
 };
 
diff --git a/pki/parse_name_unittest.cc b/pki/parse_name_unittest.cc
index 9858121..ceef9c2 100644
--- a/pki/parse_name_unittest.cc
+++ b/pki/parse_name_unittest.cc
@@ -36,7 +36,7 @@
   const uint8_t der[] = {
       0x46, 0x6f, 0x6f, 0x20, 0x62, 0x61, 0x72,
   };
-  X509NameAttribute value(der::Input(), der::kIA5String, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_IA5STRING, der::Input(der));
   std::string result_unsafe;
   ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
   ASSERT_EQ("Foo bar", result_unsafe);
@@ -49,7 +49,7 @@
   const uint8_t der[] = {
       0x46, 0x6f, 0xFF, 0x20, 0x62, 0x61, 0x72,
   };
-  X509NameAttribute value(der::Input(), der::kIA5String, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_IA5STRING, der::Input(der));
   std::string result_unsafe;
   ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
   ASSERT_EQ("Fo\377 bar", result_unsafe);
@@ -61,7 +61,8 @@
   const uint8_t der[] = {
       0x46, 0x6f, 0x6f, 0x20, 0x62, 0x61, 0x72,
   };
-  X509NameAttribute value(der::Input(), der::kPrintableString, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_PRINTABLESTRING,
+                          der::Input(der));
   std::string result_unsafe;
   ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
   ASSERT_EQ("Foo bar", result_unsafe);
@@ -74,7 +75,8 @@
   const uint8_t der[] = {
       0x46, 0x6f, 0x5f, 0x20, 0x62, 0x61, 0x72,
   };
-  X509NameAttribute value(der::Input(), der::kPrintableString, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_PRINTABLESTRING,
+                          der::Input(der));
   std::string result_unsafe;
   ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
   ASSERT_EQ("Fo_ bar", result_unsafe);
@@ -86,7 +88,8 @@
   const uint8_t der[] = {
       0x46, 0x6f, 0x5f, 0x20, 0x62, 0x61, 0x72,
   };
-  X509NameAttribute value(der::Input(), der::kPrintableString, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_PRINTABLESTRING,
+                          der::Input(der));
   std::string result;
   ASSERT_FALSE(value.ValueAsStringWithUnsafeOptions(
       X509NameAttribute::PrintableStringHandling::kDefault, &result));
@@ -99,7 +102,7 @@
   const uint8_t der[] = {
       0x46, 0x6f, 0x6f, 0x20, 0x62, 0x61, 0x72,
   };
-  X509NameAttribute value(der::Input(), der::kTeletexString, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_T61STRING, der::Input(der));
   std::string result_unsafe;
   ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
   ASSERT_EQ("Foo bar", result_unsafe);
@@ -112,7 +115,7 @@
   const uint8_t der[] = {
       0x46, 0x6f, 0xd6, 0x20, 0x62, 0x61, 0x72,
   };
-  X509NameAttribute value(der::Input(), der::kTeletexString, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_T61STRING, der::Input(der));
   std::string result_unsafe;
   ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
   ASSERT_EQ("Fo\xd6 bar", result_unsafe);
@@ -125,7 +128,7 @@
   const uint8_t der[] = {
       0x00, 0x66, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x61, 0x00, 0x72,
   };
-  X509NameAttribute value(der::Input(), der::kBmpString, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_BMPSTRING, der::Input(der));
   std::string result_unsafe;
   ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
   ASSERT_EQ("foobar", result_unsafe);
@@ -137,7 +140,7 @@
 // BmpString must encode characters in pairs of 2 bytes.
 TEST(ParseNameTest, ConvertInvalidBmpString) {
   const uint8_t der[] = {0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x72};
-  X509NameAttribute value(der::Input(), der::kBmpString, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_BMPSTRING, der::Input(der));
   std::string result;
   ASSERT_FALSE(value.ValueAsStringUnsafe(&result));
   ASSERT_FALSE(value.ValueAsString(&result));
@@ -147,7 +150,8 @@
   const uint8_t der[] = {0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x6f,
                          0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x62,
                          0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x72};
-  X509NameAttribute value(der::Input(), der::kUniversalString, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_UNIVERSALSTRING,
+                          der::Input(der));
   std::string result_unsafe;
   ASSERT_TRUE(value.ValueAsStringUnsafe(&result_unsafe));
   ASSERT_EQ("foobar", result_unsafe);
@@ -159,7 +163,8 @@
 // UniversalString must encode characters in pairs of 4 bytes.
 TEST(ParseNameTest, ConvertInvalidUniversalString) {
   const uint8_t der[] = {0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72};
-  X509NameAttribute value(der::Input(), der::kUniversalString, der::Input(der));
+  X509NameAttribute value(der::Input(), CBS_ASN1_UNIVERSALSTRING,
+                          der::Input(der));
   std::string result;
   ASSERT_FALSE(value.ValueAsStringUnsafe(&result));
   ASSERT_FALSE(value.ValueAsString(&result));
diff --git a/pki/parsed_certificate.cc b/pki/parsed_certificate.cc
index 3e9058d..9e23d30 100644
--- a/pki/parsed_certificate.cc
+++ b/pki/parsed_certificate.cc
@@ -4,7 +4,9 @@
 
 #include "parsed_certificate.h"
 
+#include <openssl/bytestring.h>
 #include <openssl/pool.h>
+
 #include "cert_errors.h"
 #include "certificate_policies.h"
 #include "extended_key_usage.h"
@@ -51,7 +53,7 @@
 
 [[nodiscard]] bool GetSequenceValue(der::Input tlv, der::Input *value) {
   der::Parser parser(tlv);
-  return parser.ReadTag(der::kSequence, value) && !parser.HasMore();
+  return parser.ReadTag(CBS_ASN1_SEQUENCE, value) && !parser.HasMore();
 }
 
 }  // namespace
diff --git a/pki/parser.cc b/pki/parser.cc
index f352178..51f2661 100644
--- a/pki/parser.cc
+++ b/pki/parser.cc
@@ -13,11 +13,11 @@
 
 Parser::Parser(Input input) { CBS_init(&cbs_, input.data(), input.size()); }
 
-bool Parser::PeekTagAndValue(Tag *tag, Input *out) {
+bool Parser::PeekTagAndValue(CBS_ASN1_TAG *tag, Input *out) {
   CBS peeker = cbs_;
   CBS tmp_out;
   size_t header_len;
-  unsigned tag_value;
+  CBS_ASN1_TAG tag_value;
   if (!CBS_get_any_asn1_element(&peeker, &tmp_out, &tag_value, &header_len) ||
       !CBS_skip(&tmp_out, header_len)) {
     return false;
@@ -48,7 +48,7 @@
   return true;
 }
 
-bool Parser::ReadTagAndValue(Tag *tag, Input *out) {
+bool Parser::ReadTagAndValue(CBS_ASN1_TAG *tag, Input *out) {
   if (!PeekTagAndValue(tag, out)) {
     return false;
   }
@@ -56,12 +56,12 @@
   return true;
 }
 
-bool Parser::ReadOptionalTag(Tag tag, std::optional<Input> *out) {
+bool Parser::ReadOptionalTag(CBS_ASN1_TAG tag, std::optional<Input> *out) {
   if (!HasMore()) {
     *out = std::nullopt;
     return true;
   }
-  Tag actual_tag;
+  CBS_ASN1_TAG actual_tag;
   Input value;
   if (!PeekTagAndValue(&actual_tag, &value)) {
     return false;
@@ -76,7 +76,7 @@
   return true;
 }
 
-bool Parser::ReadOptionalTag(Tag tag, Input *out, bool *present) {
+bool Parser::ReadOptionalTag(CBS_ASN1_TAG tag, Input *out, bool *present) {
   std::optional<Input> tmp_out;
   if (!ReadOptionalTag(tag, &tmp_out)) {
     return false;
@@ -86,13 +86,13 @@
   return true;
 }
 
-bool Parser::SkipOptionalTag(Tag tag, bool *present) {
+bool Parser::SkipOptionalTag(CBS_ASN1_TAG tag, bool *present) {
   Input out;
   return ReadOptionalTag(tag, &out, present);
 }
 
-bool Parser::ReadTag(Tag tag, Input *out) {
-  Tag actual_tag;
+bool Parser::ReadTag(CBS_ASN1_TAG tag, Input *out) {
+  CBS_ASN1_TAG actual_tag;
   Input value;
   if (!PeekTagAndValue(&actual_tag, &value) || actual_tag != tag) {
     return false;
@@ -102,15 +102,15 @@
   return true;
 }
 
-bool Parser::SkipTag(Tag tag) {
+bool Parser::SkipTag(CBS_ASN1_TAG tag) {
   Input out;
   return ReadTag(tag, &out);
 }
 
 // Type-specific variants of ReadTag
 
-bool Parser::ReadConstructed(Tag tag, Parser *out) {
-  if (!IsConstructed(tag)) {
+bool Parser::ReadConstructed(CBS_ASN1_TAG tag, Parser *out) {
+  if (!(tag & CBS_ASN1_CONSTRUCTED)) {
     return false;
   }
   Input data;
@@ -122,12 +122,12 @@
 }
 
 bool Parser::ReadSequence(Parser *out) {
-  return ReadConstructed(kSequence, out);
+  return ReadConstructed(CBS_ASN1_SEQUENCE, out);
 }
 
 bool Parser::ReadUint8(uint8_t *out) {
   Input encoded_int;
-  if (!ReadTag(kInteger, &encoded_int)) {
+  if (!ReadTag(CBS_ASN1_INTEGER, &encoded_int)) {
     return false;
   }
   return ParseUint8(encoded_int, out);
@@ -135,7 +135,7 @@
 
 bool Parser::ReadUint64(uint64_t *out) {
   Input encoded_int;
-  if (!ReadTag(kInteger, &encoded_int)) {
+  if (!ReadTag(CBS_ASN1_INTEGER, &encoded_int)) {
     return false;
   }
   return ParseUint64(encoded_int, out);
@@ -143,7 +143,7 @@
 
 std::optional<BitString> Parser::ReadBitString() {
   Input value;
-  if (!ReadTag(kBitString, &value)) {
+  if (!ReadTag(CBS_ASN1_BITSTRING, &value)) {
     return std::nullopt;
   }
   return ParseBitString(value);
@@ -151,7 +151,7 @@
 
 bool Parser::ReadGeneralizedTime(GeneralizedTime *out) {
   Input value;
-  if (!ReadTag(kGeneralizedTime, &value)) {
+  if (!ReadTag(CBS_ASN1_GENERALIZEDTIME, &value)) {
     return false;
   }
   return ParseGeneralizedTime(value, out);
diff --git a/pki/parser.h b/pki/parser.h
index c585c60..64599b5 100644
--- a/pki/parser.h
+++ b/pki/parser.h
@@ -13,7 +13,6 @@
 #include <openssl/bytestring.h>
 
 #include "input.h"
-#include "tag.h"
 
 namespace bssl::der {
 
@@ -106,7 +105,7 @@
   // encoding for the current value is invalid, this method returns false and
   // does not advance the input. Otherwise, it returns true, putting the
   // read tag in |tag| and the value in |out|.
-  [[nodiscard]] bool ReadTagAndValue(Tag *tag, Input *out);
+  [[nodiscard]] bool ReadTagAndValue(CBS_ASN1_TAG *tag, Input *out);
 
   // Reads the current TLV from the input and advances. Unlike ReadTagAndValue
   // where only the value is put in |out|, this puts the raw bytes from the
@@ -123,7 +122,7 @@
   // something else, then |out| is set to nullopt and the input is not
   // advanced. Like ReadTagAndValue, it returns false if the encoding is
   // invalid and does not advance the input.
-  [[nodiscard]] bool ReadOptionalTag(Tag tag, std::optional<Input> *out);
+  [[nodiscard]] bool ReadOptionalTag(CBS_ASN1_TAG tag, std::optional<Input> *out);
 
   // If the current tag in the input is |tag|, it puts the corresponding value
   // in |out|, sets |was_present| to true, and advances the input to the next
@@ -132,25 +131,25 @@
   // false if the encoding is invalid and does not advance the input.
   // DEPRECATED: use the std::optional version above in new code.
   // TODO(mattm): convert the existing callers and remove this override.
-  [[nodiscard]] bool ReadOptionalTag(Tag tag, Input *out, bool *was_present);
+  [[nodiscard]] bool ReadOptionalTag(CBS_ASN1_TAG tag, Input *out, bool *was_present);
 
   // Like ReadOptionalTag, but the value is discarded.
-  [[nodiscard]] bool SkipOptionalTag(Tag tag, bool *was_present);
+  [[nodiscard]] bool SkipOptionalTag(CBS_ASN1_TAG tag, bool *was_present);
 
   // If the current tag matches |tag|, it puts the current value in |out|,
   // advances the input, and returns true. Otherwise, it returns false.
-  [[nodiscard]] bool ReadTag(Tag tag, Input *out);
+  [[nodiscard]] bool ReadTag(CBS_ASN1_TAG tag, Input *out);
 
   // Advances the input and returns true if the current tag matches |tag|;
   // otherwise it returns false.
-  [[nodiscard]] bool SkipTag(Tag tag);
+  [[nodiscard]] bool SkipTag(CBS_ASN1_TAG tag);
 
   // Convenience methods to combine parsing the TLV with parsing the DER
   // encoding for a specific type.
 
   // Reads the current TLV from the input, checks that the tag matches |tag|
   // and is a constructed tag, and creates a new Parser from the value.
-  [[nodiscard]] bool ReadConstructed(Tag tag, Parser *out);
+  [[nodiscard]] bool ReadConstructed(CBS_ASN1_TAG tag, Parser *out);
 
   // A more specific form of ReadConstructed that expects the current tag
   // to be 0x30 (SEQUENCE).
@@ -196,7 +195,7 @@
   // Reads the current TLV from the input, putting the tag in |tag| and the raw
   // value in |out|, but does not advance the input. Returns true if the tag
   // and length are successfully read and the output exists.
-  [[nodiscard]] bool PeekTagAndValue(Tag *tag, Input *out);
+  [[nodiscard]] bool PeekTagAndValue(CBS_ASN1_TAG *tag, Input *out);
 
   // Advances the input to the next TLV. This method only needs to be called
   // after PeekTagAndValue; all other methods will advance the input if they
diff --git a/pki/parser_unittest.cc b/pki/parser_unittest.cc
index e813bfd..2deb1cd 100644
--- a/pki/parser_unittest.cc
+++ b/pki/parser_unittest.cc
@@ -13,10 +13,10 @@
 TEST(ParserTest, ConsumesAllBytesOfTLV) {
   const uint8_t der[] = {0x04 /* OCTET STRING */, 0x00};
   Parser parser((Input(der)));
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-  ASSERT_EQ(kOctetString, tag);
+  ASSERT_EQ(CBS_ASN1_OCTETSTRING, tag);
   ASSERT_FALSE(parser.HasMore());
 }
 
@@ -38,7 +38,7 @@
   // with an invalid encoding - its length is too long.
   const uint8_t der[] = {0x30, 0x02, 0x30, 0x7e};
   Parser parser((Input(der)));
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
 }
@@ -58,7 +58,7 @@
   ASSERT_FALSE(parser.HasMore());
 
   // Try to read the INTEGER from the SEQUENCE, which should fail.
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_FALSE(inner_sequence.ReadTagAndValue(&tag, &value));
 }
@@ -72,14 +72,14 @@
 
   Input value;
   bool present;
-  ASSERT_TRUE(parser.ReadOptionalTag(kInteger, &value, &present));
+  ASSERT_TRUE(parser.ReadOptionalTag(CBS_ASN1_INTEGER, &value, &present));
   ASSERT_TRUE(present);
   const uint8_t expected_int_value[] = {0x01};
   ASSERT_EQ(Input(expected_int_value), value);
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-  ASSERT_EQ(kOctetString, tag);
+  ASSERT_EQ(CBS_ASN1_OCTETSTRING, tag);
   const uint8_t expected_octet_string_value[] = {0x02};
   ASSERT_EQ(Input(expected_octet_string_value), value);
 
@@ -94,15 +94,15 @@
   Parser parser((Input(der)));
 
   std::optional<Input> optional_value;
-  ASSERT_TRUE(parser.ReadOptionalTag(kInteger, &optional_value));
+  ASSERT_TRUE(parser.ReadOptionalTag(CBS_ASN1_INTEGER, &optional_value));
   ASSERT_TRUE(optional_value.has_value());
   const uint8_t expected_int_value[] = {0x01};
   ASSERT_EQ(Input(expected_int_value), *optional_value);
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-  ASSERT_EQ(kOctetString, tag);
+  ASSERT_EQ(CBS_ASN1_OCTETSTRING, tag);
   const uint8_t expected_octet_string_value[] = {0x02};
   ASSERT_EQ(Input(expected_octet_string_value), value);
 
@@ -117,12 +117,12 @@
 
   Input value;
   bool present;
-  ASSERT_TRUE(parser.ReadOptionalTag(kInteger, &value, &present));
+  ASSERT_TRUE(parser.ReadOptionalTag(CBS_ASN1_INTEGER, &value, &present));
   ASSERT_FALSE(present);
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-  ASSERT_EQ(kOctetString, tag);
+  ASSERT_EQ(CBS_ASN1_OCTETSTRING, tag);
   const uint8_t expected_octet_string_value[] = {0x02};
   ASSERT_EQ(Input(expected_octet_string_value), value);
 
@@ -136,13 +136,13 @@
   Parser parser((Input(der)));
 
   std::optional<Input> optional_value;
-  ASSERT_TRUE(parser.ReadOptionalTag(kInteger, &optional_value));
+  ASSERT_TRUE(parser.ReadOptionalTag(CBS_ASN1_INTEGER, &optional_value));
   ASSERT_FALSE(optional_value.has_value());
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-  ASSERT_EQ(kOctetString, tag);
+  ASSERT_EQ(CBS_ASN1_OCTETSTRING, tag);
   const uint8_t expected_octet_string_value[] = {0x02};
   ASSERT_EQ(Input(expected_octet_string_value), value);
 
@@ -153,11 +153,11 @@
   const uint8_t der[] = {0x02 /* INTEGER */, 0x01, 0x01};
   Parser parser((Input(der)));
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
   bool present;
-  ASSERT_TRUE(parser.ReadOptionalTag(kInteger, &value, &present));
+  ASSERT_TRUE(parser.ReadOptionalTag(CBS_ASN1_INTEGER, &value, &present));
   ASSERT_FALSE(present);
   ASSERT_FALSE(parser.HasMore());
 }
@@ -167,9 +167,9 @@
   Parser parser((Input(der)));
 
   bool present;
-  ASSERT_TRUE(parser.SkipOptionalTag(kOctetString, &present));
+  ASSERT_TRUE(parser.SkipOptionalTag(CBS_ASN1_OCTETSTRING, &present));
   ASSERT_FALSE(present);
-  ASSERT_TRUE(parser.SkipOptionalTag(kInteger, &present));
+  ASSERT_TRUE(parser.SkipOptionalTag(CBS_ASN1_INTEGER, &present));
   ASSERT_TRUE(present);
   ASSERT_FALSE(parser.HasMore());
 }
@@ -179,10 +179,10 @@
   const uint8_t der[] = {0x9f, 0x1f, 0x00};
   Parser parser((Input(der)));
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-  EXPECT_EQ(kTagContextSpecific | 31u, tag);
+  EXPECT_EQ(CBS_ASN1_CONTEXT_SPECIFIC | 31u, tag);
   ASSERT_FALSE(parser.HasMore());
 }
 
@@ -192,10 +192,10 @@
     const uint8_t der[] = {0x04, 0x00};
     Parser parser((Input(der)));
 
-    Tag tag;
+    CBS_ASN1_TAG tag;
     Input value;
     ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-    EXPECT_EQ(kOctetString, tag);
+    EXPECT_EQ(CBS_ASN1_OCTETSTRING, tag);
   }
 
   {
@@ -203,10 +203,10 @@
     const uint8_t der[] = {0x30, 0x00};
     Parser parser((Input(der)));
 
-    Tag tag;
+    CBS_ASN1_TAG tag;
     Input value;
     ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-    EXPECT_EQ(kSequence, tag);
+    EXPECT_EQ(CBS_ASN1_SEQUENCE, tag);
   }
 
   {
@@ -214,10 +214,10 @@
     const uint8_t der[] = {0x41, 0x00};
     Parser parser((Input(der)));
 
-    Tag tag;
+    CBS_ASN1_TAG tag;
     Input value;
     ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-    EXPECT_EQ(kTagApplication | 1, tag);
+    EXPECT_EQ(CBS_ASN1_APPLICATION | 1, tag);
   }
 
   {
@@ -225,10 +225,10 @@
     const uint8_t der[] = {0xbe, 0x00};
     Parser parser((Input(der)));
 
-    Tag tag;
+    CBS_ASN1_TAG tag;
     Input value;
     ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-    EXPECT_EQ(kTagContextSpecific | kTagConstructed | 30, tag);
+    EXPECT_EQ(CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 30, tag);
   }
 
   {
@@ -236,10 +236,10 @@
     const uint8_t der[] = {0xcf, 0x00};
     Parser parser((Input(der)));
 
-    Tag tag;
+    CBS_ASN1_TAG tag;
     Input value;
     ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value));
-    EXPECT_EQ(kTagPrivate | 15, tag);
+    EXPECT_EQ(CBS_ASN1_PRIVATE | 15, tag);
   }
 }
 
@@ -247,7 +247,7 @@
   const uint8_t der[] = {0x01};
   Parser parser((Input(der)));
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value));
   ASSERT_TRUE(parser.HasMore());
@@ -259,7 +259,7 @@
   const uint8_t der[] = {0x04, 0x81};
   Parser parser((Input(der)));
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value));
   ASSERT_TRUE(parser.HasMore());
@@ -270,7 +270,7 @@
   const uint8_t der[] = {0x04, 0x02, 0x84};
   Parser parser((Input(der)));
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value));
   ASSERT_TRUE(parser.HasMore());
@@ -280,7 +280,7 @@
   const uint8_t der[] = {0x01, 0x81, 0x01, 0x00};
   Parser parser((Input(der)));
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value));
   ASSERT_TRUE(parser.HasMore());
@@ -304,7 +304,7 @@
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
   Parser parser((Input(der)));
 
-  Tag tag;
+  CBS_ASN1_TAG tag;
   Input value;
   ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value));
   ASSERT_TRUE(parser.HasMore());
@@ -315,7 +315,7 @@
   const uint8_t der[] = {0x10, 0x00};
   Parser parser((Input(der)));
 
-  Tag expected_tag = 0x10;
+  CBS_ASN1_TAG expected_tag = 0x10;
   Parser sequence_parser;
   ASSERT_FALSE(parser.ReadConstructed(expected_tag, &sequence_parser));
 
diff --git a/pki/path_builder.cc b/pki/path_builder.cc
index 4bb5d2f..a7fddd3 100644
--- a/pki/path_builder.cc
+++ b/pki/path_builder.cc
@@ -11,6 +11,7 @@
 
 #include <openssl/base.h>
 #include <openssl/sha.h>
+
 #include "cert_issuer_source.h"
 #include "certificate_policies.h"
 #include "common_cert_errors.h"
@@ -18,7 +19,6 @@
 #include "parse_name.h"  // For CertDebugString.
 #include "parser.h"
 #include "string_util.h"
-#include "tag.h"
 #include "trust_store.h"
 #include "verify_certificate_chain.h"
 #include "verify_name_match.h"
diff --git a/pki/signature_algorithm.cc b/pki/signature_algorithm.cc
index 8116862..73d8bc1 100644
--- a/pki/signature_algorithm.cc
+++ b/pki/signature_algorithm.cc
@@ -6,6 +6,7 @@
 
 #include <openssl/bytestring.h>
 #include <openssl/digest.h>
+
 #include "input.h"
 #include "parse_values.h"
 #include "parser.h"
@@ -126,7 +127,7 @@
 [[nodiscard]] bool IsNull(der::Input input) {
   der::Parser parser(input);
   der::Input null_value;
-  if (!parser.ReadTag(der::kNull, &null_value)) {
+  if (!parser.ReadTag(CBS_ASN1_NULL, &null_value)) {
     return false;
   }
 
@@ -237,12 +238,15 @@
   DigestAlgorithm hash, mgf1_hash;
   der::Parser salt_length_parser;
   uint64_t salt_length;
-  if (!params_parser.ReadTag(der::ContextSpecificConstructed(0), &field) ||
+  if (!params_parser.ReadTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0, &field) ||
       !ParseHashAlgorithm(field, &hash) ||
-      !params_parser.ReadTag(der::ContextSpecificConstructed(1), &field) ||
+      !params_parser.ReadTag(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1, &field) ||
       !ParseMaskGenAlgorithm(field, &mgf1_hash) ||
-      !params_parser.ReadConstructed(der::ContextSpecificConstructed(2),
-                                     &salt_length_parser) ||
+      !params_parser.ReadConstructed(
+          CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 2,
+          &salt_length_parser) ||
       !salt_length_parser.ReadUint64(&salt_length) ||
       salt_length_parser.HasMore() || params_parser.HasMore()) {
     return std::nullopt;
@@ -285,7 +289,7 @@
     return false;
   }
 
-  if (!algorithm_identifier_parser.ReadTag(der::kOid, algorithm)) {
+  if (!algorithm_identifier_parser.ReadTag(CBS_ASN1_OBJECT, algorithm)) {
     return false;
   }
 
diff --git a/pki/tag.cc b/pki/tag.cc
deleted file mode 100644
index d2192ef..0000000
--- a/pki/tag.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2015 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "tag.h"
-
-#include <openssl/base.h>
-
-namespace bssl::der {
-
-Tag ContextSpecificConstructed(uint8_t tag_number) {
-  BSSL_CHECK(tag_number == (tag_number & kTagNumberMask));
-  return (tag_number & kTagNumberMask) | kTagConstructed | kTagContextSpecific;
-}
-
-Tag ContextSpecificPrimitive(uint8_t base) {
-  BSSL_CHECK(base == (base & kTagNumberMask));
-  return (base & kTagNumberMask) | kTagPrimitive | kTagContextSpecific;
-}
-
-bool IsConstructed(Tag tag) {
-  return (tag & kTagConstructionMask) == kTagConstructed;
-}
-
-}  // namespace bssl::der
diff --git a/pki/tag.h b/pki/tag.h
deleted file mode 100644
index 683e527..0000000
--- a/pki/tag.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2015 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BSSL_DER_TAG_H_
-#define BSSL_DER_TAG_H_
-
-#include <stdint.h>
-
-#include <openssl/base.h>
-#include <openssl/bytestring.h>
-
-namespace bssl::der {
-
-// This Tag type represents the identifier for an ASN.1 tag as encoded with
-// DER. It matches the BoringSSL CBS and CBB in-memory representation for a
-// tag.
-//
-// Callers must not assume it matches the DER representation for small tag
-// numbers. Instead, constants are provided for universal class types, and
-// functions are provided for building context specific tags. Tags can also be
-// built from the provided constants and bitmasks.
-using Tag = unsigned;
-
-// Universal class primitive types
-const Tag kBool = CBS_ASN1_BOOLEAN;
-const Tag kInteger = CBS_ASN1_INTEGER;
-const Tag kBitString = CBS_ASN1_BITSTRING;
-const Tag kOctetString = CBS_ASN1_OCTETSTRING;
-const Tag kNull = CBS_ASN1_NULL;
-const Tag kOid = CBS_ASN1_OBJECT;
-const Tag kEnumerated = CBS_ASN1_ENUMERATED;
-const Tag kUtf8String = CBS_ASN1_UTF8STRING;
-const Tag kPrintableString = CBS_ASN1_PRINTABLESTRING;
-const Tag kTeletexString = CBS_ASN1_T61STRING;
-const Tag kIA5String = CBS_ASN1_IA5STRING;
-const Tag kUtcTime = CBS_ASN1_UTCTIME;
-const Tag kGeneralizedTime = CBS_ASN1_GENERALIZEDTIME;
-const Tag kVisibleString = CBS_ASN1_VISIBLESTRING;
-const Tag kUniversalString = CBS_ASN1_UNIVERSALSTRING;
-const Tag kBmpString = CBS_ASN1_BMPSTRING;
-
-// Universal class constructed types
-const Tag kSequence = CBS_ASN1_SEQUENCE;
-const Tag kSet = CBS_ASN1_SET;
-
-// Primitive/constructed bits
-const unsigned kTagPrimitive = 0x00;
-const unsigned kTagConstructed = CBS_ASN1_CONSTRUCTED;
-
-// Tag classes
-const unsigned kTagUniversal = 0x00;
-const unsigned kTagApplication = CBS_ASN1_APPLICATION;
-const unsigned kTagContextSpecific = CBS_ASN1_CONTEXT_SPECIFIC;
-const unsigned kTagPrivate = CBS_ASN1_PRIVATE;
-
-// Masks for the 3 components of a tag (class, primitive/constructed, number)
-const unsigned kTagNumberMask = CBS_ASN1_TAG_NUMBER_MASK;
-const unsigned kTagConstructionMask = CBS_ASN1_CONSTRUCTED;
-const unsigned kTagClassMask = CBS_ASN1_CLASS_MASK;
-
-// Creates the value for the outer tag of an explicitly tagged type.
-//
-// The ASN.1 keyword for this is:
-//     [tag_number] EXPLICIT
-//
-// (Note, the EXPLICIT may be omitted if the entire schema is in
-// EXPLICIT mode, the default)
-OPENSSL_EXPORT Tag ContextSpecificConstructed(uint8_t tag_number);
-
-OPENSSL_EXPORT Tag ContextSpecificPrimitive(uint8_t base);
-
-OPENSSL_EXPORT bool IsConstructed(Tag tag);
-
-}  // namespace bssl::der
-
-#endif  // BSSL_DER_TAG_H_
diff --git a/pki/test_helpers.cc b/pki/test_helpers.cc
index cc32622..b6712af 100644
--- a/pki/test_helpers.cc
+++ b/pki/test_helpers.cc
@@ -147,7 +147,7 @@
 der::Input SequenceValueFromString(std::string_view s) {
   der::Parser parser((der::Input(s)));
   der::Input data;
-  if (!parser.ReadTag(der::kSequence, &data)) {
+  if (!parser.ReadTag(CBS_ASN1_SEQUENCE, &data)) {
     ADD_FAILURE();
     return der::Input();
   }
diff --git a/pki/verify_name_match.cc b/pki/verify_name_match.cc
index 73d3f9a..09f6102 100644
--- a/pki/verify_name_match.cc
+++ b/pki/verify_name_match.cc
@@ -6,12 +6,12 @@
 
 #include <openssl/base.h>
 #include <openssl/bytestring.h>
+
 #include "cert_error_params.h"
 #include "cert_errors.h"
 #include "input.h"
 #include "parse_name.h"
 #include "parser.h"
-#include "tag.h"
 
 namespace bssl {
 
@@ -127,15 +127,15 @@
 
   bool success = false;
   switch (attribute.value_tag) {
-    case der::kPrintableString:
+    case CBS_ASN1_PRINTABLESTRING:
       success = NormalizeDirectoryString(ENFORCE_PRINTABLE_STRING, output);
       break;
-    case der::kBmpString:
-    case der::kUniversalString:
-    case der::kUtf8String:
+    case CBS_ASN1_BMPSTRING:
+    case CBS_ASN1_UNIVERSALSTRING:
+    case CBS_ASN1_UTF8STRING:
       success = NormalizeDirectoryString(NO_ENFORCEMENT, output);
       break;
-    case der::kIA5String:
+    case CBS_ASN1_IA5STRING:
       success = NormalizeDirectoryString(ENFORCE_ASCII, output);
       break;
     default:
@@ -153,15 +153,15 @@
 }
 
 // Returns true if |tag| is a string type that NormalizeValue can handle.
-bool IsNormalizableDirectoryString(der::Tag tag) {
+bool IsNormalizableDirectoryString(CBS_ASN1_TAG tag) {
   switch (tag) {
-    case der::kPrintableString:
-    case der::kUtf8String:
+    case CBS_ASN1_PRINTABLESTRING:
+    case CBS_ASN1_UTF8STRING:
     // RFC 5280 only requires handling IA5String for comparing domainComponent
     // values, but handling it here avoids the need to special case anything.
-    case der::kIA5String:
-    case der::kUniversalString:
-    case der::kBmpString:
+    case CBS_ASN1_IA5STRING:
+    case CBS_ASN1_UNIVERSALSTRING:
+    case CBS_ASN1_BMPSTRING:
       return true;
     // TeletexString isn't normalized. Section 8 of RFC 5280 briefly
     // describes the historical confusion between treating TeletexString
@@ -274,8 +274,8 @@
   der::Parser a_rdn_sequence_counter(a);
   der::Parser b_rdn_sequence_counter(b);
   while (a_rdn_sequence_counter.HasMore() && b_rdn_sequence_counter.HasMore()) {
-    if (!a_rdn_sequence_counter.SkipTag(der::kSet) ||
-        !b_rdn_sequence_counter.SkipTag(der::kSet)) {
+    if (!a_rdn_sequence_counter.SkipTag(CBS_ASN1_SET) ||
+        !b_rdn_sequence_counter.SkipTag(CBS_ASN1_SET)) {
       return false;
     }
   }
@@ -294,8 +294,8 @@
   der::Parser b_rdn_sequence(b);
   while (a_rdn_sequence.HasMore() && b_rdn_sequence.HasMore()) {
     der::Parser a_rdn, b_rdn;
-    if (!a_rdn_sequence.ReadConstructed(der::kSet, &a_rdn) ||
-        !b_rdn_sequence.ReadConstructed(der::kSet, &b_rdn)) {
+    if (!a_rdn_sequence.ReadConstructed(CBS_ASN1_SET, &a_rdn) ||
+        !b_rdn_sequence.ReadConstructed(CBS_ASN1_SET, &b_rdn)) {
       return false;
     }
     if (!VerifyRdnMatch(&a_rdn, &b_rdn)) {
@@ -324,7 +324,7 @@
   while (rdn_sequence_parser.HasMore()) {
     // RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
     der::Parser rdn_parser;
-    if (!rdn_sequence_parser.ReadConstructed(der::kSet, &rdn_parser)) {
+    if (!rdn_sequence_parser.ReadConstructed(CBS_ASN1_SET, &rdn_parser)) {
       return false;
     }
     RelativeDistinguishedName type_and_values;
@@ -412,7 +412,7 @@
   der::Parser rdn_sequence_parser(name_rdn_sequence);
   while (rdn_sequence_parser.HasMore()) {
     der::Parser rdn_parser;
-    if (!rdn_sequence_parser.ReadConstructed(der::kSet, &rdn_parser)) {
+    if (!rdn_sequence_parser.ReadConstructed(CBS_ASN1_SET, &rdn_parser)) {
       return false;
     }
 
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs
index bb80ef4..5a6e58a 100644
--- a/rust/bssl-crypto/src/lib.rs
+++ b/rust/bssl-crypto/src/lib.rs
@@ -101,9 +101,6 @@
 /// See the comment [`FfiSlice`].
 trait FfiMutSlice {
     fn as_mut_ffi_ptr(&mut self) -> *mut u8;
-    fn as_ffi_void_ptr(&mut self) -> *mut c_void {
-        self.as_mut_ffi_ptr() as *mut c_void
-    }
 }
 
 impl FfiMutSlice for [u8] {
@@ -203,17 +200,6 @@
         unsafe { &*(ptr as *mut _) }
     }
 
-    /// Constructs a mutable reference of this type from its raw type.
-    ///
-    /// # Safety
-    ///
-    /// `ptr` must be a valid, unique, instance of the type for the `'a` lifetime.
-    #[inline]
-    unsafe fn from_ptr_mut<'a>(ptr: *mut Self::CType) -> &'a mut Self {
-        debug_assert!(!ptr.is_null());
-        unsafe { &mut *(ptr as *mut _) }
-    }
-
     /// Returns a raw pointer to the wrapped value.
     #[inline]
     fn as_ptr(&self) -> *mut Self::CType {
@@ -221,29 +207,6 @@
     }
 }
 
-/// A helper trait implemented by types which has an owned reference to foreign types.
-///
-/// # Safety
-///
-/// Implementations of `ForeignType` must guarantee the following:
-///
-/// - `Self::from_ptr(x).as_ptr() == x`
-unsafe trait ForeignType {
-    /// The raw C type.
-    type CType;
-
-    /// Constructs an instance of this type from its raw type.
-    ///
-    /// # Safety
-    ///
-    /// - `ptr` must be a valid, immutable, instance of `CType`.
-    /// - Ownership of `ptr` is passed to the implementation, and will free `ptr` when dropped.
-    unsafe fn from_ptr(ptr: *mut Self::CType) -> Self;
-
-    /// Returns a raw pointer to the wrapped value.
-    fn as_ptr(&self) -> *mut Self::CType;
-}
-
 /// Returns a BoringSSL structure that is initialized by some function.
 /// Requires that the given function completely initializes the value.
 ///
diff --git a/rust/bssl-sys/CMakeLists.txt b/rust/bssl-sys/CMakeLists.txt
index d17a8f1..0ed1c95 100644
--- a/rust/bssl-sys/CMakeLists.txt
+++ b/rust/bssl-sys/CMakeLists.txt
@@ -1,19 +1,15 @@
-# Additional interop for things like macros and inlined functions.
-add_library(rust_wrapper STATIC rust_wrapper.c)
-target_link_libraries(rust_wrapper crypto)
-
 # Generate architecture-specific wrappers. bindgen must be called from
 # ${CMAKE_BINARY_DIR}, with the output path as a relative path. bindgen writes
 # the depfile using the same syntax as the command-line argument, and ninja
 # requires a path relative to the top-level build directory.
-set(wrapper wrapper_${RUST_BINDINGS}.rs)
-binary_dir_relative_path(${wrapper} wrapper_relative)
-binary_dir_relative_path(${wrapper}.d depfile_relative)
+set(wrapper_rs wrapper_${RUST_BINDINGS}.rs)
+binary_dir_relative_path(${wrapper_rs} wrapper_rs_relative)
+binary_dir_relative_path(${wrapper_rs}.d depfile_relative)
 
 add_custom_command(
-  OUTPUT ${wrapper}
+  OUTPUT ${wrapper_rs} wrapper.c
   COMMAND ${BINDGEN_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.h
-          -o ${wrapper_relative}
+          -o ${wrapper_rs_relative}
           --depfile=${depfile_relative}
           --no-derive-default
           --enable-function-attribute-detection
@@ -37,13 +33,23 @@
           # https://github.com/rust-lang/rust-bindgen/issues/2508), we can
           # switch to that.
           --allowlist-file=".*[[:punct:]]include[[:punct:]]openssl[[:punct:]].*\\.h"
-          --allowlist-file=".*[[:punct:]]rust_wrapper\\.h"
+          --experimental
+          --wrap-static-fns
+          --wrap-static-fns-path="${CMAKE_CURRENT_BINARY_DIR}/wrapper.c"
           -- # these are LLVM arg passthroughs
           -I${PROJECT_SOURCE_DIR}/include
           # https://doc.rust-lang.org/nightly/rustc/platform-support.html
           --target=${RUST_BINDINGS}
   DEPENDS wrapper.h
-  DEPFILE ${CMAKE_CURRENT_BINARY_DIR}/${wrapper}.d
+  DEPFILE ${CMAKE_CURRENT_BINARY_DIR}/${wrapper_rs}.d
   WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
 )
-add_custom_target(bssl_sys ALL DEPENDS ${wrapper})
+
+add_library(rust_wrapper STATIC wrapper.c)
+if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_COMPILER_IS_GNUCXX)
+  target_compile_options(rust_wrapper PRIVATE "-Wno-missing-prototypes")
+endif()
+target_include_directories(rust_wrapper PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+target_link_libraries(rust_wrapper crypto ssl)
+
+add_custom_target(bssl_sys ALL DEPENDS ${wrapper_rs} rust_wrapper)
diff --git a/rust/bssl-sys/README.md b/rust/bssl-sys/README.md
index c988813..13b7290 100644
--- a/rust/bssl-sys/README.md
+++ b/rust/bssl-sys/README.md
@@ -1,7 +1,7 @@
 bssl-sys
 ============
 
-A low-level binding crate for Rust that moves in lockstop with BoringSSL. BoringSSL explicitly does not have a stable ABI, `bssl-sys` is the solution for preventing subtle-memory corruption bugs due to version skew.
+A low-level binding crate for Rust that moves in lockstop with BoringSSL.
 
 ### How it works
 `bssl-sys` uses `bindgen` as part of the cmake build process to generate Rust compatibility shims for the targeted platform. It is important to generate it for the correct platform because `bindgen` uses LLVM information for alignment which varies depending on architecture.
diff --git a/rust/bssl-sys/rust_wrapper.h b/rust/bssl-sys/rust_wrapper.h
index 632622a..55d5a6f 100644
--- a/rust/bssl-sys/rust_wrapper.h
+++ b/rust/bssl-sys/rust_wrapper.h
@@ -23,8 +23,10 @@
 
 
 // The following functions are wrappers over inline functions and macros in
-// BoringSSL, which bindgen cannot currently correctly bind. These wrappers
-// ensure changes to the functions remain in lockstep with the Rust versions.
+// BoringSSL. These are not necessary, as bindgen has long supported
+// --wrap-static-fns, however Android is still missing support for this. (See
+// b/290347127.) These manual wrappers are, temporarily, retained for Android,
+// but this codepath is no longer tested or supported by BoringSSL.
 int ERR_GET_LIB_RUST(uint32_t packed_error);
 int ERR_GET_REASON_RUST(uint32_t packed_error);
 int ERR_GET_FUNC_RUST(uint32_t packed_error);
diff --git a/rust/bssl-sys/src/lib.rs b/rust/bssl-sys/src/lib.rs
index 06b907c..9edbd08 100644
--- a/rust/bssl-sys/src/lib.rs
+++ b/rust/bssl-sys/src/lib.rs
@@ -2,18 +2,56 @@
 #![allow(non_camel_case_types)]
 #![allow(non_snake_case)]
 
-// Set in build.rs
-include!(env!("BINDGEN_RS_FILE"));
+use core::ffi::c_ulong;
+
+// Wrap the bindgen output in a module and re-export it, so we can override it
+// as needed.
+mod bindgen {
+    include!(env!("BINDGEN_RS_FILE"));
+}
+pub use bindgen::*;
+
+// bindgen does not handle C constants correctly. See
+// https://github.com/rust-lang/rust-bindgen/issues/923. Work around this bug by
+// redefining some constants with the correct type. Once the bindgen bug has
+// been fixed, remove this.
+pub const ASN1_STRFLGS_ESC_2253: c_ulong = bindgen::ASN1_STRFLGS_ESC_2253 as c_ulong;
+pub const ASN1_STRFLGS_ESC_CTRL: c_ulong = bindgen::ASN1_STRFLGS_ESC_CTRL as c_ulong;
+pub const ASN1_STRFLGS_ESC_MSB: c_ulong = bindgen::ASN1_STRFLGS_ESC_MSB as c_ulong;
+pub const ASN1_STRFLGS_ESC_QUOTE: c_ulong = bindgen::ASN1_STRFLGS_ESC_QUOTE as c_ulong;
+pub const ASN1_STRFLGS_UTF8_CONVERT: c_ulong = bindgen::ASN1_STRFLGS_UTF8_CONVERT as c_ulong;
+pub const ASN1_STRFLGS_IGNORE_TYPE: c_ulong = bindgen::ASN1_STRFLGS_IGNORE_TYPE as c_ulong;
+pub const ASN1_STRFLGS_SHOW_TYPE: c_ulong = bindgen::ASN1_STRFLGS_SHOW_TYPE as c_ulong;
+pub const ASN1_STRFLGS_DUMP_ALL: c_ulong = bindgen::ASN1_STRFLGS_DUMP_ALL as c_ulong;
+pub const ASN1_STRFLGS_DUMP_UNKNOWN: c_ulong = bindgen::ASN1_STRFLGS_DUMP_UNKNOWN as c_ulong;
+pub const ASN1_STRFLGS_DUMP_DER: c_ulong = bindgen::ASN1_STRFLGS_DUMP_DER as c_ulong;
+pub const ASN1_STRFLGS_RFC2253: c_ulong = bindgen::ASN1_STRFLGS_RFC2253 as c_ulong;
+pub const XN_FLAG_COMPAT: c_ulong = bindgen::XN_FLAG_COMPAT as c_ulong;
+pub const XN_FLAG_SEP_MASK: c_ulong = bindgen::XN_FLAG_SEP_MASK as c_ulong;
+pub const XN_FLAG_SEP_COMMA_PLUS: c_ulong = bindgen::XN_FLAG_SEP_COMMA_PLUS as c_ulong;
+pub const XN_FLAG_SEP_CPLUS_SPC: c_ulong = bindgen::XN_FLAG_SEP_CPLUS_SPC as c_ulong;
+pub const XN_FLAG_SEP_SPLUS_SPC: c_ulong = bindgen::XN_FLAG_SEP_SPLUS_SPC as c_ulong;
+pub const XN_FLAG_SEP_MULTILINE: c_ulong = bindgen::XN_FLAG_SEP_MULTILINE as c_ulong;
+pub const XN_FLAG_DN_REV: c_ulong = bindgen::XN_FLAG_DN_REV as c_ulong;
+pub const XN_FLAG_FN_MASK: c_ulong = bindgen::XN_FLAG_FN_MASK as c_ulong;
+pub const XN_FLAG_FN_SN: c_ulong = bindgen::XN_FLAG_FN_SN as c_ulong;
+pub const XN_FLAG_SPC_EQ: c_ulong = bindgen::XN_FLAG_SPC_EQ as c_ulong;
+pub const XN_FLAG_DUMP_UNKNOWN_FIELDS: c_ulong = bindgen::XN_FLAG_DUMP_UNKNOWN_FIELDS as c_ulong;
+pub const XN_FLAG_RFC2253: c_ulong = bindgen::XN_FLAG_RFC2253 as c_ulong;
+pub const XN_FLAG_ONELINE: c_ulong = bindgen::XN_FLAG_ONELINE as c_ulong;
 
 // TODO(crbug.com/boringssl/596): Remove these wrappers.
+#[cfg(unsupported_inline_wrappers)]
 pub fn ERR_GET_LIB(packed_error: u32) -> i32 {
     unsafe { ERR_GET_LIB_RUST(packed_error) }
 }
 
+#[cfg(unsupported_inline_wrappers)]
 pub fn ERR_GET_REASON(packed_error: u32) -> i32 {
     unsafe { ERR_GET_REASON_RUST(packed_error) }
 }
 
+#[cfg(unsupported_inline_wrappers)]
 pub fn ERR_GET_FUNC(packed_error: u32) -> i32 {
     unsafe { ERR_GET_FUNC_RUST(packed_error) }
 }
diff --git a/sources.cmake b/sources.cmake
index febdb31..d32f24b 100644
--- a/sources.cmake
+++ b/sources.cmake
@@ -382,7 +382,6 @@
   pki/signature_algorithm.cc
   pki/simple_path_builder_delegate.cc
   pki/string_util.cc
-  pki/tag.cc
   pki/trust_store_collection.cc
   pki/trust_store_in_memory.cc
   pki/trust_store.cc
@@ -2276,4 +2275,6 @@
   pki/testdata/verify_signed_data_unittest/rsa-pss-sha256.pem
   pki/testdata/verify_signed_data_unittest/rsa-using-ec-key.pem
   pki/testdata/verify_signed_data_unittest/rsa2048-pkcs1-sha512.pem
+  pki/testdata/verify_unittest/google-leaf.der
+  pki/testdata/verify_unittest/self-issued.pem
 )
diff --git a/ssl/internal.h b/ssl/internal.h
index c9facb6..d9972dc 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -227,22 +227,10 @@
   return UniquePtr<T>(New<T>(std::forward<Args>(args)...));
 }
 
-#if defined(BORINGSSL_ALLOW_CXX_RUNTIME)
+// TODO(davidben): Remove these macros after April 2024, once the C++ runtime
+// dependency has stuck.
 #define HAS_VIRTUAL_DESTRUCTOR
 #define PURE_VIRTUAL = 0
-#else
-// HAS_VIRTUAL_DESTRUCTOR should be declared in any base class which defines a
-// virtual destructor. This avoids a dependency on |_ZdlPv| and prevents the
-// class from being used with |delete|.
-#define HAS_VIRTUAL_DESTRUCTOR \
-  void operator delete(void *) { abort(); }
-
-// PURE_VIRTUAL should be used instead of = 0 when defining pure-virtual
-// functions. This avoids a dependency on |__cxa_pure_virtual| but loses
-// compile-time checking.
-#define PURE_VIRTUAL \
-  { abort(); }
-#endif
 
 // Array<T> is an owning array of elements of |T|.
 template <typename T>
diff --git a/util/check_imported_libraries.go b/util/check_imported_libraries.go
index f3803f1..c7aa69f 100644
--- a/util/check_imported_libraries.go
+++ b/util/check_imported_libraries.go
@@ -23,36 +23,49 @@
 	"debug/elf"
 	"fmt"
 	"os"
+	"path/filepath"
 )
 
-func checkImportedLibraries(path string) {
+func checkImportedLibraries(path string) bool {
 	file, err := elf.Open(path)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error opening %s: %s\n", path, err)
-		os.Exit(1)
+		return false
 	}
 	defer file.Close()
 
 	libs, err := file.ImportedLibraries()
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", path, err)
-		os.Exit(1)
+		return false
 	}
 
+	allowCpp := filepath.Base(path) == "libssl.so"
 	for _, lib := range libs {
-		if lib != "libc.so.6" && lib != "libcrypto.so" && lib != "libpthread.so.0" {
-			fmt.Printf("Invalid dependency for %s: %s\n", path, lib)
-			fmt.Printf("All dependencies:\n")
-			for _, lib := range libs {
-				fmt.Printf("    %s\n", lib)
-			}
-			os.Exit(1)
+		if lib == "libc.so.6" || lib == "libcrypto.so" || lib == "libpthread.so.0" || lib == "libgcc_s.so.1" {
+			continue
 		}
+		if allowCpp && lib == "libstdc++.so.6" {
+			continue
+		}
+		fmt.Printf("Invalid dependency for %s: %s\n", path, lib)
+		fmt.Printf("All dependencies:\n")
+		for _, lib := range libs {
+			fmt.Printf("    %s\n", lib)
+		}
+		return false
 	}
+	return true
 }
 
 func main() {
+	ok := true
 	for _, path := range os.Args[1:] {
-		checkImportedLibraries(path)
+		if !checkImportedLibraries(path) {
+			ok = false
+		}
+	}
+	if !ok {
+		os.Exit(1)
 	}
 }
diff --git a/util/doc.config b/util/doc.config
index d2bc500..e36a960 100644
--- a/util/doc.config
+++ b/util/doc.config
@@ -57,7 +57,8 @@
     "Name": "Legacy ASN.1 and X.509 implementation (documentation in progress)",
     "Headers": [
       "include/openssl/asn1.h",
-      "include/openssl/conf.h"
+      "include/openssl/conf.h",
+      "include/openssl/x509.h"
     ]
   },{
     "Name": "SSL implementation",