Disconnect handshake message creation from init_buf.

This allows us to use CBB for all handshake messages. Now, SSL_PROTOCOL_METHOD
is responsible for implementing a trio of CBB-related hooks to assemble
handshake messages.

Change-Id: I144d3cac4f05b6637bf45d3f838673fc5c854405
Reviewed-on: https://boringssl-review.googlesource.com/8440
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index a768fd7..6ce9412 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -4140,6 +4140,10 @@
    * version. */
   const SSL3_ENC_METHOD *enc_method;
 
+  /* pending_message is the current outgoing handshake message. */
+  uint8_t *pending_message;
+  uint32_t pending_message_len;
+
   /* State pertaining to the pending handshake.
    *
    * TODO(davidben): State is current spread all over the place. Move
diff --git a/ssl/d1_both.c b/ssl/d1_both.c
index f021d0a..d6f4e7b 100644
--- a/ssl/d1_both.c
+++ b/ssl/d1_both.c
@@ -305,9 +305,16 @@
   return 1;
 }
 
-int dtls1_do_handshake_write(SSL *ssl, size_t *out_offset, const uint8_t *in,
-                             size_t offset, size_t len,
-                             enum dtls1_use_epoch_t use_epoch) {
+/* dtls1_do_handshake_write writes handshake message |in| using the given epoch,
+ * starting |offset| bytes into the message body. It returns one on success. On
+ * error, it returns <= 0 and sets |*out_offset| to the number of bytes of body
+ * that were successfully written. This may be used to retry the write
+ * later. |in| must be a reassembled handshake message with the full DTLS
+ * handshake header. */
+static int dtls1_do_handshake_write(SSL *ssl, size_t *out_offset,
+                                    const uint8_t *in, size_t offset,
+                                    size_t len,
+                                    enum dtls1_use_epoch_t use_epoch) {
   dtls1_update_mtu(ssl);
 
   int ret = -1;
@@ -404,6 +411,108 @@
   return ret;
 }
 
+void dtls_clear_outgoing_messages(SSL *ssl) {
+  size_t i;
+  for (i = 0; i < ssl->d1->outgoing_messages_len; i++) {
+    OPENSSL_free(ssl->d1->outgoing_messages[i].data);
+    ssl->d1->outgoing_messages[i].data = NULL;
+  }
+  ssl->d1->outgoing_messages_len = 0;
+}
+
+/* dtls1_buffer_change_cipher_spec adds a ChangeCipherSpec to the current
+ * handshake flight. */
+static int dtls1_buffer_change_cipher_spec(SSL *ssl) {
+  if (ssl->d1->outgoing_messages_len >= SSL_MAX_HANDSHAKE_FLIGHT) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  DTLS_OUTGOING_MESSAGE *msg =
+      &ssl->d1->outgoing_messages[ssl->d1->outgoing_messages_len];
+  msg->data = NULL;
+  msg->len = 0;
+  msg->epoch = ssl->d1->w_epoch;
+  msg->is_ccs = 1;
+
+  ssl->d1->outgoing_messages_len++;
+  return 1;
+}
+
+static int dtls1_buffer_message(SSL *ssl, uint8_t *data, size_t len) {
+  if (ssl->d1->outgoing_messages_len >= SSL_MAX_HANDSHAKE_FLIGHT) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    OPENSSL_free(data);
+    return 0;
+  }
+
+  DTLS_OUTGOING_MESSAGE *msg =
+      &ssl->d1->outgoing_messages[ssl->d1->outgoing_messages_len];
+  msg->data = data;
+  msg->len = len;
+  msg->epoch = ssl->d1->w_epoch;
+  msg->is_ccs = 0;
+
+  ssl->d1->outgoing_messages_len++;
+  return 1;
+}
+
+int dtls1_init_message(SSL *ssl, CBB *cbb, CBB *body, uint8_t type) {
+  /* Pick a modest size hint to save most of the |realloc| calls. */
+  if (!CBB_init(cbb, 64) ||
+      !CBB_add_u8(cbb, type) ||
+      !CBB_add_u24(cbb, 0 /* length (filled in later) */) ||
+      !CBB_add_u16(cbb, ssl->d1->handshake_write_seq) ||
+      !CBB_add_u24(cbb, 0 /* offset */) ||
+      !CBB_add_u24_length_prefixed(cbb, body)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+int dtls1_finish_message(SSL *ssl, CBB *cbb) {
+  uint8_t *msg = NULL;
+  size_t len;
+  if (!CBB_finish(cbb, &msg, &len) ||
+      len > 0xffffffffu ||
+      len < DTLS1_HM_HEADER_LENGTH) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    OPENSSL_free(msg);
+    return 0;
+  }
+
+  /* Fix up the header. Copy the fragment length into the total message
+   * length. */
+  memcpy(msg + 1, msg + DTLS1_HM_HEADER_LENGTH - 3, 3);
+
+  ssl3_update_handshake_hash(ssl, msg, len);
+
+  ssl->d1->handshake_write_seq++;
+  ssl->init_off = 0;
+  return dtls1_buffer_message(ssl, msg, len);
+}
+
+int dtls1_write_message(SSL *ssl) {
+  if (ssl->d1->outgoing_messages_len == 0) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return -1;
+  }
+
+  const DTLS_OUTGOING_MESSAGE *msg =
+      &ssl->d1->outgoing_messages[ssl->d1->outgoing_messages_len - 1];
+  if (msg->is_ccs) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return -1;
+  }
+
+  size_t offset = ssl->init_off;
+  int ret = dtls1_do_handshake_write(ssl, &offset, msg->data, offset, msg->len,
+                                     dtls1_use_current_epoch);
+  ssl->init_off = offset;
+  return ret;
+}
+
 /* dtls1_is_next_message_complete returns one if the next handshake message is
  * complete and zero otherwise. */
 static int dtls1_is_next_message_complete(SSL *ssl) {
@@ -693,45 +802,6 @@
   return ret;
 }
 
