Write some templated functions for the d2i/i2d convention

We open-coded a lot of these because we didn't have C++ templates in the
beginning. Now we do and we can map calling conventions much more
straightforwardly.

Bug: 42290417
Change-Id: I7e5ba9e1bf2bd556bbff614b11ac15eb6e155f49
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/81773
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/asn1/a_bool.cc b/crypto/asn1/a_bool.cc
index 7869e69..d068343 100644
--- a/crypto/asn1/a_bool.cc
+++ b/crypto/asn1/a_bool.cc
@@ -21,13 +21,10 @@
 
 
 int i2d_ASN1_BOOLEAN(ASN1_BOOLEAN a, unsigned char **outp) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 3) ||  //
-      !CBB_add_asn1_bool(&cbb, a != ASN1_BOOLEAN_FALSE)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/3, outp, [&](CBB *cbb) -> bool {
+        return CBB_add_asn1_bool(cbb, a != ASN1_BOOLEAN_FALSE);
+      });
 }
 
 ASN1_BOOLEAN d2i_ASN1_BOOLEAN(ASN1_BOOLEAN *out, const unsigned char **inp,
diff --git a/crypto/asn1/a_object.cc b/crypto/asn1/a_object.cc
index bb4bc4a..102a9f0 100644
--- a/crypto/asn1/a_object.cc
+++ b/crypto/asn1/a_object.cc
@@ -43,13 +43,11 @@
 }
 
 int i2d_ASN1_OBJECT(const ASN1_OBJECT *in, unsigned char **outp) {
-  bssl::ScopedCBB cbb;
-  if (!CBB_init(cbb.get(), static_cast<size_t>(in->length) + 2) ||
-      !asn1_marshal_object(cbb.get(), in, /*tag=*/0)) {
-    return -1;
-  }
-
-  return CBB_finish_i2d(cbb.get(), outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/static_cast<size_t>(in->length) + 2, outp,
+      [&](CBB *cbb) -> bool {
+        return asn1_marshal_object(cbb, in, /*tag=*/0);
+      });
 }
 
 int i2t_ASN1_OBJECT(char *buf, int buf_len, const ASN1_OBJECT *a) {
@@ -93,53 +91,33 @@
 
 ASN1_OBJECT *d2i_ASN1_OBJECT(ASN1_OBJECT **out, const unsigned char **inp,
                              long len) {
-  if (len < 0) {
-    return NULL;
-  }
-
-  CBS cbs, child;
-  CBS_init(&cbs, *inp, (size_t)len);
-  if (!CBS_get_asn1(&cbs, &child, CBS_ASN1_OBJECT)) {
-    OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
-    return NULL;
-  }
-
-  const uint8_t *contents = CBS_data(&child);
-  ASN1_OBJECT *ret = c2i_ASN1_OBJECT(out, &contents, CBS_len(&child));
-  if (ret != NULL) {
-    // |c2i_ASN1_OBJECT| should have consumed the entire input.
-    assert(CBS_data(&cbs) == contents);
-    *inp = CBS_data(&cbs);
-  }
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, [](CBS *cbs) -> ASN1_OBJECT * {
+    CBS child;
+    if (!CBS_get_asn1(cbs, &child, CBS_ASN1_OBJECT)) {
+      OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
+      return nullptr;
+    }
+    const uint8_t *contents = CBS_data(&child);
+    return c2i_ASN1_OBJECT(nullptr, &contents, CBS_len(&child));
+  });
 }
 
 ASN1_OBJECT *c2i_ASN1_OBJECT(ASN1_OBJECT **out, const unsigned char **inp,
                              long len) {
-  if (len < 0) {
-    OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_OBJECT_ENCODING);
-    return NULL;
-  }
-
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  if (!CBS_is_valid_asn1_oid(&cbs)) {
-    OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_OBJECT_ENCODING);
-    return NULL;
-  }
-
-  ASN1_OBJECT *ret = ASN1_OBJECT_create(NID_undef, *inp, (size_t)len,
-                                        /*sn=*/NULL, /*ln=*/NULL);
-  if (ret == NULL) {
-    return NULL;
-  }
-
-  if (out != NULL) {
-    ASN1_OBJECT_free(*out);
-    *out = ret;
-  }
-  *inp += len;  // All bytes were consumed.
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, [](CBS *cbs) -> ASN1_OBJECT * {
+    if (!CBS_is_valid_asn1_oid(cbs)) {
+      OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_OBJECT_ENCODING);
+      return nullptr;
+    }
+    ASN1_OBJECT *ret =
+        ASN1_OBJECT_create(NID_undef, CBS_data(cbs), CBS_len(cbs),
+                           /*sn=*/nullptr, /*ln=*/nullptr);
+    if (ret != nullptr) {
+      // |c2i_ASN1_OBJECT| consumes its whole input on success.
+      BSSL_CHECK(CBS_skip(cbs, CBS_len(cbs)));
+    }
+    return ret;
+  });
 }
 
 ASN1_OBJECT *asn1_parse_object(CBS *cbs, CBS_ASN1_TAG tag) {
diff --git a/crypto/bytestring/internal.h b/crypto/bytestring/internal.h
index 110fb95..0ec59e2 100644
--- a/crypto/bytestring/internal.h
+++ b/crypto/bytestring/internal.h
@@ -15,12 +15,14 @@
 #ifndef OPENSSL_HEADER_CRYPTO_BYTESTRING_INTERNAL_H
 #define OPENSSL_HEADER_CRYPTO_BYTESTRING_INTERNAL_H
 
-#include <openssl/base.h>
+#include <openssl/asn1.h>
+#include <openssl/bytestring.h>
+#include <openssl/err.h>
 
-#if defined(__cplusplus)
+#include <type_traits>
+
+
 extern "C" {
-#endif
-
 
 // CBS_asn1_ber_to_der reads a BER element from |in|. If it finds
 // indefinite-length elements or constructed strings then it converts the BER
@@ -66,9 +68,53 @@
 // This function may be used to help implement legacy i2d ASN.1 functions.
 int CBB_finish_i2d(CBB *cbb, uint8_t **outp);
 
-
-#if defined(__cplusplus)
 }  // extern C
-#endif
+
+BSSL_NAMESPACE_BEGIN
+
+// D2IFromCBS takes a functor of type |Unique<T>(CBS*)| and implements the d2i
+// calling convention. For compatibility with functions that don't tag their
+// return value (e.g. public APIs), |T*(CBS)| is also accepted. The callback can
+// assume that the |CBS|'s length fits in |long|. The callback should not access
+// |out|, |inp|, or |len| directly.
+template <typename T, typename CBSFunc>
+inline T *D2IFromCBS(T **out, const uint8_t **inp, long len, CBSFunc func) {
+  static_assert(std::is_invocable_v<CBSFunc, CBS *>);
+  static_assert(
+      std::is_same_v<std::invoke_result_t<CBSFunc, CBS *>, UniquePtr<T>> ||
+      std::is_same_v<std::invoke_result_t<CBSFunc, CBS *>, T *>);
+  if (len < 0) {
+    OPENSSL_PUT_ERROR(ASN1, ASN1_R_BUFFER_TOO_SMALL);
+    return nullptr;
+  }
+  CBS cbs;
+  CBS_init(&cbs, *inp, len);
+  UniquePtr<T> ret(func(&cbs));
+  if (ret == nullptr) {
+    return nullptr;
+  }
+  if (out != nullptr) {
+    UniquePtr<T> free_out(*out);
+    *out = ret.get();
+  }
+  *inp = CBS_data(&cbs);
+  return ret.release();
+}
+
+// I2DFromCBB takes a functor of type |bool(CBB*)| and implements the i2d
+// calling convention. It internally makes a |CBB| with the specified initial
+// capacity. The callback should not access |outp| directly.
+template <typename CBBFunc>
+inline int I2DFromCBB(size_t initial_capacity, uint8_t **outp, CBBFunc func) {
+  static_assert(std::is_invocable_v<CBBFunc, CBB *>);
+  static_assert(std::is_same_v<std::invoke_result_t<CBBFunc, CBB *>, bool>);
+  ScopedCBB cbb;
+  if (!CBB_init(cbb.get(), initial_capacity) || !func(cbb.get())) {
+    return -1;
+  }
+  return CBB_finish_i2d(cbb.get(), outp);
+}
+
+BSSL_NAMESPACE_END
 
 #endif  // OPENSSL_HEADER_CRYPTO_BYTESTRING_INTERNAL_H
diff --git a/crypto/dh/dh_asn1.cc b/crypto/dh/dh_asn1.cc
index 9295e14..8b1ec11 100644
--- a/crypto/dh/dh_asn1.cc
+++ b/crypto/dh/dh_asn1.cc
@@ -95,29 +95,11 @@
 }
 
 DH *d2i_DHparams(DH **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return NULL;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  DH *ret = DH_parse_parameters(&cbs);
-  if (ret == NULL) {
-    return NULL;
-  }
-  if (out != NULL) {
-    DH_free(*out);
-    *out = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, DH_parse_parameters);
 }
 
 int i2d_DHparams(const DH *in, uint8_t **outp) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||
-      !DH_marshal_parameters(&cbb, in)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/256, outp,
+      [&](CBB *cbb) -> bool { return DH_marshal_parameters(cbb, in); });
 }
diff --git a/crypto/dsa/dsa_asn1.cc b/crypto/dsa/dsa_asn1.cc
index af5ccad..456c03b 100644
--- a/crypto/dsa/dsa_asn1.cc
+++ b/crypto/dsa/dsa_asn1.cc
@@ -255,113 +255,41 @@
 }
 
 DSA_SIG *d2i_DSA_SIG(DSA_SIG **out_sig, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return NULL;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  DSA_SIG *ret = DSA_SIG_parse(&cbs);
-  if (ret == NULL) {
-    return NULL;
-  }
-  if (out_sig != NULL) {
-    DSA_SIG_free(*out_sig);
-    *out_sig = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(out_sig, inp, len, DSA_SIG_parse);
 }
 
 int i2d_DSA_SIG(const DSA_SIG *in, uint8_t **outp) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||
-      !DSA_SIG_marshal(&cbb, in)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/256, outp,
+      [&](CBB *cbb) -> bool { return DSA_SIG_marshal(cbb, in); });
 }
 
 DSA *d2i_DSAPublicKey(DSA **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return NULL;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  DSA *ret = DSA_parse_public_key(&cbs);
-  if (ret == NULL) {
-    return NULL;
-  }
-  if (out != NULL) {
-    DSA_free(*out);
-    *out = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, DSA_parse_public_key);
 }
 
 int i2d_DSAPublicKey(const DSA *in, uint8_t **outp) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||