-/* dtls1_buffer_change_cipher_spec adds a ChangeCipherSpec to the current
- * handshake flight. */
-static int dtls1_buffer_change_cipher_spec(SSL *ssl) {
-  if (ssl->d1->outgoing_messages_len >= SSL_MAX_HANDSHAKE_FLIGHT) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return 0;
-  }
-
-  DTLS_OUTGOING_MESSAGE *msg =
-      &ssl->d1->outgoing_messages[ssl->d1->outgoing_messages_len];
-  msg->data = NULL;
-  msg->len = 0;
-  msg->epoch = ssl->d1->w_epoch;
-  msg->is_ccs = 1;
-
-  ssl->d1->outgoing_messages_len++;
-  return 1;
-}
-
-int dtls1_buffer_message(SSL *ssl) {
-  if (ssl->d1->outgoing_messages_len >= SSL_MAX_HANDSHAKE_FLIGHT) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return 0;
-  }
-
-  DTLS_OUTGOING_MESSAGE *msg =
-      &ssl->d1->outgoing_messages[ssl->d1->outgoing_messages_len];
-  msg->data = BUF_memdup(ssl->init_buf->data, ssl->init_num);
-  if (msg->data == NULL) {
-    return 0;
-  }
-  msg->len = ssl->init_num;
-  msg->epoch = ssl->d1->w_epoch;
-  msg->is_ccs = 0;
-
-  ssl->d1->outgoing_messages_len++;
-  return 1;
-}
-
 int dtls1_send_change_cipher_spec(SSL *ssl, int a, int b) {
   if (ssl->state == a) {
     dtls1_buffer_change_cipher_spec(ssl);
@@ -741,15 +811,6 @@
   return dtls1_write_change_cipher_spec(ssl, dtls1_use_current_epoch);
 }
 
-void dtls_clear_outgoing_messages(SSL *ssl) {
-  size_t i;
-  for (i = 0; i < ssl->d1->outgoing_messages_len; i++) {
-    OPENSSL_free(ssl->d1->outgoing_messages[i].data);
-    ssl->d1->outgoing_messages[i].data = NULL;
-  }
-  ssl->d1->outgoing_messages_len = 0;
-}
-
 void dtls_clear_incoming_messages(SSL *ssl) {
   size_t i;
   for (i = 0; i < SSL_MAX_HANDSHAKE_FLIGHT; i++) {
diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c
index d738c57..e0853cf 100644
--- a/ssl/d1_lib.c
+++ b/ssl/d1_lib.c
@@ -289,39 +289,6 @@
 #endif
 }
 
-int dtls1_set_handshake_header(SSL *ssl, int htype, unsigned long len) {
-  uint8_t *message = (uint8_t *)ssl->init_buf->data;
-
-  uint16_t seq = ssl->d1->handshake_write_seq;
-  ssl->d1->handshake_write_seq++;
-
-  ssl->init_num = (int)len + DTLS1_HM_HEADER_LENGTH;
-  ssl->init_off = 0;
-
-  /* Serialize the message header as if it were a single fragment. */
-  uint8_t *p = message;
-  *p++ = htype;
-  l2n3(len, p);
-  s2n(seq, p);
-  l2n3(0, p);
-  l2n3(len, p);
-  assert(p == message + DTLS1_HM_HEADER_LENGTH);
-
-  /* Buffer the message to handle re-xmits. */
-  dtls1_buffer_message(ssl);
-
-  return ssl3_update_handshake_hash(ssl, message, ssl->init_num);
-}
-
-int dtls1_handshake_write(SSL *ssl) {
-  size_t offset = ssl->init_off;
-  int ret = dtls1_do_handshake_write(
-      ssl, &offset, (const uint8_t *)ssl->init_buf->data, offset, ssl->init_num,
-      dtls1_use_current_epoch);
-  ssl->init_off = offset;
-  return ret;
-}
-
 void dtls1_expect_flight(SSL *ssl) {
   dtls1_start_timer(ssl);
 }
diff --git a/ssl/d1_meth.c b/ssl/d1_meth.c
index 32a4651..78119a0 100644
--- a/ssl/d1_meth.c
+++ b/ssl/d1_meth.c
@@ -70,9 +70,9 @@
     dtls1_write_app_data,
     dtls1_dispatch_alert,
     dtls1_supports_cipher,
-    DTLS1_HM_HEADER_LENGTH,
-    dtls1_set_handshake_header,
-    dtls1_handshake_write,
+    dtls1_init_message,
+    dtls1_finish_message,
+    dtls1_write_message,
     dtls1_send_change_cipher_spec,
     dtls1_expect_flight,
     dtls1_received_flight,
diff --git a/ssl/handshake_client.c b/ssl/handshake_client.c
index 3b33574..e5c6e2f 100644
--- a/ssl/handshake_client.c
+++ b/ssl/handshake_client.c
@@ -611,7 +611,7 @@
 
 static int ssl3_send_client_hello(SSL *ssl) {
   if (ssl->state == SSL3_ST_CW_CLNT_HELLO_B) {
-    return ssl_do_write(ssl);
+    return ssl->method->write_message(ssl);
   }
 
   /* The handshake buffer is reset on every ClientHello. Notably, in DTLS, we
@@ -662,12 +662,11 @@
   int has_session = ssl->session != NULL &&
                     !ssl->s3->initial_handshake_complete;
 
-  CBB child;
-  if (!CBB_init_fixed(&cbb, ssl_handshake_start(ssl),
-                      ssl->init_buf->max - SSL_HM_HEADER_LENGTH(ssl)) ||
-      !CBB_add_u16(&cbb, ssl->client_version) ||
-      !CBB_add_bytes(&cbb, ssl->s3->client_random, SSL3_RANDOM_SIZE) ||
-      !CBB_add_u8_length_prefixed(&cbb, &child) ||
+  CBB body, child;
+  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_CLIENT_HELLO) ||
+      !CBB_add_u16(&body, ssl->client_version) ||
+      !CBB_add_bytes(&body, ssl->s3->client_random, SSL3_RANDOM_SIZE) ||
+      !CBB_add_u8_length_prefixed(&body, &child) ||
       (has_session &&
        !CBB_add_bytes(&child, ssl->session->session_id,
                       ssl->session->session_id_length))) {
@@ -675,25 +674,24 @@
   }
 
   if (SSL_IS_DTLS(ssl)) {
-    if (!CBB_add_u8_length_prefixed(&cbb, &child) ||
+    if (!CBB_add_u8_length_prefixed(&body, &child) ||
         !CBB_add_bytes(&child, ssl->d1->cookie, ssl->d1->cookie_len)) {
       goto err;
     }
   }
 
-  size_t length;
-  if (!ssl3_write_client_cipher_list(ssl, &cbb) ||
-      !CBB_add_u8(&cbb, 1 /* one compression method */) ||
-      !CBB_add_u8(&cbb, 0 /* null compression */) ||
-      !ssl_add_clienthello_tlsext(ssl, &cbb,
-                                  CBB_len(&cbb) + SSL_HM_HEADER_LENGTH(ssl)) ||
-      !CBB_finish(&cbb, NULL, &length) ||
-      !ssl_set_handshake_header(ssl, SSL3_MT_CLIENT_HELLO, length)) {
+  size_t header_len =
+      SSL_IS_DTLS(ssl) ? DTLS1_HM_HEADER_LENGTH : SSL3_HM_HEADER_LENGTH;
+  if (!ssl3_write_client_cipher_list(ssl, &body) ||
+      !CBB_add_u8(&body, 1 /* one compression method */) ||
+      !CBB_add_u8(&body, 0 /* null compression */) ||
+      !ssl_add_clienthello_tlsext(ssl, &body, header_len + CBB_len(&body)) ||
+      !ssl->method->finish_message(ssl, &cbb)) {
     goto err;
   }
 
   ssl->state = SSL3_ST_CW_CLNT_HELLO_B;
-  return ssl_do_write(ssl);
+  return ssl->method->write_message(ssl);
 
 err:
   CBB_cleanup(&cbb);
@@ -1594,10 +1592,10 @@
         return 1;
       }
 
-      /* In TLS, send an empty Certificate message. */
-      uint8_t *p = ssl_handshake_start(ssl);
-      l2n3(0, p);
-      if (!ssl_set_handshake_header(ssl, SSL3_MT_CERTIFICATE, 3)) {
+      CBB cbb, body;
+      if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_CERTIFICATE) ||
+          !CBB_add_u24(&body, 0 /* no certificates */) ||
+          !ssl->method->finish_message(ssl, &cbb)) {
         return -1;
       }
     } else if (!ssl3_output_cert_chain(ssl)) {
@@ -1607,7 +1605,7 @@
   }
 
   assert(ssl->state == SSL3_ST_CW_CERT_D);