-      !DSA_marshal_public_key(&cbb, in)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/256, outp,
+      [&](CBB *cbb) -> bool { return DSA_marshal_public_key(cbb, in); });
 }
 
 DSA *d2i_DSAPrivateKey(DSA **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return NULL;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  DSA *ret = DSA_parse_private_key(&cbs);
-  if (ret == NULL) {
-    return NULL;
-  }
-  if (out != NULL) {
-    DSA_free(*out);
-    *out = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, DSA_parse_private_key);
 }
 
 int i2d_DSAPrivateKey(const DSA *in, uint8_t **outp) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||
-      !DSA_marshal_private_key(&cbb, in)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/256, outp,
+      [&](CBB *cbb) -> bool { return DSA_marshal_private_key(cbb, in); });
 }
 
 DSA *d2i_DSAparams(DSA **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return NULL;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  DSA *ret = DSA_parse_parameters(&cbs);
-  if (ret == NULL) {
-    return NULL;
-  }
-  if (out != NULL) {
-    DSA_free(*out);
-    *out = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, DSA_parse_parameters);
 }
 
 int i2d_DSAparams(const DSA *in, uint8_t **outp) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||
-      !DSA_marshal_parameters(&cbb, in)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/256, outp,
+      [&](CBB *cbb) -> bool { return DSA_marshal_parameters(cbb, in); });
 }
diff --git a/crypto/ec/ec_asn1.cc b/crypto/ec/ec_asn1.cc
index 3d54e11..c28da4a 100644
--- a/crypto/ec/ec_asn1.cc
+++ b/crypto/ec/ec_asn1.cc
@@ -432,52 +432,20 @@
     group = EC_KEY_get0_group(*out);
   }
 
-  if (len < 0) {
-    OPENSSL_PUT_ERROR(EC, EC_R_DECODE_ERROR);
-    return NULL;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  EC_KEY *ret = EC_KEY_parse_private_key(&cbs, group);
-  if (ret == NULL) {
-    return NULL;
-  }
-  if (out != NULL) {
-    EC_KEY_free(*out);
-    *out = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, [&](CBS *cbs) {
+    return EC_KEY_parse_private_key(cbs, group);
+  });
 }
 
 int i2d_ECPrivateKey(const EC_KEY *key, uint8_t **outp) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||
-      !EC_KEY_marshal_private_key(&cbb, key, EC_KEY_get_enc_flags(key))) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/64, outp, [&](CBB *cbb) -> bool {
+        return EC_KEY_marshal_private_key(cbb, key, EC_KEY_get_enc_flags(key));
+      });
 }
 
 EC_GROUP *d2i_ECPKParameters(EC_GROUP **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return NULL;
-  }
-
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  EC_GROUP *ret = EC_KEY_parse_parameters(&cbs);
-  if (ret == NULL) {
-    return NULL;
-  }
-
-  if (out != NULL) {
-    EC_GROUP_free(*out);
-    *out = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, EC_KEY_parse_parameters);
 }
 
 int i2d_ECPKParameters(const EC_GROUP *group, uint8_t **outp) {
@@ -485,40 +453,24 @@
     OPENSSL_PUT_ERROR(EC, ERR_R_PASSED_NULL_PARAMETER);
     return -1;
   }
-
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||  //
-      !EC_KEY_marshal_curve_name(&cbb, group)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/16, outp,
+      [&](CBB *cbb) -> bool { return EC_KEY_marshal_curve_name(cbb, group); });
 }
 
 EC_KEY *d2i_ECParameters(EC_KEY **out_key, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return NULL;
-  }
-
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  const EC_GROUP *group = EC_KEY_parse_parameters(&cbs);
-  if (group == NULL) {
-    return NULL;
-  }
-
-  EC_KEY *ret = EC_KEY_new();
-  if (ret == NULL || !EC_KEY_set_group(ret, group)) {
-    EC_KEY_free(ret);
-    return NULL;
-  }
-
-  if (out_key != NULL) {
-    EC_KEY_free(*out_key);
-    *out_key = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(
+      out_key, inp, len, [](CBS *cbs) -> bssl::UniquePtr<EC_KEY> {
+        const EC_GROUP *group = EC_KEY_parse_parameters(cbs);
+        if (group == nullptr) {
+          return nullptr;
+        }
+        bssl::UniquePtr<EC_KEY> ret(EC_KEY_new());
+        if (ret == nullptr || !EC_KEY_set_group(ret.get(), group)) {
+          return nullptr;
+        }
+        return ret;
+      });
 }
 
 int i2d_ECParameters(const EC_KEY *key, uint8_t **outp) {
@@ -526,14 +478,10 @@
     OPENSSL_PUT_ERROR(EC, ERR_R_PASSED_NULL_PARAMETER);
     return -1;
   }
-
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||  //
-      !EC_KEY_marshal_curve_name(&cbb, key->group)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/16, outp, [&](CBB *cbb) -> bool {
+        return EC_KEY_marshal_curve_name(cbb, key->group);
+      });
 }
 
 EC_KEY *o2i_ECPublicKey(EC_KEY **keyp, const uint8_t **inp, long len) {
@@ -563,14 +511,13 @@
     OPENSSL_PUT_ERROR(EC, ERR_R_PASSED_NULL_PARAMETER);
     return 0;
   }
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||  //
-      !EC_POINT_point2cbb(&cbb, key->group, key->pub_key, key->conv_form,
-                          NULL)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  int ret = CBB_finish_i2d(&cbb, outp);
+  // No initial capacity because |EC_POINT_point2cbb| will internally reserve
+  // the right size in one shot, so it's best to leave this at zero.
+  int ret = bssl::I2DFromCBB(
+      /*initial_capacity=*/0, outp, [&](CBB *cbb) -> bool {
+        return EC_POINT_point2cbb(cbb, key->group, key->pub_key, key->conv_form,
+                                  nullptr);
+      });
   // Historically, this function used the wrong return value on error.
   return ret > 0 ? ret : 0;
 }
diff --git a/crypto/ecdsa/ecdsa_asn1.cc b/crypto/ecdsa/ecdsa_asn1.cc
index 28d5036..cf932fb 100644
--- a/crypto/ecdsa/ecdsa_asn1.cc
+++ b/crypto/ecdsa/ecdsa_asn1.cc
@@ -324,28 +324,11 @@
 }
 
 ECDSA_SIG *d2i_ECDSA_SIG(ECDSA_SIG **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return NULL;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  ECDSA_SIG *ret = ECDSA_SIG_parse(&cbs);
-  if (ret == NULL) {
-    return NULL;
-  }
-  if (out != NULL) {
-    ECDSA_SIG_free(*out);
-    *out = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, ECDSA_SIG_parse);
 }
 
 int i2d_ECDSA_SIG(const ECDSA_SIG *sig, uint8_t **outp) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) || !ECDSA_SIG_marshal(&cbb, sig)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/64, outp,
+      [&](CBB *cbb) -> bool { return ECDSA_SIG_marshal(cbb, sig); });
 }