-  return ssl_do_write(ssl);
+  return ssl->method->write_message(ssl);
 }
 
 OPENSSL_COMPILE_ASSERT(sizeof(size_t) >= sizeof(unsigned),
@@ -1615,15 +1613,15 @@
 
 static int ssl3_send_client_key_exchange(SSL *ssl) {
   if (ssl->state == SSL3_ST_CW_KEY_EXCH_B) {
-    return ssl_do_write(ssl);
+    return ssl->method->write_message(ssl);
   }
   assert(ssl->state == SSL3_ST_CW_KEY_EXCH_A);
 
   uint8_t *pms = NULL;
   size_t pms_len = 0;
-  CBB cbb;
-  if (!CBB_init_fixed(&cbb, ssl_handshake_start(ssl),
-                      ssl->init_buf->max - SSL_HM_HEADER_LENGTH(ssl))) {
+  CBB cbb, body;
+  if (!ssl->method->init_message(ssl, &cbb, &body,
+                                 SSL3_MT_CLIENT_KEY_EXCHANGE)) {
     goto err;
   }
 
@@ -1660,10 +1658,10 @@
 
     /* Write out psk_identity. */
     CBB child;
-    if (!CBB_add_u16_length_prefixed(&cbb, &child) ||
+    if (!CBB_add_u16_length_prefixed(&body, &child) ||
         !CBB_add_bytes(&child, (const uint8_t *)identity,
                        OPENSSL_strnlen(identity, sizeof(identity))) ||
-        !CBB_flush(&cbb)) {
+        !CBB_flush(&body)) {
       goto err;
     }
   }
@@ -1698,11 +1696,11 @@
       goto err;
     }
 
-    CBB child, *enc_pms = &cbb;
+    CBB child, *enc_pms = &body;
     size_t enc_pms_len;
     /* In TLS, there is a length prefix. */
     if (ssl->version > SSL3_VERSION) {
-      if (!CBB_add_u16_length_prefixed(&cbb, &child)) {
+      if (!CBB_add_u16_length_prefixed(&body, &child)) {
         goto err;
       }
       enc_pms = &child;
@@ -1715,13 +1713,13 @@
         /* Log the premaster secret, if logging is enabled. */
         !ssl_log_rsa_client_key_exchange(ssl, ptr, enc_pms_len, pms, pms_len) ||
         !CBB_did_write(enc_pms, enc_pms_len) ||
-        !CBB_flush(&cbb)) {
+        !CBB_flush(&body)) {
       goto err;
     }
   } else if (alg_k & (SSL_kECDHE|SSL_kDHE|SSL_kCECPQ1)) {
     /* Generate a keypair and serialize the public half. */
     CBB child;
-    if (!SSL_ECDH_CTX_add_key(&ssl->s3->tmp.ecdh_ctx, &cbb, &child)) {
+    if (!SSL_ECDH_CTX_add_key(&ssl->s3->tmp.ecdh_ctx, &body, &child)) {
       goto err;
     }
 
@@ -1733,7 +1731,7 @@
       ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
       goto err;
     }
-    if (!CBB_flush(&cbb)) {
+    if (!CBB_flush(&body)) {
       goto err;
     }
 
@@ -1783,9 +1781,7 @@
 
   /* The message must be added to the finished hash before calculating the
    * master secret. */
-  size_t length;
-  if (!CBB_finish(&cbb, NULL, &length) ||
-      !ssl_set_handshake_header(ssl, SSL3_MT_CLIENT_KEY_EXCHANGE, length)) {
+  if (!ssl->method->finish_message(ssl, &cbb)) {
     goto err;
   }
   ssl->state = SSL3_ST_CW_KEY_EXCH_B;
@@ -1799,8 +1795,7 @@
   OPENSSL_cleanse(pms, pms_len);
   OPENSSL_free(pms);
 
-  /* SSL3_ST_CW_KEY_EXCH_B */
-  return ssl_do_write(ssl);
+  return ssl->method->write_message(ssl);
 
 err:
   CBB_cleanup(&cbb);
@@ -1813,14 +1808,14 @@
 
 static int ssl3_send_cert_verify(SSL *ssl) {
   if (ssl->state == SSL3_ST_CW_CERT_VRFY_C) {
-    return ssl_do_write(ssl);
+    return ssl->method->write_message(ssl);
   }
 
   assert(ssl_has_private_key(ssl));
 
-  CBB cbb, child;
-  if (!CBB_init_fixed(&cbb, ssl_handshake_start(ssl),
-                      ssl->init_buf->max - SSL_HM_HEADER_LENGTH(ssl))) {
+  CBB cbb, body, child;
+  if (!ssl->method->init_message(ssl, &cbb, &body,
+                                 SSL3_MT_CERTIFICATE_VERIFY)) {
     goto err;
   }
 
@@ -1828,7 +1823,7 @@
   const EVP_MD *md = NULL;
   if (ssl3_protocol_version(ssl) >= TLS1_2_VERSION) {
     md = tls1_choose_signing_digest(ssl);
-    if (!tls12_add_sigandhash(ssl, &cbb, md)) {
+    if (!tls12_add_sigandhash(ssl, &body, md)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       goto err;
     }
@@ -1837,7 +1832,7 @@
   /* Set aside space for the signature. */
   const size_t max_sig_len = ssl_private_key_max_signature_len(ssl);
   uint8_t *ptr;
-  if (!CBB_add_u16_length_prefixed(&cbb, &child) ||
+  if (!CBB_add_u16_length_prefixed(&body, &child) ||
       !CBB_reserve(&child, &ptr, max_sig_len)) {
     goto err;
   }
@@ -1877,15 +1872,13 @@
       goto err;
   }
 
-  size_t length;
   if (!CBB_did_write(&child, sig_len) ||
-      !CBB_finish(&cbb, NULL, &length) ||
-      !ssl_set_handshake_header(ssl, SSL3_MT_CERTIFICATE_VERIFY, length)) {
+      !ssl->method->finish_message(ssl, &cbb)) {
     goto err;
   }
 
   ssl->state = SSL3_ST_CW_CERT_VRFY_C;
-  return ssl_do_write(ssl);
+  return ssl->method->write_message(ssl);
 
 err:
   CBB_cleanup(&cbb);
@@ -1894,7 +1887,7 @@
 
 static int ssl3_send_next_proto(SSL *ssl) {
   if (ssl->state == SSL3_ST_CW_NEXT_PROTO_B) {
-    return ssl_do_write(ssl);
+    return ssl->method->write_message(ssl);
   }
 
   assert(ssl->state == SSL3_ST_CW_NEXT_PROTO_A);
@@ -1902,30 +1895,26 @@
   static const uint8_t kZero[32] = {0};
   size_t padding_len = 32 - ((ssl->s3->next_proto_negotiated_len + 2) % 32);
 
-  CBB cbb, child;
-  size_t length;
-  CBB_zero(&cbb);
-  if (!CBB_init_fixed(&cbb, ssl_handshake_start(ssl),
-                      ssl->init_buf->max - SSL_HM_HEADER_LENGTH(ssl)) ||
-      !CBB_add_u8_length_prefixed(&cbb, &child) ||
+  CBB cbb, body, child;
+  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_NEXT_PROTO) ||
+      !CBB_add_u8_length_prefixed(&body, &child) ||
       !CBB_add_bytes(&child, ssl->s3->next_proto_negotiated,
                      ssl->s3->next_proto_negotiated_len) ||
-      !CBB_add_u8_length_prefixed(&cbb, &child) ||
+      !CBB_add_u8_length_prefixed(&body, &child) ||
       !CBB_add_bytes(&child, kZero, padding_len) ||
-      !CBB_finish(&cbb, NULL, &length) ||
-      !ssl_set_handshake_header(ssl, SSL3_MT_NEXT_PROTO, length)) {
+      !ssl->method->finish_message(ssl, &cbb)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     CBB_cleanup(&cbb);
     return -1;
   }
 
   ssl->state = SSL3_ST_CW_NEXT_PROTO_B;
-  return ssl_do_write(ssl);
+  return ssl->method->write_message(ssl);
 }
 
 static int ssl3_send_channel_id(SSL *ssl) {
   if (ssl->state == SSL3_ST_CW_CHANNEL_ID_B) {
-    return ssl_do_write(ssl);
+    return ssl->method->write_message(ssl);
   }
 
   assert(ssl->state == SSL3_ST_CW_CHANNEL_ID_A);
@@ -1975,27 +1964,22 @@
     goto err;
   }
 
-  CBB cbb, child;
-  size_t length;
-  CBB_zero(&cbb);
-  if (!CBB_init_fixed(&cbb, ssl_handshake_start(ssl),
-                      ssl->init_buf->max - SSL_HM_HEADER_LENGTH(ssl)) ||
-      !CBB_add_u16(&cbb, TLSEXT_TYPE_channel_id) ||
-      !CBB_add_u16_length_prefixed(&cbb, &child) ||
-      !BN_bn2cbb_padded(&child, 32, x) ||
-      !BN_bn2cbb_padded(&child, 32, y) ||
+  CBB cbb, body, child;
+  if (!ssl->method->init_message(ssl, &cbb, &body,
+                                 SSL3_MT_CHANNEL_ID_ENCRYPTED_EXTENSIONS) ||
+      !CBB_add_u16(&body, TLSEXT_TYPE_channel_id) ||
+      !CBB_add_u16_length_prefixed(&body, &child) ||
+      !BN_bn2cbb_padded(&child, 32, x) || !BN_bn2cbb_padded(&child, 32, y) ||
       !BN_bn2cbb_padded(&child, 32, sig->r) ||
       !BN_bn2cbb_padded(&child, 32, sig->s) ||
-      !CBB_finish(&cbb, NULL, &length) ||
-      !ssl_set_handshake_header(ssl, SSL3_MT_CHANNEL_ID_ENCRYPTED_EXTENSIONS,
-                                length)) {
+      !ssl->method->finish_message(ssl, &cbb)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     CBB_cleanup(&cbb);
     goto err;
   }
 
   ssl->state = SSL3_ST_CW_CHANNEL_ID_B;
-  ret = ssl_do_write(ssl);
+  ret = ssl->method->write_message(ssl);
 
 err:
   BN_free(x);
diff --git a/ssl/handshake_server.c b/ssl/handshake_server.c
index 7f7f3b8..128bb8d 100644
--- a/ssl/handshake_server.c
+++ b/ssl/handshake_server.c
@@ -1060,7 +1060,7 @@
 
 static int ssl3_send_server_hello(SSL *ssl) {
   if (ssl->state == SSL3_ST_SW_SRVR_HELLO_B) {
-    return ssl_do_write(ssl);
+    return ssl->method->write_message(ssl);
   }
 
   assert(ssl->state == SSL3_ST_SW_SRVR_HELLO_A);
@@ -1085,71 +1085,63 @@
     return -1;
   }
 
-  CBB cbb, session_id;
-  size_t length;
-  CBB_zero(&cbb);
-  if (!CBB_init_fixed(&cbb, ssl_handshake_start(ssl),
-                      ssl->init_buf->max - SSL_HM_HEADER_LENGTH(ssl)) ||
-      !CBB_add_u16(&cbb, ssl->version) ||
-      !CBB_add_bytes(&cbb, ssl->s3->server_random, SSL3_RANDOM_SIZE) ||
-      !CBB_add_u8_length_prefixed(&cbb, &session_id) ||
+  CBB cbb, body, session_id;
+  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_SERVER_HELLO) ||
+      !CBB_add_u16(&body, ssl->version) ||
+      !CBB_add_bytes(&body, ssl->s3->server_random, SSL3_RANDOM_SIZE) ||
+      !CBB_add_u8_length_prefixed(&body, &session_id) ||
       !CBB_add_bytes(&session_id, ssl->session->session_id,
                      ssl->session->session_id_length) ||
-      !CBB_add_u16(&cbb, ssl_cipher_get_value(ssl->s3->tmp.new_cipher)) ||
-      !CBB_add_u8(&cbb, 0 /* no compression */) ||
-      !ssl_add_serverhello_tlsext(ssl, &cbb) ||
-      !CBB_finish(&cbb, NULL, &length) ||
-      !ssl_set_handshake_header(ssl, SSL3_MT_SERVER_HELLO, length)) {
+      !CBB_add_u16(&body, ssl_cipher_get_value(ssl->s3->tmp.new_cipher)) ||
+      !CBB_add_u8(&body, 0 /* no compression */) ||
+      !ssl_add_serverhello_tlsext(ssl, &body) ||
+      !ssl->method->finish_message(ssl, &cbb)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     CBB_cleanup(&cbb);
     return -1;
   }
 
   ssl->state = SSL3_ST_SW_SRVR_HELLO_B;
-  return ssl_do_write(ssl);
+  return ssl->method->write_message(ssl);
 }
 
 static int ssl3_send_server_certificate(SSL *ssl) {
-  if (ssl->state == SSL3_ST_SW_CERT_A) {
-    if (!ssl3_output_cert_chain(ssl)) {
-      return 0;
-    }
-    ssl->state = SSL3_ST_SW_CERT_B;
+  if (ssl->state == SSL3_ST_SW_CERT_B) {
+    return ssl->method->write_message(ssl);
   }
 
-  /* SSL3_ST_SW_CERT_B */
-  return ssl_do_write(ssl);
+  if (!ssl3_output_cert_chain(ssl)) {
+    return 0;
+  }
+  ssl->state = SSL3_ST_SW_CERT_B;
+  return ssl->method->write_message(ssl);
 }
 
 static int ssl3_send_certificate_status(SSL *ssl) {
-  if (ssl->state == SSL3_ST_SW_CERT_STATUS_A) {
-    CBB out, ocsp_response;
-    size_t length;
-
-    CBB_zero(&out);
-    if (!CBB_init_fixed(&out, ssl_handshake_start(ssl),
-                        ssl->init_buf->max - SSL_HM_HEADER_LENGTH(ssl)) ||
-        !CBB_add_u8(&out, TLSEXT_STATUSTYPE_ocsp) ||
-        !CBB_add_u24_length_prefixed(&out, &ocsp_response) ||
-        !CBB_add_bytes(&ocsp_response, ssl->ctx->ocsp_response,
-                       ssl->ctx->ocsp_response_length) ||
-        !CBB_finish(&out, NULL, &length) ||
-        !ssl_set_handshake_header(ssl, SSL3_MT_CERTIFICATE_STATUS, length)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      CBB_cleanup(&out);
-      return -1;
-    }
-
-    ssl->state = SSL3_ST_SW_CERT_STATUS_B;
+  if (ssl->state == SSL3_ST_SW_CERT_STATUS_B) {
+    return ssl->method->write_message(ssl);
   }
 
-  /* SSL3_ST_SW_CERT_STATUS_B */
-  return ssl_do_write(ssl);
+  CBB cbb, body, ocsp_response;
+  if (!ssl->method->init_message(ssl, &cbb, &body,
+                                 SSL3_MT_CERTIFICATE_STATUS) ||
+      !CBB_add_u8(&body, TLSEXT_STATUSTYPE_ocsp) ||
+      !CBB_add_u24_length_prefixed(&body, &ocsp_response) ||
+      !CBB_add_bytes(&ocsp_response, ssl->ctx->ocsp_response,
+                     ssl->ctx->ocsp_response_length) ||
+      !ssl->method->finish_message(ssl, &cbb)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    CBB_cleanup(&cbb);
+    return -1;
+  }
+
+  ssl->state = SSL3_ST_SW_CERT_STATUS_B;
+  return ssl->method->write_message(ssl);
 }
 
 static int ssl3_send_server_key_exchange(SSL *ssl) {
   if (ssl->state == SSL3_ST_SW_KEY_EXCH_C) {
-    return ssl_do_write(ssl);
+    return ssl->method->write_message(ssl);
   }
 
   CBB cbb, child;
@@ -1243,9 +1235,10 @@
   }
 
   /* Assemble the message. */
-  if (!CBB_init_fixed(&cbb, ssl_handshake_start(ssl),
-                      ssl->init_buf->max - SSL_HM_HEADER_LENGTH(ssl)) ||
-      !CBB_add_bytes(&cbb, ssl->s3->tmp.server_params,
+  CBB body;
+  if (!ssl->method->init_message(ssl, &cbb, &body,
+                                 SSL3_MT_SERVER_KEY_EXCHANGE) ||
+      !CBB_add_bytes(&body, ssl->s3->tmp.server_params,
                      ssl->s3->tmp.server_params_len)) {
     goto err;
   }
@@ -1261,7 +1254,7 @@
     const EVP_MD *md;
     if (ssl3_protocol_version(ssl) >= TLS1_2_VERSION) {
       md = tls1_choose_signing_digest(ssl);
-      if (!tls12_add_sigandhash(ssl, &cbb, md)) {
+      if (!tls12_add_sigandhash(ssl, &body, md)) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
         goto err;
@@ -1275,7 +1268,7 @@
     /* Add space for the signature. */
     const size_t max_sig_len = ssl_private_key_max_signature_len(ssl);
     uint8_t *ptr;
-    if (!CBB_add_u16_length_prefixed(&cbb, &child) ||
+    if (!CBB_add_u16_length_prefixed(&body, &child) ||
         !CBB_reserve(&child, &ptr, max_sig_len)) {
       goto err;
     }
@@ -1322,9 +1315,7 @@
     }
   }
 
-  size_t length;
-  if (!CBB_finish(&cbb, NULL, &length) ||
-      !ssl_set_handshake_header(ssl, SSL3_MT_SERVER_KEY_EXCHANGE, length)) {
+  if (!ssl->method->finish_message(ssl, &cbb)) {
     goto err;
   }
 
@@ -1333,91 +1324,118 @@
   ssl->s3->tmp.server_params_len = 0;
 
   ssl->state = SSL3_ST_SW_KEY_EXCH_C;
-  return ssl_do_write(ssl);
+  return ssl->method->write_message(ssl);
 
 err:
   CBB_cleanup(&cbb);
   return -1;
 }
 
-static int ssl3_send_certificate_request(SSL *ssl) {
-  uint8_t *p, *d;
+static int add_cert_types(SSL *ssl, CBB *cbb) {
+  /* Get configured signature algorithms. */
+  int have_rsa_sign = 0;
+  int have_ecdsa_sign = 0;
+  const uint8_t *sig;
+  size_t siglen = tls12_get_psigalgs(ssl, &sig);
   size_t i;
-  int j, nl, off, n;
-  STACK_OF(X509_NAME) *sk = NULL;
-  X509_NAME *name;
-  BUF_MEM *buf;
+  for (i = 0; i < siglen; i += 2, sig += 2) {
+    switch (sig[1]) {
+      case TLSEXT_signature_rsa:
+        have_rsa_sign = 1;
+        break;
 
-  if (ssl->state == SSL3_ST_SW_CERT_REQ_A) {
-    buf = ssl->init_buf;
-
-    d = p = ssl_handshake_start(ssl);
-
-    /* get the list of acceptable cert types */
-    p++;
-    n = ssl3_get_req_cert_type(ssl, p);
-    d[0] = n;
-    p += n;
-    n++;
-
-    if (ssl3_protocol_version(ssl) >= TLS1_2_VERSION) {
-      const uint8_t *psigs;
-      nl = tls12_get_psigalgs(ssl, &psigs);
-      s2n(nl, p);
-      memcpy(p, psigs, nl);
-      p += nl;
-      n += nl + 2;
+      case TLSEXT_signature_ecdsa:
+        have_ecdsa_sign = 1;
+        break;
     }
-
-    off = n;
-    p += 2;
-    n += 2;
-
-    sk = SSL_get_client_CA_list(ssl);
-    nl = 0;
-    if (sk != NULL) {
-      for (i = 0; i < sk_X509_NAME_num(sk); i++) {
-        name = sk_X509_NAME_value(sk, i);
-        j = i2d_X509_NAME(name, NULL);
-        if (!BUF_MEM_grow_clean(buf, SSL_HM_HEADER_LENGTH(ssl) + n + j + 2)) {
-          OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
-          goto err;
-        }
-        p = ssl_handshake_start(ssl) + n;
-        s2n(j, p);
-        i2d_X509_NAME(name, &p);
-        n += 2 + j;
-        nl += 2 + j;
-      }
-    }
-
-    /* else no CA names */
-    p = ssl_handshake_start(ssl) + off;
-    s2n(nl, p);
-
-    if (!ssl_set_handshake_header(ssl, SSL3_MT_CERTIFICATE_REQUEST, n)) {
-      goto err;
-    }
-    ssl->state = SSL3_ST_SW_CERT_REQ_B;
   }
 
-  /* SSL3_ST_SW_CERT_REQ_B */
-  return ssl_do_write(ssl);
+  if (have_rsa_sign && !CBB_add_u8(cbb, SSL3_CT_RSA_SIGN)) {
+    return 0;
+  }
+
+  /* ECDSA certs can be used with RSA cipher suites as well so we don't need to
+   * check for SSL_kECDH or SSL_kECDHE. */
+  if (ssl->version >= TLS1_VERSION && have_ecdsa_sign &&
+      !CBB_add_u8(cbb, TLS_CT_ECDSA_SIGN)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int ssl3_send_certificate_request(SSL *ssl) {
+  if (ssl->state == SSL3_ST_SW_CERT_REQ_B) {
+    return ssl->method->write_message(ssl);
+  }
+
+  CBB cbb, body, cert_types, sigalgs_cbb, names_cbb, name_cbb;
+  if (!ssl->method->init_message(ssl, &cbb, &body,
+                                 SSL3_MT_CERTIFICATE_REQUEST) ||
+      !CBB_add_u8_length_prefixed(&body, &cert_types) ||
+      !add_cert_types(ssl, &cert_types)) {
+    goto err;
+  }
+
+  if (ssl3_protocol_version(ssl) >= TLS1_2_VERSION) {
+    const uint8_t *sigalgs;
+    size_t sigalgs_len = tls12_get_psigalgs(ssl, &sigalgs);
+    if (!CBB_add_u16_length_prefixed(&body, &sigalgs_cbb) ||
+        !CBB_add_bytes(&sigalgs_cbb, sigalgs, sigalgs_len)) {
+      goto err;
+    }
+  }
+
+  STACK_OF(X509_NAME) *sk = SSL_get_client_CA_list(ssl);
+  if (sk != NULL) {
+    if (!CBB_add_u16_length_prefixed(&body, &names_cbb)) {
+      goto err;
+    }
+
+    size_t i;
+    for (i = 0; i < sk_X509_NAME_num(sk); i++) {
+      X509_NAME *name = sk_X509_NAME_value(sk, i);
+      int len = i2d_X509_NAME(name, NULL);
+      if (len < 0) {
+        goto err;
+      }
+      uint8_t *ptr;
+      if (!CBB_add_u16_length_prefixed(&names_cbb, &name_cbb) ||
+          !CBB_add_space(&name_cbb, &ptr, (size_t)len) ||
+          (len > 0 && i2d_X509_NAME(name, &ptr) < 0)) {
+        goto err;
+      }
+    }
+  }
+
+  if (!ssl->method->finish_message(ssl, &cbb)) {
+    goto err;
+  }
+
+  ssl->state = SSL3_ST_SW_CERT_REQ_B;
+  return ssl->method->write_message(ssl);
 
 err:
+  OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+  CBB_cleanup(&cbb);
   return -1;
 }
 
 static int ssl3_send_server_hello_done(SSL *ssl) {
-  if (ssl->state == SSL3_ST_SW_SRVR_DONE_A) {
-    if (!ssl_set_handshake_header(ssl, SSL3_MT_SERVER_HELLO_DONE, 0)) {
-      return -1;
-    }
-    ssl->state = SSL3_ST_SW_SRVR_DONE_B;
+  if (ssl->state == SSL3_ST_SW_SRVR_DONE_B) {
+    return ssl->method->write_message(ssl);
   }
 
-  /* SSL3_ST_SW_SRVR_DONE_B */
-  return ssl_do_write(ssl);
+  CBB cbb, body;
+  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_SERVER_HELLO_DONE) ||
+      !ssl->method->finish_message(ssl, &cbb)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    CBB_cleanup(&cbb);
+    return -1;
+  }
+
+  ssl->state = SSL3_ST_SW_SRVR_DONE_B;
+  return ssl->method->write_message(ssl);
 }
 
 static int ssl3_get_client_certificate(SSL *ssl) {
@@ -2064,130 +2082,106 @@
   return ret;
 }
 
-/* send a new session ticket (not necessarily for a new session) */
 static int ssl3_send_new_session_ticket(SSL *ssl) {
-  int ret = -1;
-  uint8_t *session = NULL;
-  size_t session_len;
-  EVP_CIPHER_CTX ctx;
-  HMAC_CTX hctx;
-
-  EVP_CIPHER_CTX_init(&ctx);
-  HMAC_CTX_init(&hctx);
-
-  if (ssl->state == SSL3_ST_SW_SESSION_TICKET_A) {
-    uint8_t *p, *macstart;
-    int len;
-    unsigned int hlen;
-    SSL_CTX *tctx = ssl->initial_ctx;
-    uint8_t iv[EVP_MAX_IV_LENGTH];
-    uint8_t key_name[16];
-    /* The maximum overhead of encrypting the session is 16 (key name) + IV +
-     * one block of encryption overhead + HMAC.  */
-    const size_t max_ticket_overhead =
-        16 + EVP_MAX_IV_LENGTH + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE;
-
-    /* Serialize the SSL_SESSION to be encoded into the ticket. */
-    if (!SSL_SESSION_to_bytes_for_ticket(ssl->session, &session,
-                                         &session_len)) {
-      goto err;
-    }
-
-    /* If the session is too long, emit a dummy value rather than abort the
-     * connection. */
-    if (session_len > 0xFFFF - max_ticket_overhead) {
-      static const char kTicketPlaceholder[] = "TICKET TOO LARGE";
-      const size_t placeholder_len = strlen(kTicketPlaceholder);
-
-      OPENSSL_free(session);
-      session = NULL;
-
-      p = ssl_handshake_start(ssl);
-      /* Emit ticket_lifetime_hint. */
-      l2n(0, p);
-      /* Emit ticket. */
-      s2n(placeholder_len, p);
-      memcpy(p, kTicketPlaceholder, placeholder_len);
-      p += placeholder_len;
-
-      len = p - ssl_handshake_start(ssl);
-      if (!ssl_set_handshake_header(ssl, SSL3_MT_NEW_SESSION_TICKET, len)) {
-        goto err;
-      }
-      ssl->state = SSL3_ST_SW_SESSION_TICKET_B;
-      return ssl_do_write(ssl);
-    }
-
-    /* Grow buffer if need be: the length calculation is as follows:
-     * handshake_header_length + 4 (ticket lifetime hint) + 2 (ticket length) +
-     * max_ticket_overhead + * session_length */
-    if (!BUF_MEM_grow(ssl->init_buf, SSL_HM_HEADER_LENGTH(ssl) + 6 +
-                                       max_ticket_overhead + session_len)) {
-      goto err;
-    }
-    p = ssl_handshake_start(ssl);
-    /* Initialize HMAC and cipher contexts. If callback present it does all the
-     * work otherwise use generated values from parent ctx. */
-    if (tctx->tlsext_ticket_key_cb) {
-      if (tctx->tlsext_ticket_key_cb(ssl, key_name, iv, &ctx, &hctx,
-                                     1 /* encrypt */) < 0) {
-        goto err;
-      }
-    } else {
-      if (!RAND_bytes(iv, 16) ||
-          !EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL,
-                              tctx->tlsext_tick_aes_key, iv) ||
-          !HMAC_Init_ex(&hctx, tctx->tlsext_tick_hmac_key, 16, tlsext_tick_md(),
-                        NULL)) {
-        goto err;
-      }
-      memcpy(key_name, tctx->tlsext_tick_key_name, 16);
-    }
-
-    /* Ticket lifetime hint (advisory only): We leave this unspecified for
-     * resumed session (for simplicity), and guess that tickets for new
-     * sessions will live as long as their sessions. */
-    l2n(ssl->hit ? 0 : ssl->session->timeout, p);
-
-    /* Skip ticket length for now */
-    p += 2;
-    /* Output key name */
-    macstart = p;
-    memcpy(p, key_name, 16);
-    p += 16;
-    /* output IV */
-    memcpy(p, iv, EVP_CIPHER_CTX_iv_length(&ctx));
-    p += EVP_CIPHER_CTX_iv_length(&ctx);
-    /* Encrypt session data */
-    if (!EVP_EncryptUpdate(&ctx, p, &len, session, session_len)) {
-      goto err;
-    }
-    p += len;
-    if (!EVP_EncryptFinal_ex(&ctx, p, &len)) {
-      goto err;
-    }
-    p += len;
-
-    if (!HMAC_Update(&hctx, macstart, p - macstart) ||
-        !HMAC_Final(&hctx, p, &hlen)) {
-      goto err;
-    }
-
-    p += hlen;
-    /* Now write out lengths: p points to end of data written */
-    /* Total length */
-    len = p - ssl_handshake_start(ssl);
-    /* Skip ticket lifetime hint */
-    p = ssl_handshake_start(ssl) + 4;
-    s2n(len - 6, p);
-    if (!ssl_set_handshake_header(ssl, SSL3_MT_NEW_SESSION_TICKET, len)) {
-      goto err;
-    }
-    ssl->state = SSL3_ST_SW_SESSION_TICKET_B;
+  if (ssl->state == SSL3_ST_SW_SESSION_TICKET_B) {
+    return ssl->method->write_message(ssl);
   }
 
-  /* SSL3_ST_SW_SESSION_TICKET_B */
-  ret = ssl_do_write(ssl);
+  /* Serialize the SSL_SESSION to be encoded into the ticket. */
+  uint8_t *session = NULL;
+  size_t session_len;
+  if (!SSL_SESSION_to_bytes_for_ticket(ssl->session, &session,
+                                       &session_len)) {
+    return -1;
+  }
+
+  EVP_CIPHER_CTX ctx;
+  EVP_CIPHER_CTX_init(&ctx);
+  HMAC_CTX hctx;
+  HMAC_CTX_init(&hctx);
+
+  int ret = -1;
+  CBB cbb, body, ticket;
+  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_NEW_SESSION_TICKET) ||
+      /* Ticket lifetime hint (advisory only): We leave this unspecified for
+       * resumed session (for simplicity), and guess that tickets for new
+       * sessions will live as long as their sessions. */
+      !CBB_add_u32(&body, ssl->hit ? 0 : ssl->session->timeout) ||
+      !CBB_add_u16_length_prefixed(&body, &ticket)) {
+    goto err;
+  }
+
+  /* If the session is too long, emit a dummy value rather than abort the
+   * connection. */
+  const size_t max_ticket_overhead =
+      16 + EVP_MAX_IV_LENGTH + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE;
+  if (session_len > 0xffff - max_ticket_overhead) {
+    static const char kTicketPlaceholder[] = "TICKET TOO LARGE";
+
+    if (!CBB_add_bytes(&ticket, (const uint8_t *)kTicketPlaceholder,
+                       strlen(kTicketPlaceholder)) ||
+        !ssl->method->finish_message(ssl, &cbb)) {
+      goto err;
+    }
+
+    ssl->state = SSL3_ST_SW_SESSION_TICKET_B;
+    ret = 1;
+    goto err;
+  }
+
+  /* Initialize HMAC and cipher contexts. If callback present it does all the
+   * work otherwise use generated values from parent ctx. */
+  SSL_CTX *tctx = ssl->initial_ctx;
+  uint8_t iv[EVP_MAX_IV_LENGTH];
+  uint8_t key_name[16];
+  if (tctx->tlsext_ticket_key_cb != NULL) {
+    if (tctx->tlsext_ticket_key_cb(ssl, key_name, iv, &ctx, &hctx,
+                                   1 /* encrypt */) < 0) {
+      goto err;
+    }
+  } else {
+    if (!RAND_bytes(iv, 16) ||
+        !EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL,
+                            tctx->tlsext_tick_aes_key, iv) ||
+        !HMAC_Init_ex(&hctx, tctx->tlsext_tick_hmac_key, 16, tlsext_tick_md(),
+                      NULL)) {
+      goto err;
+    }
+    memcpy(key_name, tctx->tlsext_tick_key_name, 16);
+  }
+
+  uint8_t *ptr;
+  if (!CBB_add_bytes(&ticket, key_name, 16) ||
+      !CBB_add_bytes(&ticket, iv, EVP_CIPHER_CTX_iv_length(&ctx)) ||
+      !CBB_reserve(&ticket, &ptr, session_len + EVP_MAX_BLOCK_LENGTH)) {
+    goto err;
+  }
+
+  int len;
+  size_t total = 0;
+  if (!EVP_EncryptUpdate(&ctx, ptr + total, &len, session, session_len)) {
+    goto err;
+  }
+  total += len;
+  if (!EVP_EncryptFinal_ex(&ctx, ptr + total, &len)) {
+    goto err;
+  }
+  total += len;
+  if (!CBB_did_write(&ticket, total)) {
+    goto err;
+  }
+
+  unsigned hlen;
+  if (!HMAC_Update(&hctx, CBB_data(&ticket), CBB_len(&ticket)) ||
+      !CBB_reserve(&ticket, &ptr, EVP_MAX_MD_SIZE) ||
+      !HMAC_Final(&hctx, ptr, &hlen) ||
+      !CBB_did_write(&ticket, hlen) ||
+      !ssl->method->finish_message(ssl, &cbb)) {
+    goto err;
+  }
+
+  ssl->state = SSL3_ST_SW_SESSION_TICKET_B;
+  ret = ssl->method->write_message(ssl);
 
 err:
   OPENSSL_free(session);
diff --git a/ssl/internal.h b/ssl/internal.h
index 369bdcc..710fdd3 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -723,29 +723,17 @@
 void ssl_write_buffer_clear(SSL *ssl);
 
 
+/* Certificate functions. */
+
+/* ssl_add_cert_to_cbb adds |x509| to |cbb|. It returns one on success and zero
+ * on error. */
+int ssl_add_cert_to_cbb(CBB *cbb, X509 *x509);
+
+
 /* Underdocumented functions.
  *
  * Functions below here haven't been touched up and may be underdocumented. */
 
-#define l2n(l, c)                            \
-  (*((c)++) = (uint8_t)(((l) >> 24) & 0xff), \
-   *((c)++) = (uint8_t)(((l) >> 16) & 0xff), \
-   *((c)++) = (uint8_t)(((l) >> 8) & 0xff),  \
-   *((c)++) = (uint8_t)(((l)) & 0xff))
-
-#define s2n(s, c)                              \
-  ((c[0] = (uint8_t)(((s) >> 8) & 0xff), \
-    c[1] = (uint8_t)(((s)) & 0xff)),     \
-   c += 2)
-
-#define l2n3(l, c)                              \
-  ((c[0] = (uint8_t)(((l) >> 16) & 0xff), \
-    c[1] = (uint8_t)(((l) >> 8) & 0xff),  \
-    c[2] = (uint8_t)(((l)) & 0xff)),      \
-   c += 3)
-
-/* LOCAL STUFF */
-
 #define TLSEXT_CHANNEL_ID_SIZE 128
 
 /* Check if an SSL structure is using DTLS */
@@ -839,12 +827,16 @@
   /* supports_cipher returns one if |cipher| is supported by this protocol and
    * zero otherwise. */
   int (*supports_cipher)(const SSL_CIPHER *cipher);
-  /* Handshake header length */
-  unsigned int hhlen;
-  /* Set the handshake header */
-  int (*set_handshake_header)(SSL *ssl, int type, unsigned long len);
-  /* Write out handshake message */
-  int (*do_write)(SSL *ssl);
+  /* init_message begins a new handshake message of type |type|. |cbb| is the
+   * root CBB to be passed into |finish_message|. |*body| is set to a child CBB
+   * the caller should write to. It returns one on success and zero on error. */
+  int (*init_message)(SSL *ssl, CBB *cbb, CBB *body, uint8_t type);
+  /* finish_message finishes a handshake message and prepares it to be
+   * written. It returns one on success and zero on error. */
+  int (*finish_message)(SSL *ssl, CBB *cbb);
+  /* write_message writes the next message to the transport. It returns one on
+   * success and <= 0 on error. */
+  int (*write_message)(SSL *ssl);
   /* send_change_cipher_spec sends a ChangeCipherSpec message. */
   int (*send_change_cipher_spec)(SSL *ssl, int a, int b);
   /* expect_flight is called when the handshake expects a flight of messages from
@@ -870,13 +862,6 @@
   int (*cert_verify_mac)(SSL *, int, uint8_t *);
 };
 
-#define SSL_HM_HEADER_LENGTH(ssl) ssl->method->hhlen
-#define ssl_handshake_start(ssl) \
-  (((uint8_t *)ssl->init_buf->data) + ssl->method->hhlen)
-#define ssl_set_handshake_header(ssl, htype, len) \
-  ssl->method->set_handshake_header(ssl, htype, len)
-#define ssl_do_write(ssl) ssl->method->do_write(ssl)
-
 /* lengths of messages */
 #define DTLS1_COOKIE_LENGTH 256
 
@@ -988,7 +973,7 @@
                           int (*cb)(SSL *ssl, void *arg), void *arg);
 
 int ssl_verify_cert_chain(SSL *ssl, STACK_OF(X509) *cert_chain);
-int ssl_add_cert_chain(SSL *ssl, unsigned long *l);
+int ssl_add_cert_chain(SSL *ssl, CBB *cbb);
 void ssl_update_cache(SSL *ssl, int mode);
 
 /* ssl_get_compatible_server_ciphers determines the key exchange and
@@ -1008,9 +993,7 @@
 int ssl3_get_finished(SSL *ssl);
 int ssl3_send_change_cipher_spec(SSL *ssl, int state_a, int state_b);
 void ssl3_cleanup_key_block(SSL *ssl);
-int ssl3_do_write(SSL *ssl, int type);
 int ssl3_send_alert(SSL *ssl, int level, int desc);
-int ssl3_get_req_cert_type(SSL *ssl, uint8_t *p);
 long ssl3_get_message(SSL *ssl, int msg_type,
                       enum ssl_hash_message_t hash_message, int *ok);
 
@@ -1046,20 +1029,16 @@
 int ssl3_accept(SSL *ssl);
 int ssl3_connect(SSL *ssl);
 
-int ssl3_set_handshake_header(SSL *ssl, int htype, unsigned long len);
-int ssl3_handshake_write(SSL *ssl);
+int ssl3_init_message(SSL *ssl, CBB *cbb, CBB *body, uint8_t type);
+int ssl3_finish_message(SSL *ssl, CBB *cbb);
+int ssl3_write_message(SSL *ssl);
+
 void ssl3_expect_flight(SSL *ssl);
 void ssl3_received_flight(SSL *ssl);
 
-/* dtls1_do_handshake_write writes handshake message |in| using the give epoch,
- * starting |offset| bytes into the message body. It returns one on success. On
- * error, it returns <= 0 and sets |*out_offset| to the number of bytes of body
- * which were successfully written. This may be used to retry the write
- * later. |in| must be a reassembled handshake message with the full DTLS
- * handshake header. */
-int dtls1_do_handshake_write(SSL *ssl, size_t *out_offset, const uint8_t *in,
-                             size_t offset, size_t len,
-                             enum dtls1_use_epoch_t use_epoch);
+int dtls1_init_message(SSL *ssl, CBB *cbb, CBB *body, uint8_t type);
+int dtls1_finish_message(SSL *ssl, CBB *cbb);
+int dtls1_write_message(SSL *ssl);
 
 /* dtls1_get_record reads a new input record. On success, it places it in
  * |ssl->s3->rrec| and returns one. Otherwise it returns <= 0 on error or if
@@ -1079,13 +1058,11 @@
 
 int dtls1_send_change_cipher_spec(SSL *ssl, int a, int b);
 int dtls1_send_finished(SSL *ssl, int a, int b, const char *sender, int slen);
-int dtls1_buffer_message(SSL *ssl);
 int dtls1_retransmit_buffered_messages(SSL *ssl);
 void dtls1_clear_record_buffer(SSL *ssl);
 int dtls1_parse_fragment(CBS *cbs, struct hm_header_st *out_hdr,
                          CBS *out_body);
 int dtls1_check_timeout_num(SSL *ssl);
-int dtls1_set_handshake_header(SSL *ssl, int type, unsigned long len);
 int dtls1_handshake_write(SSL *ssl);
 void dtls1_expect_flight(SSL *ssl);
 void dtls1_received_flight(SSL *ssl);
diff --git a/ssl/s3_both.c b/ssl/s3_both.c
index f081066..1bdc2a2 100644
--- a/ssl/s3_both.c
+++ b/ssl/s3_both.c
@@ -117,6 +117,7 @@
 #include <string.h>
 
 #include <openssl/buf.h>
+#include <openssl/bytestring.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
 #include <openssl/mem.h>
@@ -132,61 +133,118 @@
 /* ssl3_do_write sends |ssl->init_buf| in records of type 'type'
  * (SSL3_RT_HANDSHAKE or SSL3_RT_CHANGE_CIPHER_SPEC). It returns 1 on success
  * and <= 0 on error. */
-int ssl3_do_write(SSL *ssl, int type) {
-  int ret = ssl3_write_bytes(ssl, type, ssl->init_buf->data, ssl->init_num);
+static int ssl3_do_write(SSL *ssl, int type, const uint8_t *data, size_t len) {
+  int ret = ssl3_write_bytes(ssl, type, data, len);
   if (ret <= 0) {
     return ret;
   }
 
   /* ssl3_write_bytes writes the data in its entirety. */
-  assert(ret == ssl->init_num);
-  ssl_do_msg_callback(ssl, 1 /* write */, ssl->version, type,
-                      ssl->init_buf->data, (size_t)ssl->init_num);
-  ssl->init_num = 0;
+  assert((size_t)ret == len);
+  ssl_do_msg_callback(ssl, 1 /* write */, ssl->version, type, data, len);
+  return 1;
+}
+
+int ssl3_init_message(SSL *ssl, CBB *cbb, CBB *body, uint8_t type) {
+  CBB_zero(cbb);
+  if (ssl->s3->pending_message != NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  /* Pick a modest size hint to save most of the |realloc| calls. */
+  if (!CBB_init(cbb, 64) ||
+      !CBB_add_u8(cbb, type) ||
+      !CBB_add_u24_length_prefixed(cbb, body)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  return 1;
+}
+
+int ssl3_finish_message(SSL *ssl, CBB *cbb) {
+  if (ssl->s3->pending_message != NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  uint8_t *msg = NULL;
+  size_t len;
+  if (!CBB_finish(cbb, &msg, &len) ||
+      len > 0xffffffffu) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    OPENSSL_free(msg);
+    return 0;
+  }
+
+  ssl3_update_handshake_hash(ssl, msg, len);
+
+  ssl->s3->pending_message = msg;
+  ssl->s3->pending_message_len = (uint32_t)len;
+  return 1;
+}
+
+int ssl3_write_message(SSL *ssl) {
+  if (ssl->s3->pending_message == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  int ret = ssl3_do_write(ssl, SSL3_RT_HANDSHAKE, ssl->s3->pending_message,
+                          ssl->s3->pending_message_len);
+  if (ret <= 0) {
+    return ret;
+  }
+
+  OPENSSL_free(ssl->s3->pending_message);
+  ssl->s3->pending_message = NULL;
+  ssl->s3->pending_message_len = 0;
   return 1;
 }
 
 int ssl3_send_finished(SSL *ssl, int a, int b) {
-  uint8_t *p;
-  int n;
-
-  if (ssl->state == a) {
-    p = ssl_handshake_start(ssl);
-
-    n = ssl->s3->enc_method->final_finish_mac(ssl, ssl->server,
-                                              ssl->s3->tmp.finish_md);
-    if (n == 0) {
-      return 0;
-    }
-    ssl->s3->tmp.finish_md_len = n;
-    memcpy(p, ssl->s3->tmp.finish_md, n);
-
-    /* Log the master secret, if logging is enabled. */
-    if (!ssl_log_master_secret(ssl, ssl->s3->client_random, SSL3_RANDOM_SIZE,
-                               ssl->session->master_key,
-                               ssl->session->master_key_length)) {
-      return 0;
-    }
-
-    /* Copy the finished so we can use it for renegotiation checks */
-    if (ssl->server) {
-      assert(n <= EVP_MAX_MD_SIZE);
-      memcpy(ssl->s3->previous_server_finished, ssl->s3->tmp.finish_md, n);
-      ssl->s3->previous_server_finished_len = n;
-    } else {
-      assert(n <= EVP_MAX_MD_SIZE);
-      memcpy(ssl->s3->previous_client_finished, ssl->s3->tmp.finish_md, n);
-      ssl->s3->previous_client_finished_len = n;
-    }
-
-    if (!ssl_set_handshake_header(ssl, SSL3_MT_FINISHED, n)) {
-      return 0;
-    }
-    ssl->state = b;
+  if (ssl->state == b) {
+    return ssl->method->write_message(ssl);
   }
 
-  /* SSL3_ST_SEND_xxxxxx_HELLO_B */
-  return ssl_do_write(ssl);
+  int n = ssl->s3->enc_method->final_finish_mac(ssl, ssl->server,
+                                                ssl->s3->tmp.finish_md);
+  if (n == 0) {
+    return 0;
+  }
+  ssl->s3->tmp.finish_md_len = n;
+
+  /* Log the master secret, if logging is enabled. */
+  if (!ssl_log_master_secret(ssl, ssl->s3->client_random, SSL3_RANDOM_SIZE,
+                             ssl->session->master_key,
+                             ssl->session->master_key_length)) {
+    return 0;
+  }
+
+  /* Copy the finished so we can use it for renegotiation checks */
+  if (ssl->server) {
+    assert(n <= EVP_MAX_MD_SIZE);
+    memcpy(ssl->s3->previous_server_finished, ssl->s3->tmp.finish_md, n);
+    ssl->s3->previous_server_finished_len = n;
+  } else {
+    assert(n <= EVP_MAX_MD_SIZE);
+    memcpy(ssl->s3->previous_client_finished, ssl->s3->tmp.finish_md, n);
+    ssl->s3->previous_client_finished_len = n;
+  }
+
+  CBB cbb, body;
+  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_FINISHED) ||
+      !CBB_add_bytes(&body, ssl->s3->tmp.finish_md,
+                     ssl->s3->tmp.finish_md_len) ||
+      !ssl->method->finish_message(ssl, &cbb)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    CBB_cleanup(&cbb);
+    return -1;
+  }
+
+  ssl->state = b;
+  return ssl->method->write_message(ssl);
 }
 
 /* ssl3_take_mac calculates the Finished MAC for the handshakes messages seen
@@ -262,30 +320,24 @@
 }
 
 int ssl3_send_change_cipher_spec(SSL *ssl, int a, int b) {
-  if (ssl->state == a) {
-    *((uint8_t *)ssl->init_buf->data) = SSL3_MT_CCS;
-    ssl->init_num = 1;
+  static const uint8_t kChangeCipherSpec[1] = {SSL3_MT_CCS};
 
-    ssl->state = b;
-  }
-
-  /* SSL3_ST_CW_CHANGE_B */
-  return ssl3_do_write(ssl, SSL3_RT_CHANGE_CIPHER_SPEC);
+  ssl->state = b;
+  return ssl3_do_write(ssl, SSL3_RT_CHANGE_CIPHER_SPEC, kChangeCipherSpec,
+                       sizeof(kChangeCipherSpec));
 }
 
 int ssl3_output_cert_chain(SSL *ssl) {
-  uint8_t *p;
-  unsigned long l = 3 + SSL_HM_HEADER_LENGTH(ssl);
-
-  if (!ssl_add_cert_chain(ssl, &l)) {
+  CBB cbb, body;
+  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_CERTIFICATE) ||
+      !ssl_add_cert_chain(ssl, &body) ||
+      !ssl->method->finish_message(ssl, &cbb)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    CBB_cleanup(&cbb);
     return 0;
   }
 
-  l -= 3 + SSL_HM_HEADER_LENGTH(ssl);
-  p = ssl_handshake_start(ssl);
-  l2n3(l, p);
-  l += 3;
-  return ssl_set_handshake_header(ssl, SSL3_MT_CERTIFICATE, l);
+  return 1;
 }
 
 size_t ssl_max_handshake_message_len(const SSL *ssl) {
diff --git a/ssl/s3_lib.c b/ssl/s3_lib.c
index f194267..3827f15 100644
--- a/ssl/s3_lib.c
+++ b/ssl/s3_lib.c
@@ -166,21 +166,6 @@
   return 1;
 }
 
-int ssl3_set_handshake_header(SSL *ssl, int htype, unsigned long len) {
-  uint8_t *p = (uint8_t *)ssl->init_buf->data;
-  *(p++) = htype;
-  l2n3(len, p);
-  ssl->init_num = (int)len + SSL3_HM_HEADER_LENGTH;
-
-  /* Add the message to the handshake hash. */
-  return ssl3_update_handshake_hash(ssl, (uint8_t *)ssl->init_buf->data,
-                                    ssl->init_num);
-}
-
-int ssl3_handshake_write(SSL *ssl) {
-  return ssl3_do_write(ssl, SSL3_RT_HANDSHAKE);
-}
-
 void ssl3_expect_flight(SSL *ssl) {}
 
 void ssl3_received_flight(SSL *ssl) {}
@@ -232,6 +217,7 @@
   OPENSSL_free(ssl->s3->alpn_selected);
   SSL_AEAD_CTX_free(ssl->s3->aead_read_ctx);
   SSL_AEAD_CTX_free(ssl->s3->aead_write_ctx);
+  OPENSSL_free(ssl->s3->pending_message);
 
   OPENSSL_cleanse(ssl->s3, sizeof *ssl->s3);
   OPENSSL_free(ssl->s3);
@@ -331,40 +317,6 @@
   return ret;
 }
 
-int ssl3_get_req_cert_type(SSL *ssl, uint8_t *p) {
-  int ret = 0;
-  const uint8_t *sig;
-  size_t i, siglen;
-  int have_rsa_sign = 0;
-  int have_ecdsa_sign = 0;
-
-  /* get configured sigalgs */
-  siglen = tls12_get_psigalgs(ssl, &sig);
-  for (i = 0; i < siglen; i += 2, sig += 2) {
-    switch (sig[1]) {
-      case TLSEXT_signature_rsa:
-        have_rsa_sign = 1;
-        break;
-
-      case TLSEXT_signature_ecdsa:
-        have_ecdsa_sign = 1;
-        break;
-    }
-  }
-
-  if (have_rsa_sign) {
-    p[ret++] = SSL3_CT_RSA_SIGN;
-  }
-
-  /* ECDSA certs can be used with RSA cipher suites as well so we don't need to
-   * check for SSL_kECDH or SSL_kECDHE. */
-  if (ssl->version >= TLS1_VERSION && have_ecdsa_sign) {
-      p[ret++] = TLS_CT_ECDSA_SIGN;
-  }
-
-  return ret;
-}
-
 /* If we are using default SHA1+MD5 algorithms switch to new SHA256 PRF and
  * handshake macs if required. */
 uint32_t ssl_get_algorithm_prf(const SSL *ssl) {
diff --git a/ssl/s3_meth.c b/ssl/s3_meth.c
index 8370f23..715966b 100644
--- a/ssl/s3_meth.c
+++ b/ssl/s3_meth.c
@@ -70,9 +70,9 @@
     ssl3_write_app_data,
     ssl3_dispatch_alert,
     ssl3_supports_cipher,
-    SSL3_HM_HEADER_LENGTH,
-    ssl3_set_handshake_header,
-    ssl3_handshake_write,
+    ssl3_init_message,
+    ssl3_finish_message,
+    ssl3_write_message,
     ssl3_send_change_cipher_spec,
     ssl3_expect_flight,
     ssl3_received_flight,
diff --git a/ssl/ssl_asn1.c b/ssl/ssl_asn1.c
index 5ec33eb..41987f8 100644
--- a/ssl/ssl_asn1.c
+++ b/ssl/ssl_asn1.c
@@ -165,22 +165,6 @@
 static const int kCertChainTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 19;
 
-static int add_X509(CBB *cbb, X509 *x509) {
-  int len = i2d_X509(x509, NULL);
-  if (len < 0) {
-    return 0;
-  }
-  uint8_t *buf;
-  if (!CBB_add_space(cbb, &buf, len)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return 0;
-  }
-  if (buf != NULL && i2d_X509(x509, &buf) < 0) {
-    return 0;
-  }
-  return 1;
-}
-
 static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data,
                                      size_t *out_len, int for_ticket) {
   CBB cbb, session, child, child2;
@@ -229,7 +213,7 @@
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       goto err;
     }
-    if (!add_X509(&child, in->peer)) {
+    if (!ssl_add_cert_to_cbb(&child, in->peer)) {
       goto err;
     }
   }
@@ -351,7 +335,7 @@
     }
     size_t i;
     for (i = 0; i < sk_X509_num(in->cert_chain); i++) {
-      if (!add_X509(&child, sk_X509_value(in->cert_chain, i))) {
+      if (!ssl_add_cert_to_cbb(&child, sk_X509_value(in->cert_chain, i))) {
         goto err;
       }
     }
diff --git a/ssl/ssl_cert.c b/ssl/ssl_cert.c
index 0eb0d8b..b608c73 100644
--- a/ssl/ssl_cert.c
+++ b/ssl/ssl_cert.c
@@ -416,56 +416,58 @@
   return add_client_CA(&ctx->client_CA, x509);
 }
 
-/* Add a certificate to a BUF_MEM structure */
-static int ssl_add_cert_to_buf(BUF_MEM *buf, unsigned long *l, X509 *x) {
-  int n;
-  uint8_t *p;
-
-  n = i2d_X509(x, NULL);
-  if (n < 0 || !BUF_MEM_grow_clean(buf, (int)(n + (*l) + 3))) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
+int ssl_add_cert_to_cbb(CBB *cbb, X509 *x509) {
+  int len = i2d_X509(x509, NULL);
+  if (len < 0) {
     return 0;
   }
-  p = (uint8_t *)&(buf->data[*l]);
-  l2n3(n, p);
-  n = i2d_X509(x, &p);
-  if (n < 0) {
-      /* This shouldn't happen. */
-      OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
-      return 0;
+  uint8_t *buf;
+  if (!CBB_add_space(cbb, &buf, len)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
   }
-  *l += n + 3;
-
+  if (buf != NULL && i2d_X509(x509, &buf) < 0) {
+    return 0;
+  }
   return 1;
 }
 
-/* Add certificate chain to internal SSL BUF_MEM structure. */
-int ssl_add_cert_chain(SSL *ssl, unsigned long *l) {
+static int ssl_add_cert_with_length(CBB *cbb, X509 *x509) {
+  CBB child;
+  return CBB_add_u24_length_prefixed(cbb, &child) &&
+         ssl_add_cert_to_cbb(&child, x509) &&
+         CBB_flush(cbb);
+}
+
+int ssl_add_cert_chain(SSL *ssl, CBB *cbb) {
   CERT *cert = ssl->cert;
-  BUF_MEM *buf = ssl->init_buf;
-  int no_chain = 0;
-  size_t i;
-
   X509 *x = cert->x509;
-  STACK_OF(X509) *chain = cert->chain;
-
   if (x == NULL) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET);
     return 0;
   }
 
+  CBB child;
+  if (!CBB_add_u24_length_prefixed(cbb, &child)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  int no_chain = 0;
+  STACK_OF(X509) *chain = cert->chain;
   if ((ssl->mode & SSL_MODE_NO_AUTO_CHAIN) || chain != NULL) {
     no_chain = 1;
   }
 
   if (no_chain) {
-    if (!ssl_add_cert_to_buf(buf, l, x)) {
+    if (!ssl_add_cert_to_cbb(&child, x)) {
       return 0;
     }
 
+    size_t i;
     for (i = 0; i < sk_X509_num(chain); i++) {
       x = sk_X509_value(chain, i);
-      if (!ssl_add_cert_to_buf(buf, l, x)) {
+      if (!ssl_add_cert_with_length(&child, x)) {
         return 0;
       }
     }
@@ -479,10 +481,11 @@
     X509_verify_cert(&xs_ctx);
     /* Don't leave errors in the queue */
     ERR_clear_error();
+
+    size_t i;
     for (i = 0; i < sk_X509_num(xs_ctx.chain); i++) {
       x = sk_X509_value(xs_ctx.chain, i);
-
-      if (!ssl_add_cert_to_buf(buf, l, x)) {
+      if (!ssl_add_cert_with_length(&child, x)) {
         X509_STORE_CTX_cleanup(&xs_ctx);
         return 0;
       }
@@ -490,7 +493,7 @@
     X509_STORE_CTX_cleanup(&xs_ctx);
   }
 
-  return 1;
+  return CBB_flush(cbb);
 }
 
 static int set_cert_store(X509_STORE **store_ptr, X509_STORE *new_store, int take_ref) {