diff --git a/crypto/evp/evp_asn1.cc b/crypto/evp/evp_asn1.cc
index a135b0a..c6f3870 100644
--- a/crypto/evp/evp_asn1.cc
+++ b/crypto/evp/evp_asn1.cc
@@ -221,35 +221,26 @@
 
 EVP_PKEY *d2i_PrivateKey(int type, EVP_PKEY **out, const uint8_t **inp,
                          long len) {
-  if (len < 0) {
-    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
-    return nullptr;
-  }
-
-  // Parse with the legacy format.
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  bssl::UniquePtr<EVP_PKEY> ret = old_priv_decode(&cbs, type);
-  if (ret == nullptr) {
-    // Try again with PKCS#8.
-    ERR_clear_error();
-    CBS_init(&cbs, *inp, (size_t)len);
-    ret.reset(EVP_parse_private_key(&cbs));
-    if (ret == nullptr) {
-      return nullptr;
-    }
-    if (EVP_PKEY_id(ret.get()) != type) {
-      OPENSSL_PUT_ERROR(EVP, EVP_R_DIFFERENT_KEY_TYPES);
-      return nullptr;
-    }
-  }
-
-  if (out != nullptr) {
-    EVP_PKEY_free(*out);
-    *out = ret.get();
-  }
-  *inp = CBS_data(&cbs);
-  return ret.release();
+  return bssl::D2IFromCBS(
+      out, inp, len, [&](CBS *cbs) -> bssl::UniquePtr<EVP_PKEY> {
+        // Parse with the legacy format.
+        CBS copy = *cbs;
+        bssl::UniquePtr<EVP_PKEY> ret = old_priv_decode(cbs, type);
+        if (ret == nullptr) {
+          // Try again with PKCS#8.
+          ERR_clear_error();
+          *cbs = copy;
+          ret.reset(EVP_parse_private_key(cbs));
+          if (ret == nullptr) {
+            return nullptr;
+          }
+          if (EVP_PKEY_id(ret.get()) != type) {
+            OPENSSL_PUT_ERROR(EVP, EVP_R_DIFFERENT_KEY_TYPES);
+            return nullptr;
+          }
+        }
+        return ret;
+      });
 }
 
 // num_elements parses one SEQUENCE from |in| and returns the number of elements
@@ -323,71 +314,45 @@
 
 EVP_PKEY *d2i_PublicKey(int type, EVP_PKEY **out, const uint8_t **inp,
                         long len) {
-  bssl::UniquePtr<EVP_PKEY> ret(EVP_PKEY_new());
-  if (ret == nullptr) {
-    return nullptr;
-  }
+  return bssl::D2IFromCBS(
+      out, inp, len, [&](CBS *cbs) -> bssl::UniquePtr<EVP_PKEY> {
+        bssl::UniquePtr<EVP_PKEY> ret(EVP_PKEY_new());
+        if (ret == nullptr) {
+          return nullptr;
+        }
+        switch (type) {
+          case EVP_PKEY_RSA: {
+            bssl::UniquePtr<RSA> rsa(RSA_parse_public_key(cbs));
+            if (rsa == nullptr) {
+              return nullptr;
+            }
+            EVP_PKEY_assign_RSA(ret.get(), rsa.release());
+            return ret;
+          }
 
-  CBS cbs;
-  CBS_init(&cbs, *inp, len < 0 ? 0 : (size_t)len);
-  switch (type) {
-    case EVP_PKEY_RSA: {
-      bssl::UniquePtr<RSA> rsa(RSA_parse_public_key(&cbs));
-      if (rsa == nullptr) {
-        return nullptr;
-      }
-      EVP_PKEY_assign_RSA(ret.get(), rsa.release());
-      break;
-    }
-
-    // Unlike OpenSSL, we do not support EC keys with this API. The raw EC
-    // public key serialization requires knowing the group. In OpenSSL, calling
-    // this function with |EVP_PKEY_EC| and setting |out| to nullptr does not
-    // work. It requires |*out| to include a partially-initialized |EVP_PKEY| to
-    // extract the group.
-    default:
-      OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_PUBLIC_KEY_TYPE);
-      return nullptr;
-  }
-
-  *inp = CBS_data(&cbs);
-  if (out != nullptr) {
-    EVP_PKEY_free(*out);
-    *out = ret.get();
-  }
-  return ret.release();
+          // Unlike OpenSSL, we do not support EC keys with this API. The raw EC
+          // public key serialization requires knowing the group. In OpenSSL,
+          // calling this function with |EVP_PKEY_EC| and setting |out| to
+          // nullptr does not work. It requires |*out| to include a
+          // partially-initialized |EVP_PKEY| to extract the group.
+          default:
+            OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_PUBLIC_KEY_TYPE);
+            return nullptr;
+        }
+      });
 }
 
 EVP_PKEY *d2i_PUBKEY(EVP_PKEY **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return nullptr;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  bssl::UniquePtr<EVP_PKEY> ret(EVP_parse_public_key(&cbs));
-  if (ret == nullptr) {
-    return nullptr;
-  }
-  if (out != nullptr) {
-    EVP_PKEY_free(*out);
-    *out = ret.get();
-  }
-  *inp = CBS_data(&cbs);
-  return ret.release();
+  return bssl::D2IFromCBS(out, inp, len, EVP_parse_public_key);
 }
 
 int i2d_PUBKEY(const EVP_PKEY *pkey, uint8_t **outp) {
-  if (pkey == NULL) {
+  if (pkey == nullptr) {
     return 0;
   }
-
-  CBB cbb;
-  if (!CBB_init(&cbb, 128) ||
-      !EVP_marshal_public_key(&cbb, pkey)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/128, outp,
+      [&](CBB *cbb) -> bool { return EVP_marshal_public_key(cbb, pkey); });
 }
 
 static bssl::UniquePtr<EVP_PKEY> parse_spki(
@@ -406,25 +371,13 @@
 }
 
 RSA *d2i_RSA_PUBKEY(RSA **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return nullptr;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  bssl::UniquePtr<EVP_PKEY> pkey = parse_spki(&cbs, EVP_pkey_rsa());
-  if (pkey == nullptr) {
-    return nullptr;
-  }
-  bssl::UniquePtr<RSA> rsa(EVP_PKEY_get1_RSA(pkey.get()));
-  if (rsa == nullptr) {
-    return nullptr;
-  }
-  if (out != nullptr) {
-    RSA_free(*out);
-    *out = rsa.get();
-  }
-  *inp = CBS_data(&cbs);
-  return rsa.release();
+  return bssl::D2IFromCBS(out, inp, len, [](CBS *cbs) -> bssl::UniquePtr<RSA> {
+    bssl::UniquePtr<EVP_PKEY> pkey = parse_spki(cbs, EVP_pkey_rsa());
+    if (pkey == nullptr) {
+      return nullptr;
+    }
+    return bssl::UniquePtr<RSA>(EVP_PKEY_get1_RSA(pkey.get()));
+  });
 }
 
 int i2d_RSA_PUBKEY(const RSA *rsa, uint8_t **outp) {
@@ -442,25 +395,13 @@
 }
 
 DSA *d2i_DSA_PUBKEY(DSA **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return nullptr;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  bssl::UniquePtr<EVP_PKEY> pkey = parse_spki(&cbs, EVP_pkey_dsa());
-  if (pkey == nullptr) {
-    return nullptr;
-  }
-  bssl::UniquePtr<DSA> dsa(EVP_PKEY_get1_DSA(pkey.get()));
-  if (dsa == nullptr) {
-    return nullptr;
-  }
-  if (out != nullptr) {
-    DSA_free(*out);
-    *out = dsa.get();
-  }
-  *inp = CBS_data(&cbs);
-  return dsa.release();
+  return bssl::D2IFromCBS(out, inp, len, [](CBS *cbs) -> bssl::UniquePtr<DSA> {
+    bssl::UniquePtr<EVP_PKEY> pkey = parse_spki(cbs, EVP_pkey_dsa());
+    if (pkey == nullptr) {
+      return nullptr;
+    }
+    return bssl::UniquePtr<DSA>(EVP_PKEY_get1_DSA(pkey.get()));
+  });
 }
 
 int i2d_DSA_PUBKEY(const DSA *dsa, uint8_t **outp) {
@@ -478,27 +419,17 @@
 }
 
 EC_KEY *d2i_EC_PUBKEY(EC_KEY **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return nullptr;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  const EVP_PKEY_ALG *const algs[] = {EVP_pkey_ec_p224(), EVP_pkey_ec_p256(),
-                                      EVP_pkey_ec_p384(), EVP_pkey_ec_p521()};
-  bssl::UniquePtr<EVP_PKEY> pkey = parse_spki(&cbs, algs);
-  if (pkey == nullptr) {
-    return nullptr;
-  }
-  bssl::UniquePtr<EC_KEY> ec_key(EVP_PKEY_get1_EC_KEY(pkey.get()));
-  if (ec_key == nullptr) {
-    return nullptr;
-  }
-  if (out != nullptr) {
-    EC_KEY_free(*out);
-    *out = ec_key.get();
-  }
-  *inp = CBS_data(&cbs);
-  return ec_key.release();
+  return bssl::D2IFromCBS(
+      out, inp, len, [](CBS *cbs) -> bssl::UniquePtr<EC_KEY> {
+        const EVP_PKEY_ALG *const algs[] = {
+            EVP_pkey_ec_p224(), EVP_pkey_ec_p256(), EVP_pkey_ec_p384(),
+            EVP_pkey_ec_p521()};
+        bssl::UniquePtr<EVP_PKEY> pkey = parse_spki(cbs, algs);
+        if (pkey == nullptr) {
+          return nullptr;
+        }
+        return bssl::UniquePtr<EC_KEY>(EVP_PKEY_get1_EC_KEY(pkey.get()));
+      });
 }
 
 int i2d_EC_PUBKEY(const EC_KEY *ec_key, uint8_t **outp) {
diff --git a/crypto/rsa/rsa_asn1.cc b/crypto/rsa/rsa_asn1.cc
index d22e584..a57ff0e 100644
--- a/crypto/rsa/rsa_asn1.cc
+++ b/crypto/rsa/rsa_asn1.cc
@@ -210,59 +210,23 @@
 }
 
 RSA *d2i_RSAPublicKey(RSA **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return NULL;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  RSA *ret = RSA_parse_public_key(&cbs);
-  if (ret == NULL) {
-    return NULL;
-  }
-  if (out != NULL) {
-    RSA_free(*out);
-    *out = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, RSA_parse_public_key);
 }
 
 int i2d_RSAPublicKey(const RSA *in, uint8_t **outp) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||
-      !RSA_marshal_public_key(&cbb, in)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/256, outp,
+      [&](CBB *cbb) -> bool { return RSA_marshal_public_key(cbb, in); });
 }
 
 RSA *d2i_RSAPrivateKey(RSA **out, const uint8_t **inp, long len) {
-  if (len < 0) {
-    return NULL;
-  }
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  RSA *ret = RSA_parse_private_key(&cbs);
-  if (ret == NULL) {
-    return NULL;
-  }
-  if (out != NULL) {
-    RSA_free(*out);
-    *out = ret;
-  }
-  *inp = CBS_data(&cbs);
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len, RSA_parse_private_key);
 }
 
 int i2d_RSAPrivateKey(const RSA *in, uint8_t **outp) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 0) ||
-      !RSA_marshal_private_key(&cbb, in)) {
-    CBB_cleanup(&cbb);
-    return -1;
-  }
-  return CBB_finish_i2d(&cbb, outp);
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/512, outp,
+      [&](CBB *cbb) -> bool { return RSA_marshal_private_key(cbb, in); });
 }
 
 RSA *RSAPublicKey_dup(const RSA *rsa) {
diff --git a/crypto/x509/x_x509.cc b/crypto/x509/x_x509.cc
index e2869ac..066a978 100644
--- a/crypto/x509/x_x509.cc
+++ b/crypto/x509/x_x509.cc
@@ -171,27 +171,8 @@
 }
 
 X509 *d2i_X509(X509 **out, const uint8_t **inp, long len) {
-  X509 *ret = NULL;
-  if (len < 0) {
-    OPENSSL_PUT_ERROR(ASN1, ASN1_R_BUFFER_TOO_SMALL);
-    goto err;
-  }
-
-  CBS cbs;
-  CBS_init(&cbs, *inp, (size_t)len);
-  ret = x509_parse(&cbs, NULL);
-  if (ret == NULL) {
-    goto err;
-  }
-
-  *inp = CBS_data(&cbs);
-
-err:
-  if (out != NULL) {
-    X509_free(*out);
-    *out = ret;
-  }
-  return ret;
+  return bssl::D2IFromCBS(out, inp, len,
+                          [](CBS *cbs) { return x509_parse(cbs, nullptr); });
 }
 
 int i2d_X509(X509 *x509, uint8_t **outp) {
@@ -200,26 +181,28 @@
     return -1;
   }
 
-  bssl::ScopedCBB cbb;
-  CBB cert;
-  if (!CBB_init(cbb.get(), 64) ||  //
-      !CBB_add_asn1(cbb.get(), &cert, CBS_ASN1_SEQUENCE)) {
-    return -1;
-  }
+  return bssl::I2DFromCBB(
+      /*initial_capacity=*/64, outp, [&](CBB *cbb) -> bool {
+        CBB cert;
+        if (!CBB_add_asn1(cbb, &cert, CBS_ASN1_SEQUENCE)) {
+          return false;
+        }
 
-  // TODO(crbug.com/boringssl/443): When the rest of the library is decoupled
-  // from the tasn_*.c implementation, replace this with |CBS|-based functions.
-  uint8_t *out;
-  int len = i2d_X509_CINF(x509->cert_info, NULL);
-  if (len < 0 ||  //
-      !CBB_add_space(&cert, &out, static_cast<size_t>(len)) ||
-      i2d_X509_CINF(x509->cert_info, &out) != len ||
-      !x509_marshal_algorithm(&cert, x509->sig_alg) ||
-      !asn1_marshal_bit_string(&cert, &x509->signature, /*tag=*/0)) {
-    return -1;
-  }
-
-  return CBB_finish_i2d(cbb.get(), outp);
+        // TODO(crbug.com/boringssl/443): When the rest of the library is
+        // decoupled from the tasn_*.c implementation, replace this with
+        // |CBB|-based functions.
+        uint8_t *out;
+        int len = i2d_X509_CINF(x509->cert_info, NULL);
+        if (len < 0 ||  //
+            !CBB_add_space(&cert, &out, static_cast<size_t>(len)) ||
+            i2d_X509_CINF(x509->cert_info, &out) != len ||
+            !x509_marshal_algorithm(&cert, x509->sig_alg) ||
+            !asn1_marshal_bit_string(&cert, &x509->signature, /*tag=*/0) ||
+            !CBB_flush(cbb)) {
+          return false;
+        }
+        return true;
+      });
 }
 
 static int x509_new_cb(ASN1_VALUE **pval, const ASN1_ITEM *it) {
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 51417d4..9ee7ec1 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -5354,12 +5354,12 @@
 // Use |SSL_SESSION_to_bytes| instead.
 OPENSSL_EXPORT int i2d_SSL_SESSION(SSL_SESSION *in, uint8_t **pp);
 
-// d2i_SSL_SESSION parses a serialized session from the |length| bytes pointed
-// to by |*pp|, as described in |d2i_SAMPLE|.
+// d2i_SSL_SESSION parses a serialized session from the |len| bytes pointed to
+// by |*inp|, as described in |d2i_SAMPLE|.
 //
 // Use |SSL_SESSION_from_bytes| instead.
-OPENSSL_EXPORT SSL_SESSION *d2i_SSL_SESSION(SSL_SESSION **a, const uint8_t **pp,
-                                            long length);
+OPENSSL_EXPORT SSL_SESSION *d2i_SSL_SESSION(SSL_SESSION **out,
+                                            const uint8_t **inp, long len);
 
 // i2d_SSL_SESSION_bio serializes |session| and writes the result to |bio|. It
 // returns the number of bytes written on success and <= 0 on error.
diff --git a/ssl/ssl_x509.cc b/ssl/ssl_x509.cc
index 51e4ffa..4fe0da8 100644
--- a/ssl/ssl_x509.cc
+++ b/ssl/ssl_x509.cc
@@ -26,6 +26,7 @@
 #include <openssl/x509.h>
 
 #include "../crypto/internal.h"
+#include "../crypto/bytestring/internal.h"
 #include "internal.h"
 
 
@@ -820,27 +821,11 @@
 
 IMPLEMENT_PEM_rw(SSL_SESSION, SSL_SESSION, PEM_STRING_SSL_SESSION, SSL_SESSION)
 
-SSL_SESSION *d2i_SSL_SESSION(SSL_SESSION **a, const uint8_t **pp, long length) {
-  if (length < 0) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return NULL;
-  }
-
-  CBS cbs;
-  CBS_init(&cbs, *pp, length);
-
-  UniquePtr<SSL_SESSION> ret = SSL_SESSION_parse(&cbs, &ssl_crypto_x509_method,
-                                                 NULL /* no buffer pool */);
-  if (!ret) {
-    return NULL;
-  }
-
-  if (a) {
-    SSL_SESSION_free(*a);
-    *a = ret.get();
-  }
-  *pp = CBS_data(&cbs);
-  return ret.release();
+SSL_SESSION *d2i_SSL_SESSION(SSL_SESSION **out, const uint8_t **inp, long len) {
+  return bssl::D2IFromCBS(out, inp, len, [](CBS *cbs) {
+    return SSL_SESSION_parse(cbs, &ssl_crypto_x509_method,
+                             nullptr /* no buffer pool */);
+  });
 }
 
 STACK_OF(X509_NAME) *SSL_dup_CA_list(STACK_OF(X509_NAME) *list) {