external/boringssl: Sync to 8625ec4b436ccb4098ed4aac10891eff8372be41.

This includes the following changes:

https://boringssl.googlesource.com/boringssl/+log/c596415ec62b501523d80f9afa26b135406da6bf..8625ec4b436ccb4098ed4aac10891eff8372be41

Test: cts -m CtsLibcoreTestCases
Change-Id: I47a45e6b6f46b19fcbcb6c917895867d56dcd2ca
diff --git a/src/ssl/CMakeLists.txt b/src/ssl/CMakeLists.txt
index 3eab0b5..f2f60ca 100644
--- a/src/ssl/CMakeLists.txt
+++ b/src/ssl/CMakeLists.txt
@@ -4,7 +4,6 @@
   ssl
 
   bio_ssl.cc
-  custom_extensions.cc
   d1_both.cc
   d1_lib.cc
   d1_pkt.cc
diff --git a/src/ssl/custom_extensions.cc b/src/ssl/custom_extensions.cc
deleted file mode 100644
index 85b8a33..0000000
--- a/src/ssl/custom_extensions.cc
+++ /dev/null
@@ -1,265 +0,0 @@
-/* Copyright (c) 2014, Google Inc.
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
-
-#include <openssl/ssl.h>
-
-#include <assert.h>
-#include <string.h>
-
-#include <openssl/bytestring.h>
-#include <openssl/err.h>
-#include <openssl/mem.h>
-#include <openssl/stack.h>
-
-#include "internal.h"
-
-
-namespace bssl {
-
-void SSL_CUSTOM_EXTENSION_free(SSL_CUSTOM_EXTENSION *custom_extension) {
-  OPENSSL_free(custom_extension);
-}
-
-static const SSL_CUSTOM_EXTENSION *custom_ext_find(
-    STACK_OF(SSL_CUSTOM_EXTENSION) *stack,
-    unsigned *out_index, uint16_t value) {
-  for (size_t i = 0; i < sk_SSL_CUSTOM_EXTENSION_num(stack); i++) {
-    const SSL_CUSTOM_EXTENSION *ext = sk_SSL_CUSTOM_EXTENSION_value(stack, i);
-    if (ext->value == value) {
-      if (out_index != NULL) {
-        *out_index = i;
-      }
-      return ext;
-    }
-  }
-
-  return NULL;
-}
-
-// default_add_callback is used as the |add_callback| when the user doesn't
-// provide one. For servers, it does nothing while, for clients, it causes an
-// empty extension to be included.
-static int default_add_callback(SSL *ssl, unsigned extension_value,
-                                const uint8_t **out, size_t *out_len,
-                                int *out_alert_value, void *add_arg) {
-  if (ssl->server) {
-    return 0;
-  }
-  *out_len = 0;
-  return 1;
-}
-
-static int custom_ext_add_hello(SSL_HANDSHAKE *hs, CBB *extensions) {
-  SSL *const ssl = hs->ssl;
-  STACK_OF(SSL_CUSTOM_EXTENSION) *stack = ssl->ctx->client_custom_extensions;
-  if (ssl->server) {
-    stack = ssl->ctx->server_custom_extensions;
-  }
-
-  if (stack == NULL) {
-    return 1;
-  }
-
-  for (size_t i = 0; i < sk_SSL_CUSTOM_EXTENSION_num(stack); i++) {
-    const SSL_CUSTOM_EXTENSION *ext = sk_SSL_CUSTOM_EXTENSION_value(stack, i);
-
-    if (ssl->server &&
-        !(hs->custom_extensions.received & (1u << i))) {
-      // Servers cannot echo extensions that the client didn't send.
-      continue;
-    }
-
-    const uint8_t *contents;
-    size_t contents_len;
-    int alert = SSL_AD_DECODE_ERROR;
-    CBB contents_cbb;
-
-    switch (ext->add_callback(ssl, ext->value, &contents, &contents_len, &alert,
-                              ext->add_arg)) {
-      case 1:
-        if (!CBB_add_u16(extensions, ext->value) ||
-            !CBB_add_u16_length_prefixed(extensions, &contents_cbb) ||
-            !CBB_add_bytes(&contents_cbb, contents, contents_len) ||
-            !CBB_flush(extensions)) {
-          OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-          ERR_add_error_dataf("extension %u", (unsigned) ext->value);
-          if (ext->free_callback && 0 < contents_len) {
-            ext->free_callback(ssl, ext->value, contents, ext->add_arg);
-          }
-          return 0;
-        }
-
-        if (ext->free_callback && 0 < contents_len) {
-          ext->free_callback(ssl, ext->value, contents, ext->add_arg);
-        }
-
-        if (!ssl->server) {
-          assert((hs->custom_extensions.sent & (1u << i)) == 0);
-          hs->custom_extensions.sent |= (1u << i);
-        }
-        break;
-
-      case 0:
-        break;
-
-      default:
-        ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
-        OPENSSL_PUT_ERROR(SSL, SSL_R_CUSTOM_EXTENSION_ERROR);
-        ERR_add_error_dataf("extension %u", (unsigned) ext->value);
-        return 0;
-    }
-  }
-
-  return 1;
-}
-
-int custom_ext_add_clienthello(SSL_HANDSHAKE *hs, CBB *extensions) {
-  return custom_ext_add_hello(hs, extensions);
-}
-
-int custom_ext_parse_serverhello(SSL_HANDSHAKE *hs, int *out_alert,
-                                 uint16_t value, const CBS *extension) {
-  SSL *const ssl = hs->ssl;
-  unsigned index;
-  const SSL_CUSTOM_EXTENSION *ext =
-      custom_ext_find(ssl->ctx->client_custom_extensions, &index, value);
-
-  if (// Unknown extensions are not allowed in a ServerHello.
-      ext == NULL ||
-      // Also, if we didn't send the extension, that's also unacceptable.
-      !(hs->custom_extensions.sent & (1u << index))) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
-    ERR_add_error_dataf("extension %u", (unsigned)value);
-    *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
-    return 0;
-  }
-
-  if (ext->parse_callback != NULL &&
-      !ext->parse_callback(ssl, value, CBS_data(extension), CBS_len(extension),
-                           out_alert, ext->parse_arg)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_CUSTOM_EXTENSION_ERROR);
-    ERR_add_error_dataf("extension %u", (unsigned)ext->value);
-    return 0;
-  }
-
-  return 1;
-}
-
-int custom_ext_parse_clienthello(SSL_HANDSHAKE *hs, int *out_alert,
-                                 uint16_t value, const CBS *extension) {
-  SSL *const ssl = hs->ssl;
-  unsigned index;
-  const SSL_CUSTOM_EXTENSION *ext =
-      custom_ext_find(ssl->ctx->server_custom_extensions, &index, value);
-
-  if (ext == NULL) {
-    return 1;
-  }
-
-  assert((hs->custom_extensions.received & (1u << index)) == 0);
-  hs->custom_extensions.received |= (1u << index);
-
-  if (ext->parse_callback &&
-      !ext->parse_callback(ssl, value, CBS_data(extension), CBS_len(extension),
-                           out_alert, ext->parse_arg)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_CUSTOM_EXTENSION_ERROR);
-    ERR_add_error_dataf("extension %u", (unsigned)ext->value);
-    return 0;
-  }
-
-  return 1;
-}
-
-int custom_ext_add_serverhello(SSL_HANDSHAKE *hs, CBB *extensions) {
-  return custom_ext_add_hello(hs, extensions);
-}
-
-// MAX_NUM_CUSTOM_EXTENSIONS is the maximum number of custom extensions that
-// can be set on an |SSL_CTX|. It's determined by the size of the bitset used
-// to track when an extension has been sent.
-#define MAX_NUM_CUSTOM_EXTENSIONS \
-  (sizeof(((SSL_HANDSHAKE *)NULL)->custom_extensions.sent) * 8)
-
-static int custom_ext_append(STACK_OF(SSL_CUSTOM_EXTENSION) **stack,
-                             unsigned extension_value,
-                             SSL_custom_ext_add_cb add_cb,
-                             SSL_custom_ext_free_cb free_cb, void *add_arg,
-                             SSL_custom_ext_parse_cb parse_cb,
-                             void *parse_arg) {
-  if (add_cb == NULL ||
-      0xffff < extension_value ||
-      SSL_extension_supported(extension_value) ||
-      // Specifying a free callback without an add callback is nonsensical
-      // and an error.
-      (*stack != NULL &&
-       (MAX_NUM_CUSTOM_EXTENSIONS <= sk_SSL_CUSTOM_EXTENSION_num(*stack) ||
-        custom_ext_find(*stack, NULL, extension_value) != NULL))) {
-    return 0;
-  }
-
-  SSL_CUSTOM_EXTENSION *ext =
-      (SSL_CUSTOM_EXTENSION *)OPENSSL_malloc(sizeof(SSL_CUSTOM_EXTENSION));
-  if (ext == NULL) {
-    return 0;
-  }
-  ext->add_callback = add_cb;
-  ext->add_arg = add_arg;
-  ext->free_callback = free_cb;
-  ext->parse_callback = parse_cb;
-  ext->parse_arg = parse_arg;
-  ext->value = extension_value;
-
-  if (*stack == NULL) {
-    *stack = sk_SSL_CUSTOM_EXTENSION_new_null();
-    if (*stack == NULL) {
-      SSL_CUSTOM_EXTENSION_free(ext);
-      return 0;
-    }
-  }
-
-  if (!sk_SSL_CUSTOM_EXTENSION_push(*stack, ext)) {
-    SSL_CUSTOM_EXTENSION_free(ext);
-    if (sk_SSL_CUSTOM_EXTENSION_num(*stack) == 0) {
-      sk_SSL_CUSTOM_EXTENSION_free(*stack);
-      *stack = NULL;
-    }
-    return 0;
-  }
-
-  return 1;
-}
-
-}  // namespace bssl
-
-using namespace bssl;
-
-int SSL_CTX_add_client_custom_ext(SSL_CTX *ctx, unsigned extension_value,
-                                  SSL_custom_ext_add_cb add_cb,
-                                  SSL_custom_ext_free_cb free_cb, void *add_arg,
-                                  SSL_custom_ext_parse_cb parse_cb,
-                                  void *parse_arg) {
-  return custom_ext_append(&ctx->client_custom_extensions, extension_value,
-                           add_cb ? add_cb : default_add_callback, free_cb,
-                           add_arg, parse_cb, parse_arg);
-}
-
-int SSL_CTX_add_server_custom_ext(SSL_CTX *ctx, unsigned extension_value,
-                                  SSL_custom_ext_add_cb add_cb,
-                                  SSL_custom_ext_free_cb free_cb, void *add_arg,
-                                  SSL_custom_ext_parse_cb parse_cb,
-                                  void *parse_arg) {
-  return custom_ext_append(&ctx->server_custom_extensions, extension_value,
-                           add_cb ? add_cb : default_add_callback, free_cb,
-                           add_arg, parse_cb, parse_arg);
-}
diff --git a/src/ssl/d1_both.cc b/src/ssl/d1_both.cc
index f561332..f22a498 100644
--- a/src/ssl/d1_both.cc
+++ b/src/ssl/d1_both.cc
@@ -536,6 +536,20 @@
   return true;
 }
 
+// ssl_size_t_greater_than_32_bits returns whether |v| exceeds the bounds of a
+// 32-bit value. The obvious thing doesn't work because, in some 32-bit build
+// configurations, the compiler warns that the test is always false and breaks
+// the build.
+static bool ssl_size_t_greater_than_32_bits(size_t v) {
+#if defined(OPENSSL_64_BIT)
+  return v > 0xffffffff;
+#elif defined(OPENSSL_32_BIT)
+  return false;
+#else
+#error "Building for neither 32- nor 64-bits."
+#endif
+}
+
 // add_outgoing adds a new handshake message or ChangeCipherSpec to the current
 // outgoing flight. It returns true on success and false on error.
 static bool add_outgoing(SSL *ssl, bool is_ccs, Array<uint8_t> data) {
@@ -550,7 +564,7 @@
                     (1 << 8 * sizeof(ssl->d1->outgoing_messages_len)),
                 "outgoing_messages_len is too small");
   if (ssl->d1->outgoing_messages_len >= SSL_MAX_HANDSHAKE_FLIGHT ||
-      data.size() > 0xffffffff) {
+      ssl_size_t_greater_than_32_bits(data.size())) {
     assert(false);
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return false;
@@ -604,12 +618,12 @@
   // |SSL_set_mtu|. Does this need to be so complex?
   if (ssl->d1->mtu < dtls1_min_mtu() &&
       !(SSL_get_options(ssl) & SSL_OP_NO_QUERY_MTU)) {
-    long mtu = BIO_ctrl(ssl->wbio, BIO_CTRL_DGRAM_QUERY_MTU, 0, NULL);
+    long mtu = BIO_ctrl(ssl->wbio.get(), BIO_CTRL_DGRAM_QUERY_MTU, 0, NULL);
     if (mtu >= 0 && mtu <= (1 << 30) && (unsigned)mtu >= dtls1_min_mtu()) {
       ssl->d1->mtu = (unsigned)mtu;
     } else {
       ssl->d1->mtu = kDefaultMTU;
-      BIO_ctrl(ssl->wbio, BIO_CTRL_DGRAM_SET_MTU, ssl->d1->mtu, NULL);
+      BIO_ctrl(ssl->wbio.get(), BIO_CTRL_DGRAM_SET_MTU, ssl->d1->mtu, NULL);
     }
   }
 
@@ -789,7 +803,7 @@
       goto err;
     }
 
-    int bio_ret = BIO_write(ssl->wbio, packet, packet_len);
+    int bio_ret = BIO_write(ssl->wbio.get(), packet, packet_len);
     if (bio_ret <= 0) {
       // Retry this packet the next time around.
       ssl->d1->outgoing_written = old_written;
@@ -800,7 +814,7 @@
     }
   }
 
-  if (BIO_flush(ssl->wbio) <= 0) {
+  if (BIO_flush(ssl->wbio.get()) <= 0) {
     ssl->s3->rwstate = SSL_WRITING;
     goto err;
   }
diff --git a/src/ssl/d1_lib.cc b/src/ssl/d1_lib.cc
index eff06ee..d73e538 100644
--- a/src/ssl/d1_lib.cc
+++ b/src/ssl/d1_lib.cc
@@ -171,7 +171,8 @@
   // Reduce MTU after 2 unsuccessful retransmissions
   if (ssl->d1->num_timeouts > DTLS1_MTU_TIMEOUTS &&
       !(SSL_get_options(ssl) & SSL_OP_NO_QUERY_MTU)) {
-    long mtu = BIO_ctrl(ssl->wbio, BIO_CTRL_DGRAM_GET_FALLBACK_MTU, 0, NULL);
+    long mtu =
+        BIO_ctrl(ssl->wbio.get(), BIO_CTRL_DGRAM_GET_FALLBACK_MTU, 0, nullptr);
     if (mtu >= 0 && mtu <= (1 << 30) && (unsigned)mtu >= dtls1_min_mtu()) {
       ssl->d1->mtu = (unsigned)mtu;
     }
diff --git a/src/ssl/d1_pkt.cc b/src/ssl/d1_pkt.cc
index d29a5c2..a694c5f 100644
--- a/src/ssl/d1_pkt.cc
+++ b/src/ssl/d1_pkt.cc
@@ -260,7 +260,7 @@
 
   // If the alert is fatal, flush the BIO now.
   if (ssl->s3->send_alert[0] == SSL3_AL_FATAL) {
-    BIO_flush(ssl->wbio);
+    BIO_flush(ssl->wbio.get());
   }
 
   ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_ALERT, ssl->s3->send_alert);
diff --git a/src/ssl/d1_srtp.cc b/src/ssl/d1_srtp.cc
index f27c9ff..96d7d51 100644
--- a/src/ssl/d1_srtp.cc
+++ b/src/ssl/d1_srtp.cc
@@ -158,8 +158,9 @@
   return 0;
 }
 
-static int ssl_ctx_make_profiles(const char *profiles_string,
-                                 STACK_OF(SRTP_PROTECTION_PROFILE) **out) {
+static int ssl_ctx_make_profiles(
+    const char *profiles_string,
+    UniquePtr<STACK_OF(SRTP_PROTECTION_PROFILE)> *out) {
   UniquePtr<STACK_OF(SRTP_PROTECTION_PROFILE)> profiles(
       sk_SRTP_PROTECTION_PROFILE_new_null());
   if (profiles == nullptr) {
@@ -188,8 +189,7 @@
     }
   } while (col);
 
-  sk_SRTP_PROTECTION_PROFILE_free(*out);
-  *out = profiles.release();
+  *out = std::move(profiles);
   return 1;
 }
 
@@ -198,23 +198,23 @@
 }
 
 int SSL_set_srtp_profiles(SSL *ssl, const char *profiles) {
-  return ssl_ctx_make_profiles(profiles, &ssl->srtp_profiles);
+  return ssl->config != nullptr &&
+         ssl_ctx_make_profiles(profiles, &ssl->config->srtp_profiles);
 }
 
 STACK_OF(SRTP_PROTECTION_PROFILE) *SSL_get_srtp_profiles(SSL *ssl) {
-  if (ssl == NULL) {
-    return NULL;
+  if (ssl == nullptr) {
+    return nullptr;
   }
 
-  if (ssl->srtp_profiles != NULL) {
-    return ssl->srtp_profiles;
+  if (ssl->config == nullptr) {
+    assert(0);
+    return nullptr;
   }
 
-  if (ssl->ctx->srtp_profiles != NULL) {
-    return ssl->ctx->srtp_profiles;
-  }
-
-  return NULL;
+  return ssl->config->srtp_profiles != nullptr
+             ? ssl->config->srtp_profiles.get()
+             : ssl->ctx->srtp_profiles.get();
 }
 
 const SRTP_PROTECTION_PROFILE *SSL_get_selected_srtp_profile(SSL *ssl) {
diff --git a/src/ssl/handoff.cc b/src/ssl/handoff.cc
index 2cbbaeb..68cac5b 100644
--- a/src/ssl/handoff.cc
+++ b/src/ssl/handoff.cc
@@ -55,7 +55,7 @@
     return false;
   }
 
-  ssl->handoff = false;
+  s3->hs->config->handoff = false;
   return true;
 }
 
@@ -94,18 +94,29 @@
     s3->hs->transcript.Update(transcript);
     s3->is_v2_hello = true;
   }
-  ssl->handback = true;
+  s3->hs->handback = true;
 
   return true;
 }
 
 bool SSL_serialize_handback(const SSL *ssl, CBB *out) {
-  if (!ssl->server ||
-      (ssl->s3->hs->state != state12_finish_server_handshake &&
-       ssl->s3->hs->state != state12_read_client_certificate) ||
-      ssl->method->is_dtls || ssl->version < TLS1_VERSION) {
+  if (!ssl->server || ssl->method->is_dtls) {
     return false;
   }
+  handback_t type;
+  switch (ssl->s3->hs->state) {
+    case state12_read_change_cipher_spec:
+      type = handback_after_session_resumption;
+      break;
+    case state12_read_client_certificate:
+      type = handback_after_ecdhe;
+      break;
+    case state12_finish_server_handshake:
+      type = handback_after_handshake;
+      break;
+    default:
+      return false;
+  }
 
   const SSL3_STATE *const s3 = ssl->s3;
   size_t hostname_len = 0;
@@ -113,26 +124,36 @@
     hostname_len = strlen(s3->hostname.get());
   }
 
-  size_t iv_len = 0;
-  const uint8_t *read_iv = nullptr, *write_iv = nullptr;
   Span<const uint8_t> transcript;
-  if (ssl->s3->hs->state == state12_finish_server_handshake) {
-    if (ssl->version == TLS1_VERSION &&
-        SSL_CIPHER_is_block_cipher(s3->aead_read_ctx->cipher()) &&
-        (!s3->aead_read_ctx->GetIV(&read_iv, &iv_len) ||
-         !s3->aead_write_ctx->GetIV(&write_iv, &iv_len))) {
-      return false;
-    }
-  } else {
+  if (type == handback_after_ecdhe ||
+      type == handback_after_session_resumption) {
     transcript = s3->hs->transcript.buffer();
   }
+  size_t write_iv_len = 0;
+  const uint8_t *write_iv = nullptr;
+  if ((type == handback_after_session_resumption ||
+       type == handback_after_handshake) &&
+      ssl->version == TLS1_VERSION &&
+      SSL_CIPHER_is_block_cipher(s3->aead_write_ctx->cipher()) &&
+      !s3->aead_write_ctx->GetIV(&write_iv, &write_iv_len)) {
+    return false;
+  }
+  size_t read_iv_len = 0;
+  const uint8_t *read_iv = nullptr;
+  if (type == handback_after_handshake &&
+      ssl->version == TLS1_VERSION &&
+      SSL_CIPHER_is_block_cipher(s3->aead_read_ctx->cipher()) &&
+      !s3->aead_read_ctx->GetIV(&read_iv, &read_iv_len)) {
+      return false;
+  }
 
   // TODO(mab): make sure everything is serialized.
   CBB seq, key_share;
-  SSL_SESSION *session =
-      s3->session_reused ? ssl->session : s3->hs->new_session.get();
+  const SSL_SESSION *session =
+      s3->session_reused ? ssl->session.get() : s3->hs->new_session.get();
   if (!CBB_add_asn1(out, &seq, CBS_ASN1_SEQUENCE) ||
       !CBB_add_asn1_uint64(&seq, kHandbackVersion) ||
+      !CBB_add_asn1_uint64(&seq, type) ||
       !CBB_add_asn1_octet_string(&seq, s3->read_sequence,
                                  sizeof(s3->read_sequence)) ||
       !CBB_add_asn1_octet_string(&seq, s3->write_sequence,
@@ -141,10 +162,10 @@
                                  sizeof(s3->server_random)) ||
       !CBB_add_asn1_octet_string(&seq, s3->client_random,
                                  sizeof(s3->client_random)) ||
-      !CBB_add_asn1_octet_string(&seq, read_iv, iv_len) ||
-      !CBB_add_asn1_octet_string(&seq, write_iv, iv_len) ||
+      !CBB_add_asn1_octet_string(&seq, read_iv, read_iv_len) ||
+      !CBB_add_asn1_octet_string(&seq, write_iv, write_iv_len) ||
       !CBB_add_asn1_bool(&seq, s3->session_reused) ||
-      !CBB_add_asn1_bool(&seq, s3->tlsext_channel_id_valid) ||
+      !CBB_add_asn1_bool(&seq, s3->channel_id_valid) ||
       !ssl_session_serialize(session, &seq) ||
       !CBB_add_asn1_octet_string(&seq, s3->next_proto_negotiated.data(),
                                  s3->next_proto_negotiated.size()) ||
@@ -153,8 +174,8 @@
       !CBB_add_asn1_octet_string(
           &seq, reinterpret_cast<uint8_t *>(s3->hostname.get()),
           hostname_len) ||
-      !CBB_add_asn1_octet_string(&seq, s3->tlsext_channel_id,
-                                 sizeof(s3->tlsext_channel_id)) ||
+      !CBB_add_asn1_octet_string(&seq, s3->channel_id,
+                                 sizeof(s3->channel_id)) ||
       !CBB_add_asn1_bool(&seq, ssl->s3->token_binding_negotiated) ||
       !CBB_add_asn1_uint64(&seq, ssl->s3->negotiated_token_binding_param) ||
       !CBB_add_asn1_bool(&seq, s3->hs->next_proto_neg_seen) ||
@@ -166,7 +187,7 @@
       !CBB_add_asn1(&seq, &key_share, CBS_ASN1_SEQUENCE)) {
     return false;
   }
-  if (ssl->s3->hs->state == state12_read_client_certificate &&
+  if (type == handback_after_ecdhe &&
       !s3->hs->key_share->Serialize(&key_share)) {
     return false;
   }
@@ -180,7 +201,7 @@
   }
 
   SSL3_STATE *const s3 = ssl->s3;
-  uint64_t handback_version, negotiated_token_binding_param, cipher;
+  uint64_t handback_version, negotiated_token_binding_param, cipher, type;
 
   CBS seq, read_seq, write_seq, server_rand, client_rand, read_iv, write_iv,
       next_proto, alpn, hostname, channel_id, transcript, key_share;
@@ -191,7 +212,8 @@
   CBS handback_cbs(handback);
   if (!CBS_get_asn1(&handback_cbs, &seq, CBS_ASN1_SEQUENCE) ||
       !CBS_get_asn1_uint64(&seq, &handback_version) ||
-      handback_version != kHandbackVersion) {
+      handback_version != kHandbackVersion ||
+      !CBS_get_asn1_uint64(&seq, &type)) {
     return false;
   }
 
@@ -217,9 +239,8 @@
   s3->hs = ssl_handshake_new(ssl);
   if (session_reused) {
     ssl->session =
-        SSL_SESSION_parse(&seq, ssl->ctx->x509_method, ssl->ctx->pool)
-            .release();
-    session = ssl->session;
+        SSL_SESSION_parse(&seq, ssl->ctx->x509_method, ssl->ctx->pool);
+    session = ssl->session.get();
   } else {
     s3->hs->new_session =
         SSL_SESSION_parse(&seq, ssl->ctx->x509_method, ssl->ctx->pool);
@@ -230,9 +251,9 @@
       !CBS_get_asn1(&seq, &alpn, CBS_ASN1_OCTETSTRING) ||
       !CBS_get_asn1(&seq, &hostname, CBS_ASN1_OCTETSTRING) ||
       !CBS_get_asn1(&seq, &channel_id, CBS_ASN1_OCTETSTRING) ||
-      CBS_len(&channel_id) != sizeof(s3->tlsext_channel_id) ||
-      !CBS_copy_bytes(&channel_id, s3->tlsext_channel_id,
-                      sizeof(s3->tlsext_channel_id)) ||
+      CBS_len(&channel_id) != sizeof(s3->channel_id) ||
+      !CBS_copy_bytes(&channel_id, s3->channel_id,
+                      sizeof(s3->channel_id)) ||
       !CBS_get_asn1_bool(&seq, &token_binding_negotiated) ||
       !CBS_get_asn1_uint64(&seq, &negotiated_token_binding_param) ||
       !CBS_get_asn1_bool(&seq, &next_proto_neg_seen) ||
@@ -252,14 +273,36 @@
   }
 
   ssl->version = session->ssl_version;
+  s3->have_version = true;
+  if (!ssl_method_supports_version(ssl->method, ssl->version) ||
+      session->cipher != s3->hs->new_cipher ||
+      ssl_protocol_version(ssl) < SSL_CIPHER_get_min_version(session->cipher) ||
+      SSL_CIPHER_get_max_version(session->cipher) < ssl_protocol_version(ssl)) {
+    return false;
+  }
   ssl->do_handshake = ssl_server_handshake;
   ssl->server = true;
-
-  s3->have_version = true;
-  s3->hs->state = CBS_len(&transcript) == 0 ? state12_finish_server_handshake
-                                            : state12_read_client_certificate;
+  switch (type) {
+    case handback_after_session_resumption:
+      ssl->s3->hs->state = state12_read_change_cipher_spec;
+      if (!session_reused) {
+        return false;
+      }
+      break;
+    case handback_after_ecdhe:
+      ssl->s3->hs->state = state12_read_client_certificate;
+      if (session_reused) {
+        return false;
+      }
+      break;
+    case handback_after_handshake:
+      ssl->s3->hs->state = state12_finish_server_handshake;
+      break;
+    default:
+      return false;
+  }
   s3->session_reused = session_reused;
-  s3->tlsext_channel_id_valid = channel_id_valid;
+  s3->channel_id_valid = channel_id_valid;
   s3->next_proto_negotiated.CopyFrom(next_proto);
   s3->alpn_selected.CopyFrom(alpn);
 
@@ -284,31 +327,33 @@
   s3->aead_write_ctx->SetVersionIfNullCipher(ssl->version);
   s3->hs->cert_request = cert_request;
 
-  if (s3->hs->state == state12_finish_server_handshake) {
-    Array<uint8_t> key_block;
-    if (!tls1_configure_aead(ssl, evp_aead_open, &key_block, session->cipher,
-                             read_iv) ||
-        !tls1_configure_aead(ssl, evp_aead_seal, &key_block, session->cipher,
-                             write_iv)) {
-      return false;
-    }
-
-    if (!CBS_copy_bytes(&read_seq, s3->read_sequence,
-                        sizeof(s3->read_sequence)) ||
-        !CBS_copy_bytes(&write_seq, s3->write_sequence,
-                        sizeof(s3->write_sequence))) {
-      return false;
-    }
-  } else {
-    if (!s3->hs->transcript.Init() ||
-        !s3->hs->transcript.InitHash(ssl_protocol_version(ssl),
-                                     s3->hs->new_cipher) ||
-        !s3->hs->transcript.Update(transcript)) {
-      return false;
-    }
-    if ((s3->hs->key_share = SSLKeyShare::Create(&key_share)) == nullptr) {
-      return false;
-    }
+  Array<uint8_t> key_block;
+  if ((type == handback_after_session_resumption ||
+       type == handback_after_handshake) &&
+      (!tls1_configure_aead(ssl, evp_aead_seal, &key_block, session->cipher,
+                            write_iv) ||
+       !CBS_copy_bytes(&write_seq, s3->write_sequence,
+                       sizeof(s3->write_sequence)))) {
+    return false;
+  }
+  if (type == handback_after_handshake &&
+      (!tls1_configure_aead(ssl, evp_aead_open, &key_block, session->cipher,
+                            read_iv) ||
+       !CBS_copy_bytes(&read_seq, s3->read_sequence,
+                       sizeof(s3->read_sequence)))) {
+    return false;
+  }
+  if ((type == handback_after_ecdhe ||
+       type == handback_after_session_resumption) &&
+      (!s3->hs->transcript.Init() ||
+       !s3->hs->transcript.InitHash(ssl_protocol_version(ssl),
+                                    s3->hs->new_cipher) ||
+       !s3->hs->transcript.Update(transcript))) {
+    return false;
+  }
+  if (type == handback_after_ecdhe &&
+      (s3->hs->key_share = SSLKeyShare::Create(&key_share)) == nullptr) {
+    return false;
   }
 
   return CBS_len(&seq) == 0;
diff --git a/src/ssl/handshake.cc b/src/ssl/handshake.cc
index 00a2cc5..4683ac5 100644
--- a/src/ssl/handshake.cc
+++ b/src/ssl/handshake.cc
@@ -130,7 +130,6 @@
       needs_psk_binder(false),
       received_hello_retry_request(false),
       sent_hello_retry_request(false),
-      received_custom_extension(false),
       handshake_finalized(false),
       accept_psk_mode(false),
       cert_request(false),
@@ -146,7 +145,10 @@
       ticket_expected(false),
       extended_master_secret(false),
       pending_private_key_op(false),
-      grease_seeded(false) {
+      grease_seeded(false),
+      handback(false),
+      cert_compression_negotiated(false) {
+  assert(ssl);
 }
 
 SSL_HANDSHAKE::~SSL_HANDSHAKE() {
@@ -159,6 +161,11 @@
       !hs->transcript.Init()) {
     return nullptr;
   }
+  hs->config = ssl->config.get();
+  if (!hs->config) {
+    assert(hs->config);
+    return nullptr;
+  }
   return hs;
 }
 
@@ -189,7 +196,8 @@
   static const size_t kMaxMessageLen = 16384;
 
   if (SSL_in_init(ssl)) {
-    if ((!ssl->server || (ssl->verify_mode & SSL_VERIFY_PEER)) &&
+    SSL_CONFIG *config = ssl->config.get();  // SSL_in_init() implies not NULL.
+    if ((!ssl->server || (config->verify_mode & SSL_VERIFY_PEER)) &&
         kMaxMessageLen < ssl->max_cert_list) {
       return ssl->max_cert_list;
     }
@@ -272,16 +280,6 @@
   return 1;
 }
 
-static void set_crypto_buffer(CRYPTO_BUFFER **dest, CRYPTO_BUFFER *src) {
-  // TODO(davidben): Remove this helper once |SSL_SESSION| can use |UniquePtr|
-  // and |UniquePtr| has up_ref helpers.
-  CRYPTO_BUFFER_free(*dest);
-  *dest = src;
-  if (src != nullptr) {
-    CRYPTO_BUFFER_up_ref(src);
-  }
-}
-
 enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   const SSL_SESSION *prev_session = ssl->s3->established_session.get();
@@ -291,18 +289,19 @@
     // so this check is sufficient to ensure the reported peer certificate never
     // changes on renegotiation.
     assert(!ssl->server);
-    if (sk_CRYPTO_BUFFER_num(prev_session->certs) !=
-        sk_CRYPTO_BUFFER_num(hs->new_session->certs)) {
+    if (sk_CRYPTO_BUFFER_num(prev_session->certs.get()) !=
+        sk_CRYPTO_BUFFER_num(hs->new_session->certs.get())) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_SERVER_CERT_CHANGED);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
       return ssl_verify_invalid;
     }
 
-    for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(hs->new_session->certs); i++) {
+    for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(hs->new_session->certs.get());
+         i++) {
       const CRYPTO_BUFFER *old_cert =
-          sk_CRYPTO_BUFFER_value(prev_session->certs, i);
+          sk_CRYPTO_BUFFER_value(prev_session->certs.get(), i);
       const CRYPTO_BUFFER *new_cert =
-          sk_CRYPTO_BUFFER_value(hs->new_session->certs, i);
+          sk_CRYPTO_BUFFER_value(hs->new_session->certs.get(), i);
       if (CRYPTO_BUFFER_len(old_cert) != CRYPTO_BUFFER_len(new_cert) ||
           OPENSSL_memcmp(CRYPTO_BUFFER_data(old_cert),
                          CRYPTO_BUFFER_data(new_cert),
@@ -317,25 +316,24 @@
     // certificate. Since we only authenticated the previous one, copy other
     // authentication from the established session and ignore what was newly
     // received.
-    set_crypto_buffer(&hs->new_session->ocsp_response,
-                      prev_session->ocsp_response);
-    set_crypto_buffer(&hs->new_session->signed_cert_timestamp_list,
-                      prev_session->signed_cert_timestamp_list);
+    hs->new_session->ocsp_response = UpRef(prev_session->ocsp_response);
+    hs->new_session->signed_cert_timestamp_list =
+        UpRef(prev_session->signed_cert_timestamp_list);
     hs->new_session->verify_result = prev_session->verify_result;
     return ssl_verify_ok;
   }
 
   uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN;
   enum ssl_verify_result_t ret;
-  if (ssl->custom_verify_callback != nullptr) {
-    ret = ssl->custom_verify_callback(ssl, &alert);
+  if (hs->config->custom_verify_callback != nullptr) {
+    ret = hs->config->custom_verify_callback(ssl, &alert);
     switch (ret) {
       case ssl_verify_ok:
         hs->new_session->verify_result = X509_V_OK;
         break;
       case ssl_verify_invalid:
         // If |SSL_VERIFY_NONE|, the error is non-fatal, but we keep the result.
-        if (ssl->verify_mode == SSL_VERIFY_NONE) {
+        if (hs->config->verify_mode == SSL_VERIFY_NONE) {
           ERR_clear_error();
           ret = ssl_verify_ok;
         }
@@ -346,7 +344,7 @@
     }
   } else {
     ret = ssl->ctx->x509_method->session_verify_cert_chain(
-              hs->new_session.get(), ssl, &alert)
+              hs->new_session.get(), hs, &alert)
               ? ssl_verify_ok
               : ssl_verify_invalid;
   }
@@ -356,6 +354,22 @@
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
   }
 
+  // Emulate OpenSSL's client OCSP callback. OpenSSL verifies certificates
+  // before it receives the OCSP, so it needs a second callback for OCSP.
+  if (ret == ssl_verify_ok && !ssl->server &&
+      hs->config->ocsp_stapling_enabled &&
+      ssl->ctx->legacy_ocsp_callback != nullptr) {
+    int cb_ret =
+        ssl->ctx->legacy_ocsp_callback(ssl, ssl->ctx->legacy_ocsp_callback_arg);
+    if (cb_ret <= 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_OCSP_CB_ERROR);
+      ssl_send_alert(ssl, SSL3_AL_FATAL,
+                     cb_ret == 0 ? SSL_AD_BAD_CERTIFICATE_STATUS_RESPONSE
+                                 : SSL_AD_INTERNAL_ERROR);
+      ret = ssl_verify_invalid;
+    }
+  }
+
   return ret;
 }
 
@@ -408,20 +422,18 @@
   }
 
   // Copy the Finished so we can use it for renegotiation checks.
-  if (ssl->version != SSL3_VERSION) {
-    if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
-        finished_len > sizeof(ssl->s3->previous_server_finished)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return ssl_hs_error;
-    }
+  if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
+      finished_len > sizeof(ssl->s3->previous_server_finished)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return ssl_hs_error;
+  }
 
-    if (ssl->server) {
-      OPENSSL_memcpy(ssl->s3->previous_client_finished, finished, finished_len);
-      ssl->s3->previous_client_finished_len = finished_len;
-    } else {
-      OPENSSL_memcpy(ssl->s3->previous_server_finished, finished, finished_len);
-      ssl->s3->previous_server_finished_len = finished_len;
-    }
+  if (ssl->server) {
+    OPENSSL_memcpy(ssl->s3->previous_client_finished, finished, finished_len);
+    ssl->s3->previous_client_finished_len = finished_len;
+  } else {
+    OPENSSL_memcpy(ssl->s3->previous_server_finished, finished, finished_len);
+    ssl->s3->previous_server_finished_len = finished_len;
   }
 
   ssl->method->next_message(ssl);
@@ -447,20 +459,18 @@
   }
 
   // Copy the Finished so we can use it for renegotiation checks.
-  if (ssl->version != SSL3_VERSION) {
-    if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
-        finished_len > sizeof(ssl->s3->previous_server_finished)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return 0;
-    }
+  if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
+      finished_len > sizeof(ssl->s3->previous_server_finished)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
 
-    if (ssl->server) {
-      OPENSSL_memcpy(ssl->s3->previous_server_finished, finished, finished_len);
-      ssl->s3->previous_server_finished_len = finished_len;
-    } else {
-      OPENSSL_memcpy(ssl->s3->previous_client_finished, finished, finished_len);
-      ssl->s3->previous_client_finished_len = finished_len;
-    }
+  if (ssl->server) {
+    OPENSSL_memcpy(ssl->s3->previous_server_finished, finished, finished_len);
+    ssl->s3->previous_server_finished_len = finished_len;
+  } else {
+    OPENSSL_memcpy(ssl->s3->previous_client_finished, finished, finished_len);
+    ssl->s3->previous_client_finished_len = finished_len;
   }
 
   ScopedCBB cbb;
@@ -475,12 +485,13 @@
   return 1;
 }
 
-bool ssl_output_cert_chain(SSL *ssl) {
+bool ssl_output_cert_chain(SSL_HANDSHAKE *hs) {
   ScopedCBB cbb;
   CBB body;
-  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CERTIFICATE) ||
-      !ssl_add_cert_chain(ssl, &body) ||
-      !ssl_add_message_cbb(ssl, cbb.get())) {
+  if (!hs->ssl->method->init_message(hs->ssl, cbb.get(), &body,
+                                     SSL3_MT_CERTIFICATE) ||
+      !ssl_add_cert_chain(hs, &body) ||
+      !ssl_add_message_cbb(hs->ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return false;
   }
diff --git a/src/ssl/handshake_client.cc b/src/ssl/handshake_client.cc
index fbef2e1..de5d8e9 100644
--- a/src/ssl/handshake_client.cc
+++ b/src/ssl/handshake_client.cc
@@ -199,13 +199,13 @@
 
 // ssl_get_client_disabled sets |*out_mask_a| and |*out_mask_k| to masks of
 // disabled algorithms.
-static void ssl_get_client_disabled(SSL *ssl, uint32_t *out_mask_a,
+static void ssl_get_client_disabled(SSL_HANDSHAKE *hs, uint32_t *out_mask_a,
                                     uint32_t *out_mask_k) {
   *out_mask_a = 0;
   *out_mask_k = 0;
 
   // PSK requires a client callback.
-  if (ssl->psk_client_callback == NULL) {
+  if (hs->config->psk_client_callback == NULL) {
     *out_mask_a |= SSL_aPSK;
     *out_mask_k |= SSL_kPSK;
   }
@@ -214,7 +214,7 @@
 static int ssl_write_client_cipher_list(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
   uint32_t mask_a, mask_k;
-  ssl_get_client_disabled(ssl, &mask_a, &mask_k);
+  ssl_get_client_disabled(hs, &mask_a, &mask_k);
 
   CBB child;
   if (!CBB_add_u16_length_prefixed(out, &child)) {
@@ -269,15 +269,6 @@
     }
   }
 
-  // For SSLv3, the SCSV is added. Otherwise the renegotiation extension is
-  // added.
-  if (hs->max_version == SSL3_VERSION &&
-      !ssl->s3->initial_handshake_complete) {
-    if (!CBB_add_u16(&child, SSL3_CK_SCSV & 0xffff)) {
-      return 0;
-    }
-  }
-
   if (ssl->mode & SSL_MODE_SEND_FALLBACK_SCSV) {
     if (!CBB_add_u16(&child, SSL3_CK_FALLBACK_SCSV & 0xffff)) {
       return 0;
@@ -390,16 +381,10 @@
   ssl->s3->session_reused = false;
 
   // Freeze the version range.
-  if (!ssl_get_version_range(ssl, &hs->min_version, &hs->max_version)) {
+  if (!ssl_get_version_range(hs, &hs->min_version, &hs->max_version)) {
     return ssl_hs_error;
   }
 
-  // SSL 3.0 ClientHellos should use SSL 3.0 not TLS 1.0, for the record-layer
-  // version.
-  if (hs->max_version == SSL3_VERSION) {
-    ssl->s3->aead_write_ctx->SetVersionIfNullCipher(SSL3_VERSION);
-  }
-
   // Always advertise the ClientHello version from the original maximum version,
   // even on renegotiation. The static RSA key exchange uses this field, and
   // some servers fail when it changes across handshakes.
@@ -417,9 +402,9 @@
     if (ssl->session->is_server ||
         !ssl_supports_version(hs, ssl->session->ssl_version) ||
         (ssl->session->session_id_length == 0 &&
-         ssl->session->tlsext_ticklen == 0) ||
+         ssl->session->ticket.empty()) ||
         ssl->session->not_resumable ||
-        !ssl_session_is_time_valid(ssl, ssl->session)) {
+        !ssl_session_is_time_valid(ssl, ssl->session.get())) {
       ssl_set_session(ssl, NULL);
     }
   }
@@ -480,8 +465,7 @@
   // Stash the early data session, so connection properties may be queried out
   // of it.
   hs->in_early_data = true;
-  SSL_SESSION_up_ref(ssl->session);
-  hs->early_session.reset(ssl->session);
+  hs->early_session = UpRef(ssl->session);
   hs->can_early_write = true;
 
   hs->state = state_read_server_hello;
@@ -659,7 +643,7 @@
 
   // The cipher must be allowed in the selected version and enabled.
   uint32_t mask_a, mask_k;
-  ssl_get_client_disabled(ssl, &mask_a, &mask_k);
+  ssl_get_client_disabled(hs, &mask_a, &mask_k);
   if ((cipher->algorithm_mkey & mask_k) || (cipher->algorithm_auth & mask_a) ||
       SSL_CIPHER_get_min_version(cipher) > ssl_protocol_version(ssl) ||
       SSL_CIPHER_get_max_version(cipher) < ssl_protocol_version(ssl) ||
@@ -680,7 +664,7 @@
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
       return ssl_hs_error;
     }
-    if (!ssl_session_is_context_valid(ssl, ssl->session)) {
+    if (!ssl_session_is_context_valid(hs, ssl->session.get())) {
       // This is actually a client application bug.
       OPENSSL_PUT_ERROR(SSL,
                         SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT);
@@ -788,16 +772,13 @@
 
   CBS body = msg.body;
   uint8_t alert = SSL_AD_DECODE_ERROR;
-  UniquePtr<STACK_OF(CRYPTO_BUFFER)> chain;
-  if (!ssl_parse_cert_chain(&alert, &chain, &hs->peer_pubkey, NULL, &body,
-                            ssl->ctx->pool)) {
+  if (!ssl_parse_cert_chain(&alert, &hs->new_session->certs, &hs->peer_pubkey,
+                            NULL, &body, ssl->ctx->pool)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
-  sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
-  hs->new_session->certs = chain.release();
 
-  if (sk_CRYPTO_BUFFER_num(hs->new_session->certs) == 0 ||
+  if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0 ||
       CBS_len(&body) != 0 ||
       !ssl->ctx->x509_method->session_cache_objects(hs->new_session.get())) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
@@ -807,7 +788,7 @@
 
   if (!ssl_check_leaf_certificate(
           hs, hs->peer_pubkey.get(),
-          sk_CRYPTO_BUFFER_value(hs->new_session->certs, 0))) {
+          sk_CRYPTO_BUFFER_value(hs->new_session->certs.get(), 0))) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
     return ssl_hs_error;
   }
@@ -854,9 +835,8 @@
     return ssl_hs_error;
   }
 
-  CRYPTO_BUFFER_free(hs->new_session->ocsp_response);
-  hs->new_session->ocsp_response =
-      CRYPTO_BUFFER_new_from_CBS(&ocsp_response, ssl->ctx->pool);
+  hs->new_session->ocsp_response.reset(
+      CRYPTO_BUFFER_new_from_CBS(&ocsp_response, ssl->ctx->pool));
   if (hs->new_session->ocsp_response == nullptr) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
     return ssl_hs_error;
@@ -970,7 +950,7 @@
     hs->new_session->group_id = group_id;
 
     // Ensure the group is consistent with preferences.
-    if (!tls1_check_group_id(ssl, group_id)) {
+    if (!tls1_check_group_id(hs, group_id)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
       return ssl_hs_error;
@@ -1176,8 +1156,8 @@
   }
 
   // Call cert_cb to update the certificate.
-  if (ssl->cert->cert_cb != NULL) {
-    int rv = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg);
+  if (hs->config->cert->cert_cb != NULL) {
+    int rv = hs->config->cert->cert_cb(ssl, hs->config->cert->cert_cb_arg);
     if (rv == 0) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR);
@@ -1189,23 +1169,13 @@
     }
   }
 
-  if (!ssl_has_certificate(ssl)) {
+  if (!ssl_has_certificate(hs->config)) {
     // Without a client certificate, the handshake buffer may be released.
     hs->transcript.FreeBuffer();
-
-    // In SSL 3.0, the Certificate message is replaced with a warning alert.
-    if (ssl->version == SSL3_VERSION) {
-      if (!ssl->method->add_alert(ssl, SSL3_AL_WARNING,
-                                  SSL_AD_NO_CERTIFICATE)) {
-        return ssl_hs_error;
-      }
-      hs->state = state_send_client_key_exchange;
-      return ssl_hs_ok;
-    }
   }
 
   if (!ssl_on_certificate_selected(hs) ||
-      !ssl_output_cert_chain(ssl)) {
+      !ssl_output_cert_chain(hs)) {
     return ssl_hs_error;
   }
 
@@ -1234,16 +1204,16 @@
   unsigned psk_len = 0;
   uint8_t psk[PSK_MAX_PSK_LEN];
   if (alg_a & SSL_aPSK) {
-    if (ssl->psk_client_callback == NULL) {
+    if (hs->config->psk_client_callback == NULL) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PSK_NO_CLIENT_CB);
       return ssl_hs_error;
     }
 
     char identity[PSK_MAX_IDENTITY_LEN + 1];
     OPENSSL_memset(identity, 0, sizeof(identity));
-    psk_len =
-        ssl->psk_client_callback(ssl, hs->peer_psk_identity_hint.get(),
-                                 identity, sizeof(identity), psk, sizeof(psk));
+    psk_len = hs->config->psk_client_callback(
+        ssl, hs->peer_psk_identity_hint.get(), identity, sizeof(identity), psk,
+        sizeof(psk));
     if (psk_len == 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PSK_IDENTITY_NOT_FOUND);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
@@ -1251,9 +1221,8 @@
     }
     assert(psk_len <= PSK_MAX_PSK_LEN);
 
-    OPENSSL_free(hs->new_session->psk_identity);
-    hs->new_session->psk_identity = BUF_strdup(identity);
-    if (hs->new_session->psk_identity == NULL) {
+    hs->new_session->psk_identity.reset(BUF_strdup(identity));
+    if (hs->new_session->psk_identity == nullptr) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       return ssl_hs_error;
     }
@@ -1286,21 +1255,14 @@
       return ssl_hs_error;
     }
 
-    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(&body, &child)) {
-        return ssl_hs_error;
-      }
-      enc_pms = &child;
-    }
-
+    CBB enc_pms;
     uint8_t *ptr;
-    if (!CBB_reserve(enc_pms, &ptr, RSA_size(rsa)) ||
+    size_t enc_pms_len;
+    if (!CBB_add_u16_length_prefixed(&body, &enc_pms) ||
+        !CBB_reserve(&enc_pms, &ptr, RSA_size(rsa)) ||
         !RSA_encrypt(rsa, &enc_pms_len, ptr, RSA_size(rsa), pms.data(),
                      pms.size(), RSA_PKCS1_PADDING) ||
-        !CBB_did_write(enc_pms, enc_pms_len) ||
+        !CBB_did_write(&enc_pms, enc_pms_len) ||
         !CBB_flush(&body)) {
       return ssl_hs_error;
     }
@@ -1373,12 +1335,12 @@
 static enum ssl_hs_wait_t do_send_client_certificate_verify(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
-  if (!hs->cert_request || !ssl_has_certificate(ssl)) {
+  if (!hs->cert_request || !ssl_has_certificate(hs->config)) {
     hs->state = state_send_client_finished;
     return ssl_hs_ok;
   }
 
-  assert(ssl_has_private_key(ssl));
+  assert(ssl_has_private_key(hs->config));
   ScopedCBB cbb;
   CBB body, child;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
@@ -1388,6 +1350,7 @@
 
   uint16_t signature_algorithm;
   if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
     return ssl_hs_error;
   }
   if (ssl_protocol_version(ssl) >= TLS1_2_VERSION) {
@@ -1407,40 +1370,16 @@
   }
 
   size_t sig_len = max_sig_len;
-  // The SSL3 construction for CertificateVerify does not decompose into a
-  // single final digest and signature, and must be special-cased.
-  if (ssl_protocol_version(ssl) == SSL3_VERSION) {
-    if (ssl->cert->key_method != NULL) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL_FOR_CUSTOM_KEY);
+  switch (ssl_private_key_sign(hs, ptr, &sig_len, max_sig_len,
+                               signature_algorithm,
+                               hs->transcript.buffer())) {
+    case ssl_private_key_success:
+      break;
+    case ssl_private_key_failure:
       return ssl_hs_error;
-    }
-
-    uint8_t digest[EVP_MAX_MD_SIZE];
-    size_t digest_len;
-    if (!hs->transcript.GetSSL3CertVerifyHash(
-            digest, &digest_len, hs->new_session.get(), signature_algorithm)) {
-      return ssl_hs_error;
-    }
-
-    UniquePtr<EVP_PKEY_CTX> pctx(
-        EVP_PKEY_CTX_new(ssl->cert->privatekey.get(), nullptr));
-    if (!pctx ||
-        !EVP_PKEY_sign_init(pctx.get()) ||
-        !EVP_PKEY_sign(pctx.get(), ptr, &sig_len, digest, digest_len)) {
-      return ssl_hs_error;
-    }
-  } else {
-    switch (ssl_private_key_sign(hs, ptr, &sig_len, max_sig_len,
-                                 signature_algorithm,
-                                 hs->transcript.buffer())) {
-      case ssl_private_key_success:
-        break;
-      case ssl_private_key_failure:
-        return ssl_hs_error;
-      case ssl_private_key_retry:
-        hs->state = state_send_client_certificate_verify;
-        return ssl_hs_private_key_operation;
-    }
+    case ssl_private_key_retry:
+      hs->state = state_send_client_certificate_verify;
+      return ssl_hs_private_key_operation;
   }
 
   if (!CBB_did_write(&child, sig_len) ||
@@ -1458,12 +1397,12 @@
 static enum ssl_hs_wait_t do_send_client_finished(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   // Resolve Channel ID first, before any non-idempotent operations.
-  if (ssl->s3->tlsext_channel_id_valid) {
-    if (!ssl_do_channel_id_callback(ssl)) {
+  if (ssl->s3->channel_id_valid) {
+    if (!ssl_do_channel_id_callback(hs)) {
       return ssl_hs_error;
     }
 
-    if (ssl->tlsext_channel_id_private == NULL) {
+    if (hs->config->channel_id_private == NULL) {
       hs->state = state_send_client_finished;
       return ssl_hs_channel_id_lookup;
     }
@@ -1493,7 +1432,7 @@
     }
   }
 
-  if (ssl->s3->tlsext_channel_id_valid) {
+  if (ssl->s3->channel_id_valid) {
     ScopedCBB cbb;
     CBB body;
     if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CHANNEL_ID) ||
@@ -1583,8 +1522,8 @@
   }
 
   CBS new_session_ticket = msg.body, ticket;
-  uint32_t tlsext_tick_lifetime_hint;
-  if (!CBS_get_u32(&new_session_ticket, &tlsext_tick_lifetime_hint) ||
+  uint32_t ticket_lifetime_hint;
+  if (!CBS_get_u32(&new_session_ticket, &ticket_lifetime_hint) ||
       !CBS_get_u16_length_prefixed(&new_session_ticket, &ticket) ||
       CBS_len(&new_session_ticket) != 0) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
@@ -1609,7 +1548,7 @@
     // immutable once established, so duplicate all but the ticket of the
     // existing session.
     renewed_session =
-        SSL_SESSION_dup(ssl->session, SSL_SESSION_INCLUDE_NONAUTH);
+        SSL_SESSION_dup(ssl->session.get(), SSL_SESSION_INCLUDE_NONAUTH);
     if (!renewed_session) {
       // This should never happen.
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
@@ -1618,14 +1557,13 @@
     session = renewed_session.get();
   }
 
-  // |tlsext_tick_lifetime_hint| is measured from when the ticket was issued.
+  // |ticket_lifetime_hint| is measured from when the ticket was issued.
   ssl_session_rebase_time(ssl, session);
 
-  if (!CBS_stow(&ticket, &session->tlsext_tick, &session->tlsext_ticklen)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+  if (!session->ticket.CopyFrom(ticket)) {
     return ssl_hs_error;
   }
-  session->tlsext_tick_lifetime_hint = tlsext_tick_lifetime_hint;
+  session->ticket_lifetime_hint = ticket_lifetime_hint;
 
   // Generate a session ID for this session based on the session ticket. We use
   // the session ID mechanism for detecting ticket resumption. This also fits in
@@ -1637,9 +1575,8 @@
   }
 
   if (renewed_session) {
-    session->not_resumable = 0;
-    SSL_SESSION_free(ssl->session);
-    ssl->session = renewed_session.release();
+    session->not_resumable = false;
+    ssl->session = std::move(renewed_session);
   }
 
   ssl->method->next_message(ssl);
@@ -1678,8 +1615,7 @@
   ssl->method->on_handshake_complete(ssl);
 
   if (ssl->session != NULL) {
-    SSL_SESSION_up_ref(ssl->session);
-    ssl->s3->established_session.reset(ssl->session);
+    ssl->s3->established_session = UpRef(ssl->session);
   } else {
     // We make a copy of the session in order to maintain the immutability
     // of the new established_session due to False Start. The caller may
@@ -1691,7 +1627,7 @@
     }
     // Renegotiations do not participate in session resumption.
     if (!ssl->s3->initial_handshake_complete) {
-      ssl->s3->established_session->not_resumable = 0;
+      ssl->s3->established_session->not_resumable = false;
     }
 
     hs->new_session.reset();
diff --git a/src/ssl/handshake_server.cc b/src/ssl/handshake_server.cc
index 6404cc9..0159c9e 100644
--- a/src/ssl/handshake_server.cc
+++ b/src/ssl/handshake_server.cc
@@ -172,8 +172,8 @@
 
 namespace bssl {
 
-int ssl_client_cipher_list_contains_cipher(const SSL_CLIENT_HELLO *client_hello,
-                                           uint16_t id) {
+bool ssl_client_cipher_list_contains_cipher(
+    const SSL_CLIENT_HELLO *client_hello, uint16_t id) {
   CBS cipher_suites;
   CBS_init(&cipher_suites, client_hello->cipher_suites,
            client_hello->cipher_suites_len);
@@ -181,19 +181,19 @@
   while (CBS_len(&cipher_suites) > 0) {
     uint16_t got_id;
     if (!CBS_get_u16(&cipher_suites, &got_id)) {
-      return 0;
+      return false;
     }
 
     if (got_id == id) {
-      return 1;
+      return true;
     }
   }
 
-  return 0;
+  return false;
 }
 
-static int negotiate_version(SSL_HANDSHAKE *hs, uint8_t *out_alert,
-                             const SSL_CLIENT_HELLO *client_hello) {
+static bool negotiate_version(SSL_HANDSHAKE *hs, uint8_t *out_alert,
+                              const SSL_CLIENT_HELLO *client_hello) {
   SSL *const ssl = hs->ssl;
   assert(!ssl->s3->have_version);
   CBS supported_versions, versions;
@@ -204,7 +204,7 @@
         CBS_len(&versions) == 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       *out_alert = SSL_AD_DECODE_ERROR;
-      return 0;
+      return false;
     }
   } else {
     // Convert the ClientHello version to an equivalent supported_versions
@@ -213,7 +213,6 @@
         0x03, 0x03,  // TLS 1.2
         0x03, 0x02,  // TLS 1.1
         0x03, 0x01,  // TLS 1
-        0x03, 0x00,  // SSL 3
     };
 
     static const uint8_t kDTLSVersions[] = {
@@ -232,12 +231,10 @@
                versions_len);
     } else {
       if (client_hello->version >= TLS1_2_VERSION) {
-        versions_len = 8;
-      } else if (client_hello->version >= TLS1_1_VERSION) {
         versions_len = 6;
-      } else if (client_hello->version >= TLS1_VERSION) {
+      } else if (client_hello->version >= TLS1_1_VERSION) {
         versions_len = 4;
-      } else if (client_hello->version >= SSL3_VERSION) {
+      } else if (client_hello->version >= TLS1_VERSION) {
         versions_len = 2;
       }
       CBS_init(&versions, kTLSVersions + sizeof(kTLSVersions) - versions_len,
@@ -246,7 +243,7 @@
   }
 
   if (!ssl_negotiate_version(hs, out_alert, &ssl->version, &versions)) {
-    return 0;
+    return false;
   }
 
   // At this point, the connection's version is known and |ssl->version| is
@@ -260,10 +257,10 @@
       ssl_protocol_version(ssl) < hs->max_version) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INAPPROPRIATE_FALLBACK);
     *out_alert = SSL3_AD_INAPPROPRIATE_FALLBACK;
-    return 0;
+    return false;
   }
 
-  return 1;
+  return true;
 }
 
 static UniquePtr<STACK_OF(SSL_CIPHER)> ssl_parse_client_cipher_list(
@@ -303,11 +300,10 @@
 static void ssl_get_compatible_server_ciphers(SSL_HANDSHAKE *hs,
                                               uint32_t *out_mask_k,
                                               uint32_t *out_mask_a) {
-  SSL *const ssl = hs->ssl;
   uint32_t mask_k = 0;
   uint32_t mask_a = 0;
 
-  if (ssl_has_certificate(ssl)) {
+  if (ssl_has_certificate(hs->config)) {
     mask_a |= ssl_cipher_auth_mask_for_key(hs->local_pubkey.get());
     if (EVP_PKEY_id(hs->local_pubkey.get()) == EVP_PKEY_RSA) {
       mask_k |= SSL_kRSA;
@@ -321,7 +317,7 @@
   }
 
   // PSK requires a server callback.
-  if (ssl->psk_server_callback != NULL) {
+  if (hs->config->psk_server_callback != NULL) {
     mask_k |= SSL_kPSK;
     mask_a |= SSL_aPSK;
   }
@@ -417,7 +413,7 @@
     return ssl_hs_error;
   }
 
-  if (ssl->handoff) {
+  if (hs->config->handoff) {
     return ssl_hs_handoff;
   }
 
@@ -446,7 +442,7 @@
   }
 
   // Freeze the version range after the early callback.
-  if (!ssl_get_version_range(ssl, &hs->min_version, &hs->max_version)) {
+  if (!ssl_get_version_range(hs, &hs->min_version, &hs->max_version)) {
     return ssl_hs_error;
   }
 
@@ -494,8 +490,8 @@
   }
 
   // Call |cert_cb| to update server certificates if required.
-  if (ssl->cert->cert_cb != NULL) {
-    int rv = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg);
+  if (hs->config->cert->cert_cb != NULL) {
+    int rv = hs->config->cert->cert_cb(ssl, hs->config->cert->cert_cb_arg);
     if (rv == 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
@@ -510,6 +506,22 @@
     return ssl_hs_error;
   }
 
+  if (hs->ocsp_stapling_requested &&
+      ssl->ctx->legacy_ocsp_callback != nullptr) {
+    switch (ssl->ctx->legacy_ocsp_callback(
+        ssl, ssl->ctx->legacy_ocsp_callback_arg)) {
+      case SSL_TLSEXT_ERR_OK:
+        break;
+      case SSL_TLSEXT_ERR_NOACK:
+        hs->ocsp_stapling_requested = false;
+        break;
+      default:
+        OPENSSL_PUT_ERROR(SSL, SSL_R_OCSP_CB_ERROR);
+        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+        return ssl_hs_error;
+    }
+  }
+
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     // Jump to the TLS 1.3 state machine.
     hs->state = state12_tls13;
@@ -523,8 +535,10 @@
 
   // Negotiate the cipher suite. This must be done after |cert_cb| so the
   // certificate is finalized.
-  hs->new_cipher =
-      ssl3_choose_cipher(hs, &client_hello, ssl_get_cipher_preferences(ssl));
+  SSLCipherPreferenceList *prefs = hs->config->cipher_list
+                                       ? hs->config->cipher_list.get()
+                                       : ssl->ctx->cipher_list.get();
+  hs->new_cipher = ssl3_choose_cipher(hs, &client_hello, prefs);
   if (hs->new_cipher == NULL) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
@@ -562,7 +576,7 @@
   UniquePtr<SSL_SESSION> session;
   bool tickets_supported = false, renew_ticket = false;
   enum ssl_hs_wait_t wait = ssl_get_prev_session(
-      ssl, &session, &tickets_supported, &renew_ticket, &client_hello);
+      hs, &session, &tickets_supported, &renew_ticket, &client_hello);
   if (wait != ssl_hs_ok) {
     return wait;
   }
@@ -587,7 +601,7 @@
   if (session) {
     // Use the old session.
     hs->ticket_expected = renew_ticket;
-    ssl->session = session.release();
+    ssl->session = std::move(session);
     ssl->s3->session_reused = true;
   } else {
     hs->ticket_expected = tickets_supported;
@@ -614,10 +628,10 @@
     hs->new_session->cipher = hs->new_cipher;
 
     // Determine whether to request a client certificate.
-    hs->cert_request = !!(ssl->verify_mode & SSL_VERIFY_PEER);
+    hs->cert_request = !!(hs->config->verify_mode & SSL_VERIFY_PEER);
     // Only request a certificate if Channel ID isn't negotiated.
-    if ((ssl->verify_mode & SSL_VERIFY_PEER_IF_NO_OBC) &&
-        ssl->s3->tlsext_channel_id_valid) {
+    if ((hs->config->verify_mode & SSL_VERIFY_PEER_IF_NO_OBC) &&
+        ssl->s3->channel_id_valid) {
       hs->cert_request = false;
     }
     // CertificateRequest may only be sent in certificate-based ciphers.
@@ -650,7 +664,7 @@
 
   // Handback includes the whole handshake transcript, so we cannot free the
   // transcript buffer in the handback case.
-  if (!hs->cert_request && !hs->ssl->handback) {
+  if (!hs->cert_request && !hs->handback) {
     hs->transcript.FreeBuffer();
   }
 
@@ -665,9 +679,9 @@
 
   // We only accept ChannelIDs on connections with ECDHE in order to avoid a
   // known attack while we fix ChannelID itself.
-  if (ssl->s3->tlsext_channel_id_valid &&
+  if (ssl->s3->channel_id_valid &&
       (hs->new_cipher->algorithm_mkey & SSL_kECDHE) == 0) {
-    ssl->s3->tlsext_channel_id_valid = false;
+    ssl->s3->channel_id_valid = false;
   }
 
   // If this is a resumption and the original handshake didn't support
@@ -675,7 +689,7 @@
   // session and so cannot resume with ChannelIDs.
   if (ssl->session != NULL &&
       ssl->session->original_handshake_hash_len == 0) {
-    ssl->s3->tlsext_channel_id_valid = false;
+    ssl->s3->channel_id_valid = false;
   }
 
   struct OPENSSL_timeval now;
@@ -700,8 +714,8 @@
   }
 
   const SSL_SESSION *session = hs->new_session.get();
-  if (ssl->session != NULL) {
-    session = ssl->session;
+  if (ssl->session != nullptr) {
+    session = ssl->session.get();
   }
 
   ScopedCBB cbb;
@@ -733,12 +747,12 @@
   ScopedCBB cbb;
 
   if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-    if (!ssl_has_certificate(ssl)) {
+    if (!ssl_has_certificate(hs->config)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET);
       return ssl_hs_error;
     }
 
-    if (!ssl_output_cert_chain(ssl)) {
+    if (!ssl_output_cert_chain(hs)) {
       return ssl_hs_error;
     }
 
@@ -748,9 +762,10 @@
                                      SSL3_MT_CERTIFICATE_STATUS) ||
           !CBB_add_u8(&body, TLSEXT_STATUSTYPE_ocsp) ||
           !CBB_add_u24_length_prefixed(&body, &ocsp_response) ||
-          !CBB_add_bytes(&ocsp_response,
-                         CRYPTO_BUFFER_data(ssl->cert->ocsp_response.get()),
-                         CRYPTO_BUFFER_len(ssl->cert->ocsp_response.get())) ||
+          !CBB_add_bytes(
+              &ocsp_response,
+              CRYPTO_BUFFER_data(hs->config->cert->ocsp_response.get()),
+              CRYPTO_BUFFER_len(hs->config->cert->ocsp_response.get())) ||
           !ssl_add_message_cbb(ssl, cbb.get())) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
         return ssl_hs_error;
@@ -762,8 +777,7 @@
   uint32_t alg_k = hs->new_cipher->algorithm_mkey;
   uint32_t alg_a = hs->new_cipher->algorithm_auth;
   if (ssl_cipher_requires_server_key_exchange(hs->new_cipher) ||
-      ((alg_a & SSL_aPSK) && ssl->psk_identity_hint)) {
-
+      ((alg_a & SSL_aPSK) && hs->config->psk_identity_hint)) {
     // Pre-allocate enough room to comfortably fit an ECDHE public key. Prepend
     // the client and server randoms for the signing transcript.
     CBB child;
@@ -775,10 +789,12 @@
 
     // PSK ciphers begin with an identity hint.
     if (alg_a & SSL_aPSK) {
-      size_t len =
-          (ssl->psk_identity_hint == NULL) ? 0 : strlen(ssl->psk_identity_hint);
+      size_t len = hs->config->psk_identity_hint == nullptr
+                       ? 0
+                       : strlen(hs->config->psk_identity_hint.get());
       if (!CBB_add_u16_length_prefixed(cbb.get(), &child) ||
-          !CBB_add_bytes(&child, (const uint8_t *)ssl->psk_identity_hint,
+          !CBB_add_bytes(&child,
+                         (const uint8_t *)hs->config->psk_identity_hint.get(),
                          len)) {
         return ssl_hs_error;
       }
@@ -837,7 +853,7 @@
 
   // Add a signature.
   if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-    if (!ssl_has_private_key(ssl)) {
+    if (!ssl_has_private_key(hs->config)) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       return ssl_hs_error;
     }
@@ -845,6 +861,7 @@
     // Determine the signature algorithm.
     uint16_t signature_algorithm;
     if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
       return ssl_hs_error;
     }
     if (ssl_protocol_version(ssl) >= TLS1_2_VERSION) {
@@ -900,15 +917,14 @@
                                    SSL3_MT_CERTIFICATE_REQUEST) ||
         !CBB_add_u8_length_prefixed(&body, &cert_types) ||
         !CBB_add_u8(&cert_types, SSL3_CT_RSA_SIGN) ||
-        (ssl_protocol_version(ssl) >= TLS1_VERSION &&
-         !CBB_add_u8(&cert_types, TLS_CT_ECDSA_SIGN)) ||
+        !CBB_add_u8(&cert_types, TLS_CT_ECDSA_SIGN) ||
         // TLS 1.2 has no way to specify different signature algorithms for
         // certificates and the online signature, so emit the more restrictive
         // certificate list.
         (ssl_protocol_version(ssl) >= TLS1_2_VERSION &&
          (!CBB_add_u16_length_prefixed(&body, &sigalgs_cbb) ||
           !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb, true /* certs */))) ||
-        !ssl_add_client_CA_list(ssl, &body) ||
+        !ssl_add_client_CA_list(hs, &body) ||
         !ssl_add_message_cbb(ssl, cbb.get())) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       return ssl_hs_error;
@@ -929,8 +945,8 @@
 static enum ssl_hs_wait_t do_read_client_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
-  if (ssl->handback && hs->new_cipher->algorithm_mkey == SSL_kECDHE) {
-     return ssl_hs_handback;
+  if (hs->handback && hs->new_cipher->algorithm_mkey == SSL_kECDHE) {
+    return ssl_hs_handback;
   }
   if (!hs->cert_request) {
     hs->state = state12_verify_client_certificate;
@@ -942,26 +958,7 @@
     return ssl_hs_read_message;
   }
 
-  if (msg.type != SSL3_MT_CERTIFICATE) {
-    if (ssl->version == SSL3_VERSION &&
-        msg.type == SSL3_MT_CLIENT_KEY_EXCHANGE) {
-      // In SSL 3.0, the Certificate message is omitted to signal no
-      // certificate.
-      if (ssl->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-        return ssl_hs_error;
-      }
-
-      // OpenSSL returns X509_V_OK when no certificates are received. This is
-      // classed by them as a bug, but it's assumed by at least NGINX.
-      hs->new_session->verify_result = X509_V_OK;
-      hs->state = state12_verify_client_certificate;
-      return ssl_hs_ok;
-    }
-
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+  if (!ssl_check_message_type(ssl, msg, SSL3_MT_CERTIFICATE)) {
     return ssl_hs_error;
   }
 
@@ -971,17 +968,14 @@
 
   CBS certificate_msg = msg.body;
   uint8_t alert = SSL_AD_DECODE_ERROR;
-  UniquePtr<STACK_OF(CRYPTO_BUFFER)> chain;
-  if (!ssl_parse_cert_chain(&alert, &chain, &hs->peer_pubkey,
-                            ssl->retain_only_sha256_of_client_certs
+  if (!ssl_parse_cert_chain(&alert, &hs->new_session->certs, &hs->peer_pubkey,
+                            hs->config->retain_only_sha256_of_client_certs
                                 ? hs->new_session->peer_sha256
-                                : NULL,
+                                : nullptr,
                             &certificate_msg, ssl->ctx->pool)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
-  sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
-  hs->new_session->certs = chain.release();
 
   if (CBS_len(&certificate_msg) != 0 ||
       !ssl->ctx->x509_method->session_cache_objects(hs->new_session.get())) {
@@ -990,19 +984,11 @@
     return ssl_hs_error;
   }
 
-  if (sk_CRYPTO_BUFFER_num(hs->new_session->certs) == 0) {
+  if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) {
     // No client certificate so the handshake buffer may be discarded.
     hs->transcript.FreeBuffer();
 
-    // In SSL 3.0, sending no certificate is signaled by omitting the
-    // Certificate message.
-    if (ssl->version == SSL3_VERSION) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATES_RETURNED);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-      return ssl_hs_error;
-    }
-
-    if (ssl->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
+    if (hs->config->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
       // Fail for TLS only if we required a certificate
       OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
@@ -1012,7 +998,7 @@
     // OpenSSL returns X509_V_OK when no certificates are received. This is
     // classed by them as a bug, but it's assumed by at least NGINX.
     hs->new_session->verify_result = X509_V_OK;
-  } else if (ssl->retain_only_sha256_of_client_certs) {
+  } else if (hs->config->retain_only_sha256_of_client_certs) {
     // The hash will have been filled in.
     hs->new_session->peer_sha256_valid = 1;
   }
@@ -1023,7 +1009,7 @@
 }
 
 static enum ssl_hs_wait_t do_verify_client_certificate(SSL_HANDSHAKE *hs) {
-  if (sk_CRYPTO_BUFFER_num(hs->new_session->certs) > 0) {
+  if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) > 0) {
     switch (ssl_verify_peer_cert(hs)) {
       case ssl_verify_ok:
         break;
@@ -1072,28 +1058,25 @@
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
       return ssl_hs_error;
     }
-
-    if (!CBS_strdup(&psk_identity, &hs->new_session->psk_identity)) {
+    char *raw = nullptr;
+    if (!CBS_strdup(&psk_identity, &raw)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       return ssl_hs_error;
     }
+    hs->new_session->psk_identity.reset(raw);
   }
 
   // Depending on the key exchange method, compute |premaster_secret|.
   Array<uint8_t> premaster_secret;
   if (alg_k & SSL_kRSA) {
     CBS encrypted_premaster_secret;
-    if (ssl->version > SSL3_VERSION) {
-      if (!CBS_get_u16_length_prefixed(&client_key_exchange,
-                                       &encrypted_premaster_secret) ||
-          CBS_len(&client_key_exchange) != 0) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-        return ssl_hs_error;
-      }
-    } else {
-      encrypted_premaster_secret = client_key_exchange;
+    if (!CBS_get_u16_length_prefixed(&client_key_exchange,
+                                     &encrypted_premaster_secret) ||
+        CBS_len(&client_key_exchange) != 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+      return ssl_hs_error;
     }
 
     // Allocate a buffer large enough for an RSA decryption.
@@ -1187,7 +1170,7 @@
   // For a PSK cipher suite, the actual pre-master secret is combined with the
   // pre-shared key.
   if (alg_a & SSL_aPSK) {
-    if (ssl->psk_server_callback == NULL) {
+    if (hs->config->psk_server_callback == NULL) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       return ssl_hs_error;
@@ -1195,8 +1178,8 @@
 
     // Look up the key for the identity.
     uint8_t psk[PSK_MAX_PSK_LEN];
-    unsigned psk_len = ssl->psk_server_callback(
-        ssl, hs->new_session->psk_identity, psk, sizeof(psk));
+    unsigned psk_len = hs->config->psk_server_callback(
+        ssl, hs->new_session->psk_identity.get(), psk, sizeof(psk));
     if (psk_len > PSK_MAX_PSK_LEN) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
@@ -1300,29 +1283,9 @@
     return ssl_hs_error;
   }
 
-  bool sig_ok;
-  // The SSL3 construction for CertificateVerify does not decompose into a
-  // single final digest and signature, and must be special-cased.
-  if (ssl_protocol_version(ssl) == SSL3_VERSION) {
-    uint8_t digest[EVP_MAX_MD_SIZE];
-    size_t digest_len;
-    if (!hs->transcript.GetSSL3CertVerifyHash(
-            digest, &digest_len, hs->new_session.get(), signature_algorithm)) {
-      return ssl_hs_error;
-    }
-
-    UniquePtr<EVP_PKEY_CTX> pctx(
-        EVP_PKEY_CTX_new(hs->peer_pubkey.get(), nullptr));
-    sig_ok = pctx &&
-             EVP_PKEY_verify_init(pctx.get()) &&
-             EVP_PKEY_verify(pctx.get(), CBS_data(&signature),
-                             CBS_len(&signature), digest, digest_len);
-  } else {
-    sig_ok =
-        ssl_public_key_verify(ssl, signature, signature_algorithm,
-                              hs->peer_pubkey.get(), hs->transcript.buffer());
-  }
-
+  bool sig_ok =
+      ssl_public_key_verify(ssl, signature, signature_algorithm,
+                            hs->peer_pubkey.get(), hs->transcript.buffer());
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
   sig_ok = true;
   ERR_clear_error();
@@ -1346,6 +1309,9 @@
 }
 
 static enum ssl_hs_wait_t do_read_change_cipher_spec(SSL_HANDSHAKE *hs) {
+  if (hs->handback && hs->ssl->session != NULL) {
+    return ssl_hs_handback;
+  }
   hs->state = state12_process_change_cipher_spec;
   return ssl_hs_read_change_cipher_spec;
 }
@@ -1398,7 +1364,7 @@
 static enum ssl_hs_wait_t do_read_channel_id(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
-  if (!ssl->s3->tlsext_channel_id_valid) {
+  if (!ssl->s3->channel_id_valid) {
     hs->state = state12_read_client_finished;
     return ssl_hs_ok;
   }
@@ -1435,7 +1401,7 @@
   // If this is a full handshake with ChannelID then record the handshake
   // hashes in |hs->new_session| in case we need them to verify a
   // ChannelID signature on a resumption of this session in the future.
-  if (ssl->session == NULL && ssl->s3->tlsext_channel_id_valid &&
+  if (ssl->session == NULL && ssl->s3->channel_id_valid &&
       !tls1_record_handshake_hashes_for_channel_id(hs)) {
     return ssl_hs_error;
   }
@@ -1456,7 +1422,8 @@
     } else {
       // We are renewing an existing session. Duplicate the session to adjust
       // the timeout.
-      session_copy = SSL_SESSION_dup(ssl->session, SSL_SESSION_INCLUDE_NONAUTH);
+      session_copy =
+          SSL_SESSION_dup(ssl->session.get(), SSL_SESSION_INCLUDE_NONAUTH);
       if (!session_copy) {
         return ssl_hs_error;
       }
@@ -1471,7 +1438,7 @@
                                    SSL3_MT_NEW_SESSION_TICKET) ||
         !CBB_add_u32(&body, session->timeout) ||
         !CBB_add_u16_length_prefixed(&body, &ticket) ||
-        !ssl_encrypt_ticket(ssl, &ticket, session) ||
+        !ssl_encrypt_ticket(hs, &ticket, session) ||
         !ssl_add_message_cbb(ssl, cbb.get())) {
       return ssl_hs_error;
     }
@@ -1494,25 +1461,24 @@
 static enum ssl_hs_wait_t do_finish_server_handshake(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
-  if (ssl->handback) {
+  if (hs->handback) {
     return ssl_hs_handback;
   }
 
   ssl->method->on_handshake_complete(ssl);
 
   // If we aren't retaining peer certificates then we can discard it now.
-  if (hs->new_session != NULL && ssl->retain_only_sha256_of_client_certs) {
-    sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
-    hs->new_session->certs = NULL;
+  if (hs->new_session != NULL &&
+      hs->config->retain_only_sha256_of_client_certs) {
+    hs->new_session->certs.reset();
     ssl->ctx->x509_method->session_clear(hs->new_session.get());
   }
 
   if (ssl->session != NULL) {
-    SSL_SESSION_up_ref(ssl->session);
-    ssl->s3->established_session.reset(ssl->session);
+    ssl->s3->established_session = UpRef(ssl->session);
   } else {
     ssl->s3->established_session = std::move(hs->new_session);
-    ssl->s3->established_session->not_resumable = 0;
+    ssl->s3->established_session->not_resumable = false;
   }
 
   hs->handshake_finalized = true;
diff --git a/src/ssl/internal.h b/src/ssl/internal.h
index b258589..cc3e075 100644
--- a/src/ssl/internal.h
+++ b/src/ssl/internal.h
@@ -175,8 +175,10 @@
 
 namespace bssl {
 
+struct SSL_CONFIG;
 struct SSL_HANDSHAKE;
 struct SSL_PROTOCOL_METHOD;
+struct SSL_X509_METHOD;
 
 // C++ utilities.
 
@@ -371,12 +373,16 @@
 
 // ssl_get_version_range sets |*out_min_version| and |*out_max_version| to the
 // minimum and maximum enabled protocol versions, respectively.
-bool ssl_get_version_range(const SSL *ssl, uint16_t *out_min_version,
+bool ssl_get_version_range(const SSL_HANDSHAKE *hs, uint16_t *out_min_version,
                            uint16_t *out_max_version);
 
 // ssl_supports_version returns whether |hs| supports |version|.
 bool ssl_supports_version(SSL_HANDSHAKE *hs, uint16_t version);
 
+// ssl_method_supports_version returns whether |method| supports |version|.
+bool ssl_method_supports_version(const SSL_PROTOCOL_METHOD *method,
+                                 uint16_t version);
+
 // ssl_add_supported_versions writes the supported versions of |hs| to |cbb|, in
 // decreasing preference order.
 bool ssl_add_supported_versions(SSL_HANDSHAKE *hs, CBB *cbb);
@@ -447,10 +453,8 @@
 
 // Bits for |algorithm_mac| (symmetric authentication).
 #define SSL_SHA1 0x00000001u
-#define SSL_SHA256 0x00000002u
-#define SSL_SHA384 0x00000004u
 // SSL_AEAD is set for all AEADs.
-#define SSL_AEAD 0x00000008u
+#define SSL_AEAD 0x00000002u
 
 // Bits for |algorithm_prf| (handshake digest).
 #define SSL_HANDSHAKE_MAC_DEFAULT 0x1
@@ -520,7 +524,7 @@
 // true on success and false on failure. If |strict| is true, nonsense will be
 // rejected. If false, nonsense will be silently ignored. An empty result is
 // considered an error regardless of |strict|.
-bool ssl_create_cipher_list(SSLCipherPreferenceList **out_cipher_list,
+bool ssl_create_cipher_list(UniquePtr<SSLCipherPreferenceList> *out_cipher_list,
                             const char *rule_str, bool strict);
 
 // ssl_cipher_get_value returns the cipher suite id of |cipher|.
@@ -602,14 +606,6 @@
   // the number of bytes written. Otherwise, it returns false.
   bool GetHash(uint8_t *out, size_t *out_len);
 
-  // GetSSL3CertVerifyHash writes the SSL 3.0 CertificateVerify hash into the
-  // bytes pointed to by |out| and writes the number of bytes to
-  // |*out_len|. |out| must have room for |EVP_MAX_MD_SIZE| bytes. It returns
-  // one on success and zero on failure.
-  bool GetSSL3CertVerifyHash(uint8_t *out, size_t *out_len,
-                             const SSL_SESSION *session,
-                             uint16_t signature_algorithm);
-
   // GetFinishedMAC computes the MAC for the Finished message into the bytes
   // pointed by |out| and writes the number of bytes to |*out_len|. |out| must
   // have room for |EVP_MAX_MD_SIZE| bytes. It returns true on success and false
@@ -620,12 +616,8 @@
  private:
   // buffer_, if non-null, contains the handshake transcript.
   UniquePtr<BUF_MEM> buffer_;
-  // hash, if initialized with an |EVP_MD|, maintains the handshake hash. For
-  // TLS 1.1 and below, it is the SHA-1 half.
+  // hash, if initialized with an |EVP_MD|, maintains the handshake hash.
   ScopedEVP_MD_CTX hash_;
-  // md5, if initialized with an |EVP_MD|, maintains the MD5 half of the
-  // handshake hash for TLS 1.1 and below.
-  ScopedEVP_MD_CTX md5_;
 };
 
 // tls1_prf computes the PRF function for |ssl|. It fills |out|, using |secret|
@@ -771,9 +763,6 @@
   // omit_length_in_ad_ is true if the length should be omitted in the
   // AEAD's ad parameter.
   bool omit_length_in_ad_ : 1;
-  // omit_version_in_ad_ is true if the version should be omitted
-  // in the AEAD's ad parameter.
-  bool omit_version_in_ad_ : 1;
   // omit_ad_ is true if the AEAD's ad parameter should be omitted.
   bool omit_ad_ : 1;
   // ad_is_header_ is true if the AEAD's ad parameter is the record header.
@@ -908,9 +897,9 @@
 
 // Private key operations.
 
-// ssl_has_private_key returns one if |ssl| has a private key
-// configured and zero otherwise.
-int ssl_has_private_key(const SSL *ssl);
+// ssl_has_private_key returns one if |cfg| has a private key configured and
+// zero otherwise.
+int ssl_has_private_key(const SSL_CONFIG *cfg);
 
 // ssl_private_key_* perform the corresponding operation on
 // |SSL_PRIVATE_KEY_METHOD|. If there is a custom private key configured, they
@@ -940,36 +929,6 @@
                            Span<const uint8_t> in);
 
 
-// Custom extensions
-
-}  // namespace bssl
-
-// |SSL_CUSTOM_EXTENSION| is a structure that contains information about
-// custom-extension callbacks. It is defined unnamespaced for compatibility with
-// |STACK_OF(SSL_CUSTOM_EXTENSION)|.
-typedef struct ssl_custom_extension {
-  SSL_custom_ext_add_cb add_callback;
-  void *add_arg;
-  SSL_custom_ext_free_cb free_callback;
-  SSL_custom_ext_parse_cb parse_callback;
-  void *parse_arg;
-  uint16_t value;
-} SSL_CUSTOM_EXTENSION;
-
-DEFINE_STACK_OF(SSL_CUSTOM_EXTENSION)
-
-namespace bssl {
-
-void SSL_CUSTOM_EXTENSION_free(SSL_CUSTOM_EXTENSION *custom_extension);
-
-int custom_ext_add_clienthello(SSL_HANDSHAKE *hs, CBB *extensions);
-int custom_ext_parse_serverhello(SSL_HANDSHAKE *hs, int *out_alert,
-                                 uint16_t value, const CBS *extension);
-int custom_ext_parse_clienthello(SSL_HANDSHAKE *hs, int *out_alert,
-                                 uint16_t value, const CBS *extension);
-int custom_ext_add_serverhello(SSL_HANDSHAKE *hs, CBB *extensions);
-
-
 // Key shares.
 
 // SSLKeyShare abstracts over Diffie-Hellman-like key exchanges.
@@ -1067,6 +1026,9 @@
 // |tls_has_unprocessed_handshake_data| for DTLS.
 bool dtls_has_unprocessed_handshake_data(const SSL *ssl);
 
+// tls_flush_pending_hs_data flushes any handshake plaintext data.
+bool tls_flush_pending_hs_data(SSL *ssl);
+
 struct DTLS_OUTGOING_MESSAGE {
   DTLS_OUTGOING_MESSAGE() {}
   DTLS_OUTGOING_MESSAGE(const DTLS_OUTGOING_MESSAGE &) = delete;
@@ -1175,7 +1137,7 @@
 
 // ssl_has_certificate returns one if a certificate and private key are
 // configured and zero otherwise.
-int ssl_has_certificate(const SSL *ssl);
+int ssl_has_certificate(const SSL_CONFIG *cfg);
 
 // ssl_parse_cert_chain parses a certificate list from |cbs| in the format used
 // by a TLS Certificate message. On success, it advances |cbs| and returns
@@ -1194,10 +1156,10 @@
                           uint8_t *out_leaf_sha256, CBS *cbs,
                           CRYPTO_BUFFER_POOL *pool);
 
-// ssl_add_cert_chain adds |ssl|'s certificate chain to |cbb| in the format used
-// by a TLS Certificate message. If there is no certificate chain, it emits an
-// empty certificate list. It returns one on success and zero on error.
-int ssl_add_cert_chain(SSL *ssl, CBB *cbb);
+// ssl_add_cert_chain adds |hs->ssl|'s certificate chain to |cbb| in the format
+// used by a TLS Certificate message. If there is no certificate chain, it emits
+// an empty certificate list. It returns one on success and zero on error.
+int ssl_add_cert_chain(SSL_HANDSHAKE *hs, CBB *cbb);
 
 // ssl_cert_check_digital_signature_key_usage parses the DER-encoded, X.509
 // certificate in |in| and returns one if doesn't specify a key usage or, if it
@@ -1219,12 +1181,12 @@
                                                             CBS *cbs);
 
 // ssl_has_client_CAs returns there are configured CAs.
-bool ssl_has_client_CAs(SSL *ssl);
+bool ssl_has_client_CAs(const SSL_CONFIG *cfg);
 
 // ssl_add_client_CA_list adds the configured CA list to |cbb| in the format
 // used by a TLS CertificateRequest message. It returns one on success and zero
 // on error.
-int ssl_add_client_CA_list(SSL *ssl, CBB *cbb);
+int ssl_add_client_CA_list(SSL_HANDSHAKE *hs, CBB *cbb);
 
 // ssl_check_leaf_certificate returns one if |pkey| and |leaf| are suitable as
 // a server's leaf certificate for |hs|. Otherwise, it returns zero and pushes
@@ -1371,6 +1333,14 @@
   state12_done,
 };
 
+// handback_t lists the points in the state machine where a handback can occur.
+// These are the different points at which key material is no longer needed.
+enum handback_t {
+  handback_after_session_resumption,
+  handback_after_ecdhe,
+  handback_after_handshake,
+};
+
 struct SSL_HANDSHAKE {
   explicit SSL_HANDSHAKE(SSL *ssl);
   ~SSL_HANDSHAKE();
@@ -1379,6 +1349,9 @@
   // ssl is a non-owning pointer to the parent |SSL| object.
   SSL *ssl;
 
+  // config is a non-owning pointer to the handshake configuration.
+  SSL_CONFIG *config;
+
   // wait contains the operation the handshake is currently blocking on or
   // |ssl_hs_ok| if none.
   enum ssl_hs_wait_t wait = ssl_hs_ok;
@@ -1418,17 +1391,6 @@
     uint32_t received;
   } extensions;
 
-  union {
-    // sent is a bitset where the bits correspond to elements of
-    // |client_custom_extensions| in the |SSL_CTX|. Each bit is set if that
-    // extension was sent in a ClientHello. It's not used by servers.
-    uint16_t sent = 0;
-    // received is a bitset, like |sent|, but is used by servers to record
-    // which custom extensions were received from a client. The bits here
-    // correspond to |server_custom_extensions|.
-    uint16_t received;
-  } custom_extensions;
-
   // retry_group is the group ID selected by the server in HelloRetryRequest in
   // TLS 1.3.
   uint16_t retry_group = 0;
@@ -1472,6 +1434,11 @@
   // sent.
   uint16_t negotiated_token_binding_version;
 
+  // cert_compression_alg_id, for a server, contains the negotiated certificate
+  // compression algorithm for this client. It is only valid if
+  // |cert_compression_negotiated| is true.
+  uint16_t cert_compression_alg_id;
+
   // server_params, in a TLS 1.2 server, stores the ServerKeyExchange
   // parameters. It has client and server randoms prepended for signing
   // convenience.
@@ -1524,8 +1491,6 @@
   bool received_hello_retry_request:1;
   bool sent_hello_retry_request:1;
 
-  bool received_custom_extension:1;
-
   // handshake_finalized is true once the handshake has completed, at which
   // point accessors should use the established state.
   bool handshake_finalized:1;
@@ -1586,6 +1551,14 @@
   // grease_seeded is true if |grease_seed| has been initialized.
   bool grease_seeded:1;
 
+  // handback indicates that a server should pause the handshake after
+  // finishing operations that require private key material, in such a way that
+  // |SSL_get_error| returns |SSL_HANDBACK|.  It is set by |SSL_apply_handoff|.
+  bool handback:1;
+
+  // cert_compression_negotiated is true iff |cert_compression_alg_id| is valid.
+  bool cert_compression_negotiated:1;
+
   // client_version is the value sent or received in the ClientHello version.
   uint16_t client_version = 0;
 
@@ -1678,8 +1651,8 @@
 bool ssl_ext_pre_shared_key_add_serverhello(SSL_HANDSHAKE *hs, CBB *out);
 
 // ssl_is_sct_list_valid does a shallow parse of the SCT list in |contents| and
-// returns one iff it's valid.
-int ssl_is_sct_list_valid(const CBS *contents);
+// returns whether it's valid.
+bool ssl_is_sct_list_valid(const CBS *contents);
 
 int ssl_write_client_hello(SSL_HANDSHAKE *hs);
 
@@ -1698,8 +1671,9 @@
     enum ssl_cert_verify_context_t cert_verify_context);
 
 // ssl_is_alpn_protocol_allowed returns whether |protocol| is a valid server
-// selection for |ssl|'s client preferences.
-bool ssl_is_alpn_protocol_allowed(const SSL *ssl, Span<const uint8_t> protocol);
+// selection for |hs->ssl|'s client preferences.
+bool ssl_is_alpn_protocol_allowed(const SSL_HANDSHAKE *hs,
+                                  Span<const uint8_t> protocol);
 
 // ssl_negotiate_alpn negotiates the ALPN extension, if applicable. It returns
 // true on successful negotiation or if nothing was negotiated. It returns false
@@ -1727,8 +1701,7 @@
 
 enum ssl_hs_wait_t ssl_get_finished(SSL_HANDSHAKE *hs);
 bool ssl_send_finished(SSL_HANDSHAKE *hs);
-bool ssl_output_cert_chain(SSL *ssl);
-
+bool ssl_output_cert_chain(SSL_HANDSHAKE *hs);
 
 // SSLKEYLOGFILE functions.
 
@@ -1740,14 +1713,14 @@
 
 // ClientHello functions.
 
-int ssl_client_hello_init(SSL *ssl, SSL_CLIENT_HELLO *out,
-                          const SSLMessage &msg);
+bool ssl_client_hello_init(SSL *ssl, SSL_CLIENT_HELLO *out,
+                           const SSLMessage &msg);
 
-int ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello,
-                                   CBS *out, uint16_t extension_type);
+bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello,
+                                    CBS *out, uint16_t extension_type);
 
-int ssl_client_cipher_list_contains_cipher(const SSL_CLIENT_HELLO *client_hello,
-                                           uint16_t id);
+bool ssl_client_cipher_list_contains_cipher(
+    const SSL_CLIENT_HELLO *client_hello, uint16_t id);
 
 
 // GREASE.
@@ -1869,9 +1842,6 @@
   // ticket key. Only sessions with a matching value will be accepted.
   uint8_t sid_ctx_length = 0;
   uint8_t sid_ctx[SSL_MAX_SID_CTX_LENGTH] = {0};
-
-  // If enable_early_data is true, early data can be sent and accepted.
-  bool enable_early_data:1;
 };
 
 // |SSL_PROTOCOL_METHOD| abstracts between TLS and DTLS.
@@ -1951,6 +1921,62 @@
                                     size_t *out_consumed, uint8_t *out_alert,
                                     Span<uint8_t> in);
 
+struct SSL_X509_METHOD {
+  // check_client_CA_list returns one if |names| is a good list of X.509
+  // distinguished names and zero otherwise. This is used to ensure that we can
+  // reject unparsable values at handshake time when using crypto/x509.
+  int (*check_client_CA_list)(STACK_OF(CRYPTO_BUFFER) *names);
+
+  // cert_clear frees and NULLs all X509 certificate-related state.
+  void (*cert_clear)(CERT *cert);
+  // cert_free frees all X509-related state.
+  void (*cert_free)(CERT *cert);
+  // cert_flush_cached_chain drops any cached |X509|-based certificate chain
+  // from |cert|.
+  // cert_dup duplicates any needed fields from |cert| to |new_cert|.
+  void (*cert_dup)(CERT *new_cert, const CERT *cert);
+  void (*cert_flush_cached_chain)(CERT *cert);
+  // cert_flush_cached_chain drops any cached |X509|-based leaf certificate
+  // from |cert|.
+  void (*cert_flush_cached_leaf)(CERT *cert);
+
+  // session_cache_objects fills out |sess->x509_peer| and |sess->x509_chain|
+  // from |sess->certs| and erases |sess->x509_chain_without_leaf|. It returns
+  // one on success or zero on error.
+  int (*session_cache_objects)(SSL_SESSION *session);
+  // session_dup duplicates any needed fields from |session| to |new_session|.
+  // It returns one on success or zero on error.
+  int (*session_dup)(SSL_SESSION *new_session, const SSL_SESSION *session);
+  // session_clear frees any X509-related state from |session|.
+  void (*session_clear)(SSL_SESSION *session);
+  // session_verify_cert_chain verifies the certificate chain in |session|,
+  // sets |session->verify_result| and returns one on success or zero on
+  // error.
+  int (*session_verify_cert_chain)(SSL_SESSION *session, SSL_HANDSHAKE *ssl,
+                                   uint8_t *out_alert);
+
+  // hs_flush_cached_ca_names drops any cached |X509_NAME|s from |hs|.
+  void (*hs_flush_cached_ca_names)(SSL_HANDSHAKE *hs);
+  // ssl_new does any neccessary initialisation of |hs|. It returns one on
+  // success or zero on error.
+  int (*ssl_new)(SSL_HANDSHAKE *hs);
+  // ssl_free frees anything created by |ssl_new|.
+  void (*ssl_config_free)(SSL_CONFIG *cfg);
+  // ssl_flush_cached_client_CA drops any cached |X509_NAME|s from |ssl|.
+  void (*ssl_flush_cached_client_CA)(SSL_CONFIG *cfg);
+  // ssl_auto_chain_if_needed runs the deprecated auto-chaining logic if
+  // necessary. On success, it updates |ssl|'s certificate configuration as
+  // needed and returns one. Otherwise, it returns zero.
+  int (*ssl_auto_chain_if_needed)(SSL_HANDSHAKE *hs);
+  // ssl_ctx_new does any neccessary initialisation of |ctx|. It returns one on
+  // success or zero on error.
+  int (*ssl_ctx_new)(SSL_CTX *ctx);
+  // ssl_ctx_free frees anything created by |ssl_ctx_new|.
+  void (*ssl_ctx_free)(SSL_CTX *ctx);
+  // ssl_ctx_flush_cached_client_CA drops any cached |X509_NAME|s from |ctx|.
+  void (*ssl_ctx_flush_cached_client_CA)(SSL_CTX *ssl);
+};
+
 // ssl_crypto_x509_method provides the |SSL_X509_METHOD| functions using
 // crypto/x509.
 extern const SSL_X509_METHOD ssl_crypto_x509_method;
@@ -1959,302 +1985,35 @@
 // crypto/x509.
 extern const SSL_X509_METHOD ssl_noop_x509_method;
 
-struct tlsext_ticket_key {
+struct TicketKey {
   static constexpr bool kAllowUniquePtr = true;
 
-  uint8_t name[SSL_TICKET_KEY_NAME_LEN];
-  uint8_t hmac_key[16];
-  uint8_t aes_key[16];
+  uint8_t name[SSL_TICKET_KEY_NAME_LEN] = {0};
+  uint8_t hmac_key[16] = {0};
+  uint8_t aes_key[16] = {0};
   // next_rotation_tv_sec is the time (in seconds from the epoch) when the
   // current key should be superseded by a new key, or the time when a previous
   // key should be dropped. If zero, then the key should not be automatically
   // rotated.
-  uint64_t next_rotation_tv_sec;
+  uint64_t next_rotation_tv_sec = 0;
+};
+
+struct CertCompressionAlg {
+  static constexpr bool kAllowUniquePtr = true;
+
+  ssl_cert_compression_func_t compress = nullptr;
+  ssl_cert_decompression_func_t decompress = nullptr;
+  uint16_t alg_id = 0;
 };
 
 }  // namespace bssl
 
 DECLARE_LHASH_OF(SSL_SESSION)
 
+DEFINE_NAMED_STACK_OF(CertCompressionAlg, bssl::CertCompressionAlg);
+
 namespace bssl {
 
-// SSLContext backs the public |SSL_CTX| type. Due to compatibility constraints,
-// it is a base class for |ssl_ctx_st|.
-struct SSLContext {
-  const SSL_PROTOCOL_METHOD *method;
-  const SSL_X509_METHOD *x509_method;
-
-  // lock is used to protect various operations on this object.
-  CRYPTO_MUTEX lock;
-
-  // conf_max_version is the maximum acceptable protocol version configured by
-  // |SSL_CTX_set_max_proto_version|. Note this version is normalized in DTLS
-  // and is further constrainted by |SSL_OP_NO_*|.
-  uint16_t conf_max_version;
-
-  // conf_min_version is the minimum acceptable protocol version configured by
-  // |SSL_CTX_set_min_proto_version|. Note this version is normalized in DTLS
-  // and is further constrainted by |SSL_OP_NO_*|.
-  uint16_t conf_min_version;
-
-  // tls13_variant is the variant of TLS 1.3 we are using for this
-  // configuration.
-  enum tls13_variant_t tls13_variant;
-
-  SSLCipherPreferenceList *cipher_list;
-
-  X509_STORE *cert_store;
-  LHASH_OF(SSL_SESSION) *sessions;
-  // Most session-ids that will be cached, default is
-  // SSL_SESSION_CACHE_MAX_SIZE_DEFAULT. 0 is unlimited.
-  unsigned long session_cache_size;
-  SSL_SESSION *session_cache_head;
-  SSL_SESSION *session_cache_tail;
-
-  // handshakes_since_cache_flush is the number of successful handshakes since
-  // the last cache flush.
-  int handshakes_since_cache_flush;
-
-  // This can have one of 2 values, ored together,
-  // SSL_SESS_CACHE_CLIENT,
-  // SSL_SESS_CACHE_SERVER,
-  // Default is SSL_SESSION_CACHE_SERVER, which means only
-  // SSL_accept which cache SSL_SESSIONS.
-  int session_cache_mode;
-
-  // session_timeout is the default lifetime for new sessions in TLS 1.2 and
-  // earlier, in seconds.
-  uint32_t session_timeout;
-
-  // session_psk_dhe_timeout is the default lifetime for new sessions in TLS
-  // 1.3, in seconds.
-  uint32_t session_psk_dhe_timeout;
-
-  // If this callback is not null, it will be called each time a session id is
-  // added to the cache.  If this function returns 1, it means that the
-  // callback will do a SSL_SESSION_free() when it has finished using it.
-  // Otherwise, on 0, it means the callback has finished with it. If
-  // remove_session_cb is not null, it will be called when a session-id is
-  // removed from the cache.  After the call, OpenSSL will SSL_SESSION_free()
-  // it.
-  int (*new_session_cb)(SSL *ssl, SSL_SESSION *sess);
-  void (*remove_session_cb)(SSL_CTX *ctx, SSL_SESSION *sess);
-  SSL_SESSION *(*get_session_cb)(SSL *ssl, const uint8_t *data, int len,
-                                 int *copy);
-
-  CRYPTO_refcount_t references;
-
-  // if defined, these override the X509_verify_cert() calls
-  int (*app_verify_callback)(X509_STORE_CTX *store_ctx, void *arg);
-  void *app_verify_arg;
-
-  enum ssl_verify_result_t (*custom_verify_callback)(SSL *ssl,
-                                                     uint8_t *out_alert);
-
-  // Default password callback.
-  pem_password_cb *default_passwd_callback;
-
-  // Default password callback user data.
-  void *default_passwd_callback_userdata;
-
-  // get client cert callback
-  int (*client_cert_cb)(SSL *ssl, X509 **out_x509, EVP_PKEY **out_pkey);
-
-  // get channel id callback
-  void (*channel_id_cb)(SSL *ssl, EVP_PKEY **out_pkey);
-
-  CRYPTO_EX_DATA ex_data;
-
-  // custom_*_extensions stores any callback sets for custom extensions. Note
-  // that these pointers will be NULL if the stack would otherwise be empty.
-  STACK_OF(SSL_CUSTOM_EXTENSION) *client_custom_extensions;
-  STACK_OF(SSL_CUSTOM_EXTENSION) *server_custom_extensions;
-
-  // Default values used when no per-SSL value is defined follow
-
-  void (*info_callback)(const SSL *ssl, int type, int value);
-
-  // what we put in client cert requests
-  STACK_OF(CRYPTO_BUFFER) *client_CA;
-
-  // cached_x509_client_CA is a cache of parsed versions of the elements of
-  // |client_CA|.
-  STACK_OF(X509_NAME) *cached_x509_client_CA;
-
-
-  // Default values to use in SSL structures follow (these are copied by
-  // SSL_new)
-
-  uint32_t options;
-  uint32_t mode;
-  uint32_t max_cert_list;
-
-  CERT *cert;
-
-  // callback that allows applications to peek at protocol messages
-  void (*msg_callback)(int write_p, int version, int content_type,
-                       const void *buf, size_t len, SSL *ssl, void *arg);
-  void *msg_callback_arg;
-
-  int verify_mode;
-  int (*default_verify_callback)(
-      int ok, X509_STORE_CTX *ctx);  // called 'verify_callback' in the SSL
-
-  X509_VERIFY_PARAM *param;
-
-  // select_certificate_cb is called before most ClientHello processing and
-  // before the decision whether to resume a session is made. See
-  // |ssl_select_cert_result_t| for details of the return values.
-  enum ssl_select_cert_result_t (*select_certificate_cb)(
-      const SSL_CLIENT_HELLO *);
-
-  // dos_protection_cb is called once the resumption decision for a ClientHello
-  // has been made. It returns one to continue the handshake or zero to
-  // abort.
-  int (*dos_protection_cb) (const SSL_CLIENT_HELLO *);
-
-  // Maximum amount of data to send in one fragment. actual record size can be
-  // more than this due to padding and MAC overheads.
-  uint16_t max_send_fragment;
-
-  // TLS extensions servername callback
-  int (*tlsext_servername_callback)(SSL *, int *, void *);
-  void *tlsext_servername_arg;
-
-  // RFC 4507 session ticket keys. |tlsext_ticket_key_current| may be NULL
-  // before the first handshake and |tlsext_ticket_key_prev| may be NULL at any
-  // time. Automatically generated ticket keys are rotated as needed at
-  // handshake time. Hence, all access must be synchronized through |lock|.
-  struct tlsext_ticket_key *tlsext_ticket_key_current;
-  struct tlsext_ticket_key *tlsext_ticket_key_prev;
-
-  // Callback to support customisation of ticket key setting
-  int (*tlsext_ticket_key_cb)(SSL *ssl, uint8_t *name, uint8_t *iv,
-                              EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc);
-
-  // Server-only: psk_identity_hint is the default identity hint to send in
-  // PSK-based key exchanges.
-  char *psk_identity_hint;
-
-  unsigned int (*psk_client_callback)(SSL *ssl, const char *hint,
-                                      char *identity,
-                                      unsigned int max_identity_len,
-                                      uint8_t *psk, unsigned int max_psk_len);
-  unsigned int (*psk_server_callback)(SSL *ssl, const char *identity,
-                                      uint8_t *psk, unsigned int max_psk_len);
-
-
-  // Next protocol negotiation information
-  // (for experimental NPN extension).
-
-  // For a server, this contains a callback function by which the set of
-  // advertised protocols can be provided.
-  int (*next_protos_advertised_cb)(SSL *ssl, const uint8_t **out,
-                                   unsigned *out_len, void *arg);
-  void *next_protos_advertised_cb_arg;
-  // For a client, this contains a callback function that selects the
-  // next protocol from the list provided by the server.
-  int (*next_proto_select_cb)(SSL *ssl, uint8_t **out, uint8_t *out_len,
-                              const uint8_t *in, unsigned in_len, void *arg);
-  void *next_proto_select_cb_arg;
-
-  // ALPN information
-  // (we are in the process of transitioning from NPN to ALPN.)
-
-  // For a server, this contains a callback function that allows the
-  // server to select the protocol for the connection.
-  //   out: on successful return, this must point to the raw protocol
-  //        name (without the length prefix).
-  //   outlen: on successful return, this contains the length of |*out|.
-  //   in: points to the client's list of supported protocols in
-  //       wire-format.
-  //   inlen: the length of |in|.
-  int (*alpn_select_cb)(SSL *ssl, const uint8_t **out, uint8_t *out_len,
-                        const uint8_t *in, unsigned in_len, void *arg);
-  void *alpn_select_cb_arg;
-
-  // For a client, this contains the list of supported protocols in wire
-  // format.
-  uint8_t *alpn_client_proto_list;
-  unsigned alpn_client_proto_list_len;
-
-  // SRTP profiles we are willing to do from RFC 5764
-  STACK_OF(SRTP_PROTECTION_PROFILE) *srtp_profiles;
-
-  // Supported group values inherited by SSL structure
-  size_t supported_group_list_len;
-  uint16_t *supported_group_list;
-
-  // The client's Channel ID private key.
-  EVP_PKEY *tlsext_channel_id_private;
-
-  // keylog_callback, if not NULL, is the key logging callback. See
-  // |SSL_CTX_set_keylog_callback|.
-  void (*keylog_callback)(const SSL *ssl, const char *line);
-
-  // current_time_cb, if not NULL, is the function to use to get the current
-  // time. It sets |*out_clock| to the current time. The |ssl| argument is
-  // always NULL. See |SSL_CTX_set_current_time_cb|.
-  void (*current_time_cb)(const SSL *ssl, struct timeval *out_clock);
-
-  // pool is used for all |CRYPTO_BUFFER|s in case we wish to share certificate
-  // memory.
-  CRYPTO_BUFFER_POOL *pool;
-
-  // ticket_aead_method contains function pointers for opening and sealing
-  // session tickets.
-  const SSL_TICKET_AEAD_METHOD *ticket_aead_method;
-
-  // verify_sigalgs, if not empty, is the set of signature algorithms
-  // accepted from the peer in decreasing order of preference.
-  uint16_t *verify_sigalgs;
-  size_t num_verify_sigalgs;
-
-  // retain_only_sha256_of_client_certs is true if we should compute the SHA256
-  // hash of the peer's certificate and then discard it to save memory and
-  // session space. Only effective on the server side.
-  bool retain_only_sha256_of_client_certs:1;
-
-  // quiet_shutdown is true if the connection should not send a close_notify on
-  // shutdown.
-  bool quiet_shutdown:1;
-
-  // ocsp_stapling_enabled is only used by client connections and indicates
-  // whether OCSP stapling will be requested.
-  bool ocsp_stapling_enabled:1;
-
-  // If true, a client will request certificate timestamps.
-  bool signed_cert_timestamps_enabled:1;
-
-  // tlsext_channel_id_enabled is whether Channel ID is enabled. For a server,
-  // means that we'll accept Channel IDs from clients.  For a client, means that
-  // we'll advertise support.
-  bool tlsext_channel_id_enabled:1;
-
-  // grease_enabled is whether draft-davidben-tls-grease-01 is enabled.
-  bool grease_enabled:1;
-
-  // allow_unknown_alpn_protos is whether the client allows unsolicited ALPN
-  // protocols from the peer.
-  bool allow_unknown_alpn_protos:1;
-
-  // ed25519_enabled is whether Ed25519 is advertised in the handshake.
-  bool ed25519_enabled:1;
-
-  // rsa_pss_rsae_certs_enabled is whether rsa_pss_rsae_* are supported by the
-  // certificate verifier.
-  bool rsa_pss_rsae_certs_enabled:1;
-
-  // false_start_allowed_without_alpn is whether False Start (if
-  // |SSL_MODE_ENABLE_FALSE_START| is enabled) is allowed without ALPN.
-  bool false_start_allowed_without_alpn:1;
-
-  // handoff indicates that a server should stop after receiving the
-  // ClientHello and pause the handshake in such a way that |SSL_get_error|
-  // returns |SSL_HANDOFF|.
-  bool handoff:1;
-};
-
 // An ssl_shutdown_t describes the shutdown state of one end of the connection,
 // whether it is alive or has been shutdown via close_notify or fatal alert.
 enum ssl_shutdown_t {
@@ -2359,9 +2118,8 @@
 
   // In a client, this means that the server supported Channel ID and that a
   // Channel ID was sent. In a server it means that we echoed support for
-  // Channel IDs and that tlsext_channel_id will be valid after the
-  // handshake.
-  bool tlsext_channel_id_valid:1;
+  // Channel IDs and that |channel_id| will be valid after the handshake.
+  bool channel_id_valid:1;
 
   // key_update_pending is true if we have a KeyUpdate acknowledgment
   // outstanding.
@@ -2383,6 +2141,11 @@
   // hs_buf is the buffer of handshake data to process.
   UniquePtr<BUF_MEM> hs_buf;
 
+  // pending_hs_data contains the pending handshake data that has not yet
+  // been encrypted to |pending_flight|. This allows packing the handshake into
+  // fewer records.
+  UniquePtr<BUF_MEM> pending_hs_data;
+
   // pending_flight is the pending outgoing flight. This is used to flush each
   // handshake flight in a single write. |write_buffer| must be written out
   // before this data.
@@ -2449,10 +2212,10 @@
   UniquePtr<char> hostname;
 
   // For a server:
-  //     If |tlsext_channel_id_valid| is true, then this contains the
+  //     If |channel_id_valid| is true, then this contains the
   //     verified Channel ID from the client: a P256 point, (x,y), where
   //     each are big-endian values.
-  uint8_t tlsext_channel_id[64] = {0};
+  uint8_t channel_id[64] = {0};
 
   // Contains the QUIC transport params received by the peer.
   Array<uint8_t> peer_quic_transport_params;
@@ -2580,147 +2343,89 @@
   unsigned timeout_duration_ms = 0;
 };
 
-// SSLConnection backs the public |SSL| type. Due to compatibility constraints,
-// it is a base class for |ssl_st|.
-struct SSLConnection {
-  // method is the method table corresponding to the current protocol (DTLS or
-  // TLS).
-  const SSL_PROTOCOL_METHOD *method;
+// SSL_CONFIG contains configuration bits that can be shed after the handshake
+// completes.  Objects of this type are not shared; they are unique to a
+// particular |SSL|.
+//
+// See SSL_shed_handshake_config() for more about the conditions under which
+// configuration can be shed.
+struct SSL_CONFIG {
+  static constexpr bool kAllowUniquePtr = true;
 
-  // version is the protocol version.
-  uint16_t version;
+  explicit SSL_CONFIG(SSL *ssl_arg);
+  ~SSL_CONFIG();
+
+  // ssl is a non-owning pointer to the parent |SSL| object.
+  SSL *const ssl = nullptr;
 
   // conf_max_version is the maximum acceptable protocol version configured by
   // |SSL_set_max_proto_version|. Note this version is normalized in DTLS and is
   // further constrainted by |SSL_OP_NO_*|.
-  uint16_t conf_max_version;
+  uint16_t conf_max_version = 0;
 
   // conf_min_version is the minimum acceptable protocol version configured by
   // |SSL_set_min_proto_version|. Note this version is normalized in DTLS and is
   // further constrainted by |SSL_OP_NO_*|.
-  uint16_t conf_min_version;
+  uint16_t conf_min_version = 0;
 
-  uint16_t max_send_fragment;
-
-  // There are 2 BIO's even though they are normally both the same. This is so
-  // data can be read and written to different handlers
-
-  BIO *rbio;  // used by SSL_read
-  BIO *wbio;  // used by SSL_write
-
-  // do_handshake runs the handshake. On completion, it returns |ssl_hs_ok|.
-  // Otherwise, it returns a value corresponding to what operation is needed to
-  // progress.
-  enum ssl_hs_wait_t (*do_handshake)(SSL_HANDSHAKE *hs);
-
-  SSL3_STATE *s3;   // SSLv3 variables
-  DTLS1_STATE *d1;  // DTLSv1 variables
-
-  // callback that allows applications to peek at protocol messages
-  void (*msg_callback)(int write_p, int version, int content_type,
-                       const void *buf, size_t len, SSL *ssl, void *arg);
-  void *msg_callback_arg;
-
-  X509_VERIFY_PARAM *param;
+  X509_VERIFY_PARAM *param = nullptr;
 
   // crypto
-  SSLCipherPreferenceList *cipher_list;
-
-  // session info
+  UniquePtr<SSLCipherPreferenceList> cipher_list;
 
   // This is used to hold the local certificate used (i.e. the server
   // certificate for a server or the client certificate for a client).
-  CERT *cert;
-
-  // initial_timeout_duration_ms is the default DTLS timeout duration in
-  // milliseconds. It's used to initialize the timer any time it's restarted.
-  unsigned initial_timeout_duration_ms;
-
-  // tls13_variant is the variant of TLS 1.3 we are using for this
-  // configuration.
-  enum tls13_variant_t tls13_variant;
-
-  // session is the configured session to be offered by the client. This session
-  // is immutable.
-  SSL_SESSION *session;
+  UniquePtr<CERT> cert;
 
   int (*verify_callback)(int ok,
-                         X509_STORE_CTX *ctx);  // fail if callback returns 0
+                         X509_STORE_CTX *ctx) =
+      nullptr;  // fail if callback returns 0
 
-  enum ssl_verify_result_t (*custom_verify_callback)(SSL *ssl,
-                                                     uint8_t *out_alert);
-
-  void (*info_callback)(const SSL *ssl, int type, int value);
-
+  enum ssl_verify_result_t (*custom_verify_callback)(
+      SSL *ssl, uint8_t *out_alert) = nullptr;
   // Server-only: psk_identity_hint is the identity hint to send in
   // PSK-based key exchanges.
-  char *psk_identity_hint;
+  UniquePtr<char> psk_identity_hint;
 
-  unsigned int (*psk_client_callback)(SSL *ssl, const char *hint,
-                                      char *identity,
-                                      unsigned int max_identity_len,
-                                      uint8_t *psk, unsigned int max_psk_len);
-  unsigned int (*psk_server_callback)(SSL *ssl, const char *identity,
-                                      uint8_t *psk, unsigned int max_psk_len);
-
-  SSL_CTX *ctx;
-
-  // extra application data
-  CRYPTO_EX_DATA ex_data;
+  unsigned (*psk_client_callback)(SSL *ssl, const char *hint, char *identity,
+                                  unsigned max_identity_len, uint8_t *psk,
+                                  unsigned max_psk_len) = nullptr;
+  unsigned (*psk_server_callback)(SSL *ssl, const char *identity, uint8_t *psk,
+                                  unsigned max_psk_len) = nullptr;
 
   // for server side, keep the list of CA_dn we can use
-  STACK_OF(CRYPTO_BUFFER) *client_CA;
+  UniquePtr<STACK_OF(CRYPTO_BUFFER)> client_CA;
 
   // cached_x509_client_CA is a cache of parsed versions of the elements of
   // |client_CA|.
-  STACK_OF(X509_NAME) *cached_x509_client_CA;
+  STACK_OF(X509_NAME) *cached_x509_client_CA = nullptr;
 
-  uint32_t options;  // protocol behaviour
-  uint32_t mode;     // API behaviour
-  uint32_t max_cert_list;
-  uint16_t dummy_pq_padding_len;
-  char *tlsext_hostname;
-  size_t supported_group_list_len;
-  uint16_t *supported_group_list;  // our list
-
-  // session_ctx is the |SSL_CTX| used for the session cache and related
-  // settings.
-  SSL_CTX *session_ctx;
-
-  // srtp_profiles is the list of configured SRTP protection profiles for
-  // DTLS-SRTP.
-  STACK_OF(SRTP_PROTECTION_PROFILE) *srtp_profiles;
+  uint16_t dummy_pq_padding_len = 0;
+  Array<uint16_t> supported_group_list;  // our list
 
   // The client's Channel ID private key.
-  EVP_PKEY *tlsext_channel_id_private;
+  UniquePtr<EVP_PKEY> channel_id_private;
 
   // For a client, this contains the list of supported protocols in wire
   // format.
-  uint8_t *alpn_client_proto_list;
-  unsigned alpn_client_proto_list_len;
+  Array<uint8_t> alpn_client_proto_list;
 
   // Contains a list of supported Token Binding key parameters.
-  uint8_t *token_binding_params;
-  size_t token_binding_params_len;
+  Array<uint8_t> token_binding_params;
 
   // Contains the QUIC transport params that this endpoint will send.
-  uint8_t *quic_transport_params;
-  size_t quic_transport_params_len;
+  Array<uint8_t> quic_transport_params;
 
-  // renegotiate_mode controls how peer renegotiation attempts are handled.
-  enum ssl_renegotiate_mode_t renegotiate_mode;
+  // verify_sigalgs, if not empty, is the set of signature algorithms
+  // accepted from the peer in decreasing order of preference.
+  Array<uint16_t> verify_sigalgs;
+
+  // srtp_profiles is the list of configured SRTP protection profiles for
+  // DTLS-SRTP.
+  UniquePtr<STACK_OF(SRTP_PROTECTION_PROFILE)> srtp_profiles;
 
   // verify_mode is a bitmask of |SSL_VERIFY_*| values.
-  uint8_t verify_mode;
-
-  // server is true iff the this SSL* is the server half. Note: before the SSL*
-  // is initialized by either SSL_set_accept_state or SSL_set_connect_state,
-  // the side is not determined. In this state, server is always false.
-  bool server:1;
-
-  // quiet_shutdown is true if the connection should not send a close_notify on
-  // shutdown.
-  bool quiet_shutdown:1;
+  uint8_t verify_mode = SSL_VERIFY_NONE;
 
   // Enable signed certificate time stamps. Currently client only.
   bool signed_cert_timestamps_enabled:1;
@@ -2729,10 +2434,10 @@
   // whether OCSP stapling will be requested.
   bool ocsp_stapling_enabled:1;
 
-  // tlsext_channel_id_enabled is copied from the |SSL_CTX|. For a server,
-  // means that we'll accept Channel IDs from clients. For a client, means that
-  // we'll advertise support.
-  bool tlsext_channel_id_enabled:1;
+  // channel_id_enabled is copied from the |SSL_CTX|. For a server, means that
+  // we'll accept Channel IDs from clients. For a client, means that we'll
+  // advertise support.
+  bool channel_id_enabled:1;
 
   // retain_only_sha256_of_client_certs is true if we should compute the SHA256
   // hash of the peer's certificate and then discard it to save memory and
@@ -2745,15 +2450,9 @@
   // element of the same name and may be cleared if the handoff is declined.
   bool handoff:1;
 
-  // handback indicates that a server should pause the handshake after
-  // finishing operations that require private key material, in such a way that
-  // |SSL_get_error| returns |SSL_HANDBACK|.  It is set by |SSL_apply_handoff|.
-  bool handback : 1;
-
-  // did_dummy_pq_padding is only valid for a client. In that context, it is
-  // true iff the client observed the server echoing a dummy PQ padding
-  // extension.
-  bool did_dummy_pq_padding:1;
+  // shed_handshake_config indicates that the handshake config (this object!)
+  // should be freed after the handshake completes.
+  bool shed_handshake_config : 1;
 };
 
 // From draft-ietf-tls-tls13-18, used in determining PSK modes.
@@ -2780,27 +2479,31 @@
                                        const EVP_PKEY *privkey);
 int ssl_cert_check_private_key(const CERT *cert, const EVP_PKEY *privkey);
 int ssl_get_new_session(SSL_HANDSHAKE *hs, int is_server);
-int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session);
+int ssl_encrypt_ticket(SSL_HANDSHAKE *hs, CBB *out, const SSL_SESSION *session);
 int ssl_ctx_rotate_ticket_encryption_key(SSL_CTX *ctx);
 
 // ssl_session_new returns a newly-allocated blank |SSL_SESSION| or nullptr on
 // error.
 UniquePtr<SSL_SESSION> ssl_session_new(const SSL_X509_METHOD *x509_method);
 
+// ssl_hash_session_id returns a hash of |session_id|, suitable for a hash table
+// keyed on session IDs.
+uint32_t ssl_hash_session_id(Span<const uint8_t> session_id);
+
 // SSL_SESSION_parse parses an |SSL_SESSION| from |cbs| and advances |cbs| over
 // the parsed data.
-UniquePtr<SSL_SESSION> SSL_SESSION_parse(CBS *cbs,
-                                         const SSL_X509_METHOD *x509_method,
-                                         CRYPTO_BUFFER_POOL *pool);
+OPENSSL_EXPORT UniquePtr<SSL_SESSION> SSL_SESSION_parse(
+    CBS *cbs, const SSL_X509_METHOD *x509_method, CRYPTO_BUFFER_POOL *pool);
 
 // ssl_session_serialize writes |in| to |cbb| as if it were serialising a
 // session for Session-ID resumption. It returns one on success and zero on
 // error.
-int ssl_session_serialize(const SSL_SESSION *in, CBB *cbb);
+OPENSSL_EXPORT int ssl_session_serialize(const SSL_SESSION *in, CBB *cbb);
 
 // ssl_session_is_context_valid returns one if |session|'s session ID context
-// matches the one set on |ssl| and zero otherwise.
-int ssl_session_is_context_valid(const SSL *ssl, const SSL_SESSION *session);
+// matches the one set on |hs| and zero otherwise.
+int ssl_session_is_context_valid(const SSL_HANDSHAKE *hs,
+                                 const SSL_SESSION *session);
 
 // ssl_session_is_time_valid returns one if |session| is still valid and zero if
 // it has expired.
@@ -2827,7 +2530,7 @@
 // |ssl_hs_pending_session| and should be called again. If a ticket could not be
 // decrypted immediately it returns |ssl_hs_pending_ticket| and should also
 // be called again. Otherwise, it returns |ssl_hs_error|.
-enum ssl_hs_wait_t ssl_get_prev_session(SSL *ssl,
+enum ssl_hs_wait_t ssl_get_prev_session(SSL_HANDSHAKE *hs,
                                         UniquePtr<SSL_SESSION> *out_session,
                                         bool *out_tickets_supported,
                                         bool *out_renew_ticket,
@@ -2856,10 +2559,6 @@
 void ssl_session_renew_timeout(SSL *ssl, SSL_SESSION *session,
                                uint32_t timeout);
 
-// ssl_get_cipher_preferences returns the cipher preference list for TLS 1.2 and
-// below.
-const SSLCipherPreferenceList *ssl_get_cipher_preferences(const SSL *ssl);
-
 void ssl_update_cache(SSL_HANDSHAKE *hs, int mode);
 
 int ssl_send_alert(SSL *ssl, int level, int desc);
@@ -2952,41 +2651,38 @@
                                 Span<const uint8_t> premaster);
 
 // tls1_get_grouplist returns the locally-configured group preference list.
-Span<const uint16_t> tls1_get_grouplist(const SSL *ssl);
+Span<const uint16_t> tls1_get_grouplist(const SSL_HANDSHAKE *ssl);
 
-// tls1_check_group_id returns one if |group_id| is consistent with
-// locally-configured group preferences.
-int tls1_check_group_id(const SSL *ssl, uint16_t group_id);
+// tls1_check_group_id returns whether |group_id| is consistent with locally-
+// configured group preferences.
+bool tls1_check_group_id(const SSL_HANDSHAKE *ssl, uint16_t group_id);
 
 // tls1_get_shared_group sets |*out_group_id| to the first preferred shared
-// group between client and server preferences and returns one. If none may be
-// found, it returns zero.
-int tls1_get_shared_group(SSL_HANDSHAKE *hs, uint16_t *out_group_id);
+// group between client and server preferences and returns true. If none may be
+// found, it returns false.
+bool tls1_get_shared_group(SSL_HANDSHAKE *hs, uint16_t *out_group_id);
 
-// tls1_set_curves converts the array of |ncurves| NIDs pointed to by |curves|
-// into a newly allocated array of TLS group IDs. On success, the function
-// returns one and writes the array to |*out_group_ids| and its size to
-// |*out_group_ids_len|. Otherwise, it returns zero.
-int tls1_set_curves(uint16_t **out_group_ids, size_t *out_group_ids_len,
-                    const int *curves, size_t ncurves);
+// tls1_set_curves converts the array of NIDs in |curves| into a newly allocated
+// array of TLS group IDs. On success, the function returns true and writes the
+// array to |*out_group_ids|. Otherwise, it returns false.
+bool tls1_set_curves(Array<uint16_t> *out_group_ids, Span<const int> curves);
 
 // tls1_set_curves_list converts the string of curves pointed to by |curves|
 // into a newly allocated array of TLS group IDs. On success, the function
-// returns one and writes the array to |*out_group_ids| and its size to
-// |*out_group_ids_len|. Otherwise, it returns zero.
-int tls1_set_curves_list(uint16_t **out_group_ids, size_t *out_group_ids_len,
-                         const char *curves);
+// returns true and writes the array to |*out_group_ids|. Otherwise, it returns
+// false.
+bool tls1_set_curves_list(Array<uint16_t> *out_group_ids, const char *curves);
 
-// ssl_add_clienthello_tlsext writes ClientHello extensions to |out|. It
-// returns one on success and zero on failure. The |header_len| argument is the
-// length of the ClientHello written so far and is used to compute the padding
-// length. (It does not include the record header.)
-int ssl_add_clienthello_tlsext(SSL_HANDSHAKE *hs, CBB *out, size_t header_len);
+// ssl_add_clienthello_tlsext writes ClientHello extensions to |out|. It returns
+// true on success and false on failure. The |header_len| argument is the length
+// of the ClientHello written so far and is used to compute the padding length.
+// (It does not include the record header.)
+bool ssl_add_clienthello_tlsext(SSL_HANDSHAKE *hs, CBB *out, size_t header_len);
 
-int ssl_add_serverhello_tlsext(SSL_HANDSHAKE *hs, CBB *out);
-int ssl_parse_clienthello_tlsext(SSL_HANDSHAKE *hs,
-                                 const SSL_CLIENT_HELLO *client_hello);
-int ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs);
+bool ssl_add_serverhello_tlsext(SSL_HANDSHAKE *hs, CBB *out);
+bool ssl_parse_clienthello_tlsext(SSL_HANDSHAKE *hs,
+                                  const SSL_CLIENT_HELLO *client_hello);
+bool ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs);
 
 #define tlsext_tick_md EVP_sha256
 
@@ -3000,38 +2696,41 @@
 //       Retry later.
 //   |ssl_ticket_aead_error|: an error occured that is fatal to the connection.
 enum ssl_ticket_aead_result_t ssl_process_ticket(
-    SSL *ssl, UniquePtr<SSL_SESSION> *out_session, bool *out_renew_ticket,
-    const uint8_t *ticket, size_t ticket_len, const uint8_t *session_id,
-    size_t session_id_len);
+    SSL_HANDSHAKE *hs, UniquePtr<SSL_SESSION> *out_session,
+    bool *out_renew_ticket, const uint8_t *ticket, size_t ticket_len,
+    const uint8_t *session_id, size_t session_id_len);
 
 // tls1_verify_channel_id processes |msg| as a Channel ID message, and verifies
-// the signature. If the key is valid, it saves the Channel ID and returns
-// one. Otherwise, it returns zero.
-int tls1_verify_channel_id(SSL_HANDSHAKE *hs, const SSLMessage &msg);
+// the signature. If the key is valid, it saves the Channel ID and returns true.
+// Otherwise, it returns false.
+bool tls1_verify_channel_id(SSL_HANDSHAKE *hs, const SSLMessage &msg);
 
 // tls1_write_channel_id generates a Channel ID message and puts the output in
-// |cbb|. |ssl->tlsext_channel_id_private| must already be set before calling.
-// This function returns true on success and false on error.
+// |cbb|. |ssl->channel_id_private| must already be set before calling.  This
+// function returns true on success and false on error.
 bool tls1_write_channel_id(SSL_HANDSHAKE *hs, CBB *cbb);
 
 // tls1_channel_id_hash computes the hash to be signed by Channel ID and writes
 // it to |out|, which must contain at least |EVP_MAX_MD_SIZE| bytes. It returns
-// one on success and zero on failure.
-int tls1_channel_id_hash(SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len);
+// true on success and false on failure.
+bool tls1_channel_id_hash(SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len);
 
-int tls1_record_handshake_hashes_for_channel_id(SSL_HANDSHAKE *hs);
+// tls1_record_handshake_hashes_for_channel_id records the current handshake
+// hashes in |hs->new_session| so that Channel ID resumptions can sign that
+// data.
+bool tls1_record_handshake_hashes_for_channel_id(SSL_HANDSHAKE *hs);
 
-// ssl_do_channel_id_callback checks runs |ssl->ctx->channel_id_cb| if
-// necessary. It returns one on success and zero on fatal error. Note that, on
-// success, |ssl->tlsext_channel_id_private| may be unset, in which case the
+// ssl_do_channel_id_callback checks runs |hs->ssl->ctx->channel_id_cb| if
+// necessary. It returns true on success and false on fatal error. Note that, on
+// success, |hs->ssl->channel_id_private| may be unset, in which case the
 // operation should be retried later.
-int ssl_do_channel_id_callback(SSL *ssl);
+bool ssl_do_channel_id_callback(SSL_HANDSHAKE *hs);
 
-// ssl_can_write returns one if |ssl| is allowed to write and zero otherwise.
-int ssl_can_write(const SSL *ssl);
+// ssl_can_write returns whether |ssl| is allowed to write.
+bool ssl_can_write(const SSL *ssl);
 
-// ssl_can_read returns one if |ssl| is allowed to read and zero otherwise.
-int ssl_can_read(const SSL *ssl);
+// ssl_can_read returns wheter |ssl| is allowed to read.
+bool ssl_can_read(const SSL *ssl);
 
 void ssl_get_current_time(const SSL *ssl, struct OPENSSL_timeval *out_clock);
 void ssl_ctx_get_current_time(const SSL_CTX *ctx,
@@ -3063,70 +2762,521 @@
   const bssl::SSL_PROTOCOL_METHOD *method;
   // x509_method contains pointers to functions that might deal with |X509|
   // compatibility, or might be a no-op, depending on the application.
-  const SSL_X509_METHOD *x509_method;
+  const bssl::SSL_X509_METHOD *x509_method;
 };
 
-struct ssl_x509_method_st {
-  // check_client_CA_list returns one if |names| is a good list of X.509
-  // distinguished names and zero otherwise. This is used to ensure that we can
-  // reject unparsable values at handshake time when using crypto/x509.
-  int (*check_client_CA_list)(STACK_OF(CRYPTO_BUFFER) *names);
+struct ssl_ctx_st {
+  explicit ssl_ctx_st(const SSL_METHOD *ssl_method);
+  ssl_ctx_st(const ssl_ctx_st &) = delete;
+  ssl_ctx_st &operator=(const ssl_ctx_st &) = delete;
 
-  // cert_clear frees and NULLs all X509 certificate-related state.
-  void (*cert_clear)(bssl::CERT *cert);
-  // cert_free frees all X509-related state.
-  void (*cert_free)(bssl::CERT *cert);
-  // cert_flush_cached_chain drops any cached |X509|-based certificate chain
-  // from |cert|.
-  // cert_dup duplicates any needed fields from |cert| to |new_cert|.
-  void (*cert_dup)(bssl::CERT *new_cert, const bssl::CERT *cert);
-  void (*cert_flush_cached_chain)(bssl::CERT *cert);
-  // cert_flush_cached_chain drops any cached |X509|-based leaf certificate
-  // from |cert|.
-  void (*cert_flush_cached_leaf)(bssl::CERT *cert);
+  const bssl::SSL_PROTOCOL_METHOD *method = nullptr;
+  const bssl::SSL_X509_METHOD *x509_method = nullptr;
 
-  // session_cache_objects fills out |sess->x509_peer| and |sess->x509_chain|
-  // from |sess->certs| and erases |sess->x509_chain_without_leaf|. It returns
-  // one on success or zero on error.
-  int (*session_cache_objects)(SSL_SESSION *session);
-  // session_dup duplicates any needed fields from |session| to |new_session|.
-  // It returns one on success or zero on error.
-  int (*session_dup)(SSL_SESSION *new_session, const SSL_SESSION *session);
-  // session_clear frees any X509-related state from |session|.
-  void (*session_clear)(SSL_SESSION *session);
-  // session_verify_cert_chain verifies the certificate chain in |session|,
-  // sets |session->verify_result| and returns one on success or zero on
-  // error.
-  int (*session_verify_cert_chain)(SSL_SESSION *session, SSL *ssl,
-                                   uint8_t *out_alert);
+  // lock is used to protect various operations on this object.
+  CRYPTO_MUTEX lock;
 
-  // hs_flush_cached_ca_names drops any cached |X509_NAME|s from |hs|.
-  void (*hs_flush_cached_ca_names)(bssl::SSL_HANDSHAKE *hs);
-  // ssl_new does any neccessary initialisation of |ssl|. It returns one on
-  // success or zero on error.
-  int (*ssl_new)(SSL *ssl);
-  // ssl_free frees anything created by |ssl_new|.
-  void (*ssl_free)(SSL *ssl);
-  // ssl_flush_cached_client_CA drops any cached |X509_NAME|s from |ssl|.
-  void (*ssl_flush_cached_client_CA)(SSL *ssl);
-  // ssl_auto_chain_if_needed runs the deprecated auto-chaining logic if
-  // necessary. On success, it updates |ssl|'s certificate configuration as
-  // needed and returns one. Otherwise, it returns zero.
-  int (*ssl_auto_chain_if_needed)(SSL *ssl);
-  // ssl_ctx_new does any neccessary initialisation of |ctx|. It returns one on
-  // success or zero on error.
-  int (*ssl_ctx_new)(SSL_CTX *ctx);
-  // ssl_ctx_free frees anything created by |ssl_ctx_new|.
-  void (*ssl_ctx_free)(SSL_CTX *ctx);
-  // ssl_ctx_flush_cached_client_CA drops any cached |X509_NAME|s from |ctx|.
-  void (*ssl_ctx_flush_cached_client_CA)(SSL_CTX *ssl);
+  // conf_max_version is the maximum acceptable protocol version configured by
+  // |SSL_CTX_set_max_proto_version|. Note this version is normalized in DTLS
+  // and is further constrainted by |SSL_OP_NO_*|.
+  uint16_t conf_max_version = 0;
+
+  // conf_min_version is the minimum acceptable protocol version configured by
+  // |SSL_CTX_set_min_proto_version|. Note this version is normalized in DTLS
+  // and is further constrainted by |SSL_OP_NO_*|.
+  uint16_t conf_min_version = 0;
+
+  // tls13_variant is the variant of TLS 1.3 we are using for this
+  // configuration.
+  tls13_variant_t tls13_variant = tls13_default;
+
+  bssl::UniquePtr<bssl::SSLCipherPreferenceList> cipher_list;
+
+  X509_STORE *cert_store = nullptr;
+  LHASH_OF(SSL_SESSION) *sessions = nullptr;
+  // Most session-ids that will be cached, default is
+  // SSL_SESSION_CACHE_MAX_SIZE_DEFAULT. 0 is unlimited.
+  unsigned long session_cache_size = SSL_SESSION_CACHE_MAX_SIZE_DEFAULT;
+  SSL_SESSION *session_cache_head = nullptr;
+  SSL_SESSION *session_cache_tail = nullptr;
+
+  // handshakes_since_cache_flush is the number of successful handshakes since
+  // the last cache flush.
+  int handshakes_since_cache_flush = 0;
+
+  // This can have one of 2 values, ored together,
+  // SSL_SESS_CACHE_CLIENT,
+  // SSL_SESS_CACHE_SERVER,
+  // Default is SSL_SESSION_CACHE_SERVER, which means only
+  // SSL_accept which cache SSL_SESSIONS.
+  int session_cache_mode = SSL_SESS_CACHE_SERVER;
+
+  // session_timeout is the default lifetime for new sessions in TLS 1.2 and
+  // earlier, in seconds.
+  uint32_t session_timeout = SSL_DEFAULT_SESSION_TIMEOUT;
+
+  // session_psk_dhe_timeout is the default lifetime for new sessions in TLS
+  // 1.3, in seconds.
+  uint32_t session_psk_dhe_timeout = SSL_DEFAULT_SESSION_PSK_DHE_TIMEOUT;
+
+  // If this callback is not null, it will be called each time a session id is
+  // added to the cache.  If this function returns 1, it means that the
+  // callback will do a SSL_SESSION_free() when it has finished using it.
+  // Otherwise, on 0, it means the callback has finished with it. If
+  // remove_session_cb is not null, it will be called when a session-id is
+  // removed from the cache.  After the call, OpenSSL will SSL_SESSION_free()
+  // it.
+  int (*new_session_cb)(SSL *ssl, SSL_SESSION *sess) = nullptr;
+  void (*remove_session_cb)(SSL_CTX *ctx, SSL_SESSION *sess) = nullptr;
+  SSL_SESSION *(*get_session_cb)(SSL *ssl, const uint8_t *data, int len,
+                                 int *copy) = nullptr;
+
+  CRYPTO_refcount_t references = 1;
+
+  // if defined, these override the X509_verify_cert() calls
+  int (*app_verify_callback)(X509_STORE_CTX *store_ctx, void *arg) = nullptr;
+  void *app_verify_arg = nullptr;
+
+  ssl_verify_result_t (*custom_verify_callback)(SSL *ssl,
+                                                uint8_t *out_alert) = nullptr;
+
+  // Default password callback.
+  pem_password_cb *default_passwd_callback = nullptr;
+
+  // Default password callback user data.
+  void *default_passwd_callback_userdata = nullptr;
+
+  // get client cert callback
+  int (*client_cert_cb)(SSL *ssl, X509 **out_x509, EVP_PKEY **out_pkey) = nullptr;
+
+  // get channel id callback
+  void (*channel_id_cb)(SSL *ssl, EVP_PKEY **out_pkey) = nullptr;
+
+  CRYPTO_EX_DATA ex_data;
+
+  // Default values used when no per-SSL value is defined follow
+
+  void (*info_callback)(const SSL *ssl, int type, int value) = nullptr;
+
+  // what we put in client cert requests
+  bssl::UniquePtr<STACK_OF(CRYPTO_BUFFER)> client_CA;
+
+  // cached_x509_client_CA is a cache of parsed versions of the elements of
+  // |client_CA|.
+  STACK_OF(X509_NAME) *cached_x509_client_CA = nullptr;
+
+
+  // Default values to use in SSL structures follow (these are copied by
+  // SSL_new)
+
+  uint32_t options = 0;
+  // Disable the auto-chaining feature by default. wpa_supplicant relies on this
+  // feature, but require callers opt into it.
+  uint32_t mode = SSL_MODE_NO_AUTO_CHAIN;
+  uint32_t max_cert_list = SSL_MAX_CERT_LIST_DEFAULT;
+
+  bssl::UniquePtr<bssl::CERT> cert;
+
+  // callback that allows applications to peek at protocol messages
+  void (*msg_callback)(int write_p, int version, int content_type,
+                       const void *buf, size_t len, SSL *ssl, void *arg) = nullptr;
+  void *msg_callback_arg = nullptr;
+
+  int verify_mode = SSL_VERIFY_NONE;
+  int (*default_verify_callback)(int ok, X509_STORE_CTX *ctx) =
+      nullptr;  // called 'verify_callback' in the SSL
+
+  X509_VERIFY_PARAM *param = nullptr;
+
+  // select_certificate_cb is called before most ClientHello processing and
+  // before the decision whether to resume a session is made. See
+  // |ssl_select_cert_result_t| for details of the return values.
+  ssl_select_cert_result_t (*select_certificate_cb)(const SSL_CLIENT_HELLO *) =
+      nullptr;
+
+  // dos_protection_cb is called once the resumption decision for a ClientHello
+  // has been made. It returns one to continue the handshake or zero to
+  // abort.
+  int (*dos_protection_cb) (const SSL_CLIENT_HELLO *) = nullptr;
+
+  // Maximum amount of data to send in one fragment. actual record size can be
+  // more than this due to padding and MAC overheads.
+  uint16_t max_send_fragment = SSL3_RT_MAX_PLAIN_LENGTH;
+
+  // TLS extensions servername callback
+  int (*servername_callback)(SSL *, int *, void *) = nullptr;
+  void *servername_arg = nullptr;
+
+  // RFC 4507 session ticket keys. |ticket_key_current| may be NULL before the
+  // first handshake and |ticket_key_prev| may be NULL at any time.
+  // Automatically generated ticket keys are rotated as needed at handshake
+  // time. Hence, all access must be synchronized through |lock|.
+  bssl::UniquePtr<bssl::TicketKey> ticket_key_current;
+  bssl::UniquePtr<bssl::TicketKey> ticket_key_prev;
+
+  // Callback to support customisation of ticket key setting
+  int (*ticket_key_cb)(SSL *ssl, uint8_t *name, uint8_t *iv,
+                       EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc) = nullptr;
+
+  // Server-only: psk_identity_hint is the default identity hint to send in
+  // PSK-based key exchanges.
+  bssl::UniquePtr<char> psk_identity_hint;
+
+  unsigned (*psk_client_callback)(SSL *ssl, const char *hint, char *identity,
+                                  unsigned max_identity_len, uint8_t *psk,
+                                  unsigned max_psk_len) = nullptr;
+  unsigned (*psk_server_callback)(SSL *ssl, const char *identity, uint8_t *psk,
+                                  unsigned max_psk_len) = nullptr;
+
+
+  // Next protocol negotiation information
+  // (for experimental NPN extension).
+
+  // For a server, this contains a callback function by which the set of
+  // advertised protocols can be provided.
+  int (*next_protos_advertised_cb)(SSL *ssl, const uint8_t **out,
+                                   unsigned *out_len, void *arg) = nullptr;
+  void *next_protos_advertised_cb_arg = nullptr;
+  // For a client, this contains a callback function that selects the
+  // next protocol from the list provided by the server.
+  int (*next_proto_select_cb)(SSL *ssl, uint8_t **out, uint8_t *out_len,
+                              const uint8_t *in, unsigned in_len,
+                              void *arg) = nullptr;
+  void *next_proto_select_cb_arg = nullptr;
+
+  // ALPN information
+  // (we are in the process of transitioning from NPN to ALPN.)
+
+  // For a server, this contains a callback function that allows the
+  // server to select the protocol for the connection.
+  //   out: on successful return, this must point to the raw protocol
+  //        name (without the length prefix).
+  //   outlen: on successful return, this contains the length of |*out|.
+  //   in: points to the client's list of supported protocols in
+  //       wire-format.
+  //   inlen: the length of |in|.
+  int (*alpn_select_cb)(SSL *ssl, const uint8_t **out, uint8_t *out_len,
+                        const uint8_t *in, unsigned in_len,
+                        void *arg) = nullptr;
+  void *alpn_select_cb_arg = nullptr;
+
+  // For a client, this contains the list of supported protocols in wire
+  // format.
+  bssl::Array<uint8_t> alpn_client_proto_list;
+
+  // SRTP profiles we are willing to do from RFC 5764
+  bssl::UniquePtr<STACK_OF(SRTP_PROTECTION_PROFILE)> srtp_profiles;
+
+  // Defined compression algorithms for certificates.
+  bssl::UniquePtr<STACK_OF(CertCompressionAlg)> cert_compression_algs;
+
+  // Supported group values inherited by SSL structure
+  bssl::Array<uint16_t> supported_group_list;
+
+  // The client's Channel ID private key.
+  bssl::UniquePtr<EVP_PKEY> channel_id_private;
+
+  // keylog_callback, if not NULL, is the key logging callback. See
+  // |SSL_CTX_set_keylog_callback|.
+  void (*keylog_callback)(const SSL *ssl, const char *line) = nullptr;
+
+  // current_time_cb, if not NULL, is the function to use to get the current
+  // time. It sets |*out_clock| to the current time. The |ssl| argument is
+  // always NULL. See |SSL_CTX_set_current_time_cb|.
+  void (*current_time_cb)(const SSL *ssl, struct timeval *out_clock) = nullptr;
+
+  // pool is used for all |CRYPTO_BUFFER|s in case we wish to share certificate
+  // memory.
+  CRYPTO_BUFFER_POOL *pool = nullptr;
+
+  // ticket_aead_method contains function pointers for opening and sealing
+  // session tickets.
+  const SSL_TICKET_AEAD_METHOD *ticket_aead_method = nullptr;
+
+  // legacy_ocsp_callback implements an OCSP-related callback for OpenSSL
+  // compatibility.
+  int (*legacy_ocsp_callback)(SSL *ssl, void *arg) = nullptr;
+  void *legacy_ocsp_callback_arg = nullptr;
+
+  // verify_sigalgs, if not empty, is the set of signature algorithms
+  // accepted from the peer in decreasing order of preference.
+  bssl::Array<uint16_t> verify_sigalgs;
+
+  // retain_only_sha256_of_client_certs is true if we should compute the SHA256
+  // hash of the peer's certificate and then discard it to save memory and
+  // session space. Only effective on the server side.
+  bool retain_only_sha256_of_client_certs:1;
+
+  // quiet_shutdown is true if the connection should not send a close_notify on
+  // shutdown.
+  bool quiet_shutdown:1;
+
+  // ocsp_stapling_enabled is only used by client connections and indicates
+  // whether OCSP stapling will be requested.
+  bool ocsp_stapling_enabled:1;
+
+  // If true, a client will request certificate timestamps.
+  bool signed_cert_timestamps_enabled:1;
+
+  // channel_id_enabled is whether Channel ID is enabled. For a server, means
+  // that we'll accept Channel IDs from clients.  For a client, means that we'll
+  // advertise support.
+  bool channel_id_enabled:1;
+
+  // grease_enabled is whether draft-davidben-tls-grease-01 is enabled.
+  bool grease_enabled:1;
+
+  // allow_unknown_alpn_protos is whether the client allows unsolicited ALPN
+  // protocols from the peer.
+  bool allow_unknown_alpn_protos:1;
+
+  // ed25519_enabled is whether Ed25519 is advertised in the handshake.
+  bool ed25519_enabled:1;
+
+  // rsa_pss_rsae_certs_enabled is whether rsa_pss_rsae_* are supported by the
+  // certificate verifier.
+  bool rsa_pss_rsae_certs_enabled:1;
+
+  // false_start_allowed_without_alpn is whether False Start (if
+  // |SSL_MODE_ENABLE_FALSE_START| is enabled) is allowed without ALPN.
+  bool false_start_allowed_without_alpn:1;
+
+  // handoff indicates that a server should stop after receiving the
+  // ClientHello and pause the handshake in such a way that |SSL_get_error|
+  // returns |SSL_HANDOFF|.
+  bool handoff:1;
+
+  // If enable_early_data is true, early data can be sent and accepted.
+  bool enable_early_data : 1;
+
+ private:
+  ~ssl_ctx_st();
+  friend void SSL_CTX_free(SSL_CTX *);
 };
 
-// The following types back public C-exposed types which must live in the global
-// namespace. We use subclassing so the implementations may be C++ types with
-// methods and destructor without polluting the global namespace.
-struct ssl_ctx_st : public bssl::SSLContext {};
-struct ssl_st : public bssl::SSLConnection {};
+struct ssl_st {
+  explicit ssl_st(SSL_CTX *ctx_arg);
+  ssl_st(const ssl_st &) = delete;
+  ssl_st &operator=(const ssl_st &) = delete;
+  ~ssl_st();
+
+  // method is the method table corresponding to the current protocol (DTLS or
+  // TLS).
+  const bssl::SSL_PROTOCOL_METHOD *method = nullptr;
+
+  // config is a container for handshake configuration.  Accesses to this field
+  // should check for nullptr, since configuration may be shed after the
+  // handshake completes.  (If you have the |SSL_HANDSHAKE| object at hand, use
+  // that instead, and skip the null check.)
+  bssl::UniquePtr<bssl::SSL_CONFIG> config;
+
+  // version is the protocol version.
+  uint16_t version = 0;
+
+  uint16_t max_send_fragment = 0;
+
+  // There are 2 BIO's even though they are normally both the same. This is so
+  // data can be read and written to different handlers
+
+  bssl::UniquePtr<BIO> rbio;  // used by SSL_read
+  bssl::UniquePtr<BIO> wbio;  // used by SSL_write
+
+  // do_handshake runs the handshake. On completion, it returns |ssl_hs_ok|.
+  // Otherwise, it returns a value corresponding to what operation is needed to
+  // progress.
+  bssl::ssl_hs_wait_t (*do_handshake)(bssl::SSL_HANDSHAKE *hs) = nullptr;
+
+  bssl::SSL3_STATE *s3 = nullptr;   // TLS variables
+  bssl::DTLS1_STATE *d1 = nullptr;  // DTLS variables
+
+  // callback that allows applications to peek at protocol messages
+  void (*msg_callback)(int write_p, int version, int content_type,
+                       const void *buf, size_t len, SSL *ssl,
+                       void *arg) = nullptr;
+  void *msg_callback_arg = nullptr;
+
+  // session info
+
+  // initial_timeout_duration_ms is the default DTLS timeout duration in
+  // milliseconds. It's used to initialize the timer any time it's restarted.
+  //
+  // RFC 6347 states that implementations SHOULD use an initial timer value of 1
+  // second.
+  unsigned initial_timeout_duration_ms = 1000;
+
+  // tls13_variant is the variant of TLS 1.3 we are using for this
+  // configuration.
+  tls13_variant_t tls13_variant = tls13_default;
+
+  // session is the configured session to be offered by the client. This session
+  // is immutable.
+  bssl::UniquePtr<SSL_SESSION> session;
+
+  void (*info_callback)(const SSL *ssl, int type, int value) = nullptr;
+
+  bssl::UniquePtr<SSL_CTX> ctx;
+
+  // session_ctx is the |SSL_CTX| used for the session cache and related
+  // settings.
+  bssl::UniquePtr<SSL_CTX> session_ctx;
+
+  // extra application data
+  CRYPTO_EX_DATA ex_data;
+
+  uint32_t options = 0;  // protocol behaviour
+  uint32_t mode = 0;     // API behaviour
+  uint32_t max_cert_list = 0;
+  bssl::UniquePtr<char> hostname;
+
+  // renegotiate_mode controls how peer renegotiation attempts are handled.
+  ssl_renegotiate_mode_t renegotiate_mode = ssl_renegotiate_never;
+
+  // server is true iff the this SSL* is the server half. Note: before the SSL*
+  // is initialized by either SSL_set_accept_state or SSL_set_connect_state,
+  // the side is not determined. In this state, server is always false.
+  bool server : 1;
+
+  // quiet_shutdown is true if the connection should not send a close_notify on
+  // shutdown.
+  bool quiet_shutdown : 1;
+
+  // did_dummy_pq_padding is only valid for a client. In that context, it is
+  // true iff the client observed the server echoing a dummy PQ padding
+  // extension.
+  bool did_dummy_pq_padding:1;
+
+  // If enable_early_data is true, early data can be sent and accepted.
+  bool enable_early_data : 1;
+};
+
+struct ssl_session_st {
+  explicit ssl_session_st(const bssl::SSL_X509_METHOD *method);
+  ssl_session_st(const ssl_session_st &) = delete;
+  ssl_session_st &operator=(const ssl_session_st &) = delete;
+
+  CRYPTO_refcount_t references = 1;
+  uint16_t ssl_version = 0;  // what ssl version session info is being kept in here?
+
+  // group_id is the ID of the ECDH group used to establish this session or zero
+  // if not applicable or unknown.
+  uint16_t group_id = 0;
+
+  // peer_signature_algorithm is the signature algorithm used to authenticate
+  // the peer, or zero if not applicable or unknown.
+  uint16_t peer_signature_algorithm = 0;
+
+  // master_key, in TLS 1.2 and below, is the master secret associated with the
+  // session. In TLS 1.3 and up, it is the resumption secret.
+  int master_key_length = 0;
+  uint8_t master_key[SSL_MAX_MASTER_KEY_LENGTH] = {0};
+
+  // session_id - valid?
+  unsigned session_id_length = 0;
+  uint8_t session_id[SSL_MAX_SSL_SESSION_ID_LENGTH] = {0};
+  // this is used to determine whether the session is being reused in
+  // the appropriate context. It is up to the application to set this,
+  // via SSL_new
+  uint8_t sid_ctx_length = 0;
+  uint8_t sid_ctx[SSL_MAX_SID_CTX_LENGTH] = {0};
+
+  bssl::UniquePtr<char> psk_identity;
+
+  // certs contains the certificate chain from the peer, starting with the leaf
+  // certificate.
+  bssl::UniquePtr<STACK_OF(CRYPTO_BUFFER)> certs;
+
+  const bssl::SSL_X509_METHOD *x509_method = nullptr;
+
+  // x509_peer is the peer's certificate.
+  X509 *x509_peer = nullptr;
+
+  // x509_chain is the certificate chain sent by the peer. NOTE: for historical
+  // reasons, when a client (so the peer is a server), the chain includes
+  // |peer|, but when a server it does not.
+  STACK_OF(X509) *x509_chain = nullptr;
+
+  // x509_chain_without_leaf is a lazily constructed copy of |x509_chain| that
+  // omits the leaf certificate. This exists because OpenSSL, historically,
+  // didn't include the leaf certificate in the chain for a server, but did for
+  // a client. The |x509_chain| always includes it and, if an API call requires
+  // a chain without, it is stored here.
+  STACK_OF(X509) *x509_chain_without_leaf = nullptr;
+
+  // verify_result is the result of certificate verification in the case of
+  // non-fatal certificate errors.
+  long verify_result = X509_V_ERR_INVALID_CALL;
+
+  // timeout is the lifetime of the session in seconds, measured from |time|.
+  // This is renewable up to |auth_timeout|.
+  uint32_t timeout = SSL_DEFAULT_SESSION_TIMEOUT;
+
+  // auth_timeout is the non-renewable lifetime of the session in seconds,
+  // measured from |time|.
+  uint32_t auth_timeout = SSL_DEFAULT_SESSION_TIMEOUT;
+
+  // time is the time the session was issued, measured in seconds from the UNIX
+  // epoch.
+  uint64_t time = 0;
+
+  const SSL_CIPHER *cipher = nullptr;
+
+  CRYPTO_EX_DATA ex_data;  // application specific data
+
+  // These are used to make removal of session-ids more efficient and to
+  // implement a maximum cache size.
+  SSL_SESSION *prev = nullptr, *next = nullptr;
+
+  bssl::Array<uint8_t> ticket;
+
+  bssl::UniquePtr<CRYPTO_BUFFER> signed_cert_timestamp_list;
+
+  // The OCSP response that came with the session.
+  bssl::UniquePtr<CRYPTO_BUFFER> ocsp_response;
+
+  // peer_sha256 contains the SHA-256 hash of the peer's certificate if
+  // |peer_sha256_valid| is true.
+  uint8_t peer_sha256[SHA256_DIGEST_LENGTH] = {0};
+
+  // original_handshake_hash contains the handshake hash (either SHA-1+MD5 or
+  // SHA-2, depending on TLS version) for the original, full handshake that
+  // created a session. This is used by Channel IDs during resumption.
+  uint8_t original_handshake_hash[EVP_MAX_MD_SIZE] = {0};
+  uint8_t original_handshake_hash_len = 0;
+
+  uint32_t ticket_lifetime_hint = 0;  // Session lifetime hint in seconds
+
+  uint32_t ticket_age_add = 0;
+
+  // ticket_max_early_data is the maximum amount of data allowed to be sent as
+  // early data. If zero, 0-RTT is disallowed.
+  uint32_t ticket_max_early_data = 0;
+
+  // early_alpn is the ALPN protocol from the initial handshake. This is only
+  // stored for TLS 1.3 and above in order to enforce ALPN matching for 0-RTT
+  // resumptions.
+  bssl::Array<uint8_t> early_alpn;
+
+  // extended_master_secret is whether the master secret in this session was
+  // generated using EMS and thus isn't vulnerable to the Triple Handshake
+  // attack.
+  bool extended_master_secret:1;
+
+  // peer_sha256_valid is whether |peer_sha256| is valid.
+  bool peer_sha256_valid:1;  // Non-zero if peer_sha256 is valid
+
+  // not_resumable is used to indicate that session resumption is disallowed.
+  bool not_resumable:1;
+
+  // ticket_age_add_valid is whether |ticket_age_add| is valid.
+  bool ticket_age_add_valid:1;
+
+  // is_server is whether this session was created by a server.
+  bool is_server:1;
+
+ private:
+  ~ssl_session_st();
+  friend void SSL_SESSION_free(SSL_SESSION *);
+};
 
 
 #endif  // OPENSSL_HEADER_SSL_INTERNAL_H
diff --git a/src/ssl/s3_both.cc b/src/ssl/s3_both.cc
index ede4ba7..98896a3 100644
--- a/src/ssl/s3_both.cc
+++ b/src/ssl/s3_both.cc
@@ -134,6 +134,8 @@
 
 static bool add_record_to_flight(SSL *ssl, uint8_t type,
                                  Span<const uint8_t> in) {
+  // The caller should have flushed |pending_hs_data| first.
+  assert(!ssl->s3->pending_hs_data);
   // We'll never add a flight while in the process of writing it out.
   assert(ssl->s3->pending_flight_offset == 0);
 
@@ -182,17 +184,51 @@
 }
 
 bool ssl3_add_message(SSL *ssl, Array<uint8_t> msg) {
-  // Add the message to the current flight, splitting into several records if
-  // needed.
+  // Pack handshake data into the minimal number of records. This avoids
+  // unnecessary encryption overhead, notably in TLS 1.3 where we send several
+  // encrypted messages in a row. For now, we do not do this for the null
+  // cipher. The benefit is smaller and there is a risk of breaking buggy
+  // implementations. Additionally, we tie this to draft-28 as a sanity check,
+  // on the off chance middleboxes have fixated on sizes.
+  //
+  // TODO(davidben): See if we can do this uniformly.
   Span<const uint8_t> rest = msg;
-  do {
-    Span<const uint8_t> chunk = rest.subspan(0, ssl->max_send_fragment);
-    rest = rest.subspan(chunk.size());
+  if (ssl->s3->aead_write_ctx->is_null_cipher() ||
+      ssl->version == TLS1_3_DRAFT23_VERSION) {
+    while (!rest.empty()) {
+      Span<const uint8_t> chunk = rest.subspan(0, ssl->max_send_fragment);
+      rest = rest.subspan(chunk.size());
 
-    if (!add_record_to_flight(ssl, SSL3_RT_HANDSHAKE, chunk)) {
-      return false;
+      if (!add_record_to_flight(ssl, SSL3_RT_HANDSHAKE, chunk)) {
+        return false;
+      }
     }
-  } while (!rest.empty());
+  } else {
+    while (!rest.empty()) {
+      // Flush if |pending_hs_data| is full.
+      if (ssl->s3->pending_hs_data &&
+          ssl->s3->pending_hs_data->length >= ssl->max_send_fragment &&
+          !tls_flush_pending_hs_data(ssl)) {
+        return false;
+      }
+
+      size_t pending_len =
+          ssl->s3->pending_hs_data ? ssl->s3->pending_hs_data->length : 0;
+      Span<const uint8_t> chunk =
+          rest.subspan(0, ssl->max_send_fragment - pending_len);
+      assert(!chunk.empty());
+      rest = rest.subspan(chunk.size());
+
+      if (!ssl->s3->pending_hs_data) {
+        ssl->s3->pending_hs_data.reset(BUF_MEM_new());
+      }
+      if (!ssl->s3->pending_hs_data ||
+          !BUF_MEM_append(ssl->s3->pending_hs_data.get(), chunk.data(),
+                          chunk.size())) {
+        return false;
+      }
+    }
+  }
 
   ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_HANDSHAKE, msg);
   // TODO(svaldez): Move this up a layer to fix abstraction for SSLTranscript on
@@ -204,10 +240,23 @@
   return true;
 }
 
+bool tls_flush_pending_hs_data(SSL *ssl) {
+  if (!ssl->s3->pending_hs_data || ssl->s3->pending_hs_data->length == 0) {
+    return true;
+  }
+
+  UniquePtr<BUF_MEM> pending_hs_data = std::move(ssl->s3->pending_hs_data);
+  return add_record_to_flight(
+      ssl, SSL3_RT_HANDSHAKE,
+      MakeConstSpan(reinterpret_cast<const uint8_t *>(pending_hs_data->data),
+                    pending_hs_data->length));
+}
+
 bool ssl3_add_change_cipher_spec(SSL *ssl) {
   static const uint8_t kChangeCipherSpec[1] = {SSL3_MT_CCS};
 
-  if (!add_record_to_flight(ssl, SSL3_RT_CHANGE_CIPHER_SPEC,
+  if (!tls_flush_pending_hs_data(ssl) ||
+      !add_record_to_flight(ssl, SSL3_RT_CHANGE_CIPHER_SPEC,
                             kChangeCipherSpec)) {
     return false;
   }
@@ -219,7 +268,8 @@
 
 bool ssl3_add_alert(SSL *ssl, uint8_t level, uint8_t desc) {
   uint8_t alert[2] = {level, desc};
-  if (!add_record_to_flight(ssl, SSL3_RT_ALERT, alert)) {
+  if (!tls_flush_pending_hs_data(ssl) ||
+      !add_record_to_flight(ssl, SSL3_RT_ALERT, alert)) {
     return false;
   }
 
@@ -229,6 +279,10 @@
 }
 
 int ssl3_flush_flight(SSL *ssl) {
+  if (!tls_flush_pending_hs_data(ssl)) {
+    return -1;
+  }
+
   if (ssl->s3->pending_flight == nullptr) {
     return 1;
   }
@@ -238,8 +292,8 @@
     return -1;
   }
 
-  if (ssl->s3->pending_flight->length > 0xffffffff ||
-      ssl->s3->pending_flight->length > INT_MAX) {
+  static_assert(INT_MAX <= 0xffffffff, "int is larger than 32 bits");
+  if (ssl->s3->pending_flight->length > INT_MAX) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return -1;
   }
@@ -257,7 +311,7 @@
   // Write the pending flight.
   while (ssl->s3->pending_flight_offset < ssl->s3->pending_flight->length) {
     int ret = BIO_write(
-        ssl->wbio,
+        ssl->wbio.get(),
         ssl->s3->pending_flight->data + ssl->s3->pending_flight_offset,
         ssl->s3->pending_flight->length - ssl->s3->pending_flight_offset);
     if (ret <= 0) {
@@ -268,7 +322,7 @@
     ssl->s3->pending_flight_offset += ret;
   }
 
-  if (BIO_flush(ssl->wbio) <= 0) {
+  if (BIO_flush(ssl->wbio.get()) <= 0) {
     ssl->s3->rwstate = SSL_WRITING;
     return -1;
   }
@@ -343,7 +397,7 @@
   OPENSSL_memcpy(random + (SSL3_RANDOM_SIZE - rand_len), CBS_data(&challenge),
                  rand_len);
 
-  // Write out an equivalent SSLv3 ClientHello.
+  // Write out an equivalent TLS ClientHello.
   size_t max_v3_client_hello = SSL3_HM_HEADER_LENGTH + 2 /* version */ +
                                SSL3_RANDOM_SIZE + 1 /* session ID length */ +
                                2 /* cipher list length */ +
diff --git a/src/ssl/s3_lib.cc b/src/ssl/s3_lib.cc
index b1fc5fb..9e4d7d8 100644
--- a/src/ssl/s3_lib.cc
+++ b/src/ssl/s3_lib.cc
@@ -173,7 +173,7 @@
       initial_handshake_complete(false),
       session_reused(false),
       send_connection_binding(false),
-      tlsext_channel_id_valid(false),
+      channel_id_valid(false),
       key_update_pending(false),
       wpend_pending(false),
       early_data_accepted(false),
@@ -215,12 +215,4 @@
   ssl->s3 = NULL;
 }
 
-const SSLCipherPreferenceList *ssl_get_cipher_preferences(const SSL *ssl) {
-  if (ssl->cipher_list != NULL) {
-    return ssl->cipher_list;
-  }
-
-  return ssl->ctx->cipher_list;
-}
-
 }  // namespace bssl
diff --git a/src/ssl/s3_pkt.cc b/src/ssl/s3_pkt.cc
index 5eb68f6..50e709b 100644
--- a/src/ssl/s3_pkt.cc
+++ b/src/ssl/s3_pkt.cc
@@ -234,6 +234,9 @@
     return 0;
   }
 
+  if (!tls_flush_pending_hs_data(ssl)) {
+    return -1;
+  }
   size_t flight_len = 0;
   if (ssl->s3->pending_flight != nullptr) {
     flight_len =
@@ -411,7 +414,7 @@
 
   // If the alert is fatal, flush the BIO now.
   if (ssl->s3->send_alert[0] == SSL3_AL_FATAL) {
-    BIO_flush(ssl->wbio);
+    BIO_flush(ssl->wbio.get());
   }
 
   ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_ALERT, ssl->s3->send_alert);
diff --git a/src/ssl/ssl_aead_ctx.cc b/src/ssl/ssl_aead_ctx.cc
index 363c959..322b1b5 100644
--- a/src/ssl/ssl_aead_ctx.cc
+++ b/src/ssl/ssl_aead_ctx.cc
@@ -42,7 +42,6 @@
       random_variable_nonce_(false),
       xor_fixed_nonce_(false),
       omit_length_in_ad_(false),
-      omit_version_in_ad_(false),
       omit_ad_(false),
       ad_is_header_(false) {
   OPENSSL_memset(fixed_nonce_, 0, sizeof(fixed_nonce_));
@@ -147,7 +146,6 @@
     aead_ctx->variable_nonce_included_in_record_ = true;
     aead_ctx->random_variable_nonce_ = true;
     aead_ctx->omit_length_in_ad_ = true;
-    aead_ctx->omit_version_in_ad_ = (protocol_version == SSL3_VERSION);
   }
 
   return aead_ctx;
@@ -235,10 +233,8 @@
   OPENSSL_memcpy(storage, seqnum, 8);
   size_t len = 8;
   storage[len++] = type;
-  if (!omit_version_in_ad_) {
-    storage[len++] = static_cast<uint8_t>((record_version >> 8));
-    storage[len++] = static_cast<uint8_t>(record_version);
-  }
+  storage[len++] = static_cast<uint8_t>((record_version >> 8));
+  storage[len++] = static_cast<uint8_t>(record_version);
   if (!omit_length_in_ad_) {
     storage[len++] = static_cast<uint8_t>((plaintext_len >> 8));
     storage[len++] = static_cast<uint8_t>(plaintext_len);
diff --git a/src/ssl/ssl_asn1.cc b/src/ssl/ssl_asn1.cc
index 078ad1f..5dfacb2 100644
--- a/src/ssl/ssl_asn1.cc
+++ b/src/ssl/ssl_asn1.cc
@@ -224,8 +224,8 @@
 
   // The peer certificate is only serialized if the SHA-256 isn't
   // serialized instead.
-  if (sk_CRYPTO_BUFFER_num(in->certs) > 0 && !in->peer_sha256_valid) {
-    const CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(in->certs, 0);
+  if (sk_CRYPTO_BUFFER_num(in->certs.get()) > 0 && !in->peer_sha256_valid) {
+    const CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(in->certs.get(), 0);
     if (!CBB_add_asn1(&session, &child, kPeerTag) ||
         !CBB_add_bytes(&child, CRYPTO_BUFFER_data(buffer),
                        CRYPTO_BUFFER_len(buffer))) {
@@ -252,25 +252,26 @@
 
   if (in->psk_identity) {
     if (!CBB_add_asn1(&session, &child, kPSKIdentityTag) ||
-        !CBB_add_asn1_octet_string(&child, (const uint8_t *)in->psk_identity,
-                                   strlen(in->psk_identity))) {
+        !CBB_add_asn1_octet_string(&child,
+                                   (const uint8_t *)in->psk_identity.get(),
+                                   strlen(in->psk_identity.get()))) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       return 0;
     }
   }
 
-  if (in->tlsext_tick_lifetime_hint > 0) {
+  if (in->ticket_lifetime_hint > 0) {
     if (!CBB_add_asn1(&session, &child, kTicketLifetimeHintTag) ||
-        !CBB_add_asn1_uint64(&child, in->tlsext_tick_lifetime_hint)) {
+        !CBB_add_asn1_uint64(&child, in->ticket_lifetime_hint)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       return 0;
     }
   }
 
-  if (in->tlsext_tick && !for_ticket) {
+  if (!in->ticket.empty() && !for_ticket) {
     if (!CBB_add_asn1(&session, &child, kTicketTag) ||
-        !CBB_add_asn1_octet_string(&child, in->tlsext_tick,
-                                   in->tlsext_ticklen)) {
+        !CBB_add_asn1_octet_string(&child, in->ticket.data(),
+                                   in->ticket.size())) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       return 0;
     }
@@ -297,8 +298,8 @@
   if (in->signed_cert_timestamp_list != nullptr) {
     if (!CBB_add_asn1(&session, &child, kSignedCertTimestampListTag) ||
         !CBB_add_asn1_octet_string(
-            &child, CRYPTO_BUFFER_data(in->signed_cert_timestamp_list),
-            CRYPTO_BUFFER_len(in->signed_cert_timestamp_list))) {
+            &child, CRYPTO_BUFFER_data(in->signed_cert_timestamp_list.get()),
+            CRYPTO_BUFFER_len(in->signed_cert_timestamp_list.get()))) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       return 0;
     }
@@ -306,9 +307,9 @@
 
   if (in->ocsp_response != nullptr) {
     if (!CBB_add_asn1(&session, &child, kOCSPResponseTag) ||
-        !CBB_add_asn1_octet_string(&child,
-                                   CRYPTO_BUFFER_data(in->ocsp_response),
-                                   CRYPTO_BUFFER_len(in->ocsp_response))) {
+        !CBB_add_asn1_octet_string(
+            &child, CRYPTO_BUFFER_data(in->ocsp_response.get()),
+            CRYPTO_BUFFER_len(in->ocsp_response.get()))) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       return 0;
     }
@@ -333,13 +334,13 @@
   // serialized instead.
   if (in->certs != NULL &&
       !in->peer_sha256_valid &&
-      sk_CRYPTO_BUFFER_num(in->certs) >= 2) {
+      sk_CRYPTO_BUFFER_num(in->certs.get()) >= 2) {
     if (!CBB_add_asn1(&session, &child, kCertChainTag)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       return 0;
     }
-    for (size_t i = 1; i < sk_CRYPTO_BUFFER_num(in->certs); i++) {
-      const CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(in->certs, i);
+    for (size_t i = 1; i < sk_CRYPTO_BUFFER_num(in->certs.get()); i++) {
+      const CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(in->certs.get(), i);
       if (!CBB_add_bytes(&child, CRYPTO_BUFFER_data(buffer),
                          CRYPTO_BUFFER_len(buffer))) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
@@ -386,10 +387,10 @@
     return 0;
   }
 
-  if (in->early_alpn) {
+  if (!in->early_alpn.empty()) {
     if (!CBB_add_asn1(&session, &child, kEarlyALPNTag) ||
-        !CBB_add_asn1_octet_string(&child, (const uint8_t *)in->early_alpn,
-                                   in->early_alpn_len)) {
+        !CBB_add_asn1_octet_string(&child, in->early_alpn.data(),
+                                   in->early_alpn.size())) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       return 0;
     }
@@ -398,13 +399,11 @@
   return CBB_flush(cbb);
 }
 
-// SSL_SESSION_parse_string gets an optional ASN.1 OCTET STRING
-// explicitly tagged with |tag| from |cbs| and saves it in |*out|. On
-// entry, if |*out| is not NULL, it frees the existing contents. If
-// the element was not found, it sets |*out| to NULL. It returns one
-// on success, whether or not the element was found, and zero on
-// decode error.
-static int SSL_SESSION_parse_string(CBS *cbs, char **out, unsigned tag) {
+// SSL_SESSION_parse_string gets an optional ASN.1 OCTET STRING explicitly
+// tagged with |tag| from |cbs| and saves it in |*out|. If the element was not
+// found, it sets |*out| to NULL. It returns one on success, whether or not the
+// element was found, and zero on decode error.
+static int SSL_SESSION_parse_string(CBS *cbs, UniquePtr<char> *out, unsigned tag) {
   CBS value;
   int present;
   if (!CBS_get_optional_asn1_octet_string(cbs, &value, &present, tag)) {
@@ -416,38 +415,33 @@
       OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
       return 0;
     }
-    if (!CBS_strdup(&value, out)) {
+    char *raw = nullptr;
+    if (!CBS_strdup(&value, &raw)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       return 0;
     }
+    out->reset(raw);
   } else {
-    OPENSSL_free(*out);
-    *out = NULL;
+    out->reset();
   }
   return 1;
 }
 
-// SSL_SESSION_parse_string gets an optional ASN.1 OCTET STRING
-// explicitly tagged with |tag| from |cbs| and stows it in |*out_ptr|
-// and |*out_len|. If |*out_ptr| is not NULL, it frees the existing
-// contents. On entry, if the element was not found, it sets
-// |*out_ptr| to NULL. It returns one on success, whether or not the
-// element was found, and zero on decode error.
-static int SSL_SESSION_parse_octet_string(CBS *cbs, uint8_t **out_ptr,
-                                          size_t *out_len, unsigned tag) {
+// SSL_SESSION_parse_octet_string gets an optional ASN.1 OCTET STRING explicitly
+// tagged with |tag| from |cbs| and stows it in |*out|. It returns one on
+// success, whether or not the element was found, and zero on decode error.
+static bool SSL_SESSION_parse_octet_string(CBS *cbs, Array<uint8_t> *out,
+                                           unsigned tag) {
   CBS value;
   if (!CBS_get_optional_asn1_octet_string(cbs, &value, NULL, tag)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    return 0;
+    return false;
   }
-  if (!CBS_stow(&value, out_ptr, out_len)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return 0;
-  }
-  return 1;
+  return out->CopyFrom(value);
 }
 
-static int SSL_SESSION_parse_crypto_buffer(CBS *cbs, CRYPTO_BUFFER **out,
+static int SSL_SESSION_parse_crypto_buffer(CBS *cbs,
+                                           UniquePtr<CRYPTO_BUFFER> *out,
                                            unsigned tag,
                                            CRYPTO_BUFFER_POOL *pool) {
   if (!CBS_peek_asn1_tag(cbs, tag)) {
@@ -461,8 +455,7 @@
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
     return 0;
   }
-  CRYPTO_BUFFER_free(*out);
-  *out = CRYPTO_BUFFER_new_from_CBS(&value, pool);
+  out->reset(CRYPTO_BUFFER_new_from_CBS(&value, pool));
   if (*out == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     return 0;
@@ -617,10 +610,9 @@
 
   if (!SSL_SESSION_parse_string(&session, &ret->psk_identity,
                                 kPSKIdentityTag) ||
-      !SSL_SESSION_parse_u32(&session, &ret->tlsext_tick_lifetime_hint,
+      !SSL_SESSION_parse_u32(&session, &ret->ticket_lifetime_hint,
                              kTicketLifetimeHintTag, 0) ||
-      !SSL_SESSION_parse_octet_string(&session, &ret->tlsext_tick,
-                                      &ret->tlsext_ticklen, kTicketTag)) {
+      !SSL_SESSION_parse_octet_string(&session, &ret->ticket, kTicketTag)) {
     return nullptr;
   }
 
@@ -680,8 +672,8 @@
     return nullptr;
   }
   if (has_peer || has_cert_chain) {
-    ret->certs = sk_CRYPTO_BUFFER_new_null();
-    if (ret->certs == NULL) {
+    ret->certs.reset(sk_CRYPTO_BUFFER_new_null());
+    if (ret->certs == nullptr) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       return nullptr;
     }
@@ -689,7 +681,7 @@
     if (has_peer) {
       UniquePtr<CRYPTO_BUFFER> buffer(CRYPTO_BUFFER_new_from_CBS(&peer, pool));
       if (!buffer ||
-          !PushToStack(ret->certs, std::move(buffer))) {
+          !PushToStack(ret->certs.get(), std::move(buffer))) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
         return nullptr;
       }
@@ -703,10 +695,9 @@
         return nullptr;
       }
 
-      CRYPTO_BUFFER *buffer = CRYPTO_BUFFER_new_from_CBS(&cert, pool);
-      if (buffer == NULL ||
-          !sk_CRYPTO_BUFFER_push(ret->certs, buffer)) {
-        CRYPTO_BUFFER_free(buffer);
+      UniquePtr<CRYPTO_BUFFER> buffer(CRYPTO_BUFFER_new_from_CBS(&cert, pool));
+      if (buffer == nullptr ||
+          !PushToStack(ret->certs.get(), std::move(buffer))) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
         return nullptr;
       }
@@ -727,7 +718,7 @@
       CBS_len(&age_add) != 0) {
     return nullptr;
   }
-  ret->ticket_age_add_valid = age_add_present;
+  ret->ticket_age_add_valid = age_add_present != 0;
 
   int is_server;
   if (!CBS_get_optional_asn1_bool(&session, &is_server, kIsServerTag,
@@ -747,7 +738,7 @@
       !SSL_SESSION_parse_u32(&session, &ret->auth_timeout, kAuthTimeoutTag,
                              ret->timeout) ||
       !SSL_SESSION_parse_octet_string(&session, &ret->early_alpn,
-                                      &ret->early_alpn_len, kEarlyALPNTag) ||
+                                      kEarlyALPNTag) ||
       CBS_len(&session) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
     return nullptr;
diff --git a/src/ssl/ssl_buffer.cc b/src/ssl/ssl_buffer.cc
index da1de93..72647a4 100644
--- a/src/ssl/ssl_buffer.cc
+++ b/src/ssl/ssl_buffer.cc
@@ -113,7 +113,8 @@
   }
 
   // Read a single packet from |ssl->rbio|. |buf->cap()| must fit in an int.
-  int ret = BIO_read(ssl->rbio, buf->data(), static_cast<int>(buf->cap()));
+  int ret =
+      BIO_read(ssl->rbio.get(), buf->data(), static_cast<int>(buf->cap()));
   if (ret <= 0) {
     ssl->s3->rwstate = SSL_READING;
     return ret;
@@ -134,7 +135,7 @@
   while (buf->size() < len) {
     // The amount of data to read is bounded by |buf->cap|, which must fit in an
     // int.
-    int ret = BIO_read(ssl->rbio, buf->data() + buf->size(),
+    int ret = BIO_read(ssl->rbio.get(), buf->data() + buf->size(),
                        static_cast<int>(len - buf->size()));
     if (ret <= 0) {
       ssl->s3->rwstate = SSL_READING;
@@ -163,7 +164,7 @@
     return -1;
   }
 
-  if (ssl->rbio == NULL) {
+  if (ssl->rbio == nullptr) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BIO_NOT_SET);
     return -1;
   }
@@ -240,7 +241,7 @@
   SSLBuffer *buf = &ssl->s3->write_buffer;
 
   while (!buf->empty()) {
-    int ret = BIO_write(ssl->wbio, buf->data(), buf->size());
+    int ret = BIO_write(ssl->wbio.get(), buf->data(), buf->size());
     if (ret <= 0) {
       ssl->s3->rwstate = SSL_WRITING;
       return ret;
@@ -257,7 +258,7 @@
     return 1;
   }
 
-  int ret = BIO_write(ssl->wbio, buf->data(), buf->size());
+  int ret = BIO_write(ssl->wbio.get(), buf->data(), buf->size());
   if (ret <= 0) {
     ssl->s3->rwstate = SSL_WRITING;
     // If the write failed, drop the write buffer anyway. Datagram transports
@@ -271,7 +272,7 @@
 }
 
 int ssl_write_buffer_flush(SSL *ssl) {
-  if (ssl->wbio == NULL) {
+  if (ssl->wbio == nullptr) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BIO_NOT_SET);
     return -1;
   }
diff --git a/src/ssl/ssl_cert.cc b/src/ssl/ssl_cert.cc
index 20b4514..4842974 100644
--- a/src/ssl/ssl_cert.cc
+++ b/src/ssl/ssl_cert.cc
@@ -136,7 +136,7 @@
 namespace bssl {
 
 CERT::CERT(const SSL_X509_METHOD *x509_method_arg)
-    : x509_method(x509_method_arg), enable_early_data(false) {}
+    : x509_method(x509_method_arg) {}
 
 CERT::~CERT() {
   ssl_cert_clear_certs(this);
@@ -162,11 +162,7 @@
     }
   }
 
-  if (cert->privatekey) {
-    EVP_PKEY_up_ref(cert->privatekey.get());
-    ret->privatekey.reset(cert->privatekey.get());
-  }
-
+  ret->privatekey = UpRef(cert->privatekey);
   ret->key_method = cert->key_method;
 
   if (!ret->sigalgs.CopyFrom(cert->sigalgs)) {
@@ -178,22 +174,12 @@
 
   ret->x509_method->cert_dup(ret.get(), cert);
 
-  if (cert->signed_cert_timestamp_list) {
-    CRYPTO_BUFFER_up_ref(cert->signed_cert_timestamp_list.get());
-    ret->signed_cert_timestamp_list.reset(
-        cert->signed_cert_timestamp_list.get());
-  }
-
-  if (cert->ocsp_response) {
-    CRYPTO_BUFFER_up_ref(cert->ocsp_response.get());
-    ret->ocsp_response.reset(cert->ocsp_response.get());
-  }
+  ret->signed_cert_timestamp_list = UpRef(cert->signed_cert_timestamp_list);
+  ret->ocsp_response = UpRef(cert->ocsp_response);
 
   ret->sid_ctx_length = cert->sid_ctx_length;
   OPENSSL_memcpy(ret->sid_ctx, cert->sid_ctx, sizeof(ret->sid_ctx));
 
-  ret->enable_early_data = cert->enable_early_data;
-
   return ret;
 }
 
@@ -291,16 +277,12 @@
   }
 
   for (size_t i = 0; i < num_certs; i++) {
-    if (!sk_CRYPTO_BUFFER_push(certs_sk.get(), certs[i])) {
+    if (!PushToStack(certs_sk.get(), UpRef(certs[i]))) {
       return 0;
     }
-    CRYPTO_BUFFER_up_ref(certs[i]);
   }
 
-  if (privkey != nullptr) {
-    EVP_PKEY_up_ref(privkey);
-  }
-  cert->privatekey.reset(privkey);
+  cert->privatekey = UpRef(privkey);
   cert->key_method = privkey_method;
 
   cert->chain = std::move(certs_sk);
@@ -342,10 +324,10 @@
   return 1;
 }
 
-int ssl_has_certificate(const SSL *ssl) {
-  return ssl->cert->chain != nullptr &&
-         sk_CRYPTO_BUFFER_value(ssl->cert->chain.get(), 0) != nullptr &&
-         ssl_has_private_key(ssl);
+int ssl_has_certificate(const SSL_CONFIG *cfg) {
+  return cfg->cert->chain != nullptr &&
+         sk_CRYPTO_BUFFER_value(cfg->cert->chain.get(), 0) != nullptr &&
+         ssl_has_private_key(cfg);
 }
 
 bool ssl_parse_cert_chain(uint8_t *out_alert,
@@ -412,8 +394,8 @@
   return true;
 }
 
-int ssl_add_cert_chain(SSL *ssl, CBB *cbb) {
-  if (!ssl_has_certificate(ssl)) {
+int ssl_add_cert_chain(SSL_HANDSHAKE *hs, CBB *cbb) {
+  if (!ssl_has_certificate(hs->config)) {
     return CBB_add_u24(cbb, 0);
   }
 
@@ -423,7 +405,7 @@
     return 0;
   }
 
-  STACK_OF(CRYPTO_BUFFER) *chain = ssl->cert->chain.get();
+  STACK_OF(CRYPTO_BUFFER) *chain = hs->config->cert->chain.get();
   for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(chain); i++) {
     CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(chain, i);
     CBB child;
@@ -665,7 +647,7 @@
   }
 
   if (!ssl->ctx->x509_method->check_client_CA_list(ret.get())) {
-    *out_alert = SSL_AD_INTERNAL_ERROR;
+    *out_alert = SSL_AD_DECODE_ERROR;
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     return nullptr;
   }
@@ -673,26 +655,26 @@
   return ret;
 }
 
-bool ssl_has_client_CAs(SSL *ssl) {
-  STACK_OF(CRYPTO_BUFFER) *names = ssl->client_CA;
-  if (names == NULL) {
-    names = ssl->ctx->client_CA;
+bool ssl_has_client_CAs(const SSL_CONFIG *cfg) {
+  const STACK_OF(CRYPTO_BUFFER) *names = cfg->client_CA.get();
+  if (names == nullptr) {
+    names = cfg->ssl->ctx->client_CA.get();
   }
-  if (names == NULL) {
+  if (names == nullptr) {
     return false;
   }
   return sk_CRYPTO_BUFFER_num(names) > 0;
 }
 
-int ssl_add_client_CA_list(SSL *ssl, CBB *cbb) {
+int ssl_add_client_CA_list(SSL_HANDSHAKE *hs, CBB *cbb) {
   CBB child, name_cbb;
   if (!CBB_add_u16_length_prefixed(cbb, &child)) {
     return 0;
   }
 
-  STACK_OF(CRYPTO_BUFFER) *names = ssl->client_CA;
+  const STACK_OF(CRYPTO_BUFFER) *names = hs->config->client_CA.get();
   if (names == NULL) {
-    names = ssl->ctx->client_CA;
+    names = hs->ssl->ctx->client_CA.get();
   }
   if (names == NULL) {
     return CBB_flush(cbb);
@@ -711,8 +693,7 @@
 
 int ssl_check_leaf_certificate(SSL_HANDSHAKE *hs, EVP_PKEY *pkey,
                                const CRYPTO_BUFFER *leaf) {
-  SSL *const ssl = hs->ssl;
-  assert(ssl_protocol_version(ssl) < TLS1_3_VERSION);
+  assert(ssl_protocol_version(hs->ssl) < TLS1_3_VERSION);
 
   // Check the certificate's type matches the cipher.
   if (!(hs->new_cipher->algorithm_auth & ssl_cipher_auth_mask_for_key(pkey))) {
@@ -740,7 +721,7 @@
     uint16_t group_id;
     if (!ssl_nid_to_group_id(
             &group_id, EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key))) ||
-        !tls1_check_group_id(ssl, group_id) ||
+        !tls1_check_group_id(hs, group_id) ||
         EC_KEY_get_conv_form(ec_key) != POINT_CONVERSION_UNCOMPRESSED) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECC_CERT);
       return 0;
@@ -752,18 +733,18 @@
 
 int ssl_on_certificate_selected(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  if (!ssl_has_certificate(ssl)) {
+  if (!ssl_has_certificate(hs->config)) {
     // Nothing to do.
     return 1;
   }
 
-  if (!ssl->ctx->x509_method->ssl_auto_chain_if_needed(ssl)) {
+  if (!ssl->ctx->x509_method->ssl_auto_chain_if_needed(hs)) {
     return 0;
   }
 
   CBS leaf;
-  CRYPTO_BUFFER_init_CBS(sk_CRYPTO_BUFFER_value(ssl->cert->chain.get(), 0),
-                         &leaf);
+  CRYPTO_BUFFER_init_CBS(
+      sk_CRYPTO_BUFFER_value(hs->config->cert->chain.get(), 0), &leaf);
 
   hs->local_pubkey = ssl_cert_parse_pubkey(&leaf);
   return hs->local_pubkey != NULL;
@@ -776,14 +757,17 @@
 int SSL_set_chain_and_key(SSL *ssl, CRYPTO_BUFFER *const *certs,
                           size_t num_certs, EVP_PKEY *privkey,
                           const SSL_PRIVATE_KEY_METHOD *privkey_method) {
-  return cert_set_chain_and_key(ssl->cert, certs, num_certs, privkey,
-                                privkey_method);
+  if (!ssl->config) {
+    return 0;
+  }
+  return cert_set_chain_and_key(ssl->config->cert.get(), certs, num_certs,
+                                privkey, privkey_method);
 }
 
 int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs,
                               size_t num_certs, EVP_PKEY *privkey,
                               const SSL_PRIVATE_KEY_METHOD *privkey_method) {
-  return cert_set_chain_and_key(ctx->cert, certs, num_certs, privkey,
+  return cert_set_chain_and_key(ctx->cert.get(), certs, num_certs, privkey,
                                 privkey_method);
 }
 
@@ -794,37 +778,40 @@
     return 0;
   }
 
-  return ssl_set_cert(ctx->cert, std::move(buffer));
+  return ssl_set_cert(ctx->cert.get(), std::move(buffer));
 }
 
 int SSL_use_certificate_ASN1(SSL *ssl, const uint8_t *der, size_t der_len) {
   UniquePtr<CRYPTO_BUFFER> buffer(CRYPTO_BUFFER_new(der, der_len, NULL));
-  if (!buffer) {
+  if (!buffer || !ssl->config) {
     return 0;
   }
 
-  return ssl_set_cert(ssl->cert, std::move(buffer));
+  return ssl_set_cert(ssl->config->cert.get(), std::move(buffer));
 }
 
 void SSL_CTX_set_cert_cb(SSL_CTX *ctx, int (*cb)(SSL *ssl, void *arg),
                          void *arg) {
-  ssl_cert_set_cert_cb(ctx->cert, cb, arg);
+  ssl_cert_set_cert_cb(ctx->cert.get(), cb, arg);
 }
 
 void SSL_set_cert_cb(SSL *ssl, int (*cb)(SSL *ssl, void *arg), void *arg) {
-  ssl_cert_set_cert_cb(ssl->cert, cb, arg);
+  if (!ssl->config) {
+    return;
+  }
+  ssl_cert_set_cert_cb(ssl->config->cert.get(), cb, arg);
 }
 
-STACK_OF(CRYPTO_BUFFER) *SSL_get0_peer_certificates(const SSL *ssl) {
+const STACK_OF(CRYPTO_BUFFER) *SSL_get0_peer_certificates(const SSL *ssl) {
   SSL_SESSION *session = SSL_get_session(ssl);
   if (session == NULL) {
     return NULL;
   }
 
-  return session->certs;
+  return session->certs.get();
 }
 
-STACK_OF(CRYPTO_BUFFER) *SSL_get0_server_requested_CAs(const SSL *ssl) {
+const STACK_OF(CRYPTO_BUFFER) *SSL_get0_server_requested_CAs(const SSL *ssl) {
   if (ssl->s3->hs == NULL) {
     return NULL;
   }
@@ -847,12 +834,16 @@
 
 int SSL_CTX_set_signed_cert_timestamp_list(SSL_CTX *ctx, const uint8_t *list,
                                            size_t list_len) {
-  return set_signed_cert_timestamp_list(ctx->cert, list, list_len);
+  return set_signed_cert_timestamp_list(ctx->cert.get(), list, list_len);
 }
 
 int SSL_set_signed_cert_timestamp_list(SSL *ssl, const uint8_t *list,
                                        size_t list_len) {
-  return set_signed_cert_timestamp_list(ssl->cert, list, list_len);
+  if (!ssl->config) {
+    return 0;
+  }
+  return set_signed_cert_timestamp_list(ssl->config->cert.get(), list,
+                                        list_len);
 }
 
 int SSL_CTX_set_ocsp_response(SSL_CTX *ctx, const uint8_t *response,
@@ -864,19 +855,23 @@
 
 int SSL_set_ocsp_response(SSL *ssl, const uint8_t *response,
                           size_t response_len) {
-  ssl->cert->ocsp_response.reset(
+  if (!ssl->config) {
+    return 0;
+  }
+  ssl->config->cert->ocsp_response.reset(
       CRYPTO_BUFFER_new(response, response_len, nullptr));
-  return ssl->cert->ocsp_response != nullptr;
+  return ssl->config->cert->ocsp_response != nullptr;
 }
 
 void SSL_CTX_set0_client_CAs(SSL_CTX *ctx, STACK_OF(CRYPTO_BUFFER) *name_list) {
   ctx->x509_method->ssl_ctx_flush_cached_client_CA(ctx);
-  sk_CRYPTO_BUFFER_pop_free(ctx->client_CA, CRYPTO_BUFFER_free);
-  ctx->client_CA = name_list;
+  ctx->client_CA.reset(name_list);
 }
 
 void SSL_set0_client_CAs(SSL *ssl, STACK_OF(CRYPTO_BUFFER) *name_list) {
-  ssl->ctx->x509_method->ssl_flush_cached_client_CA(ssl);
-  sk_CRYPTO_BUFFER_pop_free(ssl->client_CA, CRYPTO_BUFFER_free);
-  ssl->client_CA = name_list;
+  if (!ssl->config) {
+    return;
+  }
+  ssl->ctx->x509_method->ssl_flush_cached_client_CA(ssl->config.get());
+  ssl->config->client_CA.reset(name_list);
 }
diff --git a/src/ssl/ssl_cipher.cc b/src/ssl/ssl_cipher.cc
index f02fa8a..5899500 100644
--- a/src/ssl/ssl_cipher.cc
+++ b/src/ssl/ssl_cipher.cc
@@ -157,7 +157,7 @@
 namespace bssl {
 
 // kCiphers is an array of all supported ciphers, sorted by id.
-static const SSL_CIPHER kCiphers[] = {
+static constexpr SSL_CIPHER kCiphers[] = {
     // The RSA ciphers
     // Cipher 02
     {
@@ -210,33 +210,6 @@
      SSL_HANDSHAKE_MAC_DEFAULT,
     },
 
-
-    // TLS v1.2 ciphersuites
-
-    // Cipher 3C
-    {
-     TLS1_TXT_RSA_WITH_AES_128_SHA256,
-     "TLS_RSA_WITH_AES_128_CBC_SHA256",
-     TLS1_CK_RSA_WITH_AES_128_SHA256,
-     SSL_kRSA,
-     SSL_aRSA,
-     SSL_AES128,
-     SSL_SHA256,
-     SSL_HANDSHAKE_MAC_SHA256,
-    },
-
-    // Cipher 3D
-    {
-     TLS1_TXT_RSA_WITH_AES_256_SHA256,
-     "TLS_RSA_WITH_AES_256_CBC_SHA256",
-     TLS1_CK_RSA_WITH_AES_256_SHA256,
-     SSL_kRSA,
-     SSL_aRSA,
-     SSL_AES256,
-     SSL_SHA256,
-     SSL_HANDSHAKE_MAC_SHA256,
-    },
-
     // PSK cipher suites.
 
     // Cipher 8C
@@ -375,58 +348,6 @@
      SSL_HANDSHAKE_MAC_DEFAULT,
     },
 
-
-    // HMAC based TLS v1.2 ciphersuites from RFC5289
-
-    // Cipher C023
-    {
-     TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_SHA256,
-     "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
-     TLS1_CK_ECDHE_ECDSA_WITH_AES_128_SHA256,
-     SSL_kECDHE,
-     SSL_aECDSA,
-     SSL_AES128,
-     SSL_SHA256,
-     SSL_HANDSHAKE_MAC_SHA256,
-    },
-
-    // Cipher C024
-    {
-     TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_SHA384,
-     "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
-     TLS1_CK_ECDHE_ECDSA_WITH_AES_256_SHA384,
-     SSL_kECDHE,
-     SSL_aECDSA,
-     SSL_AES256,
-     SSL_SHA384,
-     SSL_HANDSHAKE_MAC_SHA384,
-    },
-
-    // Cipher C027
-    {
-     TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256,
-     "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
-     TLS1_CK_ECDHE_RSA_WITH_AES_128_SHA256,
-     SSL_kECDHE,
-     SSL_aRSA,
-     SSL_AES128,
-     SSL_SHA256,
-     SSL_HANDSHAKE_MAC_SHA256,
-    },
-
-    // Cipher C028
-    {
-     TLS1_TXT_ECDHE_RSA_WITH_AES_256_SHA384,
-     "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
-     TLS1_CK_ECDHE_RSA_WITH_AES_256_SHA384,
-     SSL_kECDHE,
-     SSL_aRSA,
-     SSL_AES256,
-     SSL_SHA384,
-     SSL_HANDSHAKE_MAC_SHA384,
-    },
-
-
     // GCM based TLS v1.2 ciphersuites from RFC5289
 
     // Cipher C02B
@@ -616,8 +537,6 @@
     // MAC aliases
     {"SHA1", ~0u, ~0u, ~0u, SSL_SHA1, 0},
     {"SHA", ~0u, ~0u, ~0u, SSL_SHA1, 0},
-    {"SHA256", ~0u, ~0u, ~0u, SSL_SHA256, 0},
-    {"SHA384", ~0u, ~0u, ~0u, SSL_SHA384, 0},
 
     // Legacy protocol minimum version aliases. "TLSv1" is intentionally the
     // same as "SSLv3".
@@ -628,23 +547,15 @@
     // Legacy strength classes.
     {"HIGH", ~0u, ~0u, ~0u, ~0u, 0},
     {"FIPS", ~0u, ~0u, ~0u, ~0u, 0},
+
+    // Temporary no-op aliases corresponding to removed SHA-2 legacy CBC
+    // ciphers. These should be removed after 2018-05-14.
+    {"SHA256", 0, 0, 0, 0, 0},
+    {"SHA384", 0, 0, 0, 0, 0},
 };
 
 static const size_t kCipherAliasesLen = OPENSSL_ARRAY_SIZE(kCipherAliases);
 
-static int ssl_cipher_id_cmp(const void *in_a, const void *in_b) {
-  const SSL_CIPHER *a = reinterpret_cast<const SSL_CIPHER *>(in_a);
-  const SSL_CIPHER *b = reinterpret_cast<const SSL_CIPHER *>(in_b);
-
-  if (a->id > b->id) {
-    return 1;
-  } else if (a->id < b->id) {
-    return -1;
-  } else {
-    return 0;
-  }
-}
-
 bool ssl_cipher_get_evp_aead(const EVP_AEAD **out_aead,
                              size_t *out_mac_secret_len,
                              size_t *out_fixed_iv_len, const SSL_CIPHER *cipher,
@@ -654,15 +565,26 @@
   *out_fixed_iv_len = 0;
 
   const int is_tls12 = version == TLS1_2_VERSION && !is_dtls;
+  const int is_tls13 = version == TLS1_3_VERSION && !is_dtls;
 
   if (cipher->algorithm_mac == SSL_AEAD) {
     if (cipher->algorithm_enc == SSL_AES128GCM) {
-      *out_aead =
-          is_tls12 ? EVP_aead_aes_128_gcm_tls12() : EVP_aead_aes_128_gcm();
+      if (is_tls12) {
+        *out_aead = EVP_aead_aes_128_gcm_tls12();
+      } else if (is_tls13) {
+        *out_aead = EVP_aead_aes_128_gcm_tls13();
+      } else {
+        *out_aead = EVP_aead_aes_128_gcm();
+      }
       *out_fixed_iv_len = 4;
     } else if (cipher->algorithm_enc == SSL_AES256GCM) {
-      *out_aead =
-          is_tls12 ? EVP_aead_aes_256_gcm_tls12() : EVP_aead_aes_256_gcm();
+      if (is_tls12) {
+        *out_aead = EVP_aead_aes_256_gcm_tls12();
+      } else if (is_tls13) {
+        *out_aead = EVP_aead_aes_256_gcm_tls13();
+      } else {
+        *out_aead = EVP_aead_aes_256_gcm();
+      }
       *out_fixed_iv_len = 4;
     } else if (cipher->algorithm_enc == SSL_CHACHA20POLY1305) {
       *out_aead = EVP_aead_chacha20_poly1305();
@@ -678,36 +600,23 @@
     }
   } else if (cipher->algorithm_mac == SSL_SHA1) {
     if (cipher->algorithm_enc == SSL_eNULL) {
-      if (version == SSL3_VERSION) {
-        *out_aead = EVP_aead_null_sha1_ssl3();
-      } else {
-        *out_aead = EVP_aead_null_sha1_tls();
-      }
+      *out_aead = EVP_aead_null_sha1_tls();
     } else if (cipher->algorithm_enc == SSL_3DES) {
-      if (version == SSL3_VERSION) {
-        *out_aead = EVP_aead_des_ede3_cbc_sha1_ssl3();
-        *out_fixed_iv_len = 8;
-      } else if (version == TLS1_VERSION) {
+      if (version == TLS1_VERSION) {
         *out_aead = EVP_aead_des_ede3_cbc_sha1_tls_implicit_iv();
         *out_fixed_iv_len = 8;
       } else {
         *out_aead = EVP_aead_des_ede3_cbc_sha1_tls();
       }
     } else if (cipher->algorithm_enc == SSL_AES128) {
-      if (version == SSL3_VERSION) {
-        *out_aead = EVP_aead_aes_128_cbc_sha1_ssl3();
-        *out_fixed_iv_len = 16;
-      } else if (version == TLS1_VERSION) {
+      if (version == TLS1_VERSION) {
         *out_aead = EVP_aead_aes_128_cbc_sha1_tls_implicit_iv();
         *out_fixed_iv_len = 16;
       } else {
         *out_aead = EVP_aead_aes_128_cbc_sha1_tls();
       }
     } else if (cipher->algorithm_enc == SSL_AES256) {
-      if (version == SSL3_VERSION) {
-        *out_aead = EVP_aead_aes_256_cbc_sha1_ssl3();
-        *out_fixed_iv_len = 16;
-      } else if (version == TLS1_VERSION) {
+      if (version == TLS1_VERSION) {
         *out_aead = EVP_aead_aes_256_cbc_sha1_tls_implicit_iv();
         *out_fixed_iv_len = 16;
       } else {
@@ -718,23 +627,6 @@
     }
 
     *out_mac_secret_len = SHA_DIGEST_LENGTH;
-  } else if (cipher->algorithm_mac == SSL_SHA256) {
-    if (cipher->algorithm_enc == SSL_AES128) {
-      *out_aead = EVP_aead_aes_128_cbc_sha256_tls();
-    } else if (cipher->algorithm_enc == SSL_AES256) {
-      *out_aead = EVP_aead_aes_256_cbc_sha256_tls();
-    } else {
-      return false;
-    }
-
-    *out_mac_secret_len = SHA256_DIGEST_LENGTH;
-  } else if (cipher->algorithm_mac == SSL_SHA384) {
-      if (cipher->algorithm_enc != SSL_AES256) {
-        return false;
-      }
-
-      *out_aead = EVP_aead_aes_256_cbc_sha384_tls();
-      *out_mac_secret_len = SHA384_DIGEST_LENGTH;
   } else {
     return false;
   }
@@ -1232,7 +1124,7 @@
   return true;
 }
 
-bool ssl_create_cipher_list(SSLCipherPreferenceList **out_cipher_list,
+bool ssl_create_cipher_list(UniquePtr<SSLCipherPreferenceList> *out_cipher_list,
                             const char *rule_str, bool strict) {
   // Return with error if nothing to do.
   if (rule_str == NULL || out_cipher_list == NULL) {
@@ -1350,10 +1242,7 @@
     return false;
   }
 
-  if (*out_cipher_list) {
-    Delete(*out_cipher_list);
-  }
-  *out_cipher_list = pref_list.release();
+  *out_cipher_list = std::move(pref_list);
 
   // Configuring an empty cipher list is an error but still updates the
   // output.
@@ -1367,7 +1256,8 @@
 
 uint16_t ssl_cipher_get_value(const SSL_CIPHER *cipher) {
   uint32_t id = cipher->id;
-  // All ciphers are SSLv3.
+  // All OpenSSL cipher IDs are prefaced with 0x03. Historically this referred
+  // to SSLv2 vs SSLv3.
   assert((id & 0xff000000) == 0x03000000);
   return id & 0xffff;
 }
@@ -1420,6 +1310,36 @@
 
 using namespace bssl;
 
+static constexpr int ssl_cipher_id_cmp_inner(const SSL_CIPHER *a,
+                                             const SSL_CIPHER *b) {
+  // C++11's constexpr functions must have a body consisting of just a
+  // return-statement.
+  return (a->id > b->id) ? 1 : ((a->id < b->id) ? -1 : 0);
+}
+
+static int ssl_cipher_id_cmp(const void *in_a, const void *in_b) {
+  return ssl_cipher_id_cmp_inner(reinterpret_cast<const SSL_CIPHER *>(in_a),
+                                 reinterpret_cast<const SSL_CIPHER *>(in_b));
+}
+
+template <typename T, size_t N>
+static constexpr size_t countof(T const (&)[N]) {
+  return N;
+}
+
+template <typename T, size_t I>
+static constexpr int check_order(const T (&arr)[I], size_t N) {
+  // C++11's constexpr functions must have a body consisting of just a
+  // return-statement.
+  return N > 1 ? ((ssl_cipher_id_cmp_inner(&arr[N - 2], &arr[N - 1]) < 0)
+                      ? check_order(arr, N - 1)
+                      : 0)
+               : 1;
+}
+
+static_assert(check_order(kCiphers, countof(kCiphers)) == 1,
+              "Ciphers are not sorted, bsearch won't work");
+
 const SSL_CIPHER *SSL_get_cipher_by_value(uint16_t value) {
   SSL_CIPHER c;
 
@@ -1461,10 +1381,6 @@
       return NID_undef;
     case SSL_SHA1:
       return NID_sha1;
-    case SSL_SHA256:
-      return NID_sha256;
-    case SSL_SHA384:
-      return NID_sha384;
   }
   assert(0);
   return NID_undef;
@@ -1731,14 +1647,6 @@
       mac = "SHA1";
       break;
 
-    case SSL_SHA256:
-      mac = "SHA256";
-      break;
-
-    case SSL_SHA384:
-      mac = "SHA384";
-      break;
-
     case SSL_AEAD:
       mac = "AEAD";
       break;
diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc
index 78a1860..36c26cd 100644
--- a/src/ssl/ssl_lib.cc
+++ b/src/ssl/ssl_lib.cc
@@ -219,11 +219,11 @@
   return true;
 }
 
-int ssl_can_write(const SSL *ssl) {
+bool ssl_can_write(const SSL *ssl) {
   return !SSL_in_init(ssl) || ssl->s3->hs->can_early_write;
 }
 
-int ssl_can_read(const SSL *ssl) {
+bool ssl_can_read(const SSL *ssl) {
   return !SSL_in_init(ssl) || ssl->s3->hs->can_early_read;
 }
 
@@ -274,7 +274,7 @@
 
 void ssl_update_cache(SSL_HANDSHAKE *hs, int mode) {
   SSL *const ssl = hs->ssl;
-  SSL_CTX *ctx = ssl->session_ctx;
+  SSL_CTX *ctx = ssl->session_ctx.get();
   // Never cache sessions with empty session IDs.
   if (ssl->s3->established_session->session_id_length == 0 ||
       ssl->s3->established_session->not_resumable ||
@@ -289,16 +289,16 @@
   // A client may see new sessions on abbreviated handshakes if the server
   // decides to renew the ticket. Once the handshake is completed, it should be
   // inserted into the cache.
-  if (ssl->s3->established_session.get() != ssl->session ||
+  if (ssl->s3->established_session.get() != ssl->session.get() ||
       (!ssl->server && hs->ticket_expected)) {
     if (use_internal_cache) {
       SSL_CTX_add_session(ctx, ssl->s3->established_session.get());
     }
     if (ctx->new_session_cb != NULL) {
-      SSL_SESSION_up_ref(ssl->s3->established_session.get());
-      if (!ctx->new_session_cb(ssl, ssl->s3->established_session.get())) {
+      UniquePtr<SSL_SESSION> ref = UpRef(ssl->s3->established_session);
+      if (ctx->new_session_cb(ssl, ref.get())) {
         // |new_session_cb|'s return value signals whether it took ownership.
-        SSL_SESSION_free(ssl->s3->established_session.get());
+        ref.release();
       }
     }
   }
@@ -406,7 +406,7 @@
 void ssl_get_current_time(const SSL *ssl, struct OPENSSL_timeval *out_clock) {
   // TODO(martinkr): Change callers to |ssl_ctx_get_current_time| and drop the
   // |ssl| arg from |current_time_cb| if possible.
-  ssl_ctx_get_current_time(ssl->ctx, out_clock);
+  ssl_ctx_get_current_time(ssl->ctx.get(), out_clock);
 }
 
 void ssl_ctx_get_current_time(const SSL_CTX *ctx,
@@ -459,6 +459,53 @@
   ctx->handoff = on;
 }
 
+static bool ssl_can_renegotiate(const SSL *ssl) {
+  if (ssl->server || SSL_is_dtls(ssl)) {
+    return false;
+  }
+
+  if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
+    return false;
+  }
+
+  // The config has already been shed.
+  if (!ssl->config) {
+    return false;
+  }
+
+  switch (ssl->renegotiate_mode) {
+    case ssl_renegotiate_ignore:
+    case ssl_renegotiate_never:
+      return false;
+
+    case ssl_renegotiate_freely:
+      return true;
+    case ssl_renegotiate_once:
+      return ssl->s3->total_renegotiations == 0;
+  }
+
+  assert(0);
+  return false;
+}
+
+static void ssl_maybe_shed_handshake_config(SSL *ssl) {
+  if (ssl->s3->hs != nullptr ||
+      ssl->config == nullptr ||
+      !ssl->config->shed_handshake_config ||
+      ssl_can_renegotiate(ssl)) {
+    return;
+  }
+
+  ssl->config.reset();
+}
+
+void SSL_set_handoff_mode(SSL *ssl, bool on) {
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->handoff = on;
+}
+
 }  // namespace bssl
 
 using namespace bssl;
@@ -474,34 +521,11 @@
 }
 
 static uint32_t ssl_session_hash(const SSL_SESSION *sess) {
-  const uint8_t *session_id = sess->session_id;
-
-  uint8_t tmp_storage[sizeof(uint32_t)];
-  if (sess->session_id_length < sizeof(tmp_storage)) {
-    OPENSSL_memset(tmp_storage, 0, sizeof(tmp_storage));
-    OPENSSL_memcpy(tmp_storage, sess->session_id, sess->session_id_length);
-    session_id = tmp_storage;
-  }
-
-  uint32_t hash =
-      ((uint32_t)session_id[0]) |
-      ((uint32_t)session_id[1] << 8) |
-      ((uint32_t)session_id[2] << 16) |
-      ((uint32_t)session_id[3] << 24);
-
-  return hash;
+  return ssl_hash_session_id(
+      MakeConstSpan(sess->session_id, sess->session_id_length));
 }
 
-// NB: If this function (or indeed the hash function which uses a sort of
-// coarser function than this one) is changed, ensure
-// SSL_CTX_has_matching_session_id() is checked accordingly. It relies on being
-// able to construct an SSL_SESSION that will collide with any existing session
-// with a matching session ID.
 static int ssl_session_cmp(const SSL_SESSION *a, const SSL_SESSION *b) {
-  if (a->ssl_version != b->ssl_version) {
-    return 1;
-  }
-
   if (a->session_id_length != b->session_id_length) {
     return 1;
   }
@@ -509,84 +533,69 @@
   return OPENSSL_memcmp(a->session_id, b->session_id, a->session_id_length);
 }
 
-SSL_CTX *SSL_CTX_new(const SSL_METHOD *method) {
-  SSL_CTX *ret = NULL;
+ssl_ctx_st::ssl_ctx_st(const SSL_METHOD *ssl_method)
+    : method(ssl_method->method),
+      x509_method(ssl_method->x509_method),
+      retain_only_sha256_of_client_certs(false),
+      quiet_shutdown(false),
+      ocsp_stapling_enabled(false),
+      signed_cert_timestamps_enabled(false),
+      channel_id_enabled(false),
+      grease_enabled(false),
+      allow_unknown_alpn_protos(false),
+      ed25519_enabled(false),
+      rsa_pss_rsae_certs_enabled(true),
+      false_start_allowed_without_alpn(false),
+      handoff(false),
+      enable_early_data(false) {
+  CRYPTO_MUTEX_init(&lock);
+  CRYPTO_new_ex_data(&ex_data);
+}
 
+ssl_ctx_st::~ssl_ctx_st() {
+  // Free the internal session cache. Note that this calls the caller-supplied
+  // remove callback, so we must do it before clearing ex_data. (See ticket
+  // [openssl.org #212].)
+  SSL_CTX_flush_sessions(this, 0);
+
+  CRYPTO_free_ex_data(&g_ex_data_class_ssl_ctx, this, &ex_data);
+
+  CRYPTO_MUTEX_cleanup(&lock);
+  lh_SSL_SESSION_free(sessions);
+  x509_method->ssl_ctx_free(this);
+}
+
+SSL_CTX *SSL_CTX_new(const SSL_METHOD *method) {
   if (method == NULL) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NULL_SSL_METHOD_PASSED);
-    return NULL;
+    return nullptr;
   }
 
-  ret = (SSL_CTX *)OPENSSL_malloc(sizeof(SSL_CTX));
-  if (ret == NULL) {
-    goto err;
+  UniquePtr<SSL_CTX> ret = MakeUnique<SSL_CTX>(method);
+  if (!ret) {
+    return nullptr;
   }
 
-  OPENSSL_memset(ret, 0, sizeof(SSL_CTX));
-
-  ret->method = method->method;
-  ret->x509_method = method->x509_method;
-
-  CRYPTO_MUTEX_init(&ret->lock);
-
-  ret->session_cache_mode = SSL_SESS_CACHE_SERVER;
-  ret->session_cache_size = SSL_SESSION_CACHE_MAX_SIZE_DEFAULT;
-
-  ret->session_timeout = SSL_DEFAULT_SESSION_TIMEOUT;
-  ret->session_psk_dhe_timeout = SSL_DEFAULT_SESSION_PSK_DHE_TIMEOUT;
-
-  ret->references = 1;
-
-  ret->max_cert_list = SSL_MAX_CERT_LIST_DEFAULT;
-  ret->verify_mode = SSL_VERIFY_NONE;
-  ret->cert = New<CERT>(method->x509_method);
-  if (ret->cert == NULL) {
-    goto err;
-  }
-
+  ret->cert = MakeUnique<CERT>(method->x509_method);
   ret->sessions = lh_SSL_SESSION_new(ssl_session_hash, ssl_session_cmp);
-  if (ret->sessions == NULL) {
-    goto err;
+  ret->client_CA.reset(sk_CRYPTO_BUFFER_new_null());
+  if (ret->cert == nullptr ||
+      ret->sessions == nullptr ||
+      ret->client_CA == nullptr ||
+      !ret->x509_method->ssl_ctx_new(ret.get())) {
+    return nullptr;
   }
 
-  if (!ret->x509_method->ssl_ctx_new(ret)) {
-    goto err;
-  }
-
-  if (!SSL_CTX_set_strict_cipher_list(ret, SSL_DEFAULT_CIPHER_LIST)) {
-    goto err2;
-  }
-
-  ret->client_CA = sk_CRYPTO_BUFFER_new_null();
-  if (ret->client_CA == NULL) {
-    goto err;
-  }
-
-  CRYPTO_new_ex_data(&ret->ex_data);
-
-  ret->max_send_fragment = SSL3_RT_MAX_PLAIN_LENGTH;
-
-  // Disable the auto-chaining feature by default. Once this has stuck without
-  // problems, the feature will be removed entirely.
-  ret->mode = SSL_MODE_NO_AUTO_CHAIN;
-
-  ret->rsa_pss_rsae_certs_enabled = true;
-
-  // Lock the SSL_CTX to the specified version, for compatibility with legacy
-  // uses of SSL_METHOD.
-  if (!SSL_CTX_set_max_proto_version(ret, method->version) ||
-      !SSL_CTX_set_min_proto_version(ret, method->version)) {
+  if (!SSL_CTX_set_strict_cipher_list(ret.get(), SSL_DEFAULT_CIPHER_LIST) ||
+      // Lock the SSL_CTX to the specified version, for compatibility with
+      // legacy uses of SSL_METHOD.
+      !SSL_CTX_set_max_proto_version(ret.get(), method->version) ||
+      !SSL_CTX_set_min_proto_version(ret.get(), method->version)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    goto err2;
+    return nullptr;
   }
 
-  return ret;
-
-err:
-  OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-err2:
-  SSL_CTX_free(ret);
-  return NULL;
+  return ret.release();
 }
 
 int SSL_CTX_up_ref(SSL_CTX *ctx) {
@@ -600,184 +609,118 @@
     return;
   }
 
-  // Free internal session cache. However: the remove_cb() may reference the
-  // ex_data of SSL_CTX, thus the ex_data store can only be removed after the
-  // sessions were flushed. As the ex_data handling routines might also touch
-  // the session cache, the most secure solution seems to be: empty (flush) the
-  // cache, then free ex_data, then finally free the cache. (See ticket
-  // [openssl.org #212].)
-  SSL_CTX_flush_sessions(ctx, 0);
-
-  CRYPTO_free_ex_data(&g_ex_data_class_ssl_ctx, ctx, &ctx->ex_data);
-
-  CRYPTO_MUTEX_cleanup(&ctx->lock);
-  lh_SSL_SESSION_free(ctx->sessions);
-  Delete(ctx->cipher_list);
-  Delete(ctx->cert);
-  sk_SSL_CUSTOM_EXTENSION_pop_free(ctx->client_custom_extensions,
-                                   SSL_CUSTOM_EXTENSION_free);
-  sk_SSL_CUSTOM_EXTENSION_pop_free(ctx->server_custom_extensions,
-                                   SSL_CUSTOM_EXTENSION_free);
-  sk_CRYPTO_BUFFER_pop_free(ctx->client_CA, CRYPTO_BUFFER_free);
-  ctx->x509_method->ssl_ctx_free(ctx);
-  sk_SRTP_PROTECTION_PROFILE_free(ctx->srtp_profiles);
-  OPENSSL_free(ctx->psk_identity_hint);
-  OPENSSL_free(ctx->supported_group_list);
-  OPENSSL_free(ctx->alpn_client_proto_list);
-  EVP_PKEY_free(ctx->tlsext_channel_id_private);
-  OPENSSL_free(ctx->verify_sigalgs);
-  OPENSSL_free(ctx->tlsext_ticket_key_current);
-  OPENSSL_free(ctx->tlsext_ticket_key_prev);
-
+  ctx->~ssl_ctx_st();
   OPENSSL_free(ctx);
 }
 
+ssl_st::ssl_st(SSL_CTX *ctx_arg)
+    : method(ctx_arg->method),
+      max_send_fragment(ctx_arg->max_send_fragment),
+      msg_callback(ctx_arg->msg_callback),
+      msg_callback_arg(ctx_arg->msg_callback_arg),
+      tls13_variant(ctx_arg->tls13_variant),
+      ctx(UpRef(ctx_arg)),
+      session_ctx(UpRef(ctx_arg)),
+      options(ctx->options),
+      mode(ctx->mode),
+      max_cert_list(ctx->max_cert_list),
+      server(false),
+      quiet_shutdown(ctx->quiet_shutdown),
+      did_dummy_pq_padding(false),
+      enable_early_data(ctx->enable_early_data) {
+  CRYPTO_new_ex_data(&ex_data);
+}
+
+ssl_st::~ssl_st() {
+  CRYPTO_free_ex_data(&g_ex_data_class_ssl, this, &ex_data);
+  // |config| refers to |this|, so we must release it earlier.
+  config.reset();
+  if (method != NULL) {
+    method->ssl_free(this);
+  }
+}
+
 SSL *SSL_new(SSL_CTX *ctx) {
-  if (ctx == NULL) {
+  if (ctx == nullptr) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NULL_SSL_CTX);
-    return NULL;
-  }
-  if (ctx->method == NULL) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_CTX_HAS_NO_DEFAULT_SSL_VERSION);
-    return NULL;
+    return nullptr;
   }
 
-  SSL *ssl = (SSL *)OPENSSL_malloc(sizeof(SSL));
-  if (ssl == NULL) {
-    goto err;
-  }
-  OPENSSL_memset(ssl, 0, sizeof(SSL));
-
-  ssl->conf_min_version = ctx->conf_min_version;
-  ssl->conf_max_version = ctx->conf_max_version;
-  ssl->tls13_variant = ctx->tls13_variant;
-
-  // RFC 6347 states that implementations SHOULD use an initial timer value of
-  // 1 second.
-  ssl->initial_timeout_duration_ms = 1000;
-
-  ssl->options = ctx->options;
-  ssl->mode = ctx->mode;
-  ssl->max_cert_list = ctx->max_cert_list;
-
-  ssl->cert = ssl_cert_dup(ctx->cert).release();
-  if (ssl->cert == NULL) {
-    goto err;
+  UniquePtr<SSL> ssl = MakeUnique<SSL>(ctx);
+  if (ssl == nullptr) {
+    return nullptr;
   }
 
-  ssl->msg_callback = ctx->msg_callback;
-  ssl->msg_callback_arg = ctx->msg_callback_arg;
-  ssl->verify_mode = ctx->verify_mode;
-  ssl->verify_callback = ctx->default_verify_callback;
-  ssl->custom_verify_callback = ctx->custom_verify_callback;
-  ssl->retain_only_sha256_of_client_certs =
+  ssl->config = MakeUnique<SSL_CONFIG>(ssl.get());
+  if (ssl->config == nullptr) {
+    return nullptr;
+  }
+  ssl->config->conf_min_version = ctx->conf_min_version;
+  ssl->config->conf_max_version = ctx->conf_max_version;
+
+  ssl->config->cert = ssl_cert_dup(ctx->cert.get());
+  if (ssl->config->cert == nullptr) {
+    return nullptr;
+  }
+
+  ssl->config->verify_mode = ctx->verify_mode;
+  ssl->config->verify_callback = ctx->default_verify_callback;
+  ssl->config->custom_verify_callback = ctx->custom_verify_callback;
+  ssl->config->retain_only_sha256_of_client_certs =
       ctx->retain_only_sha256_of_client_certs;
 
-  ssl->quiet_shutdown = ctx->quiet_shutdown;
-  ssl->max_send_fragment = ctx->max_send_fragment;
-
-  SSL_CTX_up_ref(ctx);
-  ssl->ctx = ctx;
-  SSL_CTX_up_ref(ctx);
-  ssl->session_ctx = ctx;
-
-  if (!ssl->ctx->x509_method->ssl_new(ssl)) {
-    goto err;
+  if (!ssl->config->supported_group_list.CopyFrom(ctx->supported_group_list) ||
+      !ssl->config->alpn_client_proto_list.CopyFrom(
+          ctx->alpn_client_proto_list) ||
+      !ssl->config->verify_sigalgs.CopyFrom(ctx->verify_sigalgs)) {
+    return nullptr;
   }
 
-  if (ctx->supported_group_list) {
-    ssl->supported_group_list = (uint16_t *)BUF_memdup(
-        ctx->supported_group_list, ctx->supported_group_list_len * 2);
-    if (!ssl->supported_group_list) {
-      goto err;
-    }
-    ssl->supported_group_list_len = ctx->supported_group_list_len;
-  }
-
-  if (ctx->alpn_client_proto_list) {
-    ssl->alpn_client_proto_list = (uint8_t *)BUF_memdup(
-        ctx->alpn_client_proto_list, ctx->alpn_client_proto_list_len);
-    if (ssl->alpn_client_proto_list == NULL) {
-      goto err;
-    }
-    ssl->alpn_client_proto_list_len = ctx->alpn_client_proto_list_len;
-  }
-
-  ssl->method = ctx->method;
-
-  if (!ssl->method->ssl_new(ssl)) {
-    goto err;
-  }
-
-  CRYPTO_new_ex_data(&ssl->ex_data);
-
-  ssl->psk_identity_hint = NULL;
   if (ctx->psk_identity_hint) {
-    ssl->psk_identity_hint = BUF_strdup(ctx->psk_identity_hint);
-    if (ssl->psk_identity_hint == NULL) {
-      goto err;
+    ssl->config->psk_identity_hint.reset(
+        BUF_strdup(ctx->psk_identity_hint.get()));
+    if (ssl->config->psk_identity_hint == nullptr) {
+      return nullptr;
     }
   }
-  ssl->psk_client_callback = ctx->psk_client_callback;
-  ssl->psk_server_callback = ctx->psk_server_callback;
+  ssl->config->psk_client_callback = ctx->psk_client_callback;
+  ssl->config->psk_server_callback = ctx->psk_server_callback;
 
-  ssl->tlsext_channel_id_enabled = ctx->tlsext_channel_id_enabled;
-  if (ctx->tlsext_channel_id_private) {
-    EVP_PKEY_up_ref(ctx->tlsext_channel_id_private);
-    ssl->tlsext_channel_id_private = ctx->tlsext_channel_id_private;
+  ssl->config->channel_id_enabled = ctx->channel_id_enabled;
+  ssl->config->channel_id_private = UpRef(ctx->channel_id_private);
+
+  ssl->config->signed_cert_timestamps_enabled =
+      ctx->signed_cert_timestamps_enabled;
+  ssl->config->ocsp_stapling_enabled = ctx->ocsp_stapling_enabled;
+  ssl->config->handoff = ctx->handoff;
+
+  if (!ssl->method->ssl_new(ssl.get()) ||
+      !ssl->ctx->x509_method->ssl_new(ssl->s3->hs.get())) {
+    return nullptr;
   }
 
-  ssl->signed_cert_timestamps_enabled = ctx->signed_cert_timestamps_enabled;
-  ssl->ocsp_stapling_enabled = ctx->ocsp_stapling_enabled;
-  ssl->handoff = ctx->handoff;
+  return ssl.release();
+}
 
-  return ssl;
+SSL_CONFIG::SSL_CONFIG(SSL *ssl_arg)
+    : ssl(ssl_arg),
+      signed_cert_timestamps_enabled(false),
+      ocsp_stapling_enabled(false),
+      channel_id_enabled(false),
+      retain_only_sha256_of_client_certs(false),
+      handoff(false),
+      shed_handshake_config(false) {
+  assert(ssl);
+}
 
-err:
-  SSL_free(ssl);
-  OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-
-  return NULL;
+SSL_CONFIG::~SSL_CONFIG() {
+  if (ssl->ctx != nullptr) {
+    ssl->ctx->x509_method->ssl_config_free(this);
+  }
 }
 
 void SSL_free(SSL *ssl) {
-  if (ssl == NULL) {
-    return;
-  }
-
-  if (ssl->ctx != NULL) {
-    ssl->ctx->x509_method->ssl_free(ssl);
-  }
-
-  CRYPTO_free_ex_data(&g_ex_data_class_ssl, ssl, &ssl->ex_data);
-
-  BIO_free_all(ssl->rbio);
-  BIO_free_all(ssl->wbio);
-
-  // add extra stuff
-  Delete(ssl->cipher_list);
-
-  SSL_SESSION_free(ssl->session);
-
-  Delete(ssl->cert);
-
-  OPENSSL_free(ssl->tlsext_hostname);
-  SSL_CTX_free(ssl->session_ctx);
-  OPENSSL_free(ssl->supported_group_list);
-  OPENSSL_free(ssl->alpn_client_proto_list);
-  OPENSSL_free(ssl->token_binding_params);
-  OPENSSL_free(ssl->quic_transport_params);
-  EVP_PKEY_free(ssl->tlsext_channel_id_private);
-  OPENSSL_free(ssl->psk_identity_hint);
-  sk_CRYPTO_BUFFER_pop_free(ssl->client_CA, CRYPTO_BUFFER_free);
-  sk_SRTP_PROTECTION_PROFILE_free(ssl->srtp_profiles);
-
-  if (ssl->method != NULL) {
-    ssl->method->ssl_free(ssl);
-  }
-  SSL_CTX_free(ssl->ctx);
-
-  OPENSSL_free(ssl);
+  Delete(ssl);
 }
 
 void SSL_set_connect_state(SSL *ssl) {
@@ -791,13 +734,11 @@
 }
 
 void SSL_set0_rbio(SSL *ssl, BIO *rbio) {
-  BIO_free_all(ssl->rbio);
-  ssl->rbio = rbio;
+  ssl->rbio.reset(rbio);
 }
 
 void SSL_set0_wbio(SSL *ssl, BIO *wbio) {
-  BIO_free_all(ssl->wbio);
-  ssl->wbio = wbio;
+  ssl->wbio.reset(wbio);
 }
 
 void SSL_set_bio(SSL *ssl, BIO *rbio, BIO *wbio) {
@@ -834,9 +775,9 @@
   SSL_set0_wbio(ssl, wbio);
 }
 
-BIO *SSL_get_rbio(const SSL *ssl) { return ssl->rbio; }
+BIO *SSL_get_rbio(const SSL *ssl) { return ssl->rbio.get(); }
 
-BIO *SSL_get_wbio(const SSL *ssl) { return ssl->wbio; }
+BIO *SSL_get_wbio(const SSL *ssl) { return ssl->wbio.get(); }
 
 int SSL_do_handshake(SSL *ssl) {
   ssl_reset_error_state(ssl);
@@ -864,6 +805,7 @@
   // Destroy the handshake object if the handshake has completely finished.
   if (!early_return) {
     ssl->s3->hs.reset();
+    ssl_maybe_shed_handshake_config(ssl);
   }
 
   return 1;
@@ -892,11 +834,12 @@
     return tls13_post_handshake(ssl, msg);
   }
 
-  // We do not accept renegotiations as a server or SSL 3.0. SSL 3.0 will be
-  // removed entirely in the future and requires retaining more data for
-  // renegotiation_info.
-  if (ssl->server || ssl->version == SSL3_VERSION) {
-    goto no_renegotiation;
+  // Check for renegotiation on the server before parsing to use the correct
+  // error. Renegotiation is triggered by a different message for servers.
+  if (ssl->server) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_NO_RENEGOTIATION);
+    return 0;
   }
 
   if (msg.type != SSL3_MT_HELLO_REQUEST || CBS_len(&msg.body) != 0) {
@@ -905,31 +848,20 @@
     return 0;
   }
 
-  switch (ssl->renegotiate_mode) {
-    case ssl_renegotiate_ignore:
-      // Ignore the HelloRequest.
-      return 1;
-
-    case ssl_renegotiate_once:
-      if (ssl->s3->total_renegotiations != 0) {
-        goto no_renegotiation;
-      }
-      break;
-
-    case ssl_renegotiate_never:
-      goto no_renegotiation;
-
-    case ssl_renegotiate_freely:
-      break;
+  if (ssl->renegotiate_mode == ssl_renegotiate_ignore) {
+    return 1;  // Ignore the HelloRequest.
   }
 
-  // Renegotiation is only supported at quiescent points in the application
-  // protocol, namely in HTTPS, just before reading the HTTP response. Require
-  // the record-layer be idle and avoid complexities of sending a handshake
-  // record while an application_data record is being written.
-  if (!ssl->s3->write_buffer.empty() ||
+  if (!ssl_can_renegotiate(ssl) ||
+      // Renegotiation is only supported at quiescent points in the application
+      // protocol, namely in HTTPS, just before reading the HTTP response.
+      // Require the record-layer be idle and avoid complexities of sending a
+      // handshake record while an application_data record is being written.
+      !ssl->s3->write_buffer.empty() ||
       ssl->s3->write_shutdown != ssl_shutdown_none) {
-    goto no_renegotiation;
+    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_NO_RENEGOTIATION);
+    return 0;
   }
 
   // Begin a new handshake.
@@ -944,11 +876,6 @@
 
   ssl->s3->total_renegotiations++;
   return 1;
-
-no_renegotiation:
-  OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
-  ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_NO_RENEGOTIATION);
-  return 0;
 }
 
 static int ssl_read_impl(SSL *ssl) {
@@ -1127,13 +1054,11 @@
       }
       ssl->s3->read_shutdown = ssl_shutdown_close_notify;
     } else {
-      // Keep discarding data until we see a close_notify.
-      for (;;) {
-        ssl->s3->pending_app_data = Span<uint8_t>();
-        int ret = ssl_read_impl(ssl);
-        if (ret <= 0) {
-          break;
-        }
+      // Process records until an error, close_notify, or application data.
+      if (ssl_read_impl(ssl) > 0) {
+        // We received some unexpected application data.
+        OPENSSL_PUT_ERROR(SSL, SSL_R_APPLICATION_DATA_ON_SHUTDOWN);
+        return -1;
       }
       if (ssl->s3->read_shutdown != ssl_shutdown_close_notify) {
         return -1;
@@ -1161,12 +1086,8 @@
 
 int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
                                   size_t params_len) {
-  ssl->quic_transport_params = (uint8_t *)BUF_memdup(params, params_len);
-  if (!ssl->quic_transport_params) {
-    return 0;
-  }
-  ssl->quic_transport_params_len = params_len;
-  return 1;
+  return ssl->config && ssl->config->quic_transport_params.CopyFrom(
+                            MakeConstSpan(params, params_len));
 }
 
 void SSL_get_peer_quic_transport_params(const SSL *ssl,
@@ -1177,7 +1098,7 @@
 }
 
 void SSL_CTX_set_early_data_enabled(SSL_CTX *ctx, int enabled) {
-  ctx->cert->enable_early_data = !!enabled;
+  ctx->enable_early_data = !!enabled;
 }
 
 void SSL_CTX_set_tls13_variant(SSL_CTX *ctx, enum tls13_variant_t variant) {
@@ -1189,7 +1110,7 @@
 }
 
 void SSL_set_early_data_enabled(SSL *ssl, int enabled) {
-  ssl->cert->enable_early_data = !!enabled;
+  ssl->enable_early_data = !!enabled;
 }
 
 int SSL_in_early_data(const SSL *ssl) {
@@ -1386,9 +1307,8 @@
   *out_len = 0;
   OPENSSL_memset(out, 0, max_out);
 
-  // tls-unique is not defined for SSL 3.0 or TLS 1.3.
+  // tls-unique is not defined for TLS 1.3.
   if (!ssl->s3->initial_handshake_complete ||
-      ssl_protocol_version(ssl) < TLS1_VERSION ||
       ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return 0;
   }
@@ -1431,20 +1351,33 @@
 
 int SSL_CTX_set_session_id_context(SSL_CTX *ctx, const uint8_t *sid_ctx,
                                    size_t sid_ctx_len) {
-  return set_session_id_context(ctx->cert, sid_ctx, sid_ctx_len);
+  return set_session_id_context(ctx->cert.get(), sid_ctx, sid_ctx_len);
 }
 
 int SSL_set_session_id_context(SSL *ssl, const uint8_t *sid_ctx,
                                size_t sid_ctx_len) {
-  return set_session_id_context(ssl->cert, sid_ctx, sid_ctx_len);
+  if (!ssl->config) {
+    return 0;
+  }
+  return set_session_id_context(ssl->config->cert.get(), sid_ctx, sid_ctx_len);
 }
 
 const uint8_t *SSL_get0_session_id_context(const SSL *ssl, size_t *out_len) {
-  *out_len = ssl->cert->sid_ctx_length;
-  return ssl->cert->sid_ctx;
+  if (!ssl->config) {
+    assert(ssl->config);
+    *out_len = 0;
+    return NULL;
+  }
+  *out_len = ssl->config->cert->sid_ctx_length;
+  return ssl->config->cert->sid_ctx;
 }
 
-void SSL_certs_clear(SSL *ssl) { ssl_cert_clear_certs(ssl->cert); }
+void SSL_certs_clear(SSL *ssl) {
+  if (!ssl->config) {
+    return;
+  }
+  ssl_cert_clear_certs(ssl->config->cert.get());
+}
 
 int SSL_get_fd(const SSL *ssl) { return SSL_get_rfd(ssl); }
 
@@ -1527,7 +1460,6 @@
 
 size_t SSL_get_finished(const SSL *ssl, void *buf, size_t count) {
   if (!ssl->s3->initial_handshake_complete ||
-      ssl_protocol_version(ssl) < TLS1_VERSION ||
       ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return 0;
   }
@@ -1543,7 +1475,6 @@
 
 size_t SSL_get_peer_finished(const SSL *ssl, void *buf, size_t count) {
   if (!ssl->s3->initial_handshake_complete ||
-      ssl_protocol_version(ssl) < TLS1_VERSION ||
       ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return 0;
   }
@@ -1557,7 +1488,13 @@
                        ssl->s3->previous_server_finished_len);
 }
 
-int SSL_get_verify_mode(const SSL *ssl) { return ssl->verify_mode; }
+int SSL_get_verify_mode(const SSL *ssl) {
+  if (!ssl->config) {
+    assert(ssl->config);
+    return -1;
+  }
+  return ssl->config->verify_mode;
+}
 
 int SSL_get_extms_support(const SSL *ssl) {
   // TLS 1.3 does not require extended master secret and always reports as
@@ -1586,22 +1523,25 @@
 
 int SSL_get_read_ahead(const SSL *ssl) { return 0; }
 
-void SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes) { }
+int SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes) { return 1; }
 
-void SSL_set_read_ahead(SSL *ssl, int yes) { }
+int SSL_set_read_ahead(SSL *ssl, int yes) { return 1; }
 
 int SSL_pending(const SSL *ssl) {
   return static_cast<int>(ssl->s3->pending_app_data.size());
 }
 
-// Fix this so it checks all the valid key/cert options
 int SSL_CTX_check_private_key(const SSL_CTX *ctx) {
-  return ssl_cert_check_private_key(ctx->cert, ctx->cert->privatekey.get());
+  return ssl_cert_check_private_key(ctx->cert.get(),
+                                    ctx->cert->privatekey.get());
 }
 
-// Fix this function so that it takes an optional type parameter
 int SSL_check_private_key(const SSL *ssl) {
-  return ssl_cert_check_private_key(ssl->cert, ssl->cert->privatekey.get());
+  if (!ssl->config) {
+    return 0;
+  }
+  return ssl_cert_check_private_key(ssl->config->cert.get(),
+                                    ssl->config->cert->privatekey.get());
 }
 
 long SSL_get_default_timeout(const SSL *ssl) {
@@ -1727,9 +1667,9 @@
 
   uint8_t *out_bytes = reinterpret_cast<uint8_t *>(out);
   MutexReadLock lock(&ctx->lock);
-  OPENSSL_memcpy(out_bytes, ctx->tlsext_ticket_key_current->name, 16);
-  OPENSSL_memcpy(out_bytes + 16, ctx->tlsext_ticket_key_current->hmac_key, 16);
-  OPENSSL_memcpy(out_bytes + 32, ctx->tlsext_ticket_key_current->aes_key, 16);
+  OPENSSL_memcpy(out_bytes, ctx->ticket_key_current->name, 16);
+  OPENSSL_memcpy(out_bytes + 16, ctx->ticket_key_current->hmac_key, 16);
+  OPENSSL_memcpy(out_bytes + 32, ctx->ticket_key_current->aes_key, 16);
   return 1;
 }
 
@@ -1741,22 +1681,19 @@
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_TICKET_KEYS_LENGTH);
     return 0;
   }
-  if (!ctx->tlsext_ticket_key_current) {
-    ctx->tlsext_ticket_key_current =
-        (tlsext_ticket_key *)OPENSSL_malloc(sizeof(tlsext_ticket_key));
-    if (!ctx->tlsext_ticket_key_current) {
-      return 0;
-    }
+  auto key = MakeUnique<TicketKey>();
+  if (!key) {
+    return 0;
   }
-  OPENSSL_memset(ctx->tlsext_ticket_key_current, 0, sizeof(tlsext_ticket_key));
   const uint8_t *in_bytes = reinterpret_cast<const uint8_t *>(in);
-  OPENSSL_memcpy(ctx->tlsext_ticket_key_current->name, in_bytes, 16);
-  OPENSSL_memcpy(ctx->tlsext_ticket_key_current->hmac_key, in_bytes + 16, 16);
-  OPENSSL_memcpy(ctx->tlsext_ticket_key_current->aes_key, in_bytes + 32, 16);
-  OPENSSL_free(ctx->tlsext_ticket_key_prev);
-  ctx->tlsext_ticket_key_prev = nullptr;
-  // Disable automatic key rotation.
-  ctx->tlsext_ticket_key_current->next_rotation_tv_sec = 0;
+  OPENSSL_memcpy(key->name, in_bytes, 16);
+  OPENSSL_memcpy(key->hmac_key, in_bytes + 16, 16);
+  OPENSSL_memcpy(key->aes_key, in_bytes + 32, 16);
+  // Disable automatic key rotation for manually-configured keys. This is now
+  // the caller's responsibility.
+  key->next_rotation_tv_sec = 0;
+  ctx->ticket_key_current = std::move(key);
+  ctx->ticket_key_prev.reset();
   return 1;
 }
 
@@ -1764,30 +1701,32 @@
     SSL_CTX *ctx, int (*callback)(SSL *ssl, uint8_t *key_name, uint8_t *iv,
                                   EVP_CIPHER_CTX *ctx, HMAC_CTX *hmac_ctx,
                                   int encrypt)) {
-  ctx->tlsext_ticket_key_cb = callback;
+  ctx->ticket_key_cb = callback;
   return 1;
 }
 
 int SSL_CTX_set1_curves(SSL_CTX *ctx, const int *curves, size_t curves_len) {
   return tls1_set_curves(&ctx->supported_group_list,
-                         &ctx->supported_group_list_len, curves,
-                         curves_len);
+                         MakeConstSpan(curves, curves_len));
 }
 
 int SSL_set1_curves(SSL *ssl, const int *curves, size_t curves_len) {
-  return tls1_set_curves(&ssl->supported_group_list,
-                         &ssl->supported_group_list_len, curves,
-                         curves_len);
+  if (!ssl->config) {
+    return 0;
+  }
+  return tls1_set_curves(&ssl->config->supported_group_list,
+                         MakeConstSpan(curves, curves_len));
 }
 
 int SSL_CTX_set1_curves_list(SSL_CTX *ctx, const char *curves) {
-  return tls1_set_curves_list(&ctx->supported_group_list,
-                              &ctx->supported_group_list_len, curves);
+  return tls1_set_curves_list(&ctx->supported_group_list, curves);
 }
 
 int SSL_set1_curves_list(SSL *ssl, const char *curves) {
-  return tls1_set_curves_list(&ssl->supported_group_list,
-                              &ssl->supported_group_list_len, curves);
+  if (!ssl->config) {
+    return 0;
+  }
+  return tls1_set_curves_list(&ssl->config->supported_group_list, curves);
 }
 
 uint16_t SSL_get_curve_id(const SSL *ssl) {
@@ -1824,9 +1763,13 @@
   if (ssl == NULL) {
     return NULL;
   }
+  if (ssl->config == NULL) {
+    assert(ssl->config);
+    return NULL;
+  }
 
-  const SSLCipherPreferenceList *prefs = ssl_get_cipher_preferences(ssl);
-  return prefs == nullptr ? nullptr : prefs->ciphers.get();
+  return ssl->config->cipher_list ? ssl->config->cipher_list->ciphers.get()
+      : ssl->ctx->cipher_list->ciphers.get();
 }
 
 const char *SSL_get_cipher_list(const SSL *ssl, int n) {
@@ -1856,11 +1799,19 @@
 }
 
 int SSL_set_cipher_list(SSL *ssl, const char *str) {
-  return ssl_create_cipher_list(&ssl->cipher_list, str, false /* not strict */);
+  if (!ssl->config) {
+    return 0;
+  }
+  return ssl_create_cipher_list(&ssl->config->cipher_list, str,
+                                false /* not strict */);
 }
 
 int SSL_set_strict_cipher_list(SSL *ssl, const char *str) {
-  return ssl_create_cipher_list(&ssl->cipher_list, str, true /* strict */);
+  if (!ssl->config) {
+    return 0;
+  }
+  return ssl_create_cipher_list(&ssl->config->cipher_list, str,
+                                true /* strict */);
 }
 
 const char *SSL_get_servername(const SSL *ssl, const int type) {
@@ -1870,8 +1821,8 @@
 
   // Historically, |SSL_get_servername| was also the configuration getter
   // corresponding to |SSL_set_tlsext_host_name|.
-  if (ssl->tlsext_hostname != NULL) {
-    return ssl->tlsext_hostname;
+  if (ssl->hostname != nullptr) {
+    return ssl->hostname.get();
   }
 
   return ssl->s3->hostname.get();
@@ -1894,8 +1845,11 @@
 void SSL_set_custom_verify(
     SSL *ssl, int mode,
     enum ssl_verify_result_t (*callback)(SSL *ssl, uint8_t *out_alert)) {
-  ssl->verify_mode = mode;
-  ssl->custom_verify_callback = callback;
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->verify_mode = mode;
+  ssl->config->custom_verify_callback = callback;
 }
 
 void SSL_CTX_enable_signed_cert_timestamps(SSL_CTX *ctx) {
@@ -1903,7 +1857,10 @@
 }
 
 void SSL_enable_signed_cert_timestamps(SSL *ssl) {
-  ssl->signed_cert_timestamps_enabled = true;
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->signed_cert_timestamps_enabled = true;
 }
 
 void SSL_CTX_enable_ocsp_stapling(SSL_CTX *ctx) {
@@ -1911,7 +1868,10 @@
 }
 
 void SSL_enable_ocsp_stapling(SSL *ssl) {
-  ssl->ocsp_stapling_enabled = true;
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->ocsp_stapling_enabled = true;
 }
 
 void SSL_get0_signed_cert_timestamp_list(const SSL *ssl, const uint8_t **out,
@@ -1923,8 +1883,8 @@
     return;
   }
 
-  *out = CRYPTO_BUFFER_data(session->signed_cert_timestamp_list);
-  *out_len = CRYPTO_BUFFER_len(session->signed_cert_timestamp_list);
+  *out = CRYPTO_BUFFER_data(session->signed_cert_timestamp_list.get());
+  *out_len = CRYPTO_BUFFER_len(session->signed_cert_timestamp_list.get());
 }
 
 void SSL_get0_ocsp_response(const SSL *ssl, const uint8_t **out,
@@ -1936,15 +1896,13 @@
     return;
   }
 
-  *out = CRYPTO_BUFFER_data(session->ocsp_response);
-  *out_len = CRYPTO_BUFFER_len(session->ocsp_response);
+  *out = CRYPTO_BUFFER_data(session->ocsp_response.get());
+  *out_len = CRYPTO_BUFFER_len(session->ocsp_response.get());
 }
 
 int SSL_set_tlsext_host_name(SSL *ssl, const char *name) {
-  OPENSSL_free(ssl->tlsext_hostname);
-  ssl->tlsext_hostname = NULL;
-
-  if (name == NULL) {
+  ssl->hostname.reset();
+  if (name == nullptr) {
     return 1;
   }
 
@@ -1953,8 +1911,8 @@
     OPENSSL_PUT_ERROR(SSL, SSL_R_SSL3_EXT_INVALID_SERVERNAME);
     return 0;
   }
-  ssl->tlsext_hostname = BUF_strdup(name);
-  if (ssl->tlsext_hostname == NULL) {
+  ssl->hostname.reset(BUF_strdup(name));
+  if (ssl->hostname == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     return 0;
   }
@@ -1963,12 +1921,12 @@
 
 int SSL_CTX_set_tlsext_servername_callback(
     SSL_CTX *ctx, int (*callback)(SSL *ssl, int *out_alert, void *arg)) {
-  ctx->tlsext_servername_callback = callback;
+  ctx->servername_callback = callback;
   return 1;
 }
 
 int SSL_CTX_set_tlsext_servername_arg(SSL_CTX *ctx, void *arg) {
-  ctx->tlsext_servername_arg = arg;
+  ctx->servername_arg = arg;
   return 1;
 }
 
@@ -2029,25 +1987,21 @@
 
 int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const uint8_t *protos,
                             unsigned protos_len) {
-  OPENSSL_free(ctx->alpn_client_proto_list);
-  ctx->alpn_client_proto_list = (uint8_t *)BUF_memdup(protos, protos_len);
-  if (!ctx->alpn_client_proto_list) {
-    return 1;
-  }
-  ctx->alpn_client_proto_list_len = protos_len;
-
-  return 0;
+  // Note this function's calling convention is backwards.
+  return ctx->alpn_client_proto_list.CopyFrom(MakeConstSpan(protos, protos_len))
+             ? 0
+             : 1;
 }
 
 int SSL_set_alpn_protos(SSL *ssl, const uint8_t *protos, unsigned protos_len) {
-  OPENSSL_free(ssl->alpn_client_proto_list);
-  ssl->alpn_client_proto_list = (uint8_t *)BUF_memdup(protos, protos_len);
-  if (!ssl->alpn_client_proto_list) {
+  // Note this function's calling convention is backwards.
+  if (!ssl->config) {
     return 1;
   }
-  ssl->alpn_client_proto_list_len = protos_len;
-
-  return 0;
+  return ssl->config->alpn_client_proto_list.CopyFrom(
+             MakeConstSpan(protos, protos_len))
+             ? 0
+             : 1;
 }
 
 void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx,
@@ -2062,8 +2016,8 @@
 void SSL_get0_alpn_selected(const SSL *ssl, const uint8_t **out_data,
                             unsigned *out_len) {
   if (SSL_in_early_data(ssl) && !ssl->server) {
-    *out_data = ssl->s3->hs->early_session->early_alpn;
-    *out_len = ssl->s3->hs->early_session->early_alpn_len;
+    *out_data = ssl->s3->hs->early_session->early_alpn.data();
+    *out_len = ssl->s3->hs->early_session->early_alpn.size();
   } else {
     *out_data = ssl->s3->alpn_selected.data();
     *out_len = ssl->s3->alpn_selected.size();
@@ -2074,8 +2028,45 @@
   ctx->allow_unknown_alpn_protos = !!enabled;
 }
 
+int SSL_CTX_add_cert_compression_alg(SSL_CTX *ctx, uint16_t alg_id,
+                                     ssl_cert_compression_func_t compress,
+                                     ssl_cert_decompression_func_t decompress) {
+  assert(compress != nullptr || decompress != nullptr);
+
+  for (const auto *alg : ctx->cert_compression_algs.get()) {
+    if (alg->alg_id == alg_id) {
+      return 0;
+    }
+  }
+
+  UniquePtr<CertCompressionAlg> alg = MakeUnique<CertCompressionAlg>();
+  if (alg == nullptr) {
+    return 0;
+  }
+
+  alg->alg_id = alg_id;
+  alg->compress = compress;
+  alg->decompress = decompress;
+
+  if (ctx->cert_compression_algs == nullptr) {
+    ctx->cert_compression_algs.reset(sk_CertCompressionAlg_new_null());
+    if (ctx->cert_compression_algs == nullptr) {
+      return 0;
+    }
+  }
+
+  if (!PushToStack(ctx->cert_compression_algs.get(), std::move(alg))) {
+    if (sk_CertCompressionAlg_num(ctx->cert_compression_algs.get()) == 0) {
+      ctx->cert_compression_algs.reset();
+    }
+    return 0;
+  }
+
+  return 1;
+}
+
 void SSL_CTX_set_tls_channel_id_enabled(SSL_CTX *ctx, int enabled) {
-  ctx->tlsext_channel_id_enabled = !!enabled;
+  ctx->channel_id_enabled = !!enabled;
 }
 
 int SSL_CTX_enable_tls_channel_id(SSL_CTX *ctx) {
@@ -2084,7 +2075,10 @@
 }
 
 void SSL_set_tls_channel_id_enabled(SSL *ssl, int enabled) {
-  ssl->tlsext_channel_id_enabled = !!enabled;
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->channel_id_enabled = !!enabled;
 }
 
 int SSL_enable_tls_channel_id(SSL *ssl) {
@@ -2105,49 +2099,44 @@
     return 0;
   }
 
-  EVP_PKEY_free(ctx->tlsext_channel_id_private);
-  EVP_PKEY_up_ref(private_key);
-  ctx->tlsext_channel_id_private = private_key;
-  ctx->tlsext_channel_id_enabled = true;
+  ctx->channel_id_private = UpRef(private_key);
+  ctx->channel_id_enabled = true;
 
   return 1;
 }
 
 int SSL_set1_tls_channel_id(SSL *ssl, EVP_PKEY *private_key) {
+  if (!ssl->config) {
+    return 0;
+  }
   if (!is_p256_key(private_key)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_CHANNEL_ID_NOT_P256);
     return 0;
   }
 
-  EVP_PKEY_free(ssl->tlsext_channel_id_private);
-  EVP_PKEY_up_ref(private_key);
-  ssl->tlsext_channel_id_private = private_key;
-  ssl->tlsext_channel_id_enabled = true;
+  ssl->config->channel_id_private = UpRef(private_key);
+  ssl->config->channel_id_enabled = true;
 
   return 1;
 }
 
 size_t SSL_get_tls_channel_id(SSL *ssl, uint8_t *out, size_t max_out) {
-  if (!ssl->s3->tlsext_channel_id_valid) {
+  if (!ssl->s3->channel_id_valid) {
     return 0;
   }
-  OPENSSL_memcpy(out, ssl->s3->tlsext_channel_id,
-                 (max_out < 64) ? max_out : 64);
+  OPENSSL_memcpy(out, ssl->s3->channel_id, (max_out < 64) ? max_out : 64);
   return 64;
 }
 
 int SSL_set_token_binding_params(SSL *ssl, const uint8_t *params, size_t len) {
+  if (!ssl->config) {
+    return 0;
+  }
   if (len > 256) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
     return 0;
   }
-  OPENSSL_free(ssl->token_binding_params);
-  ssl->token_binding_params = (uint8_t *)BUF_memdup(params, len);
-  if (!ssl->token_binding_params) {
-    return 0;
-  }
-  ssl->token_binding_params_len = len;
-  return 1;
+  return ssl->config->token_binding_params.CopyFrom(MakeConstSpan(params, len));
 }
 
 int SSL_is_token_binding_negotiated(const SSL *ssl) {
@@ -2158,18 +2147,32 @@
   return ssl->s3->negotiated_token_binding_param;
 }
 
-size_t SSL_get0_certificate_types(SSL *ssl, const uint8_t **out_types) {
-  if (ssl->server || ssl->s3->hs == NULL) {
-    *out_types = NULL;
-    return 0;
+size_t SSL_get0_certificate_types(const SSL *ssl, const uint8_t **out_types) {
+  Span<const uint8_t> types;
+  if (!ssl->server && ssl->s3->hs != nullptr) {
+    types = ssl->s3->hs->certificate_types;
   }
-  *out_types = ssl->s3->hs->certificate_types.data();
-  return ssl->s3->hs->certificate_types.size();
+  *out_types = types.data();
+  return types.size();
+}
+
+size_t SSL_get0_peer_verify_algorithms(const SSL *ssl,
+                                       const uint16_t **out_sigalgs) {
+  Span<const uint16_t> sigalgs;
+  if (ssl->s3->hs != nullptr) {
+    sigalgs = ssl->s3->hs->peer_sigalgs;
+  }
+  *out_sigalgs = sigalgs.data();
+  return sigalgs.size();
 }
 
 EVP_PKEY *SSL_get_privatekey(const SSL *ssl) {
-  if (ssl->cert != NULL) {
-    return ssl->cert->privatekey.get();
+  if (!ssl->config) {
+    assert(ssl->config);
+    return NULL;
+  }
+  if (ssl->config->cert != NULL) {
+    return ssl->config->cert->privatekey.get();
   }
 
   return NULL;
@@ -2195,7 +2198,7 @@
 
 const COMP_METHOD *SSL_get_current_expansion(SSL *ssl) { return NULL; }
 
-int *SSL_get_server_tmp_key(SSL *ssl, EVP_PKEY **out_key) { return 0; }
+int SSL_get_server_tmp_key(SSL *ssl, EVP_PKEY **out_key) { return 0; }
 
 void SSL_CTX_set_quiet_shutdown(SSL_CTX *ctx, int mode) {
   ctx->quiet_shutdown = (mode != 0);
@@ -2241,11 +2244,14 @@
   return ret;
 }
 
-SSL_CTX *SSL_get_SSL_CTX(const SSL *ssl) { return ssl->ctx; }
+SSL_CTX *SSL_get_SSL_CTX(const SSL *ssl) { return ssl->ctx.get(); }
 
 SSL_CTX *SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx) {
-  if (ssl->ctx == ctx) {
-    return ssl->ctx;
+  if (!ssl->config) {
+    return NULL;
+  }
+  if (ssl->ctx.get() == ctx) {
+    return ssl->ctx.get();
   }
 
   // One cannot change the X.509 callbacks during a connection.
@@ -2255,17 +2261,19 @@
   }
 
   if (ctx == NULL) {
-    ctx = ssl->session_ctx;
+    ctx = ssl->session_ctx.get();
   }
 
-  Delete(ssl->cert);
-  ssl->cert = ssl_cert_dup(ctx->cert).release();
+  UniquePtr<CERT> new_cert = ssl_cert_dup(ctx->cert.get());
+  if (!new_cert) {
+    return nullptr;
+  }
 
-  SSL_CTX_up_ref(ctx);
-  SSL_CTX_free(ssl->ctx);
-  ssl->ctx = ctx;
+  ssl->config->cert = std::move(new_cert);
+  ssl->ctx = UpRef(ctx);
+  ssl->enable_early_data = ssl->ctx->enable_early_data;
 
-  return ssl->ctx;
+  return ssl->ctx.get();
 }
 
 void SSL_set_info_callback(SSL *ssl,
@@ -2345,23 +2353,23 @@
 void SSL_set_tmp_dh_callback(SSL *ssl, DH *(*cb)(SSL *ssl, int is_export,
                                                  int keylength)) {}
 
-static int use_psk_identity_hint(char **out, const char *identity_hint) {
+static int use_psk_identity_hint(UniquePtr<char> *out,
+                                 const char *identity_hint) {
   if (identity_hint != NULL && strlen(identity_hint) > PSK_MAX_IDENTITY_LEN) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DATA_LENGTH_TOO_LONG);
     return 0;
   }
 
   // Clear currently configured hint, if any.
-  OPENSSL_free(*out);
-  *out = NULL;
+  out->reset();
 
   // Treat the empty hint as not supplying one. Plain PSK makes it possible to
   // send either no hint (omit ServerKeyExchange) or an empty hint, while
   // ECDHE_PSK can only spell empty hint. Having different capabilities is odd,
   // so we interpret empty and missing as identical.
   if (identity_hint != NULL && identity_hint[0] != '\0') {
-    *out = BUF_strdup(identity_hint);
-    if (*out == NULL) {
+    out->reset(BUF_strdup(identity_hint));
+    if (*out == nullptr) {
       return 0;
     }
   }
@@ -2374,14 +2382,21 @@
 }
 
 int SSL_use_psk_identity_hint(SSL *ssl, const char *identity_hint) {
-  return use_psk_identity_hint(&ssl->psk_identity_hint, identity_hint);
+  if (!ssl->config) {
+    return 0;
+  }
+  return use_psk_identity_hint(&ssl->config->psk_identity_hint, identity_hint);
 }
 
 const char *SSL_get_psk_identity_hint(const SSL *ssl) {
   if (ssl == NULL) {
     return NULL;
   }
-  return ssl->psk_identity_hint;
+  if (ssl->config == NULL) {
+    assert(ssl->config);
+    return NULL;
+  }
+  return ssl->config->psk_identity_hint.get();
 }
 
 const char *SSL_get_psk_identity(const SSL *ssl) {
@@ -2392,14 +2407,17 @@
   if (session == NULL) {
     return NULL;
   }
-  return session->psk_identity;
+  return session->psk_identity.get();
 }
 
 void SSL_set_psk_client_callback(
     SSL *ssl, unsigned (*cb)(SSL *ssl, const char *hint, char *identity,
                              unsigned max_identity_len, uint8_t *psk,
                              unsigned max_psk_len)) {
-  ssl->psk_client_callback = cb;
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->psk_client_callback = cb;
 }
 
 void SSL_CTX_set_psk_client_callback(
@@ -2412,7 +2430,10 @@
 void SSL_set_psk_server_callback(
     SSL *ssl, unsigned (*cb)(SSL *ssl, const char *identity, uint8_t *psk,
                              unsigned max_psk_len)) {
-  ssl->psk_server_callback = cb;
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->psk_server_callback = cb;
 }
 
 void SSL_CTX_set_psk_server_callback(
@@ -2422,11 +2443,14 @@
 }
 
 int SSL_set_dummy_pq_padding_size(SSL *ssl, size_t num_bytes) {
+  if (!ssl->config) {
+    return 0;
+  }
   if (num_bytes > 0xffff) {
     return 0;
   }
 
-  ssl->dummy_pq_padding_len = num_bytes;
+  ssl->config->dummy_pq_padding_len = num_bytes;
   return 1;
 }
 
@@ -2523,6 +2547,11 @@
 
 void SSL_set_renegotiate_mode(SSL *ssl, enum ssl_renegotiate_mode_t mode) {
   ssl->renegotiate_mode = mode;
+
+  // Check if |ssl_can_renegotiate| has changed and the configuration may now be
+  // shed. HTTP clients may initially allow renegotiation for HTTP/1.1, and then
+  // disable after the handshake once the ALPN protocol is known to be HTTP/2.
+  ssl_maybe_shed_handshake_config(ssl);
 }
 
 int SSL_get_ivs(const SSL *ssl, const uint8_t **out_read_iv,
@@ -2605,7 +2634,10 @@
 }
 
 void SSL_set_retain_only_sha256_of_client_certs(SSL *ssl, int enabled) {
-  ssl->retain_only_sha256_of_client_certs = !!enabled;
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->retain_only_sha256_of_client_certs = !!enabled;
 }
 
 void SSL_CTX_set_retain_only_sha256_of_client_certs(SSL_CTX *ctx, int enabled) {
@@ -2626,14 +2658,24 @@
 
 int SSL_is_draft_downgrade(const SSL *ssl) { return ssl->s3->draft_downgrade; }
 
+void SSL_set_shed_handshake_config(SSL *ssl, int enable) {
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->shed_handshake_config = !!enable;
+}
+
 int SSL_clear(SSL *ssl) {
+  if (!ssl->config) {
+    return 0;  // SSL_clear may not be used after shedding config.
+  }
+
   // In OpenSSL, reusing a client |SSL| with |SSL_clear| causes the previously
   // established session to be offered the next time around. wpa_supplicant
   // depends on this behavior, so emulate it.
   UniquePtr<SSL_SESSION> session;
   if (!ssl->server && ssl->s3->established_session != NULL) {
-    session.reset(ssl->s3->established_session.get());
-    SSL_SESSION_up_ref(session.get());
+    session = UpRef(ssl->s3->established_session);
   }
 
   // The ssl->d1->mtu is simultaneously configuration (preserved across
@@ -2707,3 +2749,36 @@
                                     const SSL_TICKET_AEAD_METHOD *aead_method) {
   ctx->ticket_aead_method = aead_method;
 }
+
+int SSL_set_tlsext_status_type(SSL *ssl, int type) {
+  if (!ssl->config) {
+    return 0;
+  }
+  ssl->config->ocsp_stapling_enabled = type == TLSEXT_STATUSTYPE_ocsp;
+  return 1;
+}
+
+int SSL_set_tlsext_status_ocsp_resp(SSL *ssl, uint8_t *resp, size_t resp_len) {
+  if (SSL_set_ocsp_response(ssl, resp, resp_len)) {
+    OPENSSL_free(resp);
+    return 1;
+  }
+  return 0;
+}
+
+size_t SSL_get_tlsext_status_ocsp_resp(const SSL *ssl, const uint8_t **out) {
+  size_t ret;
+  SSL_get0_ocsp_response(ssl, out, &ret);
+  return ret;
+}
+
+int SSL_CTX_set_tlsext_status_cb(SSL_CTX *ctx,
+                                 int (*callback)(SSL *ssl, void *arg)) {
+  ctx->legacy_ocsp_callback = callback;
+  return 1;
+}
+
+int SSL_CTX_set_tlsext_status_arg(SSL_CTX *ctx, void *arg) {
+  ctx->legacy_ocsp_callback_arg = arg;
+  return 1;
+}
diff --git a/src/ssl/ssl_privkey.cc b/src/ssl/ssl_privkey.cc
index bba03b7..fecac39 100644
--- a/src/ssl/ssl_privkey.cc
+++ b/src/ssl/ssl_privkey.cc
@@ -89,9 +89,7 @@
     return 0;
   }
 
-  EVP_PKEY_up_ref(pkey);
-  cert->privatekey.reset(pkey);
-
+  cert->privatekey = UpRef(pkey);
   return 1;
 }
 
@@ -134,8 +132,8 @@
   return NULL;
 }
 
-int ssl_has_private_key(const SSL *ssl) {
-  return ssl->cert->privatekey != nullptr || ssl->cert->key_method != nullptr;
+int ssl_has_private_key(const SSL_CONFIG *cfg) {
+  return cfg->cert->privatekey != nullptr || cfg->cert->key_method != nullptr;
 }
 
 static int pkey_supports_algorithm(const SSL *ssl, EVP_PKEY *pkey,
@@ -196,13 +194,13 @@
     SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, size_t max_out,
     uint16_t sigalg, Span<const uint8_t> in) {
   SSL *const ssl = hs->ssl;
-  if (ssl->cert->key_method != NULL) {
+  if (hs->config->cert->key_method != NULL) {
     enum ssl_private_key_result_t ret;
     if (hs->pending_private_key_op) {
-      ret = ssl->cert->key_method->complete(ssl, out, out_len, max_out);
+      ret = hs->config->cert->key_method->complete(ssl, out, out_len, max_out);
     } else {
-      ret = ssl->cert->key_method->sign(ssl, out, out_len, max_out, sigalg,
-                                        in.data(), in.size());
+      ret = hs->config->cert->key_method->sign(ssl, out, out_len, max_out,
+                                               sigalg, in.data(), in.size());
     }
     if (ret == ssl_private_key_failure) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PRIVATE_KEY_OPERATION_FAILED);
@@ -213,7 +211,7 @@
 
   *out_len = max_out;
   ScopedEVP_MD_CTX ctx;
-  if (!setup_ctx(ssl, ctx.get(), ssl->cert->privatekey.get(), sigalg,
+  if (!setup_ctx(ssl, ctx.get(), hs->config->cert->privatekey.get(), sigalg,
                  0 /* sign */) ||
       !EVP_DigestSign(ctx.get(), out, out_len, in.data(), in.size())) {
     return ssl_private_key_failure;
@@ -236,13 +234,13 @@
                                                       size_t max_out,
                                                       Span<const uint8_t> in) {
   SSL *const ssl = hs->ssl;
-  if (ssl->cert->key_method != NULL) {
+  if (hs->config->cert->key_method != NULL) {
     enum ssl_private_key_result_t ret;
     if (hs->pending_private_key_op) {
-      ret = ssl->cert->key_method->complete(ssl, out, out_len, max_out);
+      ret = hs->config->cert->key_method->complete(ssl, out, out_len, max_out);
     } else {
-      ret = ssl->cert->key_method->decrypt(ssl, out, out_len, max_out,
-                                           in.data(), in.size());
+      ret = hs->config->cert->key_method->decrypt(ssl, out, out_len, max_out,
+                                                  in.data(), in.size());
     }
     if (ret == ssl_private_key_failure) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PRIVATE_KEY_OPERATION_FAILED);
@@ -251,7 +249,7 @@
     return ret;
   }
 
-  RSA *rsa = EVP_PKEY_get0_RSA(ssl->cert->privatekey.get());
+  RSA *rsa = EVP_PKEY_get0_RSA(hs->config->cert->privatekey.get());
   if (rsa == NULL) {
     // Decrypt operations are only supported for RSA keys.
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
@@ -294,7 +292,7 @@
 using namespace bssl;
 
 int SSL_use_RSAPrivateKey(SSL *ssl, RSA *rsa) {
-  if (rsa == NULL) {
+  if (rsa == NULL || ssl->config == NULL) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER);
     return 0;
   }
@@ -306,7 +304,7 @@
     return 0;
   }
 
-  return ssl_set_pkey(ssl->cert, pkey.get());
+  return ssl_set_pkey(ssl->config->cert.get(), pkey.get());
 }
 
 int SSL_use_RSAPrivateKey_ASN1(SSL *ssl, const uint8_t *der, size_t der_len) {
@@ -320,12 +318,12 @@
 }
 
 int SSL_use_PrivateKey(SSL *ssl, EVP_PKEY *pkey) {
-  if (pkey == NULL) {
+  if (pkey == NULL || ssl->config == NULL) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER);
     return 0;
   }
 
-  return ssl_set_pkey(ssl->cert, pkey);
+  return ssl_set_pkey(ssl->config->cert.get(), pkey);
 }
 
 int SSL_use_PrivateKey_ASN1(int type, SSL *ssl, const uint8_t *der,
@@ -358,7 +356,7 @@
     return 0;
   }
 
-  return ssl_set_pkey(ctx->cert, pkey.get());
+  return ssl_set_pkey(ctx->cert.get(), pkey.get());
 }
 
 int SSL_CTX_use_RSAPrivateKey_ASN1(SSL_CTX *ctx, const uint8_t *der,
@@ -378,7 +376,7 @@
     return 0;
   }
 
-  return ssl_set_pkey(ctx->cert, pkey);
+  return ssl_set_pkey(ctx->cert.get(), pkey);
 }
 
 int SSL_CTX_use_PrivateKey_ASN1(int type, SSL_CTX *ctx, const uint8_t *der,
@@ -400,7 +398,10 @@
 
 void SSL_set_private_key_method(SSL *ssl,
                                 const SSL_PRIVATE_KEY_METHOD *key_method) {
-  ssl->cert->key_method = key_method;
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->cert->key_method = key_method;
 }
 
 void SSL_CTX_set_private_key_method(SSL_CTX *ctx,
@@ -408,38 +409,49 @@
   ctx->cert->key_method = key_method;
 }
 
+static constexpr size_t kMaxSignatureAlgorithmNameLen = 23;
+
+// This was "constexpr" rather than "const", but that triggered a bug in MSVC
+// where it didn't pad the strings to the correct length.
+static const struct {
+  uint16_t signature_algorithm;
+  const char name[kMaxSignatureAlgorithmNameLen];
+} kSignatureAlgorithmNames[] = {
+    {SSL_SIGN_RSA_PKCS1_MD5_SHA1, "rsa_pkcs1_md5_sha1"},
+    {SSL_SIGN_RSA_PKCS1_SHA1, "rsa_pkcs1_sha1"},
+    {SSL_SIGN_RSA_PKCS1_SHA256, "rsa_pkcs1_sha256"},
+    {SSL_SIGN_RSA_PKCS1_SHA384, "rsa_pkcs1_sha384"},
+    {SSL_SIGN_RSA_PKCS1_SHA512, "rsa_pkcs1_sha512"},
+    {SSL_SIGN_ECDSA_SHA1, "ecdsa_sha1"},
+    {SSL_SIGN_ECDSA_SECP256R1_SHA256, "ecdsa_secp256r1_sha256"},
+    {SSL_SIGN_ECDSA_SECP384R1_SHA384, "ecdsa_secp384r1_sha384"},
+    {SSL_SIGN_ECDSA_SECP521R1_SHA512, "ecdsa_secp521r1_sha512"},
+    {SSL_SIGN_RSA_PSS_RSAE_SHA256, "rsa_pss_rsae_sha256"},
+    {SSL_SIGN_RSA_PSS_RSAE_SHA384, "rsa_pss_rsae_sha384"},
+    {SSL_SIGN_RSA_PSS_RSAE_SHA512, "rsa_pss_rsae_sha512"},
+    {SSL_SIGN_ED25519, "ed25519"},
+};
+
 const char *SSL_get_signature_algorithm_name(uint16_t sigalg,
                                              int include_curve) {
-  switch (sigalg) {
-    case SSL_SIGN_RSA_PKCS1_MD5_SHA1:
-      return "rsa_pkcs1_md5_sha1";
-    case SSL_SIGN_RSA_PKCS1_SHA1:
-      return "rsa_pkcs1_sha1";
-    case SSL_SIGN_RSA_PKCS1_SHA256:
-      return "rsa_pkcs1_sha256";
-    case SSL_SIGN_RSA_PKCS1_SHA384:
-      return "rsa_pkcs1_sha384";
-    case SSL_SIGN_RSA_PKCS1_SHA512:
-      return "rsa_pkcs1_sha512";
-    case SSL_SIGN_ECDSA_SHA1:
-      return "ecdsa_sha1";
-    case SSL_SIGN_ECDSA_SECP256R1_SHA256:
-      return include_curve ? "ecdsa_secp256r1_sha256" : "ecdsa_sha256";
-    case SSL_SIGN_ECDSA_SECP384R1_SHA384:
-      return include_curve ? "ecdsa_secp384r1_sha384" : "ecdsa_sha384";
-    case SSL_SIGN_ECDSA_SECP521R1_SHA512:
-      return include_curve ? "ecdsa_secp521r1_sha512" : "ecdsa_sha512";
-    case SSL_SIGN_RSA_PSS_RSAE_SHA256:
-      return "rsa_pss_rsae_sha256";
-    case SSL_SIGN_RSA_PSS_RSAE_SHA384:
-      return "rsa_pss_rsae_sha384";
-    case SSL_SIGN_RSA_PSS_RSAE_SHA512:
-      return "rsa_pss_rsae_sha512";
-    case SSL_SIGN_ED25519:
-      return "ed25519";
-    default:
-      return NULL;
+  if (!include_curve) {
+    switch (sigalg) {
+      case SSL_SIGN_ECDSA_SECP256R1_SHA256:
+        return "ecdsa_sha256";
+      case SSL_SIGN_ECDSA_SECP384R1_SHA384:
+        return "ecdsa_sha384";
+      case SSL_SIGN_ECDSA_SECP521R1_SHA512:
+        return "ecdsa_sha512";
+    }
   }
+
+  for (const auto &candidate : kSignatureAlgorithmNames) {
+    if (candidate.signature_algorithm == sigalg) {
+      return candidate.name;
+    }
+  }
+
+  return NULL;
 }
 
 int SSL_get_signature_algorithm_key_type(uint16_t sigalg) {
@@ -460,21 +472,6 @@
   return alg != nullptr && alg->is_rsa_pss;
 }
 
-static int set_algorithm_prefs(uint16_t **out_prefs, size_t *out_num_prefs,
-                               const uint16_t *prefs, size_t num_prefs) {
-  OPENSSL_free(*out_prefs);
-
-  *out_num_prefs = 0;
-  *out_prefs = (uint16_t *)BUF_memdup(prefs, num_prefs * sizeof(prefs[0]));
-  if (*out_prefs == NULL) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return 0;
-  }
-  *out_num_prefs = num_prefs;
-
-  return 1;
-}
-
 int SSL_CTX_set_signing_algorithm_prefs(SSL_CTX *ctx, const uint16_t *prefs,
                                         size_t num_prefs) {
   return ctx->cert->sigalgs.CopyFrom(MakeConstSpan(prefs, num_prefs));
@@ -482,11 +479,322 @@
 
 int SSL_set_signing_algorithm_prefs(SSL *ssl, const uint16_t *prefs,
                                     size_t num_prefs) {
-  return ssl->cert->sigalgs.CopyFrom(MakeConstSpan(prefs, num_prefs));
+  if (!ssl->config) {
+    return 0;
+  }
+  return ssl->config->cert->sigalgs.CopyFrom(MakeConstSpan(prefs, num_prefs));
+}
+
+static constexpr struct {
+  int pkey_type;
+  int hash_nid;
+  uint16_t signature_algorithm;
+} kSignatureAlgorithmsMapping[] = {
+    {EVP_PKEY_RSA, NID_sha1, SSL_SIGN_RSA_PKCS1_SHA1},
+    {EVP_PKEY_RSA, NID_sha256, SSL_SIGN_RSA_PKCS1_SHA256},
+    {EVP_PKEY_RSA, NID_sha384, SSL_SIGN_RSA_PKCS1_SHA384},
+    {EVP_PKEY_RSA, NID_sha512, SSL_SIGN_RSA_PKCS1_SHA512},
+    {EVP_PKEY_RSA_PSS, NID_sha256, SSL_SIGN_RSA_PSS_RSAE_SHA256},
+    {EVP_PKEY_RSA_PSS, NID_sha384, SSL_SIGN_RSA_PSS_RSAE_SHA384},
+    {EVP_PKEY_RSA_PSS, NID_sha512, SSL_SIGN_RSA_PSS_RSAE_SHA512},
+    {EVP_PKEY_EC, NID_sha1, SSL_SIGN_ECDSA_SHA1},
+    {EVP_PKEY_EC, NID_sha256, SSL_SIGN_ECDSA_SECP256R1_SHA256},
+    {EVP_PKEY_EC, NID_sha384, SSL_SIGN_ECDSA_SECP384R1_SHA384},
+    {EVP_PKEY_EC, NID_sha512, SSL_SIGN_ECDSA_SECP521R1_SHA512},
+    {EVP_PKEY_ED25519, NID_undef, SSL_SIGN_ED25519},
+};
+
+static bool parse_sigalg_pairs(Array<uint16_t> *out, const int *values,
+                               size_t num_values) {
+  if ((num_values & 1) == 1) {
+    return false;
+  }
+
+  const size_t num_pairs = num_values / 2;
+  if (!out->Init(num_pairs)) {
+    return false;
+  }
+
+  for (size_t i = 0; i < num_values; i += 2) {
+    const int hash_nid = values[i];
+    const int pkey_type = values[i+1];
+
+    bool found = false;
+    for (const auto &candidate : kSignatureAlgorithmsMapping) {
+      if (candidate.pkey_type == pkey_type && candidate.hash_nid == hash_nid) {
+        (*out)[i / 2] = candidate.signature_algorithm;
+        found = true;
+        break;
+      }
+    }
+
+    if (!found) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+      ERR_add_error_dataf("unknown hash:%d pkey:%d", hash_nid, pkey_type);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static int compare_uint16_t(const void *p1, const void *p2) {
+  uint16_t u1 = *((const uint16_t *)p1);
+  uint16_t u2 = *((const uint16_t *)p2);
+  if (u1 < u2) {
+    return -1;
+  } else if (u1 > u2) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+static bool sigalgs_unique(Span<const uint16_t> in_sigalgs) {
+  Array<uint16_t> sigalgs;
+  if (!sigalgs.CopyFrom(in_sigalgs)) {
+    return false;
+  }
+
+  qsort(sigalgs.data(), sigalgs.size(), sizeof(uint16_t), compare_uint16_t);
+
+  for (size_t i = 1; i < sigalgs.size(); i++) {
+    if (sigalgs[i - 1] == sigalgs[i]) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DUPLICATE_SIGNATURE_ALGORITHM);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+int SSL_CTX_set1_sigalgs(SSL_CTX *ctx, const int *values, size_t num_values) {
+  Array<uint16_t> sigalgs;
+  if (!parse_sigalg_pairs(&sigalgs, values, num_values) ||
+      !sigalgs_unique(sigalgs)) {
+    return 0;
+  }
+
+  if (!SSL_CTX_set_signing_algorithm_prefs(ctx, sigalgs.data(),
+                                           sigalgs.size()) ||
+      !ctx->verify_sigalgs.CopyFrom(sigalgs)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+int SSL_set1_sigalgs(SSL *ssl, const int *values, size_t num_values) {
+  if (!ssl->config) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  Array<uint16_t> sigalgs;
+  if (!parse_sigalg_pairs(&sigalgs, values, num_values) ||
+      !sigalgs_unique(sigalgs)) {
+    return 0;
+  }
+
+  if (!SSL_set_signing_algorithm_prefs(ssl, sigalgs.data(), sigalgs.size()) ||
+      !ssl->config->verify_sigalgs.CopyFrom(sigalgs)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+static bool parse_sigalgs_list(Array<uint16_t> *out, const char *str) {
+  // str looks like "RSA+SHA1:ECDSA+SHA256:ecdsa_secp256r1_sha256".
+
+  // Count colons to give the number of output elements from any successful
+  // parse.
+  size_t num_elements = 1;
+  size_t len = 0;
+  for (const char *p = str; *p; p++) {
+    len++;
+    if (*p == ':') {
+      num_elements++;
+    }
+  }
+
+  if (!out->Init(num_elements)) {
+    return false;
+  }
+  size_t out_i = 0;
+
+  enum {
+    pkey_or_name,
+    hash_name,
+  } state = pkey_or_name;
+
+  char buf[kMaxSignatureAlgorithmNameLen];
+  // buf_used is always < sizeof(buf). I.e. it's always safe to write
+  // buf[buf_used] = 0.
+  size_t buf_used = 0;
+
+  int pkey_type = 0, hash_nid = 0;
+
+  // Note that the loop runs to len+1, i.e. it'll process the terminating NUL.
+  for (size_t offset = 0; offset < len+1; offset++) {
+    const char c = str[offset];
+
+    switch (c) {
+      case '+':
+        if (state == hash_name) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("+ found in hash name at offset %zu", offset);
+          return false;
+        }
+        if (buf_used == 0) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("empty public key type at offset %zu", offset);
+          return false;
+        }
+        buf[buf_used] = 0;
+
+        if (strcmp(buf, "RSA") == 0) {
+          pkey_type = EVP_PKEY_RSA;
+        } else if (strcmp(buf, "RSA-PSS") == 0 ||
+                   strcmp(buf, "PSS") == 0) {
+          pkey_type = EVP_PKEY_RSA_PSS;
+        } else if (strcmp(buf, "ECDSA") == 0) {
+          pkey_type = EVP_PKEY_EC;
+        } else {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("unknown public key type '%s'", buf);
+          return false;
+        }
+
+        state = hash_name;
+        buf_used = 0;
+        break;
+
+      case ':':
+        OPENSSL_FALLTHROUGH;
+      case 0:
+        if (buf_used == 0) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("empty element at offset %zu", offset);
+          return false;
+        }
+
+        buf[buf_used] = 0;
+
+        if (state == pkey_or_name) {
+          // No '+' was seen thus this is a TLS 1.3-style name.
+          bool found = false;
+          for (const auto &candidate : kSignatureAlgorithmNames) {
+            if (strcmp(candidate.name, buf) == 0) {
+              assert(out_i < num_elements);
+              (*out)[out_i++] = candidate.signature_algorithm;
+              found = true;
+              break;
+            }
+          }
+
+          if (!found) {
+            OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+            ERR_add_error_dataf("unknown signature algorithm '%s'", buf);
+            return false;
+          }
+        } else {
+          if (strcmp(buf, "SHA1") == 0) {
+            hash_nid = NID_sha1;
+          } else if (strcmp(buf, "SHA256") == 0) {
+            hash_nid = NID_sha256;
+          } else if (strcmp(buf, "SHA384") == 0) {
+            hash_nid = NID_sha384;
+          } else if (strcmp(buf, "SHA512") == 0) {
+            hash_nid = NID_sha512;
+          } else {
+            OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+            ERR_add_error_dataf("unknown hash function '%s'", buf);
+            return false;
+          }
+
+          bool found = false;
+          for (const auto &candidate : kSignatureAlgorithmsMapping) {
+            if (candidate.pkey_type == pkey_type &&
+                candidate.hash_nid == hash_nid) {
+              assert(out_i < num_elements);
+              (*out)[out_i++] = candidate.signature_algorithm;
+              found = true;
+              break;
+            }
+          }
+
+          if (!found) {
+            OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+            ERR_add_error_dataf("unknown pkey:%d hash:%s", pkey_type, buf);
+            return false;
+          }
+        }
+
+        state = pkey_or_name;
+        buf_used = 0;
+        break;
+
+      default:
+        if (buf_used == sizeof(buf) - 1) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("substring too long at offset %zu", offset);
+          return false;
+        }
+
+        if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
+            (c >= 'A' && c <= 'Z') || c == '-' || c == '_') {
+          buf[buf_used++] = c;
+        } else {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+          ERR_add_error_dataf("invalid character 0x%02x at offest %zu", c,
+                              offset);
+          return false;
+        }
+    }
+  }
+
+  assert(out_i == out->size());
+  return true;
+}
+
+int SSL_CTX_set1_sigalgs_list(SSL_CTX *ctx, const char *str) {
+  Array<uint16_t> sigalgs;
+  if (!parse_sigalgs_list(&sigalgs, str) ||
+      !sigalgs_unique(sigalgs)) {
+    return 0;
+  }
+
+  if (!SSL_CTX_set_signing_algorithm_prefs(ctx, sigalgs.data(),
+                                           sigalgs.size()) ||
+      !ctx->verify_sigalgs.CopyFrom(sigalgs)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+int SSL_set1_sigalgs_list(SSL *ssl, const char *str) {
+  if (!ssl->config) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  Array<uint16_t> sigalgs;
+  if (!parse_sigalgs_list(&sigalgs, str) ||
+      !sigalgs_unique(sigalgs)) {
+    return 0;
+  }
+
+  if (!SSL_set_signing_algorithm_prefs(ssl, sigalgs.data(), sigalgs.size()) ||
+      !ssl->config->verify_sigalgs.CopyFrom(sigalgs)) {
+    return 0;
+  }
+
+  return 1;
 }
 
 int SSL_CTX_set_verify_algorithm_prefs(SSL_CTX *ctx, const uint16_t *prefs,
                                        size_t num_prefs) {
-  return set_algorithm_prefs(&ctx->verify_sigalgs, &ctx->num_verify_sigalgs,
-                             prefs, num_prefs);
+  return ctx->verify_sigalgs.CopyFrom(MakeConstSpan(prefs, num_prefs));
 }
diff --git a/src/ssl/ssl_session.cc b/src/ssl/ssl_session.cc
index 272fc55..70be17e 100644
--- a/src/ssl/ssl_session.cc
+++ b/src/ssl/ssl_session.cc
@@ -166,22 +166,27 @@
 static int remove_session_lock(SSL_CTX *ctx, SSL_SESSION *session, int lock);
 
 UniquePtr<SSL_SESSION> ssl_session_new(const SSL_X509_METHOD *x509_method) {
-  UniquePtr<SSL_SESSION> session(
-      (SSL_SESSION *)OPENSSL_malloc(sizeof(SSL_SESSION)));
-  if (!session) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return 0;
-  }
-  OPENSSL_memset(session.get(), 0, sizeof(SSL_SESSION));
+  return MakeUnique<SSL_SESSION>(x509_method);
+}
 
-  session->x509_method = x509_method;
-  session->verify_result = X509_V_ERR_INVALID_CALL;
-  session->references = 1;
-  session->timeout = SSL_DEFAULT_SESSION_TIMEOUT;
-  session->auth_timeout = SSL_DEFAULT_SESSION_TIMEOUT;
-  session->time = time(NULL);
-  CRYPTO_new_ex_data(&session->ex_data);
-  return session;
+uint32_t ssl_hash_session_id(Span<const uint8_t> session_id) {
+  // Take the first four bytes of |session_id|. Session IDs are generated by the
+  // server randomly, so we can assume even using the first four bytes results
+  // in a good distribution.
+  uint8_t tmp_storage[sizeof(uint32_t)];
+  if (session_id.size() < sizeof(tmp_storage)) {
+    OPENSSL_memset(tmp_storage, 0, sizeof(tmp_storage));
+    OPENSSL_memcpy(tmp_storage, session_id.data(), session_id.size());
+    session_id = tmp_storage;
+  }
+
+  uint32_t hash =
+      ((uint32_t)session_id[0]) |
+      ((uint32_t)session_id[1] << 8) |
+      ((uint32_t)session_id[2] << 16) |
+      ((uint32_t)session_id[3] << 24);
+
+  return hash;
 }
 
 UniquePtr<SSL_SESSION> SSL_SESSION_dup(SSL_SESSION *session, int dup_flags) {
@@ -202,24 +207,22 @@
   new_session->cipher = session->cipher;
 
   // Copy authentication state.
-  if (session->psk_identity != NULL) {
-    new_session->psk_identity = BUF_strdup(session->psk_identity);
-    if (new_session->psk_identity == NULL) {
+  if (session->psk_identity != nullptr) {
+    new_session->psk_identity.reset(BUF_strdup(session->psk_identity.get()));
+    if (new_session->psk_identity == nullptr) {
       return nullptr;
     }
   }
-  if (session->certs != NULL) {
-    new_session->certs = sk_CRYPTO_BUFFER_new_null();
-    if (new_session->certs == NULL) {
+  if (session->certs != nullptr) {
+    auto buf_up_ref = [](CRYPTO_BUFFER *buf) {
+      CRYPTO_BUFFER_up_ref(buf);
+      return buf;
+    };
+    new_session->certs.reset(sk_CRYPTO_BUFFER_deep_copy(
+        session->certs.get(), buf_up_ref, CRYPTO_BUFFER_free));
+    if (new_session->certs == nullptr) {
       return nullptr;
     }
-    for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(session->certs); i++) {
-      CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(session->certs, i);
-      if (!sk_CRYPTO_BUFFER_push(new_session->certs, buffer)) {
-        return nullptr;
-      }
-      CRYPTO_BUFFER_up_ref(buffer);
-    }
   }
 
   if (!session->x509_method->session_dup(new_session.get(), session)) {
@@ -228,16 +231,9 @@
 
   new_session->verify_result = session->verify_result;
 
-  if (session->ocsp_response != NULL) {
-    new_session->ocsp_response = session->ocsp_response;
-    CRYPTO_BUFFER_up_ref(new_session->ocsp_response);
-  }
-
-  if (session->signed_cert_timestamp_list != NULL) {
-    new_session->signed_cert_timestamp_list =
-        session->signed_cert_timestamp_list;
-    CRYPTO_BUFFER_up_ref(new_session->signed_cert_timestamp_list);
-  }
+  new_session->ocsp_response = UpRef(session->ocsp_response);
+  new_session->signed_cert_timestamp_list =
+      UpRef(session->signed_cert_timestamp_list);
 
   OPENSSL_memcpy(new_session->peer_sha256, session->peer_sha256,
                  SHA256_DIGEST_LENGTH);
@@ -262,36 +258,25 @@
                    session->original_handshake_hash_len);
     new_session->original_handshake_hash_len =
         session->original_handshake_hash_len;
-    new_session->tlsext_tick_lifetime_hint = session->tlsext_tick_lifetime_hint;
+    new_session->ticket_lifetime_hint = session->ticket_lifetime_hint;
     new_session->ticket_age_add = session->ticket_age_add;
     new_session->ticket_max_early_data = session->ticket_max_early_data;
     new_session->extended_master_secret = session->extended_master_secret;
 
-    if (session->early_alpn != NULL) {
-      new_session->early_alpn =
-          (uint8_t *)BUF_memdup(session->early_alpn, session->early_alpn_len);
-      if (new_session->early_alpn == NULL) {
-        return nullptr;
-      }
+    if (!new_session->early_alpn.CopyFrom(session->early_alpn)) {
+      return nullptr;
     }
-    new_session->early_alpn_len = session->early_alpn_len;
   }
 
   // Copy the ticket.
-  if (dup_flags & SSL_SESSION_INCLUDE_TICKET) {
-    if (session->tlsext_tick != NULL) {
-      new_session->tlsext_tick =
-          (uint8_t *)BUF_memdup(session->tlsext_tick, session->tlsext_ticklen);
-      if (new_session->tlsext_tick == NULL) {
-        return nullptr;
-      }
-    }
-    new_session->tlsext_ticklen = session->tlsext_ticklen;
+  if (dup_flags & SSL_SESSION_INCLUDE_TICKET &&
+      !new_session->ticket.CopyFrom(session->ticket)) {
+    return nullptr;
   }
 
   // The new_session does not get a copy of the ex_data.
 
-  new_session->not_resumable = 1;
+  new_session->not_resumable = true;
   return new_session;
 }
 
@@ -405,16 +390,16 @@
     session->session_id_length = 0;
   }
 
-  if (ssl->cert->sid_ctx_length > sizeof(session->sid_ctx)) {
+  if (hs->config->cert->sid_ctx_length > sizeof(session->sid_ctx)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return 0;
   }
-  OPENSSL_memcpy(session->sid_ctx, ssl->cert->sid_ctx,
-                 ssl->cert->sid_ctx_length);
-  session->sid_ctx_length = ssl->cert->sid_ctx_length;
+  OPENSSL_memcpy(session->sid_ctx, hs->config->cert->sid_ctx,
+                 hs->config->cert->sid_ctx_length);
+  session->sid_ctx_length = hs->config->cert->sid_ctx_length;
 
   // The session is marked not resumable until it is completely filled in.
-  session->not_resumable = 1;
+  session->not_resumable = true;
   session->verify_result = X509_V_ERR_INVALID_CALL;
 
   hs->new_session = std::move(session);
@@ -429,53 +414,50 @@
     // Avoid acquiring a write lock in the common case (i.e. a non-default key
     // is used or the default keys have not expired yet).
     MutexReadLock lock(&ctx->lock);
-    if (ctx->tlsext_ticket_key_current &&
-        (ctx->tlsext_ticket_key_current->next_rotation_tv_sec == 0 ||
-         ctx->tlsext_ticket_key_current->next_rotation_tv_sec > now.tv_sec) &&
-        (!ctx->tlsext_ticket_key_prev ||
-         ctx->tlsext_ticket_key_prev->next_rotation_tv_sec > now.tv_sec)) {
+    if (ctx->ticket_key_current &&
+        (ctx->ticket_key_current->next_rotation_tv_sec == 0 ||
+         ctx->ticket_key_current->next_rotation_tv_sec > now.tv_sec) &&
+        (!ctx->ticket_key_prev ||
+         ctx->ticket_key_prev->next_rotation_tv_sec > now.tv_sec)) {
       return 1;
     }
   }
 
   MutexWriteLock lock(&ctx->lock);
-  if (!ctx->tlsext_ticket_key_current ||
-      (ctx->tlsext_ticket_key_current->next_rotation_tv_sec != 0 &&
-       ctx->tlsext_ticket_key_current->next_rotation_tv_sec <= now.tv_sec)) {
+  if (!ctx->ticket_key_current ||
+      (ctx->ticket_key_current->next_rotation_tv_sec != 0 &&
+       ctx->ticket_key_current->next_rotation_tv_sec <= now.tv_sec)) {
     // The current key has not been initialized or it is expired.
-    auto new_key = bssl::MakeUnique<struct tlsext_ticket_key>();
+    auto new_key = bssl::MakeUnique<TicketKey>();
     if (!new_key) {
       return 0;
     }
-    OPENSSL_memset(new_key.get(), 0, sizeof(struct tlsext_ticket_key));
-    if (ctx->tlsext_ticket_key_current) {
+    RAND_bytes(new_key->name, 16);
+    RAND_bytes(new_key->hmac_key, 16);
+    RAND_bytes(new_key->aes_key, 16);
+    new_key->next_rotation_tv_sec =
+        now.tv_sec + SSL_DEFAULT_TICKET_KEY_ROTATION_INTERVAL;
+    if (ctx->ticket_key_current) {
       // The current key expired. Rotate it to prev and bump up its rotation
       // timestamp. Note that even with the new rotation time it may still be
-      // expired and get droppped below.
-      ctx->tlsext_ticket_key_current->next_rotation_tv_sec +=
+      // expired and get dropped below.
+      ctx->ticket_key_current->next_rotation_tv_sec +=
           SSL_DEFAULT_TICKET_KEY_ROTATION_INTERVAL;
-      OPENSSL_free(ctx->tlsext_ticket_key_prev);
-      ctx->tlsext_ticket_key_prev = ctx->tlsext_ticket_key_current;
+      ctx->ticket_key_prev = std::move(ctx->ticket_key_current);
     }
-    ctx->tlsext_ticket_key_current = new_key.release();
-    RAND_bytes(ctx->tlsext_ticket_key_current->name, 16);
-    RAND_bytes(ctx->tlsext_ticket_key_current->hmac_key, 16);
-    RAND_bytes(ctx->tlsext_ticket_key_current->aes_key, 16);
-    ctx->tlsext_ticket_key_current->next_rotation_tv_sec =
-        now.tv_sec + SSL_DEFAULT_TICKET_KEY_ROTATION_INTERVAL;
+    ctx->ticket_key_current = std::move(new_key);
   }
 
   // Drop an expired prev key.
-  if (ctx->tlsext_ticket_key_prev &&
-      ctx->tlsext_ticket_key_prev->next_rotation_tv_sec <= now.tv_sec) {
-    OPENSSL_free(ctx->tlsext_ticket_key_prev);
-    ctx->tlsext_ticket_key_prev = nullptr;
+  if (ctx->ticket_key_prev &&
+      ctx->ticket_key_prev->next_rotation_tv_sec <= now.tv_sec) {
+    ctx->ticket_key_prev.reset();
   }
 
   return 1;
 }
 
-static int ssl_encrypt_ticket_with_cipher_ctx(SSL *ssl, CBB *out,
+static int ssl_encrypt_ticket_with_cipher_ctx(SSL_HANDSHAKE *hs, CBB *out,
                                               const uint8_t *session_buf,
                                               size_t session_len) {
   ScopedEVP_CIPHER_CTX ctx;
@@ -493,12 +475,12 @@
 
   // Initialize HMAC and cipher contexts. If callback present it does all the
   // work otherwise use generated values from parent ctx.
-  SSL_CTX *tctx = ssl->session_ctx;
+  SSL_CTX *tctx = hs->ssl->session_ctx.get();
   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.get(), hctx.get(),
-                                   1 /* encrypt */) < 0) {
+  if (tctx->ticket_key_cb != NULL) {
+    if (tctx->ticket_key_cb(hs->ssl, key_name, iv, ctx.get(), hctx.get(),
+                            1 /* encrypt */) < 0) {
       return 0;
     }
   } else {
@@ -509,12 +491,12 @@
     MutexReadLock lock(&tctx->lock);
     if (!RAND_bytes(iv, 16) ||
         !EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cbc(), NULL,
-                            tctx->tlsext_ticket_key_current->aes_key, iv) ||
-        !HMAC_Init_ex(hctx.get(), tctx->tlsext_ticket_key_current->hmac_key, 16,
+                            tctx->ticket_key_current->aes_key, iv) ||
+        !HMAC_Init_ex(hctx.get(), tctx->ticket_key_current->hmac_key, 16,
                       tlsext_tick_md(), NULL)) {
       return 0;
     }
-    OPENSSL_memcpy(key_name, tctx->tlsext_ticket_key_current->name, 16);
+    OPENSSL_memcpy(key_name, tctx->ticket_key_current->name, 16);
   }
 
   uint8_t *ptr;
@@ -554,9 +536,10 @@
   return 1;
 }
 
-static int ssl_encrypt_ticket_with_method(SSL *ssl, CBB *out,
+static int ssl_encrypt_ticket_with_method(SSL_HANDSHAKE *hs, CBB *out,
                                           const uint8_t *session_buf,
                                           size_t session_len) {
+  SSL *const ssl = hs->ssl;
   const SSL_TICKET_AEAD_METHOD *method = ssl->session_ctx->ticket_aead_method;
   const size_t max_overhead = method->max_overhead(ssl);
   const size_t max_out = session_len + max_overhead;
@@ -571,7 +554,8 @@
   }
 
   size_t out_len;
-  if (!method->seal(ssl, ptr, &out_len, max_out, session_buf, session_len)) {
+  if (!method->seal(ssl, ptr, &out_len, max_out, session_buf,
+                    session_len)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_TICKET_ENCRYPTION_FAILED);
     return 0;
   }
@@ -583,7 +567,8 @@
   return 1;
 }
 
-int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session) {
+int ssl_encrypt_ticket(SSL_HANDSHAKE *hs, CBB *out,
+                       const SSL_SESSION *session) {
   // Serialize the SSL_SESSION to be encoded into the ticket.
   uint8_t *session_buf = NULL;
   size_t session_len;
@@ -592,25 +577,25 @@
   }
 
   int ret = 0;
-  if (ssl->session_ctx->ticket_aead_method) {
-    ret = ssl_encrypt_ticket_with_method(ssl, out, session_buf, session_len);
+  if (hs->ssl->session_ctx->ticket_aead_method) {
+    ret = ssl_encrypt_ticket_with_method(hs, out, session_buf, session_len);
   } else {
-    ret =
-        ssl_encrypt_ticket_with_cipher_ctx(ssl, out, session_buf, session_len);
+    ret = ssl_encrypt_ticket_with_cipher_ctx(hs, out, session_buf, session_len);
   }
 
   OPENSSL_free(session_buf);
   return ret;
 }
 
-int ssl_session_is_context_valid(const SSL *ssl, const SSL_SESSION *session) {
+int ssl_session_is_context_valid(const SSL_HANDSHAKE *hs,
+                                 const SSL_SESSION *session) {
   if (session == NULL) {
     return 0;
   }
 
-  return session->sid_ctx_length == ssl->cert->sid_ctx_length &&
-         OPENSSL_memcmp(session->sid_ctx, ssl->cert->sid_ctx,
-                        ssl->cert->sid_ctx_length) == 0;
+  return session->sid_ctx_length == hs->config->cert->sid_ctx_length &&
+         OPENSSL_memcmp(session->sid_ctx, hs->config->cert->sid_ctx,
+                        hs->config->cert->sid_ctx_length) == 0;
 }
 
 int ssl_session_is_time_valid(const SSL *ssl, const SSL_SESSION *session) {
@@ -632,34 +617,35 @@
 int ssl_session_is_resumable(const SSL_HANDSHAKE *hs,
                              const SSL_SESSION *session) {
   const SSL *const ssl = hs->ssl;
-  return ssl_session_is_context_valid(ssl, session) &&
+  return ssl_session_is_context_valid(hs, session) &&
          // The session must have been created by the same type of end point as
          // we're now using it with.
          ssl->server == session->is_server &&
          // The session must not be expired.
          ssl_session_is_time_valid(ssl, session) &&
          /* Only resume if the session's version matches the negotiated
-           * version. */
+          * version. */
          ssl->version == session->ssl_version &&
          // Only resume if the session's cipher matches the negotiated one.
          hs->new_cipher == session->cipher &&
          // If the session contains a client certificate (either the full
          // certificate or just the hash) then require that the form of the
          // certificate matches the current configuration.
-         ((sk_CRYPTO_BUFFER_num(session->certs) == 0 &&
+         ((sk_CRYPTO_BUFFER_num(session->certs.get()) == 0 &&
            !session->peer_sha256_valid) ||
           session->peer_sha256_valid ==
-              ssl->retain_only_sha256_of_client_certs);
+              hs->config->retain_only_sha256_of_client_certs);
 }
 
 // ssl_lookup_session looks up |session_id| in the session cache and sets
 // |*out_session| to an |SSL_SESSION| object if found.
 static enum ssl_hs_wait_t ssl_lookup_session(
-    SSL *ssl, UniquePtr<SSL_SESSION> *out_session, const uint8_t *session_id,
-    size_t session_id_len) {
+    SSL_HANDSHAKE *hs, UniquePtr<SSL_SESSION> *out_session,
+    Span<const uint8_t> session_id) {
+  SSL *const ssl = hs->ssl;
   out_session->reset();
 
-  if (session_id_len == 0 || session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH) {
+  if (session_id.empty() || session_id.size() > SSL_MAX_SSL_SESSION_ID_LENGTH) {
     return ssl_hs_ok;
   }
 
@@ -667,25 +653,26 @@
   // Try the internal cache, if it exists.
   if (!(ssl->session_ctx->session_cache_mode &
         SSL_SESS_CACHE_NO_INTERNAL_LOOKUP)) {
-    SSL_SESSION data;
-    data.ssl_version = ssl->version;
-    data.session_id_length = session_id_len;
-    OPENSSL_memcpy(data.session_id, session_id, session_id_len);
-
+    uint32_t hash = ssl_hash_session_id(session_id);
+    auto cmp = [](const void *key, const SSL_SESSION *sess) -> int {
+      Span<const uint8_t> key_id =
+          *reinterpret_cast<const Span<const uint8_t> *>(key);
+      Span<const uint8_t> sess_id =
+          MakeConstSpan(sess->session_id, sess->session_id_length);
+      return key_id == sess_id ? 0 : 1;
+    };
     MutexReadLock lock(&ssl->session_ctx->lock);
-    session.reset(lh_SSL_SESSION_retrieve(ssl->session_ctx->sessions, &data));
-    if (session) {
-      // |lh_SSL_SESSION_retrieve| returns a non-owning pointer.
-      SSL_SESSION_up_ref(session.get());
-    }
+    // |lh_SSL_SESSION_retrieve_key| returns a non-owning pointer.
+    session = UpRef(lh_SSL_SESSION_retrieve_key(ssl->session_ctx->sessions,
+                                                &session_id, hash, cmp));
     // TODO(davidben): This should probably move it to the front of the list.
   }
 
   // Fall back to the external cache, if it exists.
   if (!session && ssl->session_ctx->get_session_cb != nullptr) {
     int copy = 1;
-    session.reset(ssl->session_ctx->get_session_cb(ssl, session_id,
-                                                   session_id_len, &copy));
+    session.reset(ssl->session_ctx->get_session_cb(ssl, session_id.data(),
+                                                   session_id.size(), &copy));
     if (!session) {
       return ssl_hs_ok;
     }
@@ -706,13 +693,13 @@
     // Add the externally cached session to the internal cache if necessary.
     if (!(ssl->session_ctx->session_cache_mode &
           SSL_SESS_CACHE_NO_INTERNAL_STORE)) {
-      SSL_CTX_add_session(ssl->session_ctx, session.get());
+      SSL_CTX_add_session(ssl->session_ctx.get(), session.get());
     }
   }
 
   if (session && !ssl_session_is_time_valid(ssl, session.get())) {
     // The session was from the cache, so remove it.
-    SSL_CTX_remove_session(ssl->session_ctx, session.get());
+    SSL_CTX_remove_session(ssl->session_ctx.get(), session.get());
     session.reset();
   }
 
@@ -720,13 +707,13 @@
   return ssl_hs_ok;
 }
 
-enum ssl_hs_wait_t ssl_get_prev_session(SSL *ssl,
+enum ssl_hs_wait_t ssl_get_prev_session(SSL_HANDSHAKE *hs,
                                         UniquePtr<SSL_SESSION> *out_session,
                                         bool *out_tickets_supported,
                                         bool *out_renew_ticket,
                                         const SSL_CLIENT_HELLO *client_hello) {
   // This is used only by servers.
-  assert(ssl->server);
+  assert(hs->ssl->server);
   UniquePtr<SSL_SESSION> session;
   bool renew_ticket = false;
 
@@ -734,12 +721,11 @@
   const uint8_t *ticket = NULL;
   size_t ticket_len = 0;
   const bool tickets_supported =
-      !(SSL_get_options(ssl) & SSL_OP_NO_TICKET) &&
-      ssl->version > SSL3_VERSION &&
+      !(SSL_get_options(hs->ssl) & SSL_OP_NO_TICKET) &&
       SSL_early_callback_ctx_extension_get(
           client_hello, TLSEXT_TYPE_session_ticket, &ticket, &ticket_len);
   if (tickets_supported && ticket_len > 0) {
-    switch (ssl_process_ticket(ssl, &session, &renew_ticket, ticket, ticket_len,
+    switch (ssl_process_ticket(hs, &session, &renew_ticket, ticket, ticket_len,
                                client_hello->session_id,
                                client_hello->session_id_len)) {
       case ssl_ticket_aead_success:
@@ -755,7 +741,8 @@
   } else {
     // The client didn't send a ticket, so the session ID is a real ID.
     enum ssl_hs_wait_t lookup_ret = ssl_lookup_session(
-        ssl, &session, client_hello->session_id, client_hello->session_id_len);
+        hs, &session,
+        MakeConstSpan(client_hello->session_id, client_hello->session_id_len));
     if (lookup_ret != ssl_hs_ok) {
       return lookup_ret;
     }
@@ -798,15 +785,11 @@
 }
 
 void ssl_set_session(SSL *ssl, SSL_SESSION *session) {
-  if (ssl->session == session) {
+  if (ssl->session.get() == session) {
     return;
   }
 
-  SSL_SESSION_free(ssl->session);
-  ssl->session = session;
-  if (session != NULL) {
-    SSL_SESSION_up_ref(session);
-  }
+  ssl->session = UpRef(session);
 }
 
 // locked by SSL_CTX in the calling function
@@ -860,6 +843,22 @@
 
 using namespace bssl;
 
+ssl_session_st::ssl_session_st(const SSL_X509_METHOD *method)
+    : x509_method(method),
+      extended_master_secret(false),
+      peer_sha256_valid(false),
+      not_resumable(false),
+      ticket_age_add_valid(false),
+      is_server(false) {
+  CRYPTO_new_ex_data(&ex_data);
+  time = ::time(nullptr);
+}
+
+ssl_session_st::~ssl_session_st() {
+  CRYPTO_free_ex_data(&g_ex_data_class, this, &ex_data);
+  x509_method->session_clear(this);
+}
+
 SSL_SESSION *SSL_SESSION_new(const SSL_CTX *ctx) {
   return ssl_session_new(ctx->x509_method).release();
 }
@@ -875,17 +874,7 @@
     return;
   }
 
-  CRYPTO_free_ex_data(&g_ex_data_class, session, &session->ex_data);
-
-  OPENSSL_cleanse(session->master_key, sizeof(session->master_key));
-  OPENSSL_cleanse(session->session_id, sizeof(session->session_id));
-  sk_CRYPTO_BUFFER_pop_free(session->certs, CRYPTO_BUFFER_free);
-  session->x509_method->session_clear(session);
-  OPENSSL_free(session->tlsext_tick);
-  CRYPTO_BUFFER_free(session->signed_cert_timestamp_list);
-  CRYPTO_BUFFER_free(session->ocsp_response);
-  OPENSSL_free(session->psk_identity);
-  OPENSSL_free(session->early_alpn);
+  session->~ssl_session_st();
   OPENSSL_free(session);
 }
 
@@ -897,6 +886,19 @@
   return session->session_id;
 }
 
+int SSL_SESSION_set1_id(SSL_SESSION *session, const uint8_t *sid,
+                        size_t sid_len) {
+  if (sid_len > SSL_MAX_SSL_SESSION_ID_LENGTH) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_SESSION_ID_TOO_LONG);
+    return 0;
+  }
+
+  // Use memmove in case someone passes in the output of |SSL_SESSION_get_id|.
+  OPENSSL_memmove(session->session_id, sid, sid_len);
+  session->session_id_length = sid_len;
+  return 1;
+}
+
 uint32_t SSL_SESSION_get_timeout(const SSL_SESSION *session) {
   return session->timeout;
 }
@@ -915,7 +917,30 @@
 
 const STACK_OF(CRYPTO_BUFFER) *
     SSL_SESSION_get0_peer_certificates(const SSL_SESSION *session) {
-  return session->certs;
+  return session->certs.get();
+}
+
+void SSL_SESSION_get0_signed_cert_timestamp_list(const SSL_SESSION *session,
+                                                 const uint8_t **out,
+                                                 size_t *out_len) {
+  if (session->signed_cert_timestamp_list) {
+    *out = CRYPTO_BUFFER_data(session->signed_cert_timestamp_list.get());
+    *out_len = CRYPTO_BUFFER_len(session->signed_cert_timestamp_list.get());
+  } else {
+    *out = nullptr;
+    *out_len = 0;
+  }
+}
+
+void SSL_SESSION_get0_ocsp_response(const SSL_SESSION *session,
+                                    const uint8_t **out, size_t *out_len) {
+  if (session->ocsp_response) {
+    *out = CRYPTO_BUFFER_data(session->ocsp_response.get());
+    *out_len = CRYPTO_BUFFER_len(session->ocsp_response.get());
+  } else {
+    *out = nullptr;
+    *out_len = 0;
+  }
 }
 
 size_t SSL_SESSION_get_master_key(const SSL_SESSION *session, uint8_t *out,
@@ -950,6 +975,14 @@
   return 1;
 }
 
+const uint8_t *SSL_SESSION_get0_id_context(const SSL_SESSION *session,
+                                           unsigned *out_len) {
+  if (out_len != NULL) {
+    *out_len = session->sid_ctx_length;
+  }
+  return session->sid_ctx;
+}
+
 int SSL_SESSION_set1_id_context(SSL_SESSION *session, const uint8_t *sid_ctx,
                                 size_t sid_ctx_len) {
   if (sid_ctx_len > sizeof(session->sid_ctx)) {
@@ -973,25 +1006,45 @@
 }
 
 int SSL_SESSION_has_ticket(const SSL_SESSION *session) {
-  return session->tlsext_ticklen > 0;
+  return !session->ticket.empty();
 }
 
 void SSL_SESSION_get0_ticket(const SSL_SESSION *session,
                              const uint8_t **out_ticket, size_t *out_len) {
   if (out_ticket != nullptr) {
-    *out_ticket = session->tlsext_tick;
+    *out_ticket = session->ticket.data();
   }
-  *out_len = session->tlsext_ticklen;
+  *out_len = session->ticket.size();
+}
+
+int SSL_SESSION_set_ticket(SSL_SESSION *session, const uint8_t *ticket,
+                           size_t ticket_len) {
+  return session->ticket.CopyFrom(MakeConstSpan(ticket, ticket_len));
 }
 
 uint32_t SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *session) {
-  return session->tlsext_tick_lifetime_hint;
+  return session->ticket_lifetime_hint;
 }
 
 const SSL_CIPHER *SSL_SESSION_get0_cipher(const SSL_SESSION *session) {
   return session->cipher;
 }
 
+int SSL_SESSION_has_peer_sha256(const SSL_SESSION *session) {
+  return session->peer_sha256_valid;
+}
+
+void SSL_SESSION_get0_peer_sha256(const SSL_SESSION *session,
+                                  const uint8_t **out_ptr, size_t *out_len) {
+  if (session->peer_sha256_valid) {
+    *out_ptr = session->peer_sha256;
+    *out_len = sizeof(session->peer_sha256);
+  } else {
+    *out_ptr = nullptr;
+    *out_len = 0;
+  }
+}
+
 SSL_SESSION *SSL_magic_pending_session_ptr(void) {
   return (SSL_SESSION *)&g_pending_session_magic;
 }
@@ -1010,7 +1063,7 @@
   if (hs->new_session) {
     return hs->new_session.get();
   }
-  return ssl->session;
+  return ssl->session.get();
 }
 
 SSL_SESSION *SSL_get1_session(SSL *ssl) {
@@ -1044,8 +1097,7 @@
 int SSL_CTX_add_session(SSL_CTX *ctx, SSL_SESSION *session) {
   // Although |session| is inserted into two structures (a doubly-linked list
   // and the hash table), |ctx| only takes one reference.
-  SSL_SESSION_up_ref(session);
-  UniquePtr<SSL_SESSION> owned_session(session);
+  UniquePtr<SSL_SESSION> owned_session = UpRef(session);
 
   SSL_SESSION *old_session;
   MutexWriteLock lock(&ctx->lock);
diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc
index 5fb4fb4..74c4e9e 100644
--- a/src/ssl/ssl_test.cc
+++ b/src/ssl/ssl_test.cc
@@ -48,6 +48,10 @@
 #include <sys/time.h>
 #endif
 
+#if !defined(OPENSSL_NO_THREADS)
+#include <thread>
+#endif
+
 
 namespace bssl {
 
@@ -71,7 +75,6 @@
 static const size_t kTicketKeyLen = 48;
 
 static const VersionParam kAllVersions[] = {
-    {SSL3_VERSION, VersionParam::is_tls, "SSL3"},
     {TLS1_VERSION, VersionParam::is_tls, "TLS1"},
     {TLS1_1_VERSION, VersionParam::is_tls, "TLS1_1"},
     {TLS1_2_VERSION, VersionParam::is_tls, "TLS1_2"},
@@ -227,7 +230,7 @@
     {
         // To simplify things, banish all but {ECDHE_RSA,RSA} x
         // {CHACHA20,AES_256_CBC,AES_128_CBC} x SHA1.
-        "!AESGCM:!3DES:!SHA256:!SHA384:"
+        "!AESGCM:!3DES:"
         // Order some ciphers backwards by strength.
         "ALL:-CHACHA20:-AES256:-AES128:-ALL:"
         // Select ECDHE ones and sort them by strength. Ties should resolve
@@ -286,15 +289,15 @@
     },
     // SSLv3 matches everything that existed before TLS 1.2.
     {
-        "AES128-SHA:AES128-SHA256:!SSLv3",
+        "AES128-SHA:ECDHE-RSA-AES128-GCM-SHA256:!SSLv3",
         {
-            {TLS1_CK_RSA_WITH_AES_128_SHA256, 0},
+            {TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
         },
         false,
     },
     // TLSv1.2 matches everything added in TLS 1.2.
     {
-        "AES128-SHA:AES128-SHA256:!TLSv1.2",
+        "AES128-SHA:ECDHE-RSA-AES128-GCM-SHA256:!TLSv1.2",
         {
             {TLS1_CK_RSA_WITH_AES_128_SHA, 0},
         },
@@ -303,21 +306,21 @@
     // The two directives have no intersection.  But each component is valid, so
     // even in strict mode it is accepted.
     {
-        "AES128-SHA:AES128-SHA256:!TLSv1.2+SSLv3",
+        "AES128-SHA:ECDHE-RSA-AES128-GCM-SHA256:!TLSv1.2+SSLv3",
         {
             {TLS1_CK_RSA_WITH_AES_128_SHA, 0},
-            {TLS1_CK_RSA_WITH_AES_128_SHA256, 0},
+            {TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
         },
         false,
     },
     // Spaces, semi-colons and commas are separators.
     {
-        "AES128-SHA: AES128-SHA256 AES256-SHA ,AES256-SHA256 ; AES128-GCM-SHA256",
+        "AES128-SHA: ECDHE-RSA-AES128-GCM-SHA256 AES256-SHA ,ECDHE-ECDSA-AES128-GCM-SHA256 ; AES128-GCM-SHA256",
         {
             {TLS1_CK_RSA_WITH_AES_128_SHA, 0},
-            {TLS1_CK_RSA_WITH_AES_128_SHA256, 0},
+            {TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
             {TLS1_CK_RSA_WITH_AES_256_SHA, 0},
-            {TLS1_CK_RSA_WITH_AES_256_SHA256, 0},
+            {TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 0},
             {TLS1_CK_RSA_WITH_AES_128_GCM_SHA256, 0},
         },
         // …but not in strict mode.
@@ -494,7 +497,7 @@
     ASSERT_TRUE(ctx);
 
     ASSERT_TRUE(SSL_CTX_set1_curves_list(ctx.get(), t.rule));
-    ASSERT_EQ(t.expected.size(), ctx->supported_group_list_len);
+    ASSERT_EQ(t.expected.size(), ctx->supported_group_list.size());
     for (size_t i = 0; i < t.expected.size(); i++) {
       EXPECT_EQ(t.expected[i], ctx->supported_group_list[i]);
     }
@@ -864,22 +867,22 @@
           NID_md5_sha1,
       },
       {
-          TLS1_CK_ECDHE_RSA_WITH_AES_128_SHA256,
-          "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
+          TLS1_CK_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+          "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
           NID_aes_128_cbc,
-          NID_sha256,
+          NID_sha1,
           NID_kx_ecdhe,
           NID_auth_rsa,
-          NID_sha256,
+          NID_md5_sha1,
       },
       {
-          TLS1_CK_ECDHE_RSA_WITH_AES_256_SHA384,
-          "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
+          TLS1_CK_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+          "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
           NID_aes_256_cbc,
-          NID_sha384,
+          NID_sha1,
           NID_kx_ecdhe,
           NID_auth_rsa,
-          NID_sha384,
+          NID_md5_sha1,
       },
       {
           TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
@@ -987,28 +990,20 @@
   if (!ssl_ctx) {
     return nullptr;
   }
+  // Use a garbage ticket.
+  std::vector<uint8_t> ticket(ticket_len, 'a');
   bssl::UniquePtr<SSL_SESSION> session(
       SSL_SESSION_from_bytes(der.data(), der.size(), ssl_ctx.get()));
-  if (!session) {
+  if (!session ||
+      !SSL_SESSION_set_protocol_version(session.get(), version) ||
+      !SSL_SESSION_set_ticket(session.get(), ticket.data(), ticket.size())) {
     return nullptr;
   }
-
-  session->ssl_version = version;
-
-  // Swap out the ticket for a garbage one.
-  OPENSSL_free(session->tlsext_tick);
-  session->tlsext_tick = reinterpret_cast<uint8_t*>(OPENSSL_malloc(ticket_len));
-  if (session->tlsext_tick == nullptr) {
-    return nullptr;
-  }
-  OPENSSL_memset(session->tlsext_tick, 'a', ticket_len);
-  session->tlsext_ticklen = ticket_len;
-
   // Fix up the timeout.
 #if defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE)
-  session->time = 1234;
+  SSL_SESSION_set_time(session.get(), 1234);
 #else
-  session->time = time(NULL);
+  SSL_SESSION_set_time(session.get(), time(nullptr));
 #endif
   return session;
 }
@@ -1331,9 +1326,7 @@
 
   bssl::UniquePtr<STACK_OF(X509_NAME)> stack(sk_X509_NAME_new_null());
   ASSERT_TRUE(stack);
-
-  ASSERT_TRUE(sk_X509_NAME_push(stack.get(), name_dup.get()));
-  name_dup.release();
+  ASSERT_TRUE(PushToStack(stack.get(), std::move(name_dup)));
 
   // |SSL_set_client_CA_list| takes ownership.
   SSL_set_client_CA_list(ssl.get(), stack.release());
@@ -1424,9 +1417,11 @@
     return nullptr;
   }
 
-  ret->session_id_length = SSL3_SSL_SESSION_ID_LENGTH;
-  OPENSSL_memset(ret->session_id, 0, ret->session_id_length);
-  OPENSSL_memcpy(ret->session_id, &number, sizeof(number));
+  uint8_t id[SSL3_SSL_SESSION_ID_LENGTH] = {0};
+  OPENSSL_memcpy(id, &number, sizeof(number));
+  if (!SSL_SESSION_set1_id(ret.get(), id, sizeof(id))) {
+    return nullptr;
+  }
   return ret;
 }
 
@@ -1538,7 +1533,8 @@
                                    bssl::UniquePtr<SSL> *out_server,
                                    SSL_CTX *client_ctx, SSL_CTX *server_ctx,
                                    const ClientConfig &config = ClientConfig(),
-                                   bool do_handshake = true) {
+                                   bool do_handshake = true,
+                                   bool shed_handshake_config = true) {
   bssl::UniquePtr<SSL> client(SSL_new(client_ctx)), server(SSL_new(server_ctx));
   if (!client || !server) {
     return false;
@@ -1562,6 +1558,9 @@
   SSL_set_bio(client.get(), bio1, bio1);
   SSL_set_bio(server.get(), bio2, bio2);
 
+  SSL_set_shed_handshake_config(client.get(), shed_handshake_config);
+  SSL_set_shed_handshake_config(server.get(), shed_handshake_config);
+
   if (do_handshake && !CompleteHandshakes(client.get(), server.get())) {
     return false;
   }
@@ -1608,7 +1607,8 @@
 
   bool Connect(const ClientConfig &config = ClientConfig()) {
     return ConnectClientAndServer(&client_, &server_, client_ctx_.get(),
-                                  server_ctx_.get(), config);
+                                  server_ctx_.get(), config, true,
+                                  shed_handshake_config_);
   }
 
   uint16_t version() const { return GetParam().version; }
@@ -1617,6 +1617,7 @@
     return GetParam().ssl_method == VersionParam::is_dtls;
   }
 
+  bool shed_handshake_config_ = true;
   bssl::UniquePtr<SSL> client_, server_;
   bssl::UniquePtr<SSL_CTX> server_ctx_, client_ctx_;
   bssl::UniquePtr<X509> cert_;
@@ -1719,7 +1720,7 @@
       bssl::SSL_SESSION_dup(session0, SSL_SESSION_DUP_ALL);
   ASSERT_TRUE(session1);
 
-  session1->not_resumable = 0;
+  session1->not_resumable = false;
 
   uint8_t *s0_bytes, *s1_bytes;
   size_t s0_len, s1_len;
@@ -1943,9 +1944,12 @@
   EXPECT_FALSE(peer);
 
   SSL_SESSION *session = SSL_get_session(server_.get());
-  EXPECT_TRUE(session->peer_sha256_valid);
+  EXPECT_TRUE(SSL_SESSION_has_peer_sha256(session));
 
-  EXPECT_EQ(Bytes(cert_sha256), Bytes(session->peer_sha256));
+  const uint8_t *peer_sha256;
+  size_t peer_sha256_len;
+  SSL_SESSION_get0_peer_sha256(session, &peer_sha256, &peer_sha256_len);
+  EXPECT_EQ(Bytes(cert_sha256), Bytes(peer_sha256, peer_sha256_len));
 }
 
 // Tests that our ClientHellos do not change unexpectedly. These are purely
@@ -1956,13 +1960,6 @@
     uint16_t max_version;
     std::vector<uint8_t> expected;
   } kTests[] = {
-    {SSL3_VERSION,
-     {0x16, 0x03, 0x00, 0x00, 0x3b, 0x01, 0x00, 0x00, 0x37, 0x03, 0x00,
-      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-      0x00, 0x10, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0x00,
-      0x2f, 0x00, 0x35, 0x00, 0x0a, 0x00, 0xff, 0x01, 0x00}},
     {TLS1_VERSION,
      {0x16, 0x03, 0x01, 0x00, 0x5a, 0x01, 0x00, 0x00, 0x56, 0x03, 0x01, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -1982,14 +1979,13 @@
       0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00,
       0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18}},
     {TLS1_2_VERSION,
-     {0x16, 0x03, 0x01, 0x00, 0x8e, 0x01, 0x00, 0x00, 0x8a, 0x03, 0x03, 0x00,
+     {0x16, 0x03, 0x01, 0x00, 0x82, 0x01, 0x00, 0x00, 0x7e, 0x03, 0x03, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xcc, 0xa9,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xcc, 0xa9,
       0xcc, 0xa8, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xc0, 0x09,
-      0xc0, 0x23, 0xc0, 0x13, 0xc0, 0x27, 0xc0, 0x0a, 0xc0, 0x24, 0xc0, 0x14,
-      0xc0, 0x28, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x3c, 0x00, 0x35,
-      0x00, 0x3d, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x37, 0xff, 0x01, 0x00, 0x01,
+      0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f,
+      0x00, 0x35, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x37, 0xff, 0x01, 0x00, 0x01,
       0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,
       0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08,
       0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x0b, 0x00,
@@ -2007,8 +2003,6 @@
     // Our default cipher list varies by CPU capabilities, so manually place the
     // ChaCha20 ciphers in front.
     const char *cipher_list = "CHACHA20:ALL";
-    // SSLv3 is off by default.
-    ASSERT_TRUE(SSL_CTX_set_min_proto_version(ctx.get(), SSL3_VERSION));
     ASSERT_TRUE(SSL_CTX_set_max_proto_version(ctx.get(), t.max_version));
     ASSERT_TRUE(SSL_CTX_set_strict_cipher_list(ctx.get(), cipher_list));
 
@@ -2247,12 +2241,15 @@
 }
 
 static bool GetServerTicketTime(long *out, const SSL_SESSION *session) {
-  if (session->tlsext_ticklen < 16 + 16 + SHA256_DIGEST_LENGTH) {
+  const uint8_t *ticket;
+  size_t ticket_len;
+  SSL_SESSION_get0_ticket(session, &ticket, &ticket_len);
+  if (ticket_len < 16 + 16 + SHA256_DIGEST_LENGTH) {
     return false;
   }
 
-  const uint8_t *ciphertext = session->tlsext_tick + 16 + 16;
-  size_t len = session->tlsext_ticklen - 16 - 16 - SHA256_DIGEST_LENGTH;
+  const uint8_t *ciphertext = ticket + 16 + 16;
+  size_t len = ticket_len - 16 - 16 - SHA256_DIGEST_LENGTH;
   std::unique_ptr<uint8_t[]> plaintext(new uint8_t[len]);
 
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
@@ -2260,7 +2257,7 @@
   OPENSSL_memcpy(plaintext.get(), ciphertext, len);
 #else
   static const uint8_t kZeros[16] = {0};
-  const uint8_t *iv = session->tlsext_tick + 16;
+  const uint8_t *iv = ticket + 16;
   bssl::ScopedEVP_CIPHER_CTX ctx;
   int len1, len2;
   if (!EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, kZeros, iv) ||
@@ -2282,7 +2279,7 @@
     return false;
   }
 
-  *out = server_session->time;
+  *out = SSL_SESSION_get_time(server_session.get());
   return true;
 }
 
@@ -2341,11 +2338,6 @@
                                     session.get(),
                                     false /* expect session not reused */));
 
-    // SSL 3.0 cannot renew sessions.
-    if (version() == SSL3_VERSION) {
-      continue;
-    }
-
     // Renew the session 10 seconds before expiration.
     time_t new_start_time = kStartTime + timeout - 10;
     g_current_time.tv_sec = new_start_time;
@@ -2361,7 +2353,7 @@
     if (server_test) {
       ASSERT_TRUE(GetServerTicketTime(&session_time, new_session.get()));
     } else {
-      session_time = new_session->time;
+      session_time = SSL_SESSION_get_time(new_session.get());
     }
 
     ASSERT_EQ(session_time, g_current_time.tv_sec);
@@ -2428,13 +2420,8 @@
 }
 
 TEST_P(SSLVersionTest, DefaultTicketKeyRotation) {
-  if (GetParam().version == SSL3_VERSION) {
-    return;
-  }
-
   static const time_t kStartTime = 1001;
   g_current_time.tv_sec = kStartTime;
-  uint8_t ticket_key[kTicketKeyLen];
 
   // We use session reuse as a proxy for ticket decryption success, hence
   // disable session timeouts.
@@ -2448,7 +2435,9 @@
   SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
   SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_OFF);
 
-  // Initialize ticket_key with the current key.
+  // Initialize ticket_key with the current key and check that it was
+  // initialized to something, not all zeros.
+  uint8_t ticket_key[kTicketKeyLen] = {0};
   TRACED_CALL(ExpectTicketKeyChanged(server_ctx_.get(), ticket_key,
                                      true /* changed */));
 
@@ -2501,11 +2490,6 @@
 }
 
 TEST_P(SSLVersionTest, SNICallback) {
-  // SSL 3.0 lacks extensions.
-  if (version() == SSL3_VERSION) {
-    return;
-  }
-
   bssl::UniquePtr<X509> cert2 = GetECDSATestCertificate();
   ASSERT_TRUE(cert2);
   bssl::UniquePtr<EVP_PKEY> key2 = GetECDSATestKey();
@@ -2611,12 +2595,13 @@
   EXPECT_TRUE(SSL_CTX_set_min_proto_version(ctx.get(), 0));
   EXPECT_EQ(TLS1_VERSION, ctx->conf_min_version);
 
-  // SSL 3.0 and TLS 1.3 are available, but not by default.
-  EXPECT_TRUE(SSL_CTX_set_min_proto_version(ctx.get(), SSL3_VERSION));
-  EXPECT_EQ(SSL3_VERSION, ctx->conf_min_version);
+  // TLS 1.3 is available, but not by default.
   EXPECT_TRUE(SSL_CTX_set_max_proto_version(ctx.get(), TLS1_3_VERSION));
   EXPECT_EQ(TLS1_3_VERSION, ctx->conf_max_version);
 
+  // SSL 3.0 is not available.
+  EXPECT_FALSE(SSL_CTX_set_min_proto_version(ctx.get(), SSL3_VERSION));
+
   // TLS1_3_DRAFT_VERSION is not an API-level version.
   EXPECT_FALSE(
       SSL_CTX_set_max_proto_version(ctx.get(), TLS1_3_DRAFT23_VERSION));
@@ -2647,8 +2632,6 @@
 
 static const char *GetVersionName(uint16_t version) {
   switch (version) {
-    case SSL3_VERSION:
-      return "SSLv3";
     case TLS1_VERSION:
       return "TLSv1";
     case TLS1_1_VERSION:
@@ -2689,11 +2672,6 @@
 // Tests that that |SSL_get_pending_cipher| is available during the ALPN
 // selection callback.
 TEST_P(SSLVersionTest, ALPNCipherAvailable) {
-  // SSL 3.0 lacks extensions.
-  if (version() == SSL3_VERSION) {
-    return;
-  }
-
   ASSERT_TRUE(UseCertAndKey(client_ctx_.get()));
 
   static const uint8_t kALPNProtos[] = {0x03, 'f', 'o', 'o'};
@@ -2729,6 +2707,7 @@
     return;
   }
 
+  shed_handshake_config_ = false;
   ASSERT_TRUE(Connect());
 
   EXPECT_FALSE(SSL_session_reused(client_.get()));
@@ -2746,6 +2725,25 @@
   EXPECT_TRUE(SSL_session_reused(server_.get()));
 }
 
+TEST_P(SSLVersionTest, SSLClearFailsWithShedding) {
+  shed_handshake_config_ = false;
+  ASSERT_TRUE(Connect());
+  ASSERT_TRUE(CompleteHandshakes(client_.get(), server_.get()));
+
+  // Reset everything.
+  ASSERT_TRUE(SSL_clear(client_.get()));
+  ASSERT_TRUE(SSL_clear(server_.get()));
+
+  // Now enable shedding, and connect a second time.
+  shed_handshake_config_ = true;
+  ASSERT_TRUE(Connect());
+  ASSERT_TRUE(CompleteHandshakes(client_.get(), server_.get()));
+
+  // |SSL_clear| should now fail.
+  ASSERT_FALSE(SSL_clear(client_.get()));
+  ASSERT_FALSE(SSL_clear(server_.get()));
+}
+
 static bool ChainsEqual(STACK_OF(X509) * chain,
                         const std::vector<X509 *> &expected) {
   if (sk_X509_num(chain) != expected.size()) {
@@ -2990,11 +2988,6 @@
 }
 
 TEST_P(SSLVersionTest, GetServerName) {
-  // No extensions in SSL 3.0.
-  if (version() == SSL3_VERSION) {
-    return;
-  }
-
   ClientConfig config;
   config.servername = "host1";
 
@@ -3245,8 +3238,7 @@
   bssl::UniquePtr<STACK_OF(CRYPTO_BUFFER)> ca_names(
       sk_CRYPTO_BUFFER_new_null());
   ASSERT_TRUE(ca_names);
-  ASSERT_TRUE(sk_CRYPTO_BUFFER_push(ca_names.get(), ca_name.get()));
-  ca_name.release();
+  ASSERT_TRUE(PushToStack(ca_names.get(), std::move(ca_name)));
   SSL_CTX_set0_client_CAs(server_ctx.get(), ca_names.release());
 
   // Configure client and server to accept all certificates.
@@ -3265,7 +3257,7 @@
   SSL_CTX_set_cert_cb(
       client_ctx.get(),
       [](SSL *ssl, void *arg) -> int {
-        STACK_OF(CRYPTO_BUFFER) *peer_names =
+        const STACK_OF(CRYPTO_BUFFER) *peer_names =
             SSL_get0_server_requested_CAs(ssl);
         EXPECT_EQ(1u, sk_CRYPTO_BUFFER_num(peer_names));
         CRYPTO_BUFFER *peer_name = sk_CRYPTO_BUFFER_value(peer_names, 0);
@@ -3926,6 +3918,69 @@
   EXPECT_TRUE(SSL_is_signature_algorithm_rsa_pss(SSL_SIGN_RSA_PSS_RSAE_SHA384));
 }
 
+static int XORCompressFunc(SSL *ssl, CBB *out, const uint8_t *in,
+                           size_t in_len) {
+  for (size_t i = 0; i < in_len; i++) {
+    if (!CBB_add_u8(out, in[i] ^ 0x55)) {
+      return 0;
+    }
+  }
+
+  SSL_set_app_data(ssl, XORCompressFunc);
+
+  return 1;
+}
+
+static int XORDecompressFunc(SSL *ssl, CRYPTO_BUFFER **out,
+                             size_t uncompressed_len, const uint8_t *in,
+                             size_t in_len) {
+  if (in_len != uncompressed_len) {
+    return 0;
+  }
+
+  uint8_t *data;
+  *out = CRYPTO_BUFFER_alloc(&data, uncompressed_len);
+  if (*out == nullptr) {
+    return 0;
+  }
+
+  for (size_t i = 0; i < in_len; i++) {
+    data[i] = in[i] ^ 0x55;
+  }
+
+  SSL_set_app_data(ssl, XORDecompressFunc);
+
+  return 1;
+}
+
+TEST(SSLTest, CertCompression) {
+  bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
+  bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
+  ASSERT_TRUE(client_ctx);
+  ASSERT_TRUE(server_ctx);
+
+  bssl::UniquePtr<X509> cert = GetTestCertificate();
+  bssl::UniquePtr<EVP_PKEY> key = GetTestKey();
+  ASSERT_TRUE(cert);
+  ASSERT_TRUE(key);
+  ASSERT_TRUE(SSL_CTX_use_certificate(server_ctx.get(), cert.get()));
+  ASSERT_TRUE(SSL_CTX_use_PrivateKey(server_ctx.get(), key.get()));
+
+  ASSERT_TRUE(SSL_CTX_set_max_proto_version(client_ctx.get(), TLS1_3_VERSION));
+  ASSERT_TRUE(SSL_CTX_set_max_proto_version(server_ctx.get(), TLS1_3_VERSION));
+  ASSERT_TRUE(SSL_CTX_add_cert_compression_alg(
+      client_ctx.get(), 0x1234, XORCompressFunc, XORDecompressFunc));
+  ASSERT_TRUE(SSL_CTX_add_cert_compression_alg(
+      server_ctx.get(), 0x1234, XORCompressFunc, XORDecompressFunc));
+
+  bssl::UniquePtr<SSL> client, server;
+  ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
+                                     server_ctx.get()));
+
+  EXPECT_TRUE(SSL_get_app_data(client.get()) == XORDecompressFunc);
+  EXPECT_TRUE(SSL_get_app_data(server.get()) == XORCompressFunc);
+}
+
 void MoveBIOs(SSL *dest, SSL *src) {
   BIO *rbio = SSL_get_rbio(src);
   BIO_up_ref(rbio);
@@ -4064,6 +4119,294 @@
   EXPECT_EQ(43, byte);
 }
 
+static std::string SigAlgsToString(Span<const uint16_t> sigalgs) {
+  std::string ret = "{";
+
+  for (uint16_t v : sigalgs) {
+    if (ret.size() > 1) {
+      ret += ", ";
+    }
+
+    char buf[8];
+    snprintf(buf, sizeof(buf) - 1, "0x%02x", v);
+    buf[sizeof(buf)-1] = 0;
+    ret += std::string(buf);
+  }
+
+  ret += "}";
+  return ret;
+}
+
+void ExpectSigAlgsEqual(Span<const uint16_t> expected,
+                        Span<const uint16_t> actual) {
+  bool matches = false;
+  if (expected.size() == actual.size()) {
+    matches = true;
+
+    for (size_t i = 0; i < expected.size(); i++) {
+      if (expected[i] != actual[i]) {
+        matches = false;
+        break;
+      }
+    }
+  }
+
+  if (!matches) {
+    ADD_FAILURE() << "expected: " << SigAlgsToString(expected)
+                  << " got: " << SigAlgsToString(actual);
+  }
+}
+
+TEST(SSLTest, SigAlgs) {
+  static const struct {
+    std::vector<int> input;
+    bool ok;
+    std::vector<uint16_t> expected;
+  } kTests[] = {
+      {{}, true, {}},
+      {{1}, false, {}},
+      {{1, 2, 3}, false, {}},
+      {{NID_sha256, EVP_PKEY_ED25519}, false, {}},
+      {{NID_sha256, EVP_PKEY_RSA, NID_sha256, EVP_PKEY_RSA}, false, {}},
+
+      {{NID_sha256, EVP_PKEY_RSA}, true, {SSL_SIGN_RSA_PKCS1_SHA256}},
+      {{NID_sha512, EVP_PKEY_RSA}, true, {SSL_SIGN_RSA_PKCS1_SHA512}},
+      {{NID_sha256, EVP_PKEY_RSA_PSS}, true, {SSL_SIGN_RSA_PSS_RSAE_SHA256}},
+      {{NID_undef, EVP_PKEY_ED25519}, true, {SSL_SIGN_ED25519}},
+      {{NID_undef, EVP_PKEY_ED25519, NID_sha384, EVP_PKEY_EC},
+       true,
+       {SSL_SIGN_ED25519, SSL_SIGN_ECDSA_SECP384R1_SHA384}},
+  };
+
+  UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
+
+  unsigned n = 1;
+  for (const auto &test : kTests) {
+    SCOPED_TRACE(n++);
+
+    const bool ok =
+        SSL_CTX_set1_sigalgs(ctx.get(), test.input.data(), test.input.size());
+    EXPECT_EQ(ok, test.ok);
+
+    if (!ok) {
+      ERR_clear_error();
+    }
+
+    if (!test.ok) {
+      continue;
+    }
+
+    ExpectSigAlgsEqual(test.expected, ctx->cert->sigalgs);
+  }
+}
+
+TEST(SSLTest, SigAlgsList) {
+  static const struct {
+    const char *input;
+    bool ok;
+    std::vector<uint16_t> expected;
+  } kTests[] = {
+      {"", false, {}},
+      {":", false, {}},
+      {"+", false, {}},
+      {"RSA", false, {}},
+      {"RSA+", false, {}},
+      {"RSA+SHA256:", false, {}},
+      {":RSA+SHA256:", false, {}},
+      {":RSA+SHA256+:", false, {}},
+      {"!", false, {}},
+      {"\x01", false, {}},
+      {"RSA+SHA256:RSA+SHA384:RSA+SHA256", false, {}},
+      {"RSA-PSS+SHA256:rsa_pss_rsae_sha256", false, {}},
+
+      {"RSA+SHA256", true, {SSL_SIGN_RSA_PKCS1_SHA256}},
+      {"RSA+SHA256:ed25519",
+       true,
+       {SSL_SIGN_RSA_PKCS1_SHA256, SSL_SIGN_ED25519}},
+      {"ECDSA+SHA256:RSA+SHA512",
+       true,
+       {SSL_SIGN_ECDSA_SECP256R1_SHA256, SSL_SIGN_RSA_PKCS1_SHA512}},
+      {"ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256",
+       true,
+       {SSL_SIGN_ECDSA_SECP256R1_SHA256, SSL_SIGN_RSA_PSS_RSAE_SHA256}},
+      {"RSA-PSS+SHA256", true, {SSL_SIGN_RSA_PSS_RSAE_SHA256}},
+      {"PSS+SHA256", true, {SSL_SIGN_RSA_PSS_RSAE_SHA256}},
+  };
+
+  UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
+
+  unsigned n = 1;
+  for (const auto &test : kTests) {
+    SCOPED_TRACE(n++);
+
+    const bool ok = SSL_CTX_set1_sigalgs_list(ctx.get(), test.input);
+    EXPECT_EQ(ok, test.ok);
+
+    if (!ok) {
+      if (test.ok) {
+        ERR_print_errors_fp(stderr);
+      }
+      ERR_clear_error();
+    }
+
+    if (!test.ok) {
+      continue;
+    }
+
+    ExpectSigAlgsEqual(test.expected, ctx->cert->sigalgs);
+  }
+}
+
+TEST_P(SSLVersionTest, VerifyBeforeCertRequest) {
+  // Configure the server to request client certificates.
+  SSL_CTX_set_custom_verify(
+      server_ctx_.get(), SSL_VERIFY_PEER,
+      [](SSL *ssl, uint8_t *out_alert) { return ssl_verify_ok; });
+
+  // Configure the client to reject the server certificate.
+  SSL_CTX_set_custom_verify(
+      client_ctx_.get(), SSL_VERIFY_PEER,
+      [](SSL *ssl, uint8_t *out_alert) { return ssl_verify_invalid; });
+
+  // cert_cb should not be called. Verification should fail first.
+  SSL_CTX_set_cert_cb(client_ctx_.get(),
+                      [](SSL *ssl, void *arg) {
+                        ADD_FAILURE() << "cert_cb unexpectedly called";
+                        return 0;
+                      },
+                      nullptr);
+
+  bssl::UniquePtr<SSL> client, server;
+  EXPECT_FALSE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
+                                      server_ctx_.get()));
+}
+
+// These tests test multi-threaded behavior. They are intended to run with
+// ThreadSanitizer.
+#if !defined(OPENSSL_NO_THREADS)
+TEST_P(SSLVersionTest, SessionCacheThreads) {
+  SSL_CTX_set_options(server_ctx_.get(), SSL_OP_NO_TICKET);
+  SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
+  SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
+
+  if (version() == TLS1_3_VERSION) {
+    // Our TLS 1.3 implementation does not support stateful resumption.
+    ASSERT_FALSE(CreateClientSession(client_ctx_.get(), server_ctx_.get()));
+    return;
+  }
+
+  // Establish two client sessions to test with.
+  bssl::UniquePtr<SSL_SESSION> session1 =
+      CreateClientSession(client_ctx_.get(), server_ctx_.get());
+  ASSERT_TRUE(session1);
+  bssl::UniquePtr<SSL_SESSION> session2 =
+      CreateClientSession(client_ctx_.get(), server_ctx_.get());
+  ASSERT_TRUE(session2);
+
+  auto connect_with_session = [&](SSL_SESSION *session) {
+    ClientConfig config;
+    config.session = session;
+    UniquePtr<SSL> client, server;
+    EXPECT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
+                                       server_ctx_.get(), config));
+  };
+
+  // Resume sessions in parallel with establishing new ones.
+  {
+    std::vector<std::thread> threads;
+    threads.emplace_back([&] { connect_with_session(nullptr); });
+    threads.emplace_back([&] { connect_with_session(nullptr); });
+    threads.emplace_back([&] { connect_with_session(session1.get()); });
+    threads.emplace_back([&] { connect_with_session(session1.get()); });
+    threads.emplace_back([&] { connect_with_session(session2.get()); });
+    threads.emplace_back([&] { connect_with_session(session2.get()); });
+    for (auto &thread : threads) {
+      thread.join();
+    }
+  }
+
+  // Hit the maximum session cache size across multiple threads
+  size_t limit = SSL_CTX_sess_number(server_ctx_.get()) + 2;
+  SSL_CTX_sess_set_cache_size(server_ctx_.get(), limit);
+  {
+    std::vector<std::thread> threads;
+    for (int i = 0; i < 4; i++) {
+      threads.emplace_back([&]() {
+        connect_with_session(nullptr);
+        EXPECT_LE(SSL_CTX_sess_number(server_ctx_.get()), limit);
+      });
+    }
+    for (auto &thread : threads) {
+      thread.join();
+    }
+    EXPECT_EQ(SSL_CTX_sess_number(server_ctx_.get()), limit);
+  }
+}
+
+TEST_P(SSLVersionTest, SessionTicketThreads) {
+  for (bool renew_ticket : {false, true}) {
+    SCOPED_TRACE(renew_ticket);
+    ResetContexts();
+    SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH);
+    SSL_CTX_set_session_cache_mode(server_ctx_.get(), SSL_SESS_CACHE_BOTH);
+    if (renew_ticket) {
+      SSL_CTX_set_tlsext_ticket_key_cb(server_ctx_.get(), RenewTicketCallback);
+    }
+
+    // Establish two client sessions to test with.
+    bssl::UniquePtr<SSL_SESSION> session1 =
+        CreateClientSession(client_ctx_.get(), server_ctx_.get());
+    ASSERT_TRUE(session1);
+    bssl::UniquePtr<SSL_SESSION> session2 =
+        CreateClientSession(client_ctx_.get(), server_ctx_.get());
+    ASSERT_TRUE(session2);
+
+    auto connect_with_session = [&](SSL_SESSION *session) {
+      ClientConfig config;
+      config.session = session;
+      UniquePtr<SSL> client, server;
+      EXPECT_TRUE(ConnectClientAndServer(&client, &server, client_ctx_.get(),
+                                         server_ctx_.get(), config));
+    };
+
+    // Resume sessions in parallel with establishing new ones.
+    {
+      std::vector<std::thread> threads;
+      threads.emplace_back([&] { connect_with_session(nullptr); });
+      threads.emplace_back([&] { connect_with_session(nullptr); });
+      threads.emplace_back([&] { connect_with_session(session1.get()); });
+      threads.emplace_back([&] { connect_with_session(session1.get()); });
+      threads.emplace_back([&] { connect_with_session(session2.get()); });
+      threads.emplace_back([&] { connect_with_session(session2.get()); });
+      for (auto &thread : threads) {
+        thread.join();
+      }
+    }
+  }
+}
+
+// SSL_CTX_get0_certificate needs to lock internally. Test this works.
+TEST(SSLTest, GetCertificateThreads) {
+  bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
+  ASSERT_TRUE(ctx);
+  bssl::UniquePtr<X509> cert = GetTestCertificate();
+  ASSERT_TRUE(cert);
+  ASSERT_TRUE(SSL_CTX_use_certificate(ctx.get(), cert.get()));
+
+  // Existing code expects |SSL_CTX_get0_certificate| to be callable from two
+  // threads concurrently. It originally was an immutable operation. Now we
+  // implement it with a thread-safe cache, so it is worth testing.
+  X509 *cert2_thread;
+  std::thread thread(
+      [&] { cert2_thread = SSL_CTX_get0_certificate(ctx.get()); });
+  X509 *cert2 = SSL_CTX_get0_certificate(ctx.get());
+  thread.join();
+
+  EXPECT_EQ(cert2, cert2_thread);
+  EXPECT_EQ(0, X509_cmp(cert.get(), cert2));
+}
+#endif
+
 // TODO(davidben): Convert this file to GTest properly.
 TEST(SSLTest, AllTests) {
   if (!TestSSL_SESSIONEncoding(kOpenSSLSession) ||
@@ -4079,7 +4422,7 @@
       !TestPaddingExtension(TLS1_3_VERSION, TLS1_2_VERSION) ||
       // Test the padding extension at TLS 1.3 with a TLS 1.3 session, so there
       // will be a PSK binder after the padding extension.
-      !TestPaddingExtension(TLS1_3_VERSION, TLS1_3_DRAFT23_VERSION)) {
+      !TestPaddingExtension(TLS1_3_VERSION, TLS1_3_VERSION)) {
     ADD_FAILURE() << "Tests failed";
   }
 }
diff --git a/src/ssl/ssl_transcript.cc b/src/ssl/ssl_transcript.cc
index 345f9d3..24b86bf 100644
--- a/src/ssl/ssl_transcript.cc
+++ b/src/ssl/ssl_transcript.cc
@@ -135,18 +135,9 @@
 
 #include <openssl/ssl.h>
 
-#include <assert.h>
-#include <string.h>
-
 #include <openssl/buf.h>
 #include <openssl/digest.h>
-#include <openssl/err.h>
-#include <openssl/mem.h>
-#include <openssl/md5.h>
-#include <openssl/nid.h>
-#include <openssl/sha.h>
 
-#include "../crypto/internal.h"
 #include "internal.h"
 
 
@@ -163,7 +154,6 @@
   }
 
   hash_.Reset();
-  md5_.Reset();
   return true;
 }
 
@@ -180,17 +170,6 @@
 
 bool SSLTranscript::InitHash(uint16_t version, const SSL_CIPHER *cipher) {
   const EVP_MD *md = ssl_get_handshake_digest(version, cipher);
-
-  // To support SSL 3.0's Finished and CertificateVerify constructions,
-  // EVP_md5_sha1() is split into MD5 and SHA-1 halves. When SSL 3.0 is removed,
-  // we can simplify this.
-  if (md == EVP_md5_sha1()) {
-    if (!InitDigestWithData(md5_.get(), EVP_md5(), buffer_.get())) {
-      return false;
-    }
-    md = EVP_sha1();
-  }
-
   return InitDigestWithData(hash_.get(), md, buffer_.get());
 }
 
@@ -203,9 +182,6 @@
 }
 
 const EVP_MD *SSLTranscript::Digest() const {
-  if (EVP_MD_CTX_md(md5_.get()) != nullptr) {
-    return EVP_md5_sha1();
-  }
   return EVP_MD_CTX_md(hash_.get());
 }
 
@@ -244,146 +220,40 @@
   if (EVP_MD_CTX_md(hash_.get()) != NULL) {
     EVP_DigestUpdate(hash_.get(), in.data(), in.size());
   }
-  if (EVP_MD_CTX_md(md5_.get()) != NULL) {
-    EVP_DigestUpdate(md5_.get(), in.data(), in.size());
-  }
 
   return true;
 }
 
 bool SSLTranscript::GetHash(uint8_t *out, size_t *out_len) {
   ScopedEVP_MD_CTX ctx;
-  unsigned md5_len = 0;
-  if (EVP_MD_CTX_md(md5_.get()) != NULL) {
-    if (!EVP_MD_CTX_copy_ex(ctx.get(), md5_.get()) ||
-        !EVP_DigestFinal_ex(ctx.get(), out, &md5_len)) {
-      return false;
-    }
-  }
-
   unsigned len;
   if (!EVP_MD_CTX_copy_ex(ctx.get(), hash_.get()) ||
-      !EVP_DigestFinal_ex(ctx.get(), out + md5_len, &len)) {
+      !EVP_DigestFinal_ex(ctx.get(), out, &len)) {
     return false;
   }
-
-  *out_len = md5_len + len;
-  return true;
-}
-
-static bool SSL3HandshakeMAC(const SSL_SESSION *session,
-                             const EVP_MD_CTX *ctx_template, const char *sender,
-                             size_t sender_len, uint8_t *p, size_t *out_len) {
-  ScopedEVP_MD_CTX ctx;
-  if (!EVP_MD_CTX_copy_ex(ctx.get(), ctx_template)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
-    return false;
-  }
-
-  static const uint8_t kPad1[48] = {
-      0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-      0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-      0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-      0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-  };
-
-  static const uint8_t kPad2[48] = {
-      0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
-      0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
-      0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
-      0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
-  };
-
-  size_t n = EVP_MD_CTX_size(ctx.get());
-
-  size_t npad = (48 / n) * n;
-  EVP_DigestUpdate(ctx.get(), sender, sender_len);
-  EVP_DigestUpdate(ctx.get(), session->master_key, session->master_key_length);
-  EVP_DigestUpdate(ctx.get(), kPad1, npad);
-  unsigned md_buf_len;
-  uint8_t md_buf[EVP_MAX_MD_SIZE];
-  EVP_DigestFinal_ex(ctx.get(), md_buf, &md_buf_len);
-
-  if (!EVP_DigestInit_ex(ctx.get(), EVP_MD_CTX_md(ctx.get()), NULL)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
-    return false;
-  }
-  EVP_DigestUpdate(ctx.get(), session->master_key, session->master_key_length);
-  EVP_DigestUpdate(ctx.get(), kPad2, npad);
-  EVP_DigestUpdate(ctx.get(), md_buf, md_buf_len);
-  unsigned len;
-  EVP_DigestFinal_ex(ctx.get(), p, &len);
-
   *out_len = len;
   return true;
 }
 
-bool SSLTranscript::GetSSL3CertVerifyHash(uint8_t *out, size_t *out_len,
-                                          const SSL_SESSION *session,
-                                          uint16_t signature_algorithm) {
-  if (Digest() != EVP_md5_sha1()) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return false;
-  }
-
-  if (signature_algorithm == SSL_SIGN_RSA_PKCS1_MD5_SHA1) {
-    size_t md5_len, len;
-    if (!SSL3HandshakeMAC(session, md5_.get(), NULL, 0, out, &md5_len) ||
-        !SSL3HandshakeMAC(session, hash_.get(), NULL, 0, out + md5_len, &len)) {
-      return false;
-    }
-    *out_len = md5_len + len;
-    return true;
-  }
-
-  if (signature_algorithm == SSL_SIGN_ECDSA_SHA1) {
-    return SSL3HandshakeMAC(session, hash_.get(), NULL, 0, out, out_len);
-  }
-
-  OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-  return false;
-}
-
 bool SSLTranscript::GetFinishedMAC(uint8_t *out, size_t *out_len,
                                    const SSL_SESSION *session,
                                    bool from_server) {
-  if (session->ssl_version == SSL3_VERSION) {
-    if (Digest() != EVP_md5_sha1()) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return false;
-    }
-
-    const char *sender = from_server ? SSL3_MD_SERVER_FINISHED_CONST
-                                     : SSL3_MD_CLIENT_FINISHED_CONST;
-    const size_t sender_len = 4;
-    size_t md5_len, len;
-    if (!SSL3HandshakeMAC(session, md5_.get(), sender, sender_len, out,
-                          &md5_len) ||
-        !SSL3HandshakeMAC(session, hash_.get(), sender, sender_len,
-                          out + md5_len, &len)) {
-      return false;
-    }
-
-    *out_len = md5_len + len;
-    return true;
-  }
-
   static const char kClientLabel[] = "client finished";
   static const char kServerLabel[] = "server finished";
   auto label = from_server
                    ? MakeConstSpan(kServerLabel, sizeof(kServerLabel) - 1)
                    : MakeConstSpan(kClientLabel, sizeof(kClientLabel) - 1);
 
-  uint8_t digests[EVP_MAX_MD_SIZE];
-  size_t digests_len;
-  if (!GetHash(digests, &digests_len)) {
+  uint8_t digest[EVP_MAX_MD_SIZE];
+  size_t digest_len;
+  if (!GetHash(digest, &digest_len)) {
     return false;
   }
 
   static const size_t kFinishedLen = 12;
   if (!tls1_prf(Digest(), MakeSpan(out, kFinishedLen),
                 MakeConstSpan(session->master_key, session->master_key_length),
-                label, MakeConstSpan(digests, digests_len), {})) {
+                label, MakeConstSpan(digest, digest_len), {})) {
     return false;
   }
 
diff --git a/src/ssl/ssl_versions.cc b/src/ssl/ssl_versions.cc
index 73ea26f..6a8143d 100644
--- a/src/ssl/ssl_versions.cc
+++ b/src/ssl/ssl_versions.cc
@@ -27,7 +27,6 @@
 
 bool ssl_protocol_version_from_wire(uint16_t *out, uint16_t version) {
   switch (version) {
-    case SSL3_VERSION:
     case TLS1_VERSION:
     case TLS1_1_VERSION:
     case TLS1_2_VERSION:
@@ -57,12 +56,11 @@
 // decreasing preference.
 
 static const uint16_t kTLSVersions[] = {
-    TLS1_3_DRAFT23_VERSION,
     TLS1_3_DRAFT28_VERSION,
+    TLS1_3_DRAFT23_VERSION,
     TLS1_2_VERSION,
     TLS1_1_VERSION,
     TLS1_VERSION,
-    SSL3_VERSION,
 };
 
 static const uint16_t kDTLSVersions[] = {
@@ -81,8 +79,8 @@
   }
 }
 
-static bool method_supports_version(const SSL_PROTOCOL_METHOD *method,
-                                    uint16_t version) {
+bool ssl_method_supports_version(const SSL_PROTOCOL_METHOD *method,
+                                 uint16_t version) {
   const uint16_t *versions;
   size_t num_versions;
   get_method_versions(method, &versions, &num_versions);
@@ -114,9 +112,6 @@
     case TLS1_VERSION:
       return "TLSv1";
 
-    case SSL3_VERSION:
-      return "SSLv3";
-
     case DTLS1_VERSION:
       return "DTLSv1";
 
@@ -164,7 +159,7 @@
 static bool set_version_bound(const SSL_PROTOCOL_METHOD *method, uint16_t *out,
                               uint16_t version) {
   if (!api_version_to_wire(&version, version) ||
-      !method_supports_version(method, version) ||
+      !ssl_method_supports_version(method, version) ||
       !ssl_protocol_version_from_wire(out, version)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_SSL_VERSION);
     return false;
@@ -177,7 +172,7 @@
                             uint16_t version) {
   // Zero is interpreted as the default minimum version.
   if (version == 0) {
-    // SSL 3.0 is disabled by default and TLS 1.0 does not exist in DTLS.
+    // TLS 1.0 does not exist in DTLS.
     *out = method->is_dtls ? TLS1_1_VERSION : TLS1_VERSION;
     return true;
   }
@@ -200,27 +195,26 @@
   uint16_t version;
   uint32_t flag;
 } kProtocolVersions[] = {
-    {SSL3_VERSION, SSL_OP_NO_SSLv3},
     {TLS1_VERSION, SSL_OP_NO_TLSv1},
     {TLS1_1_VERSION, SSL_OP_NO_TLSv1_1},
     {TLS1_2_VERSION, SSL_OP_NO_TLSv1_2},
     {TLS1_3_VERSION, SSL_OP_NO_TLSv1_3},
 };
 
-bool ssl_get_version_range(const SSL *ssl, uint16_t *out_min_version,
+bool ssl_get_version_range(const SSL_HANDSHAKE *hs, uint16_t *out_min_version,
                            uint16_t *out_max_version) {
   // For historical reasons, |SSL_OP_NO_DTLSv1| aliases |SSL_OP_NO_TLSv1|, but
   // DTLS 1.0 should be mapped to TLS 1.1.
-  uint32_t options = ssl->options;
-  if (SSL_is_dtls(ssl)) {
+  uint32_t options = hs->ssl->options;
+  if (SSL_is_dtls(hs->ssl)) {
     options &= ~SSL_OP_NO_TLSv1_1;
     if (options & SSL_OP_NO_DTLSv1) {
       options |= SSL_OP_NO_TLSv1_1;
     }
   }
 
-  uint16_t min_version = ssl->conf_min_version;
-  uint16_t max_version = ssl->conf_max_version;
+  uint16_t min_version = hs->config->conf_min_version;
+  uint16_t max_version = hs->config->conf_max_version;
 
   // OpenSSL's API for controlling versions entails blacklisting individual
   // protocols. This has two problems. First, on the client, the protocol can
@@ -292,24 +286,27 @@
 bool ssl_supports_version(SSL_HANDSHAKE *hs, uint16_t version) {
   SSL *const ssl = hs->ssl;
   uint16_t protocol_version;
-  if (!method_supports_version(ssl->method, version) ||
+  if (!ssl_method_supports_version(ssl->method, version) ||
       !ssl_protocol_version_from_wire(&protocol_version, version) ||
       hs->min_version > protocol_version ||
       protocol_version > hs->max_version) {
     return false;
   }
 
-  // This logic is part of the TLS 1.3 variants mechanism used in TLS 1.3
-  // experimentation. TLS 1.3 variants must match the enabled |tls13_variant|.
-  if (protocol_version != TLS1_3_VERSION ||
-      (ssl->tls13_variant == tls13_draft28 &&
-       version == TLS1_3_DRAFT28_VERSION) ||
-      (ssl->tls13_variant == tls13_default &&
-       version == TLS1_3_DRAFT23_VERSION)) {
-    return true;
+  // If the TLS 1.3 variant is set to |tls13_default|, all variants are enabled,
+  // otherwise only the matching version is enabled.
+  if (protocol_version == TLS1_3_VERSION) {
+    switch (ssl->tls13_variant) {
+      case tls13_draft23:
+        return version == TLS1_3_DRAFT23_VERSION;
+      case tls13_draft28:
+        return version == TLS1_3_DRAFT28_VERSION;
+      case tls13_default:
+        return true;
+    }
   }
 
-  return false;
+  return true;
 }
 
 bool ssl_add_supported_versions(SSL_HANDSHAKE *hs, CBB *cbb) {
@@ -373,11 +370,17 @@
 }
 
 int SSL_set_min_proto_version(SSL *ssl, uint16_t version) {
-  return set_min_version(ssl->method, &ssl->conf_min_version, version);
+  if (!ssl->config) {
+    return 0;
+  }
+  return set_min_version(ssl->method, &ssl->config->conf_min_version, version);
 }
 
 int SSL_set_max_proto_version(SSL *ssl, uint16_t version) {
-  return set_max_version(ssl->method, &ssl->conf_max_version, version);
+  if (!ssl->config) {
+    return 0;
+  }
+  return set_max_version(ssl->method, &ssl->config->conf_max_version, version);
 }
 
 int SSL_version(const SSL *ssl) {
diff --git a/src/ssl/ssl_x509.cc b/src/ssl/ssl_x509.cc
index cb35339..ef09589 100644
--- a/src/ssl/ssl_x509.cc
+++ b/src/ssl/ssl_x509.cc
@@ -209,13 +209,10 @@
       return 0;
     }
 
-    CRYPTO_BUFFER *leaf = sk_CRYPTO_BUFFER_value(cert->chain.get(), 0);
-    if (!sk_CRYPTO_BUFFER_push(new_chain.get(), leaf)) {
-      return 0;
-    }
     // |leaf| might be NULL if it's a “leafless” chain.
-    if (leaf != nullptr) {
-      CRYPTO_BUFFER_up_ref(leaf);
+    CRYPTO_BUFFER *leaf = sk_CRYPTO_BUFFER_value(cert->chain.get(), 0);
+    if (!PushToStack(new_chain.get(), UpRef(leaf))) {
+      return 0;
     }
   }
 
@@ -252,11 +249,10 @@
     STACK_OF(CRYPTO_BUFFER) *names) {
   for (const CRYPTO_BUFFER *buffer : names) {
     const uint8_t *inp = CRYPTO_BUFFER_data(buffer);
-    X509_NAME *name = d2i_X509_NAME(NULL, &inp, CRYPTO_BUFFER_len(buffer));
-    const int ok = name != NULL && inp == CRYPTO_BUFFER_data(buffer) +
-                                              CRYPTO_BUFFER_len(buffer);
-    X509_NAME_free(name);
-    if (!ok) {
+    UniquePtr<X509_NAME> name(
+        d2i_X509_NAME(nullptr, &inp, CRYPTO_BUFFER_len(buffer)));
+    if (name == nullptr ||
+        inp != CRYPTO_BUFFER_data(buffer) + CRYPTO_BUFFER_len(buffer)) {
       return 0;
     }
   }
@@ -286,7 +282,7 @@
 
 static int ssl_crypto_x509_session_cache_objects(SSL_SESSION *sess) {
   bssl::UniquePtr<STACK_OF(X509)> chain;
-  if (sk_CRYPTO_BUFFER_num(sess->certs) > 0) {
+  if (sk_CRYPTO_BUFFER_num(sess->certs.get()) > 0) {
     chain.reset(sk_X509_new_null());
     if (!chain) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
@@ -295,7 +291,7 @@
   }
 
   X509 *leaf = nullptr;
-  for (CRYPTO_BUFFER *cert : sess->certs) {
+  for (CRYPTO_BUFFER *cert : sess->certs.get()) {
     UniquePtr<X509> x509(X509_parse_from_buffer(cert));
     if (!x509) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
@@ -349,7 +345,7 @@
 }
 
 static int ssl_crypto_x509_session_verify_cert_chain(SSL_SESSION *session,
-                                                     SSL *ssl,
+                                                     SSL_HANDSHAKE *hs,
                                                      uint8_t *out_alert) {
   *out_alert = SSL_AD_INTERNAL_ERROR;
   STACK_OF(X509) *const cert_chain = session->x509_chain;
@@ -357,9 +353,10 @@
     return 0;
   }
 
-  X509_STORE *verify_store = ssl->ctx->cert_store;
-  if (ssl->cert->verify_store != NULL) {
-    verify_store = ssl->cert->verify_store;
+  SSL_CTX *ssl_ctx = hs->ssl->ctx.get();
+  X509_STORE *verify_store = ssl_ctx->cert_store;
+  if (hs->config->cert->verify_store != NULL) {
+    verify_store = hs->config->cert->verify_store;
   }
 
   X509 *leaf = sk_X509_value(cert_chain, 0);
@@ -368,8 +365,8 @@
     OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB);
     return 0;
   }
-  if (!X509_STORE_CTX_set_ex_data(ctx.get(),
-                                  SSL_get_ex_data_X509_STORE_CTX_idx(), ssl)) {
+  if (!X509_STORE_CTX_set_ex_data(
+          ctx.get(), SSL_get_ex_data_X509_STORE_CTX_idx(), hs->ssl)) {
     return 0;
   }
 
@@ -377,19 +374,20 @@
   // context: if its a server it will verify SSL client certificates or vice
   // versa.
   X509_STORE_CTX_set_default(ctx.get(),
-                             ssl->server ? "ssl_client" : "ssl_server");
+                             hs->ssl->server ? "ssl_client" : "ssl_server");
 
   // Anything non-default in "param" should overwrite anything in the ctx.
-  X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(ctx.get()), ssl->param);
+  X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(ctx.get()),
+                         hs->config->param);
 
-  if (ssl->verify_callback) {
-    X509_STORE_CTX_set_verify_cb(ctx.get(), ssl->verify_callback);
+  if (hs->config->verify_callback) {
+    X509_STORE_CTX_set_verify_cb(ctx.get(), hs->config->verify_callback);
   }
 
   int verify_ret;
-  if (ssl->ctx->app_verify_callback != NULL) {
+  if (ssl_ctx->app_verify_callback != NULL) {
     verify_ret =
-        ssl->ctx->app_verify_callback(ctx.get(), ssl->ctx->app_verify_arg);
+        ssl_ctx->app_verify_callback(ctx.get(), ssl_ctx->app_verify_arg);
   } else {
     verify_ret = X509_verify_cert(ctx.get());
   }
@@ -397,7 +395,7 @@
   session->verify_result = ctx->error;
 
   // If |SSL_VERIFY_NONE|, the error is non-fatal, but we keep the result.
-  if (verify_ret <= 0 && ssl->verify_mode != SSL_VERIFY_NONE) {
+  if (verify_ret <= 0 && hs->config->verify_mode != SSL_VERIFY_NONE) {
     *out_alert = SSL_alert_from_verify_result(ctx->error);
     return 0;
   }
@@ -411,44 +409,45 @@
   hs->cached_x509_ca_names = NULL;
 }
 
-static int ssl_crypto_x509_ssl_new(SSL *ssl) {
-  ssl->param = X509_VERIFY_PARAM_new();
-  if (ssl->param == NULL) {
+static int ssl_crypto_x509_ssl_new(SSL_HANDSHAKE *hs) {
+  hs->config->param = X509_VERIFY_PARAM_new();
+  if (hs->config->param == NULL) {
     return 0;
   }
-  X509_VERIFY_PARAM_inherit(ssl->param, ssl->ctx->param);
+  X509_VERIFY_PARAM_inherit(hs->config->param, hs->ssl->ctx->param);
   return 1;
 }
 
-static void ssl_crypto_x509_ssl_flush_cached_client_CA(SSL *ssl) {
-  sk_X509_NAME_pop_free(ssl->cached_x509_client_CA, X509_NAME_free);
-  ssl->cached_x509_client_CA = NULL;
+static void ssl_crypto_x509_ssl_flush_cached_client_CA(SSL_CONFIG *cfg) {
+  sk_X509_NAME_pop_free(cfg->cached_x509_client_CA, X509_NAME_free);
+  cfg->cached_x509_client_CA = NULL;
 }
 
-static void ssl_crypto_x509_ssl_free(SSL *ssl) {
-  ssl_crypto_x509_ssl_flush_cached_client_CA(ssl);
-  X509_VERIFY_PARAM_free(ssl->param);
+static void ssl_crypto_x509_ssl_config_free(SSL_CONFIG *cfg) {
+  sk_X509_NAME_pop_free(cfg->cached_x509_client_CA, X509_NAME_free);
+  cfg->cached_x509_client_CA = NULL;
+  X509_VERIFY_PARAM_free(cfg->param);
 }
 
-static int ssl_crypto_x509_ssl_auto_chain_if_needed(SSL *ssl) {
+static int ssl_crypto_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) {
   // Only build a chain if there are no intermediates configured and the feature
   // isn't disabled.
-  if ((ssl->mode & SSL_MODE_NO_AUTO_CHAIN) ||
-      !ssl_has_certificate(ssl) ||
-      ssl->cert->chain == nullptr ||
-      sk_CRYPTO_BUFFER_num(ssl->cert->chain.get()) > 1) {
+  if ((hs->ssl->mode & SSL_MODE_NO_AUTO_CHAIN) ||
+      !ssl_has_certificate(hs->config) || hs->config->cert->chain == NULL ||
+      sk_CRYPTO_BUFFER_num(hs->config->cert->chain.get()) > 1) {
     return 1;
   }
 
   UniquePtr<X509> leaf(X509_parse_from_buffer(
-      sk_CRYPTO_BUFFER_value(ssl->cert->chain.get(), 0)));
+      sk_CRYPTO_BUFFER_value(hs->config->cert->chain.get(), 0)));
   if (!leaf) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB);
     return 0;
   }
 
   ScopedX509_STORE_CTX ctx;
-  if (!X509_STORE_CTX_init(ctx.get(), ssl->ctx->cert_store, leaf.get(), NULL)) {
+  if (!X509_STORE_CTX_init(ctx.get(), hs->ssl->ctx->cert_store, leaf.get(),
+                           NULL)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB);
     return 0;
   }
@@ -460,11 +459,11 @@
   // Remove the leaf from the generated chain.
   X509_free(sk_X509_shift(ctx->chain));
 
-  if (!ssl_cert_set_chain(ssl->cert, ctx->chain)) {
+  if (!ssl_cert_set_chain(hs->config->cert.get(), ctx->chain)) {
     return 0;
   }
 
-  ssl_crypto_x509_cert_flush_cached_chain(ssl->cert);
+  ssl_crypto_x509_cert_flush_cached_chain(hs->config->cert.get());
 
   return 1;
 }
@@ -499,7 +498,7 @@
   ssl_crypto_x509_session_verify_cert_chain,
   ssl_crypto_x509_hs_flush_cached_ca_names,
   ssl_crypto_x509_ssl_new,
-  ssl_crypto_x509_ssl_free,
+  ssl_crypto_x509_ssl_config_free,
   ssl_crypto_x509_ssl_flush_cached_client_CA,
   ssl_crypto_x509_ssl_auto_chain_if_needed,
   ssl_crypto_x509_ssl_ctx_new,
@@ -549,12 +548,11 @@
 
     for (size_t i = 1; i < sk_X509_num(session->x509_chain); i++) {
       X509 *cert = sk_X509_value(session->x509_chain, i);
-      if (!sk_X509_push(session->x509_chain_without_leaf, cert)) {
+      if (!PushToStack(session->x509_chain_without_leaf, UpRef(cert))) {
         sk_X509_pop_free(session->x509_chain_without_leaf, X509_free);
         session->x509_chain_without_leaf = NULL;
         return NULL;
       }
-      X509_up_ref(cert);
     }
   }
 
@@ -578,7 +576,10 @@
 
 int SSL_set_purpose(SSL *ssl, int purpose) {
   check_ssl_x509_method(ssl);
-  return X509_VERIFY_PARAM_set_purpose(ssl->param, purpose);
+  if (!ssl->config) {
+    return 0;
+  }
+  return X509_VERIFY_PARAM_set_purpose(ssl->config->param, purpose);
 }
 
 int SSL_CTX_set_trust(SSL_CTX *ctx, int trust) {
@@ -588,7 +589,10 @@
 
 int SSL_set_trust(SSL *ssl, int trust) {
   check_ssl_x509_method(ssl);
-  return X509_VERIFY_PARAM_set_trust(ssl->param, trust);
+  if (!ssl->config) {
+    return 0;
+  }
+  return X509_VERIFY_PARAM_set_trust(ssl->config->param, trust);
 }
 
 int SSL_CTX_set1_param(SSL_CTX *ctx, const X509_VERIFY_PARAM *param) {
@@ -598,7 +602,10 @@
 
 int SSL_set1_param(SSL *ssl, const X509_VERIFY_PARAM *param) {
   check_ssl_x509_method(ssl);
-  return X509_VERIFY_PARAM_set1(ssl->param, param);
+  if (!ssl->config) {
+    return 0;
+  }
+  return X509_VERIFY_PARAM_set1(ssl->config->param, param);
 }
 
 X509_VERIFY_PARAM *SSL_CTX_get0_param(SSL_CTX *ctx) {
@@ -608,17 +615,29 @@
 
 X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl) {
   check_ssl_x509_method(ssl);
-  return ssl->param;
+  if (!ssl->config) {
+    assert(ssl->config);
+    return 0;
+  }
+  return ssl->config->param;
 }
 
 int SSL_get_verify_depth(const SSL *ssl) {
   check_ssl_x509_method(ssl);
-  return X509_VERIFY_PARAM_get_depth(ssl->param);
+  if (!ssl->config) {
+    assert(ssl->config);
+    return 0;
+  }
+  return X509_VERIFY_PARAM_get_depth(ssl->config->param);
 }
 
 int (*SSL_get_verify_callback(const SSL *ssl))(int, X509_STORE_CTX *) {
   check_ssl_x509_method(ssl);
-  return ssl->verify_callback;
+  if (!ssl->config) {
+    assert(ssl->config);
+    return 0;
+  }
+  return ssl->config->verify_callback;
 }
 
 int SSL_CTX_get_verify_mode(const SSL_CTX *ctx) {
@@ -640,15 +659,21 @@
 void SSL_set_verify(SSL *ssl, int mode,
                     int (*callback)(int ok, X509_STORE_CTX *store_ctx)) {
   check_ssl_x509_method(ssl);
-  ssl->verify_mode = mode;
+  if (!ssl->config) {
+    return;
+  }
+  ssl->config->verify_mode = mode;
   if (callback != NULL) {
-    ssl->verify_callback = callback;
+    ssl->config->verify_callback = callback;
   }
 }
 
 void SSL_set_verify_depth(SSL *ssl, int depth) {
   check_ssl_x509_method(ssl);
-  X509_VERIFY_PARAM_set_depth(ssl->param, depth);
+  if (!ssl->config) {
+    return;
+  }
+  X509_VERIFY_PARAM_set_depth(ssl->config->param, depth);
 }
 
 void SSL_CTX_set_cert_verify_callback(SSL_CTX *ctx,
@@ -726,12 +751,15 @@
 
 int SSL_use_certificate(SSL *ssl, X509 *x) {
   check_ssl_x509_method(ssl);
-  return ssl_use_certificate(ssl->cert, x);
+  if (!ssl->config) {
+    return 0;
+  }
+  return ssl_use_certificate(ssl->config->cert.get(), x);
 }
 
 int SSL_CTX_use_certificate(SSL_CTX *ctx, X509 *x) {
   check_ssl_ctx_x509_method(ctx);
-  return ssl_use_certificate(ctx->cert, x);
+  return ssl_use_certificate(ctx->cert.get(), x);
 }
 
 // ssl_cert_cache_leaf_cert sets |cert->x509_leaf|, if currently NULL, from the
@@ -764,13 +792,17 @@
 
 X509 *SSL_get_certificate(const SSL *ssl) {
   check_ssl_x509_method(ssl);
-  return ssl_cert_get0_leaf(ssl->cert);
+  if (!ssl->config) {
+    assert(ssl->config);
+    return 0;
+  }
+  return ssl_cert_get0_leaf(ssl->config->cert.get());
 }
 
 X509 *SSL_CTX_get0_certificate(const SSL_CTX *ctx) {
   check_ssl_ctx_x509_method(ctx);
   MutexWriteLock lock(const_cast<CRYPTO_MUTEX*>(&ctx->lock));
-  return ssl_cert_get0_leaf(ctx->cert);
+  return ssl_cert_get0_leaf(ctx->cert.get());
 }
 
 static int ssl_cert_set0_chain(CERT *cert, STACK_OF(X509) *chain) {
@@ -836,32 +868,38 @@
 
 int SSL_CTX_set0_chain(SSL_CTX *ctx, STACK_OF(X509) *chain) {
   check_ssl_ctx_x509_method(ctx);
-  return ssl_cert_set0_chain(ctx->cert, chain);
+  return ssl_cert_set0_chain(ctx->cert.get(), chain);
 }
 
 int SSL_CTX_set1_chain(SSL_CTX *ctx, STACK_OF(X509) *chain) {
   check_ssl_ctx_x509_method(ctx);
-  return ssl_cert_set1_chain(ctx->cert, chain);
+  return ssl_cert_set1_chain(ctx->cert.get(), chain);
 }
 
 int SSL_set0_chain(SSL *ssl, STACK_OF(X509) *chain) {
   check_ssl_x509_method(ssl);
-  return ssl_cert_set0_chain(ssl->cert, chain);
+  if (!ssl->config) {
+    return 0;
+  }
+  return ssl_cert_set0_chain(ssl->config->cert.get(), chain);
 }
 
 int SSL_set1_chain(SSL *ssl, STACK_OF(X509) *chain) {
   check_ssl_x509_method(ssl);
-  return ssl_cert_set1_chain(ssl->cert, chain);
+  if (!ssl->config) {
+    return 0;
+  }
+  return ssl_cert_set1_chain(ssl->config->cert.get(), chain);
 }
 
 int SSL_CTX_add0_chain_cert(SSL_CTX *ctx, X509 *x509) {
   check_ssl_ctx_x509_method(ctx);
-  return ssl_cert_add0_chain_cert(ctx->cert, x509);
+  return ssl_cert_add0_chain_cert(ctx->cert.get(), x509);
 }
 
 int SSL_CTX_add1_chain_cert(SSL_CTX *ctx, X509 *x509) {
   check_ssl_ctx_x509_method(ctx);
-  return ssl_cert_add1_chain_cert(ctx->cert, x509);
+  return ssl_cert_add1_chain_cert(ctx->cert.get(), x509);
 }
 
 int SSL_CTX_add_extra_chain_cert(SSL_CTX *ctx, X509 *x509) {
@@ -871,12 +909,18 @@
 
 int SSL_add0_chain_cert(SSL *ssl, X509 *x509) {
   check_ssl_x509_method(ssl);
-  return ssl_cert_add0_chain_cert(ssl->cert, x509);
+  if (!ssl->config) {
+    return 0;
+  }
+  return ssl_cert_add0_chain_cert(ssl->config->cert.get(), x509);
 }
 
 int SSL_add1_chain_cert(SSL *ssl, X509 *x509) {
   check_ssl_x509_method(ssl);
-  return ssl_cert_add1_chain_cert(ssl->cert, x509);
+  if (!ssl->config) {
+    return 0;
+  }
+  return ssl_cert_add1_chain_cert(ssl->config->cert.get(), x509);
 }
 
 int SSL_CTX_clear_chain_certs(SSL_CTX *ctx) {
@@ -926,7 +970,7 @@
 int SSL_CTX_get0_chain_certs(const SSL_CTX *ctx, STACK_OF(X509) **out_chain) {
   check_ssl_ctx_x509_method(ctx);
   MutexWriteLock lock(const_cast<CRYPTO_MUTEX*>(&ctx->lock));
-  if (!ssl_cert_cache_chain_certs(ctx->cert)) {
+  if (!ssl_cert_cache_chain_certs(ctx->cert.get())) {
     *out_chain = NULL;
     return 0;
   }
@@ -942,12 +986,16 @@
 
 int SSL_get0_chain_certs(const SSL *ssl, STACK_OF(X509) **out_chain) {
   check_ssl_x509_method(ssl);
-  if (!ssl_cert_cache_chain_certs(ssl->cert)) {
+  if (!ssl->config) {
+    assert(ssl->config);
+    return 0;
+  }
+  if (!ssl_cert_cache_chain_certs(ssl->config->cert.get())) {
     *out_chain = NULL;
     return 0;
   }
 
-  *out_chain = ssl->cert->x509_chain;
+  *out_chain = ssl->config->cert->x509_chain;
   return 1;
 }
 
@@ -993,7 +1041,7 @@
   return sk_X509_NAME_deep_copy(list, X509_NAME_dup, X509_NAME_free);
 }
 
-static void set_client_CA_list(STACK_OF(CRYPTO_BUFFER) **ca_list,
+static void set_client_CA_list(UniquePtr<STACK_OF(CRYPTO_BUFFER)> *ca_list,
                                const STACK_OF(X509_NAME) *name_list,
                                CRYPTO_BUFFER_POOL *pool) {
   UniquePtr<STACK_OF(CRYPTO_BUFFER)> buffers(sk_CRYPTO_BUFFER_new_null());
@@ -1016,14 +1064,16 @@
     }
   }
 
-  sk_CRYPTO_BUFFER_pop_free(*ca_list, CRYPTO_BUFFER_free);
-  *ca_list = buffers.release();
+  *ca_list = std::move(buffers);
 }
 
 void SSL_set_client_CA_list(SSL *ssl, STACK_OF(X509_NAME) *name_list) {
   check_ssl_x509_method(ssl);
-  ssl->ctx->x509_method->ssl_flush_cached_client_CA(ssl);
-  set_client_CA_list(&ssl->client_CA, name_list, ssl->ctx->pool);
+  if (!ssl->config) {
+    return;
+  }
+  ssl->ctx->x509_method->ssl_flush_cached_client_CA(ssl->config.get());
+  set_client_CA_list(&ssl->config->client_CA, name_list, ssl->ctx->pool);
   sk_X509_NAME_pop_free(name_list, X509_NAME_free);
 }
 
@@ -1068,6 +1118,10 @@
 
 STACK_OF(X509_NAME) *SSL_get_client_CA_list(const SSL *ssl) {
   check_ssl_x509_method(ssl);
+  if (!ssl->config) {
+    assert(ssl->config);
+    return NULL;
+  }
   // For historical reasons, this function is used both to query configuration
   // state on a server as well as handshake state on a client. However, whether
   // |ssl| is a client or server is not known until explicitly configured with
@@ -1082,11 +1136,12 @@
     return NULL;
   }
 
-  if (ssl->client_CA != NULL) {
+  if (ssl->config->client_CA != NULL) {
     return buffer_names_to_x509(
-        ssl->client_CA, (STACK_OF(X509_NAME) **)&ssl->cached_x509_client_CA);
+        ssl->config->client_CA.get(),
+        (STACK_OF(X509_NAME) **)&ssl->config->cached_x509_client_CA);
   }
-  return SSL_CTX_get_client_CA_list(ssl->ctx);
+  return SSL_CTX_get_client_CA_list(ssl->ctx.get());
 }
 
 STACK_OF(X509_NAME) *SSL_CTX_get_client_CA_list(const SSL_CTX *ctx) {
@@ -1095,11 +1150,11 @@
   // so it needs to lock around updating |cached_x509_client_CA|.
   MutexWriteLock lock(const_cast<CRYPTO_MUTEX *>(&ctx->lock));
   return buffer_names_to_x509(
-      ctx->client_CA,
+      ctx->client_CA.get(),
       const_cast<STACK_OF(X509_NAME) **>(&ctx->cached_x509_client_CA));
 }
 
-static int add_client_CA(STACK_OF(CRYPTO_BUFFER) **names, X509 *x509,
+static int add_client_CA(UniquePtr<STACK_OF(CRYPTO_BUFFER)> *names, X509 *x509,
                          CRYPTO_BUFFER_POOL *pool) {
   if (x509 == NULL) {
     return 0;
@@ -1118,8 +1173,8 @@
   }
 
   int alloced = 0;
-  if (*names == NULL) {
-    *names = sk_CRYPTO_BUFFER_new_null();
+  if (*names == nullptr) {
+    names->reset(sk_CRYPTO_BUFFER_new_null());
     alloced = 1;
 
     if (*names == NULL) {
@@ -1127,10 +1182,9 @@
     }
   }
 
-  if (!PushToStack(*names, std::move(buffer))) {
+  if (!PushToStack(names->get(), std::move(buffer))) {
     if (alloced) {
-      sk_CRYPTO_BUFFER_pop_free(*names, CRYPTO_BUFFER_free);
-      *names = NULL;
+      names->reset();
     }
     return 0;
   }
@@ -1140,11 +1194,14 @@
 
 int SSL_add_client_CA(SSL *ssl, X509 *x509) {
   check_ssl_x509_method(ssl);
-  if (!add_client_CA(&ssl->client_CA, x509, ssl->ctx->pool)) {
+  if (!ssl->config) {
+    return 0;
+  }
+  if (!add_client_CA(&ssl->config->client_CA, x509, ssl->ctx->pool)) {
     return 0;
   }
 
-  ssl_crypto_x509_ssl_flush_cached_client_CA(ssl);
+  ssl_crypto_x509_ssl_flush_cached_client_CA(ssl->config.get());
   return 1;
 }
 
@@ -1159,7 +1216,13 @@
 }
 
 static int do_client_cert_cb(SSL *ssl, void *arg) {
-  if (ssl_has_certificate(ssl) || ssl->ctx->client_cert_cb == NULL) {
+  // Should only be called during handshake, but check to be sure.
+  if (!ssl->config) {
+    assert(ssl->config);
+    return -1;
+  }
+  if (ssl_has_certificate(ssl->config.get()) ||
+      ssl->ctx->client_cert_cb == NULL) {
     return 1;
   }
 
@@ -1223,12 +1286,18 @@
 
 int SSL_set0_verify_cert_store(SSL *ssl, X509_STORE *store) {
   check_ssl_x509_method(ssl);
-  return set_cert_store(&ssl->cert->verify_store, store, 0);
+  if (!ssl->config) {
+    return 0;
+  }
+  return set_cert_store(&ssl->config->cert->verify_store, store, 0);
 }
 
 int SSL_set1_verify_cert_store(SSL *ssl, X509_STORE *store) {
   check_ssl_x509_method(ssl);
-  return set_cert_store(&ssl->cert->verify_store, store, 1);
+  if (!ssl->config) {
+    return 0;
+  }
+  return set_cert_store(&ssl->config->cert->verify_store, store, 1);
 }
 
 int SSL_alert_from_verify_result(long result) {
diff --git a/src/ssl/t1_enc.cc b/src/ssl/t1_enc.cc
index 5947627..93170b9 100644
--- a/src/ssl/t1_enc.cc
+++ b/src/ssl/t1_enc.cc
@@ -164,56 +164,6 @@
                               seed2.size());
 }
 
-static bool ssl3_prf(Span<uint8_t> out, Span<const uint8_t> secret,
-                     Span<const char> label, Span<const uint8_t> seed1,
-                     Span<const uint8_t> seed2) {
-  ScopedEVP_MD_CTX md5;
-  ScopedEVP_MD_CTX sha1;
-  uint8_t buf[16], smd[SHA_DIGEST_LENGTH];
-  uint8_t c = 'A';
-  size_t k = 0;
-  while (!out.empty()) {
-    k++;
-    if (k > sizeof(buf)) {
-      // bug: 'buf' is too small for this ciphersuite
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return false;
-    }
-
-    for (size_t j = 0; j < k; j++) {
-      buf[j] = c;
-    }
-    c++;
-    if (!EVP_DigestInit_ex(sha1.get(), EVP_sha1(), NULL)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
-      return false;
-    }
-    EVP_DigestUpdate(sha1.get(), buf, k);
-    EVP_DigestUpdate(sha1.get(), secret.data(), secret.size());
-    // |label| is ignored for SSLv3.
-    EVP_DigestUpdate(sha1.get(), seed1.data(), seed1.size());
-    EVP_DigestUpdate(sha1.get(), seed2.data(), seed2.size());
-    EVP_DigestFinal_ex(sha1.get(), smd, NULL);
-
-    if (!EVP_DigestInit_ex(md5.get(), EVP_md5(), NULL)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
-      return false;
-    }
-    EVP_DigestUpdate(md5.get(), secret.data(), secret.size());
-    EVP_DigestUpdate(md5.get(), smd, SHA_DIGEST_LENGTH);
-    if (out.size() < MD5_DIGEST_LENGTH) {
-      EVP_DigestFinal_ex(md5.get(), smd, NULL);
-      OPENSSL_memcpy(out.data(), smd, out.size());
-      break;
-    }
-    EVP_DigestFinal_ex(md5.get(), out.data(), NULL);
-    out = out.subspan(MD5_DIGEST_LENGTH);
-  }
-
-  OPENSSL_cleanse(smd, SHA_DIGEST_LENGTH);
-  return true;
-}
-
 static bool get_key_block_lengths(const SSL *ssl, size_t *out_mac_secret_len,
                                   size_t *out_key_len, size_t *out_iv_len,
                                   const SSL_CIPHER *cipher) {
@@ -318,16 +268,9 @@
   } else {
     auto label =
         MakeConstSpan(kMasterSecretLabel, sizeof(kMasterSecretLabel) - 1);
-    if (ssl_protocol_version(ssl) == SSL3_VERSION) {
-      if (!ssl3_prf(out_span, premaster, label, ssl->s3->client_random,
-                    ssl->s3->server_random)) {
-        return 0;
-      }
-    } else {
-      if (!tls1_prf(hs->transcript.Digest(), out_span, premaster, label,
-                    ssl->s3->client_random, ssl->s3->server_random)) {
-        return 0;
-      }
+    if (!tls1_prf(hs->transcript.Digest(), out_span, premaster, label,
+                  ssl->s3->client_random, ssl->s3->server_random)) {
+      return 0;
     }
   }
 
@@ -357,11 +300,6 @@
   static const char kLabel[] = "key expansion";
   auto label = MakeConstSpan(kLabel, sizeof(kLabel) - 1);
 
-  if (ssl_protocol_version(ssl) == SSL3_VERSION) {
-    return ssl3_prf(out_span, master_key, label, ssl->s3->server_random,
-                    ssl->s3->client_random);
-  }
-
   const EVP_MD *digest = ssl_session_get_digest(session);
   return tls1_prf(digest, out_span, master_key, label, ssl->s3->server_random,
                   ssl->s3->client_random);
@@ -371,11 +309,6 @@
                                const char *label, size_t label_len,
                                const uint8_t *context, size_t context_len,
                                int use_context) {
-  if (!ssl->s3->have_version || ssl->version == SSL3_VERSION) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_NOT_COMPLETE);
-    return 0;
-  }
-
   // Exporters may be used in False Start and server 0-RTT, where the handshake
   // has progressed enough. Otherwise, they may not be used during a handshake.
   if (SSL_in_init(ssl) &&
diff --git a/src/ssl/t1_lib.cc b/src/ssl/t1_lib.cc
index a821246..dde767e 100644
--- a/src/ssl/t1_lib.cc
+++ b/src/ssl/t1_lib.cc
@@ -131,7 +131,7 @@
 
 namespace bssl {
 
-static int ssl_check_clienthello_tlsext(SSL_HANDSHAKE *hs);
+static bool ssl_check_clienthello_tlsext(SSL_HANDSHAKE *hs);
 
 static int compare_uint16_t(const void *p1, const void *p2) {
   uint16_t u1 = *((const uint16_t *)p1);
@@ -149,7 +149,7 @@
 // more than one extension of the same type in a ClientHello or ServerHello.
 // This function does an initial scan over the extensions block to filter those
 // out.
-static int tls1_check_duplicate_extensions(const CBS *cbs) {
+static bool tls1_check_duplicate_extensions(const CBS *cbs) {
   // First pass: count the extensions.
   size_t num_extensions = 0;
   CBS extensions = *cbs;
@@ -159,19 +159,19 @@
 
     if (!CBS_get_u16(&extensions, &type) ||
         !CBS_get_u16_length_prefixed(&extensions, &extension)) {
-      return 0;
+      return false;
     }
 
     num_extensions++;
   }
 
   if (num_extensions == 0) {
-    return 1;
+    return true;
   }
 
   Array<uint16_t> extension_types;
   if (!extension_types.Init(num_extensions)) {
-    return 0;
+    return false;
   }
 
   // Second pass: gather the extension types.
@@ -182,7 +182,7 @@
     if (!CBS_get_u16(&extensions, &extension_types[i]) ||
         !CBS_get_u16_length_prefixed(&extensions, &extension)) {
       // This should not happen.
-      return 0;
+      return false;
     }
   }
   assert(CBS_len(&extensions) == 0);
@@ -192,15 +192,15 @@
         compare_uint16_t);
   for (size_t i = 1; i < num_extensions; i++) {
     if (extension_types[i - 1] == extension_types[i]) {
-      return 0;
+      return false;
     }
   }
 
-  return 1;
+  return true;
 }
 
-int ssl_client_hello_init(SSL *ssl, SSL_CLIENT_HELLO *out,
-                          const SSLMessage &msg) {
+bool ssl_client_hello_init(SSL *ssl, SSL_CLIENT_HELLO *out,
+                           const SSLMessage &msg) {
   OPENSSL_memset(out, 0, sizeof(*out));
   out->ssl = ssl;
   out->client_hello = CBS_data(&msg.body);
@@ -212,7 +212,7 @@
       !CBS_get_bytes(&client_hello, &random, SSL3_RANDOM_SIZE) ||
       !CBS_get_u8_length_prefixed(&client_hello, &session_id) ||
       CBS_len(&session_id) > SSL_MAX_SSL_SESSION_ID_LENGTH) {
-    return 0;
+    return false;
   }
 
   out->random = CBS_data(&random);
@@ -225,7 +225,7 @@
     CBS cookie;
     if (!CBS_get_u8_length_prefixed(&client_hello, &cookie) ||
         CBS_len(&cookie) > DTLS1_COOKIE_LENGTH) {
-      return 0;
+      return false;
     }
   }
 
@@ -234,7 +234,7 @@
       CBS_len(&cipher_suites) < 2 || (CBS_len(&cipher_suites) & 1) != 0 ||
       !CBS_get_u8_length_prefixed(&client_hello, &compression_methods) ||
       CBS_len(&compression_methods) < 1) {
-    return 0;
+    return false;
   }
 
   out->cipher_suites = CBS_data(&cipher_suites);
@@ -243,11 +243,11 @@
   out->compression_methods_len = CBS_len(&compression_methods);
 
   // If the ClientHello ends here then it's valid, but doesn't have any
-  // extensions. (E.g. SSLv3.)
+  // extensions.
   if (CBS_len(&client_hello) == 0) {
     out->extensions = NULL;
     out->extensions_len = 0;
-    return 1;
+    return true;
   }
 
   // Extract extensions and check it is valid.
@@ -255,17 +255,17 @@
   if (!CBS_get_u16_length_prefixed(&client_hello, &extensions) ||
       !tls1_check_duplicate_extensions(&extensions) ||
       CBS_len(&client_hello) != 0) {
-    return 0;
+    return false;
   }
 
   out->extensions = CBS_data(&extensions);
   out->extensions_len = CBS_len(&extensions);
 
-  return 1;
+  return true;
 }
 
-int ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello,
-                                   CBS *out, uint16_t extension_type) {
+bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello,
+                                    CBS *out, uint16_t extension_type) {
   CBS extensions;
   CBS_init(&extensions, client_hello->extensions, client_hello->extensions_len);
   while (CBS_len(&extensions) != 0) {
@@ -274,16 +274,16 @@
     CBS extension;
     if (!CBS_get_u16(&extensions, &type) ||
         !CBS_get_u16_length_prefixed(&extensions, &extension)) {
-      return 0;
+      return false;
     }
 
     if (type == extension_type) {
       *out = extension;
-      return 1;
+      return true;
     }
   }
 
-  return 0;
+  return false;
 }
 
 static const uint16_t kDefaultGroups[] = {
@@ -292,15 +292,14 @@
     SSL_CURVE_SECP384R1,
 };
 
-Span<const uint16_t> tls1_get_grouplist(const SSL *ssl) {
-  if (ssl->supported_group_list != nullptr) {
-    return MakeConstSpan(ssl->supported_group_list,
-                         ssl->supported_group_list_len);
+Span<const uint16_t> tls1_get_grouplist(const SSL_HANDSHAKE *hs) {
+  if (!hs->config->supported_group_list.empty()) {
+    return hs->config->supported_group_list;
   }
   return Span<const uint16_t>(kDefaultGroups);
 }
 
-int tls1_get_shared_group(SSL_HANDSHAKE *hs, uint16_t *out_group_id) {
+bool tls1_get_shared_group(SSL_HANDSHAKE *hs, uint16_t *out_group_id) {
   SSL *const ssl = hs->ssl;
   assert(ssl->server);
 
@@ -313,7 +312,7 @@
   // support our favoured group. Thus we do not special-case an emtpy
   // |peer_supported_group_list|.
 
-  Span<const uint16_t> groups = tls1_get_grouplist(ssl);
+  Span<const uint16_t> groups = tls1_get_grouplist(hs);
   Span<const uint16_t> pref, supp;
   if (ssl->options & SSL_OP_CIPHER_SERVER_PREFERENCE) {
     pref = groups;
@@ -327,86 +326,73 @@
     for (uint16_t supp_group : supp) {
       if (pref_group == supp_group) {
         *out_group_id = pref_group;
-        return 1;
+        return true;
       }
     }
   }
 
-  return 0;
+  return false;
 }
 
-int tls1_set_curves(uint16_t **out_group_ids, size_t *out_group_ids_len,
-                    const int *curves, size_t ncurves) {
-  uint16_t *group_ids = (uint16_t *)OPENSSL_malloc(ncurves * sizeof(uint16_t));
-  if (group_ids == NULL) {
-    return 0;
+bool tls1_set_curves(Array<uint16_t> *out_group_ids, Span<const int> curves) {
+  Array<uint16_t> group_ids;
+  if (!group_ids.Init(curves.size())) {
+    return false;
   }
 
-  for (size_t i = 0; i < ncurves; i++) {
+  for (size_t i = 0; i < curves.size(); i++) {
     if (!ssl_nid_to_group_id(&group_ids[i], curves[i])) {
-      OPENSSL_free(group_ids);
-      return 0;
+      return false;
     }
   }
 
-  OPENSSL_free(*out_group_ids);
-  *out_group_ids = group_ids;
-  *out_group_ids_len = ncurves;
-
-  return 1;
+  *out_group_ids = std::move(group_ids);
+  return true;
 }
 
-int tls1_set_curves_list(uint16_t **out_group_ids, size_t *out_group_ids_len,
-                         const char *curves) {
-  uint16_t *group_ids = NULL;
-  size_t ncurves = 0;
-
-  const char *col;
-  const char *ptr = curves;
-
+bool tls1_set_curves_list(Array<uint16_t> *out_group_ids, const char *curves) {
+  // Count the number of curves in the list.
+  size_t count = 0;
+  const char *ptr = curves, *col;
   do {
     col = strchr(ptr, ':');
-
-    uint16_t group_id;
-    if (!ssl_name_to_group_id(&group_id, ptr,
-                              col ? (size_t)(col - ptr) : strlen(ptr))) {
-      goto err;
-    }
-
-    uint16_t *new_group_ids = (uint16_t *)OPENSSL_realloc(
-        group_ids, (ncurves + 1) * sizeof(uint16_t));
-    if (new_group_ids == NULL) {
-      goto err;
-    }
-    group_ids = new_group_ids;
-
-    group_ids[ncurves] = group_id;
-    ncurves++;
-
+    count++;
     if (col) {
       ptr = col + 1;
     }
   } while (col);
 
-  OPENSSL_free(*out_group_ids);
-  *out_group_ids = group_ids;
-  *out_group_ids_len = ncurves;
+  Array<uint16_t> group_ids;
+  if (!group_ids.Init(count)) {
+    return false;
+  }
 
-  return 1;
+  size_t i = 0;
+  ptr = curves;
+  do {
+    col = strchr(ptr, ':');
+    if (!ssl_name_to_group_id(&group_ids[i++], ptr,
+                              col ? (size_t)(col - ptr) : strlen(ptr))) {
+      return false;
+    }
+    if (col) {
+      ptr = col + 1;
+    }
+  } while (col);
 
-err:
-  OPENSSL_free(group_ids);
-  return 0;
+  assert(i == count);
+  *out_group_ids = std::move(group_ids);
+  return true;
 }
 
-int tls1_check_group_id(const SSL *ssl, uint16_t group_id) {
-  for (uint16_t supported : tls1_get_grouplist(ssl)) {
+bool tls1_check_group_id(const SSL_HANDSHAKE *hs, uint16_t group_id) {
+  for (uint16_t supported : tls1_get_grouplist(hs)) {
     if (supported == group_id) {
-      return 1;
+      return true;
     }
   }
 
-  return 0;
+  return false;
 }
 
 // kVerifySignatureAlgorithms is the default list of accepted signature
@@ -506,9 +492,8 @@
 static SSLSignatureAlgorithmList tls12_get_verify_sigalgs(const SSL *ssl,
                                                           bool for_certs) {
   SSLSignatureAlgorithmList ret;
-  if (ssl->ctx->num_verify_sigalgs != 0) {
-    ret.list =
-        MakeConstSpan(ssl->ctx->verify_sigalgs, ssl->ctx->num_verify_sigalgs);
+  if (!ssl->config->verify_sigalgs.empty()) {
+    ret.list = ssl->config->verify_sigalgs;
   } else {
     ret.list = kVerifySignatureAlgorithms;
     ret.skip_ed25519 = !ssl->ctx->ed25519_enabled;
@@ -606,7 +591,7 @@
 
 static bool ext_sni_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
-  if (ssl->tlsext_hostname == NULL) {
+  if (ssl->hostname == nullptr) {
     return true;
   }
 
@@ -616,8 +601,8 @@
       !CBB_add_u16_length_prefixed(&contents, &server_name_list) ||
       !CBB_add_u8(&server_name_list, TLSEXT_NAMETYPE_host_name) ||
       !CBB_add_u16_length_prefixed(&server_name_list, &name) ||
-      !CBB_add_bytes(&name, (const uint8_t *)ssl->tlsext_hostname,
-                     strlen(ssl->tlsext_hostname)) ||
+      !CBB_add_bytes(&name, (const uint8_t *)ssl->hostname.get(),
+                     strlen(ssl->hostname.get())) ||
       !CBB_flush(out)) {
     return false;
   }
@@ -862,7 +847,7 @@
 
 static bool ext_ems_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
   // Extended master secret is not necessary in TLS 1.3.
-  if (hs->min_version >= TLS1_3_VERSION || hs->max_version <= SSL3_VERSION) {
+  if (hs->min_version >= TLS1_3_VERSION) {
     return true;
   }
 
@@ -880,7 +865,6 @@
 
   if (contents != NULL) {
     if (ssl_protocol_version(ssl) >= TLS1_3_VERSION ||
-        ssl->version == SSL3_VERSION ||
         CBS_len(contents) != 0) {
       return false;
     }
@@ -902,9 +886,7 @@
 
 static bool ext_ems_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                       CBS *contents) {
-  uint16_t version = ssl_protocol_version(hs->ssl);
-  if (version >= TLS1_3_VERSION ||
-      version == SSL3_VERSION) {
+  if (ssl_protocol_version(hs->ssl) >= TLS1_3_VERSION) {
     return true;
   }
 
@@ -946,26 +928,24 @@
     return true;
   }
 
-  const uint8_t *ticket_data = NULL;
-  int ticket_len = 0;
+  Span<const uint8_t> ticket;
 
   // Renegotiation does not participate in session resumption. However, still
   // advertise the extension to avoid potentially breaking servers which carry
   // over the state from the previous handshake, such as OpenSSL servers
   // without upstream's 3c3f0259238594d77264a78944d409f2127642c4.
   if (!ssl->s3->initial_handshake_complete &&
-      ssl->session != NULL &&
-      ssl->session->tlsext_tick != NULL &&
+      ssl->session != nullptr &&
+      !ssl->session->ticket.empty() &&
       // Don't send TLS 1.3 session tickets in the ticket extension.
-      ssl_session_protocol_version(ssl->session) < TLS1_3_VERSION) {
-    ticket_data = ssl->session->tlsext_tick;
-    ticket_len = ssl->session->tlsext_ticklen;
+      ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION) {
+    ticket = ssl->session->ticket;
   }
 
-  CBB ticket;
+  CBB ticket_cbb;
   if (!CBB_add_u16(out, TLSEXT_TYPE_session_ticket) ||
-      !CBB_add_u16_length_prefixed(out, &ticket) ||
-      !CBB_add_bytes(&ticket, ticket_data, ticket_len) ||
+      !CBB_add_u16_length_prefixed(out, &ticket_cbb) ||
+      !CBB_add_bytes(&ticket_cbb, ticket.data(), ticket.size()) ||
       !CBB_flush(out)) {
     return false;
   }
@@ -1101,8 +1081,7 @@
 // https://tools.ietf.org/html/rfc6066#section-8
 
 static bool ext_ocsp_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
-  if (!ssl->ocsp_stapling_enabled) {
+  if (!hs->config->ocsp_stapling_enabled) {
     return true;
   }
 
@@ -1166,8 +1145,7 @@
 static bool ext_ocsp_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION ||
-      !hs->ocsp_stapling_requested ||
-      ssl->cert->ocsp_response == NULL ||
+      !hs->ocsp_stapling_requested || hs->config->cert->ocsp_response == NULL ||
       ssl->s3->session_reused ||
       !ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
     return true;
@@ -1308,8 +1286,7 @@
 // https://tools.ietf.org/html/rfc6962#section-3.3.1
 
 static bool ext_sct_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
-  if (!ssl->signed_cert_timestamps_enabled) {
+  if (!hs->config->signed_cert_timestamps_enabled) {
     return true;
   }
 
@@ -1336,7 +1313,7 @@
 
   // If this is false then we should never have sent the SCT extension in the
   // ClientHello and thus this function should never have been called.
-  assert(ssl->signed_cert_timestamps_enabled);
+  assert(hs->config->signed_cert_timestamps_enabled);
 
   if (!ssl_is_sct_list_valid(contents)) {
     *out_alert = SSL_AD_DECODE_ERROR;
@@ -1349,9 +1326,8 @@
   //
   // TODO(davidben): Enforce this anyway.
   if (!ssl->s3->session_reused) {
-    CRYPTO_BUFFER_free(hs->new_session->signed_cert_timestamp_list);
-    hs->new_session->signed_cert_timestamp_list =
-        CRYPTO_BUFFER_new_from_CBS(contents, ssl->ctx->pool);
+    hs->new_session->signed_cert_timestamp_list.reset(
+        CRYPTO_BUFFER_new_from_CBS(contents, ssl->ctx->pool));
     if (hs->new_session->signed_cert_timestamp_list == nullptr) {
       *out_alert = SSL_AD_INTERNAL_ERROR;
       return false;
@@ -1378,9 +1354,8 @@
 static bool ext_sct_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
   // The extension shouldn't be sent when resuming sessions.
-  if (ssl_protocol_version(ssl) >= TLS1_3_VERSION ||
-      ssl->s3->session_reused ||
-      ssl->cert->signed_cert_timestamp_list == NULL) {
+  if (ssl_protocol_version(ssl) >= TLS1_3_VERSION || ssl->s3->session_reused ||
+      hs->config->cert->signed_cert_timestamp_list == NULL) {
     return true;
   }
 
@@ -1389,8 +1364,10 @@
          CBB_add_u16_length_prefixed(out, &contents) &&
          CBB_add_bytes(
              &contents,
-             CRYPTO_BUFFER_data(ssl->cert->signed_cert_timestamp_list.get()),
-             CRYPTO_BUFFER_len(ssl->cert->signed_cert_timestamp_list.get())) &&
+             CRYPTO_BUFFER_data(
+                 hs->config->cert->signed_cert_timestamp_list.get()),
+             CRYPTO_BUFFER_len(
+                 hs->config->cert->signed_cert_timestamp_list.get())) &&
          CBB_flush(out);
 }
 
@@ -1401,7 +1378,7 @@
 
 static bool ext_alpn_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
-  if (ssl->alpn_client_proto_list == NULL ||
+  if (hs->config->alpn_client_proto_list.empty() ||
       ssl->s3->initial_handshake_complete) {
     return true;
   }
@@ -1410,8 +1387,8 @@
   if (!CBB_add_u16(out, TLSEXT_TYPE_application_layer_protocol_negotiation) ||
       !CBB_add_u16_length_prefixed(out, &contents) ||
       !CBB_add_u16_length_prefixed(&contents, &proto_list) ||
-      !CBB_add_bytes(&proto_list, ssl->alpn_client_proto_list,
-                     ssl->alpn_client_proto_list_len) ||
+      !CBB_add_bytes(&proto_list, hs->config->alpn_client_proto_list.data(),
+                     hs->config->alpn_client_proto_list.size()) ||
       !CBB_flush(out)) {
     return false;
   }
@@ -1427,7 +1404,7 @@
   }
 
   assert(!ssl->s3->initial_handshake_complete);
-  assert(ssl->alpn_client_proto_list != NULL);
+  assert(!hs->config->alpn_client_proto_list.empty());
 
   if (hs->next_proto_neg_seen) {
     // NPN and ALPN may not be negotiated in the same connection.
@@ -1448,7 +1425,7 @@
     return false;
   }
 
-  if (!ssl_is_alpn_protocol_allowed(ssl, protocol_name)) {
+  if (!ssl_is_alpn_protocol_allowed(hs, protocol_name)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ALPN_PROTOCOL);
     *out_alert = SSL_AD_ILLEGAL_PARAMETER;
     return false;
@@ -1462,20 +1439,20 @@
   return true;
 }
 
-bool ssl_is_alpn_protocol_allowed(const SSL *ssl,
+bool ssl_is_alpn_protocol_allowed(const SSL_HANDSHAKE *hs,
                                   Span<const uint8_t> protocol) {
-  if (ssl->alpn_client_proto_list == nullptr) {
+  if (hs->config->alpn_client_proto_list.empty()) {
     return false;
   }
 
-  if (ssl->ctx->allow_unknown_alpn_protos) {
+  if (hs->ssl->ctx->allow_unknown_alpn_protos) {
     return true;
   }
 
   // Check that the protocol name is one of the ones we advertised.
-  CBS client_protocol_name_list, client_protocol_name;
-  CBS_init(&client_protocol_name_list, ssl->alpn_client_proto_list,
-           ssl->alpn_client_proto_list_len);
+  CBS client_protocol_name_list =
+          MakeConstSpan(hs->config->alpn_client_proto_list),
+      client_protocol_name;
   while (CBS_len(&client_protocol_name_list) > 0) {
     if (!CBS_get_u8_length_prefixed(&client_protocol_name_list,
                                     &client_protocol_name)) {
@@ -1534,6 +1511,11 @@
           ssl, &selected, &selected_len, CBS_data(&protocol_name_list),
           CBS_len(&protocol_name_list),
           ssl->ctx->alpn_select_cb_arg) == SSL_TLSEXT_ERR_OK) {
+    if (selected_len == 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ALPN_PROTOCOL);
+      *out_alert = SSL_AD_INTERNAL_ERROR;
+      return false;
+    }
     if (!ssl->s3->alpn_selected.CopyFrom(
             MakeConstSpan(selected, selected_len))) {
       *out_alert = SSL_AD_INTERNAL_ERROR;
@@ -1570,13 +1552,12 @@
 // https://tools.ietf.org/html/draft-balfanz-tls-channelid-01
 
 static void ext_channel_id_init(SSL_HANDSHAKE *hs) {
-  hs->ssl->s3->tlsext_channel_id_valid = false;
+  hs->ssl->s3->channel_id_valid = false;
 }
 
 static bool ext_channel_id_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
-  if (!ssl->tlsext_channel_id_enabled ||
-      SSL_is_dtls(ssl)) {
+  if (!hs->config->channel_id_enabled || SSL_is_dtls(ssl)) {
     return true;
   }
 
@@ -1597,13 +1578,13 @@
   }
 
   assert(!SSL_is_dtls(ssl));
-  assert(ssl->tlsext_channel_id_enabled);
+  assert(hs->config->channel_id_enabled);
 
   if (CBS_len(contents) != 0) {
     return false;
   }
 
-  ssl->s3->tlsext_channel_id_valid = true;
+  ssl->s3->channel_id_valid = true;
   return true;
 }
 
@@ -1611,9 +1592,7 @@
                                              uint8_t *out_alert,
                                              CBS *contents) {
   SSL *const ssl = hs->ssl;
-  if (contents == NULL ||
-      !ssl->tlsext_channel_id_enabled ||
-      SSL_is_dtls(ssl)) {
+  if (contents == NULL || !hs->config->channel_id_enabled || SSL_is_dtls(ssl)) {
     return true;
   }
 
@@ -1621,13 +1600,13 @@
     return false;
   }
 
-  ssl->s3->tlsext_channel_id_valid = true;
+  ssl->s3->channel_id_valid = true;
   return true;
 }
 
 static bool ext_channel_id_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
-  if (!ssl->s3->tlsext_channel_id_valid) {
+  if (!ssl->s3->channel_id_valid) {
     return true;
   }
 
@@ -1871,20 +1850,20 @@
 
 static size_t ext_pre_shared_key_clienthello_length(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  if (hs->max_version < TLS1_3_VERSION || ssl->session == NULL ||
-      ssl_session_protocol_version(ssl->session) < TLS1_3_VERSION) {
+  if (hs->max_version < TLS1_3_VERSION || ssl->session == nullptr ||
+      ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION) {
     return 0;
   }
 
-  size_t binder_len = EVP_MD_size(ssl_session_get_digest(ssl->session));
-  return 15 + ssl->session->tlsext_ticklen + binder_len;
+  size_t binder_len = EVP_MD_size(ssl_session_get_digest(ssl->session.get()));
+  return 15 + ssl->session->ticket.size() + binder_len;
 }
 
 static bool ext_pre_shared_key_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
   hs->needs_psk_binder = false;
-  if (hs->max_version < TLS1_3_VERSION || ssl->session == NULL ||
-      ssl_session_protocol_version(ssl->session) < TLS1_3_VERSION) {
+  if (hs->max_version < TLS1_3_VERSION || ssl->session == nullptr ||
+      ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION) {
     return true;
   }
 
@@ -1904,15 +1883,15 @@
   // Fill in a placeholder zero binder of the appropriate length. It will be
   // computed and filled in later after length prefixes are computed.
   uint8_t zero_binder[EVP_MAX_MD_SIZE] = {0};
-  size_t binder_len = EVP_MD_size(ssl_session_get_digest(ssl->session));
+  size_t binder_len = EVP_MD_size(ssl_session_get_digest(ssl->session.get()));
 
   CBB contents, identity, ticket, binders, binder;
   if (!CBB_add_u16(out, TLSEXT_TYPE_pre_shared_key) ||
       !CBB_add_u16_length_prefixed(out, &contents) ||
       !CBB_add_u16_length_prefixed(&contents, &identity) ||
       !CBB_add_u16_length_prefixed(&identity, &ticket) ||
-      !CBB_add_bytes(&ticket, ssl->session->tlsext_tick,
-                     ssl->session->tlsext_ticklen) ||
+      !CBB_add_bytes(&ticket, ssl->session->ticket.data(),
+                     ssl->session->ticket.size()) ||
       !CBB_add_u32(&identity, obfuscated_ticket_age) ||
       !CBB_add_u16_length_prefixed(&contents, &binders) ||
       !CBB_add_u8_length_prefixed(&binders, &binder) ||
@@ -2069,19 +2048,17 @@
 
 static bool ext_early_data_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
-  if (!ssl->cert->enable_early_data ||
+  if (!ssl->enable_early_data ||
       // Session must be 0-RTT capable.
-      ssl->session == NULL ||
-      ssl_session_protocol_version(ssl->session) < TLS1_3_VERSION ||
+      ssl->session == nullptr ||
+      ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION ||
       ssl->session->ticket_max_early_data == 0 ||
       // The second ClientHello never offers early data.
       hs->received_hello_retry_request ||
       // In case ALPN preferences changed since this session was established,
       // avoid reporting a confusing value in |SSL_get0_alpn_selected|.
-      (ssl->session->early_alpn_len != 0 &&
-       !ssl_is_alpn_protocol_allowed(
-           ssl, MakeConstSpan(ssl->session->early_alpn,
-                              ssl->session->early_alpn_len)))) {
+      (!ssl->session->early_alpn.empty() &&
+       !ssl_is_alpn_protocol_allowed(hs, ssl->session->early_alpn))) {
     return true;
   }
 
@@ -2191,7 +2168,7 @@
     }
 
     // Predict the most preferred group.
-    Span<const uint16_t> groups = tls1_get_grouplist(ssl);
+    Span<const uint16_t> groups = tls1_get_grouplist(hs);
     if (groups.empty()) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_NO_GROUPS_SPECIFIED);
       return false;
@@ -2422,7 +2399,7 @@
 }
 
 static bool ext_dummy_pq_padding_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
-  const size_t len = hs->ssl->dummy_pq_padding_len;
+  const size_t len = hs->config->dummy_pq_padding_len;
   if (len == 0) {
     return true;
   }
@@ -2437,7 +2414,7 @@
     return true;
   }
 
-  if (CBS_len(contents) != hs->ssl->dummy_pq_padding_len) {
+  if (CBS_len(contents) != hs->config->dummy_pq_padding_len) {
     return false;
   }
 
@@ -2485,7 +2462,7 @@
     return false;
   }
 
-  for (uint16_t group : tls1_get_grouplist(ssl)) {
+  for (uint16_t group : tls1_get_grouplist(hs)) {
     if (!CBB_add_u16(&groups_bytes, group)) {
       return false;
     }
@@ -2556,7 +2533,7 @@
 
 static bool ext_token_binding_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
-  if (ssl->token_binding_params == nullptr || SSL_is_dtls(ssl)) {
+  if (hs->config->token_binding_params.empty() || SSL_is_dtls(ssl)) {
     return true;
   }
 
@@ -2565,8 +2542,8 @@
       !CBB_add_u16_length_prefixed(out, &contents) ||
       !CBB_add_u16(&contents, kTokenBindingMaxVersion) ||
       !CBB_add_u8_length_prefixed(&contents, &params) ||
-      !CBB_add_bytes(&params, ssl->token_binding_params,
-                     ssl->token_binding_params_len) ||
+      !CBB_add_bytes(&params, hs->config->token_binding_params.data(),
+                     hs->config->token_binding_params.size()) ||
       !CBB_flush(out)) {
     return false;
   }
@@ -2606,8 +2583,8 @@
     return true;
   }
 
-  for (size_t i = 0; i < ssl->token_binding_params_len; ++i) {
-    if (param == ssl->token_binding_params[i]) {
+  for (uint8_t config_param : hs->config->token_binding_params) {
+    if (param == config_param) {
       ssl->s3->negotiated_token_binding_param = param;
       ssl->s3->token_binding_negotiated = true;
       return true;
@@ -2619,15 +2596,15 @@
 }
 
 // select_tb_param looks for the first token binding param in
-// |ssl->token_binding_params| that is also in |params| and puts it in
-// |ssl->negotiated_token_binding_param|. It returns true if a token binding
+// |hs->ssl->token_binding_params| that is also in |params| and puts it in
+// |hs->ssl->negotiated_token_binding_param|. It returns true if a token binding
 // param is found, and false otherwise.
-static bool select_tb_param(SSL *ssl, Span<const uint8_t> peer_params) {
-  for (size_t i = 0; i < ssl->token_binding_params_len; ++i) {
-    uint8_t tb_param = ssl->token_binding_params[i];
+static bool select_tb_param(SSL_HANDSHAKE *hs,
+                            Span<const uint8_t> peer_params) {
+  for (uint8_t tb_param : hs->config->token_binding_params) {
     for (uint8_t peer_param : peer_params) {
       if (tb_param == peer_param) {
-        ssl->s3->negotiated_token_binding_param = tb_param;
+        hs->ssl->s3->negotiated_token_binding_param = tb_param;
         return true;
       }
     }
@@ -2639,7 +2616,7 @@
                                                 uint8_t *out_alert,
                                                 CBS *contents) {
   SSL *const ssl = hs->ssl;
-  if (contents == nullptr || ssl->token_binding_params == nullptr) {
+  if (contents == nullptr || hs->config->token_binding_params.empty()) {
     return true;
   }
 
@@ -2663,7 +2640,7 @@
   // version. Otherwise, use the client's version.
   hs->negotiated_token_binding_version =
       std::min(version, kTokenBindingMaxVersion);
-  if (!select_tb_param(ssl, params)) {
+  if (!select_tb_param(hs, params)) {
     return true;
   }
 
@@ -2695,16 +2672,16 @@
 
 static bool ext_quic_transport_params_add_clienthello(SSL_HANDSHAKE *hs,
                                                       CBB *out) {
-  SSL *const ssl = hs->ssl;
-  if (!ssl->quic_transport_params || hs->max_version <= TLS1_2_VERSION) {
+  if (hs->config->quic_transport_params.empty() ||
+      hs->max_version <= TLS1_2_VERSION) {
     return true;
   }
 
   CBB contents;
   if (!CBB_add_u16(out, TLSEXT_TYPE_quic_transport_parameters) ||
       !CBB_add_u16_length_prefixed(out, &contents) ||
-      !CBB_add_bytes(&contents, ssl->quic_transport_params,
-                     ssl->quic_transport_params_len) ||
+      !CBB_add_bytes(&contents, hs->config->quic_transport_params.data(),
+                     hs->config->quic_transport_params.size()) ||
       !CBB_flush(out)) {
     return false;
   }
@@ -2731,7 +2708,7 @@
                                                         uint8_t *out_alert,
                                                         CBS *contents) {
   SSL *const ssl = hs->ssl;
-  if (!contents || !ssl->quic_transport_params) {
+  if (!contents || hs->config->quic_transport_params.empty()) {
     return true;
   }
   // Ignore the extension before TLS 1.3.
@@ -2744,16 +2721,15 @@
 
 static bool ext_quic_transport_params_add_serverhello(SSL_HANDSHAKE *hs,
                                                       CBB *out) {
-  SSL *const ssl = hs->ssl;
-  if (!ssl->quic_transport_params) {
+  if (hs->config->quic_transport_params.empty()) {
     return true;
   }
 
   CBB contents;
   if (!CBB_add_u16(out, TLSEXT_TYPE_quic_transport_parameters) ||
       !CBB_add_u16_length_prefixed(out, &contents) ||
-      !CBB_add_bytes(&contents, ssl->quic_transport_params,
-                     ssl->quic_transport_params_len) ||
+      !CBB_add_bytes(&contents, hs->config->quic_transport_params.data(),
+                     hs->config->quic_transport_params.size()) ||
       !CBB_flush(out)) {
     return false;
   }
@@ -2761,6 +2737,113 @@
   return true;
 }
 
+// Certificate compression
+
+static bool cert_compression_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
+  bool first = true;
+  CBB contents, algs;
+
+  for (const auto *alg : hs->ssl->ctx->cert_compression_algs.get()) {
+    if (alg->decompress == nullptr) {
+      continue;
+    }
+
+    if (first && (!CBB_add_u16(out, TLSEXT_TYPE_cert_compression) ||
+                  !CBB_add_u16_length_prefixed(out, &contents) ||
+                  !CBB_add_u8_length_prefixed(&contents, &algs))) {
+      return false;
+    }
+    first = false;
+    if (!CBB_add_u16(&algs, alg->alg_id)) {
+      return false;
+    }
+  }
+
+  return first || CBB_flush(out);
+}
+
+static bool cert_compression_parse_serverhello(SSL_HANDSHAKE *hs,
+                                               uint8_t *out_alert,
+                                               CBS *contents) {
+  if (contents == nullptr) {
+    return true;
+  }
+
+  // The server may not echo this extension. Any server to client negotiation is
+  // advertised in the CertificateRequest message.
+  return false;
+}
+
+static bool cert_compression_parse_clienthello(SSL_HANDSHAKE *hs,
+                                               uint8_t *out_alert,
+                                               CBS *contents) {
+  if (contents == nullptr) {
+    return true;
+  }
+
+  const size_t num_algs =
+      sk_CertCompressionAlg_num(hs->ssl->ctx->cert_compression_algs.get());
+
+  CBS alg_ids;
+  if (!CBS_get_u8_length_prefixed(contents, &alg_ids) ||
+      CBS_len(contents) != 0 ||
+      CBS_len(&alg_ids) == 0 ||
+      CBS_len(&alg_ids) % 2 == 1) {
+    return false;
+  }
+
+  const size_t num_given_alg_ids = CBS_len(&alg_ids) / 2;
+  Array<uint16_t> given_alg_ids;
+  if (!given_alg_ids.Init(num_given_alg_ids)) {
+    return false;
+  }
+
+  size_t best_index = num_algs;
+  size_t given_alg_idx = 0;
+
+  while (CBS_len(&alg_ids) > 0) {
+    uint16_t alg_id;
+    if (!CBS_get_u16(&alg_ids, &alg_id)) {
+      return false;
+    }
+
+    given_alg_ids[given_alg_idx++] = alg_id;
+
+    for (size_t i = 0; i < num_algs; i++) {
+      const auto *alg = sk_CertCompressionAlg_value(
+          hs->ssl->ctx->cert_compression_algs.get(), i);
+      if (alg->alg_id == alg_id && alg->compress != nullptr) {
+        if (i < best_index) {
+          best_index = i;
+        }
+        break;
+      }
+    }
+  }
+
+  qsort(given_alg_ids.data(), given_alg_ids.size(), sizeof(uint16_t),
+        compare_uint16_t);
+  for (size_t i = 1; i < num_given_alg_ids; i++) {
+    if (given_alg_ids[i - 1] == given_alg_ids[i]) {
+      return false;
+    }
+  }
+
+  if (best_index < num_algs &&
+      ssl_protocol_version(hs->ssl) >= TLS1_3_VERSION) {
+    hs->cert_compression_negotiated = true;
+    hs->cert_compression_alg_id =
+        sk_CertCompressionAlg_value(hs->ssl->ctx->cert_compression_algs.get(),
+                                    best_index)
+            ->alg_id;
+  }
+
+  return true;
+}
+
+static bool cert_compression_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
+  return true;
+}
 
 // kExtensions contains all the supported extensions.
 static const struct tls_extension kExtensions[] = {
@@ -2945,6 +3028,14 @@
     ext_token_binding_parse_clienthello,
     ext_token_binding_add_serverhello,
   },
+  {
+    TLSEXT_TYPE_cert_compression,
+    NULL,
+    cert_compression_add_clienthello,
+    cert_compression_parse_serverhello,
+    cert_compression_parse_clienthello,
+    cert_compression_add_serverhello,
+  },
 };
 
 #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
@@ -2969,22 +3060,16 @@
   return NULL;
 }
 
-int ssl_add_clienthello_tlsext(SSL_HANDSHAKE *hs, CBB *out, size_t header_len) {
+bool ssl_add_clienthello_tlsext(SSL_HANDSHAKE *hs, CBB *out,
+                                size_t header_len) {
   SSL *const ssl = hs->ssl;
-  // Don't add extensions for SSLv3 unless doing secure renegotiation.
-  if (hs->client_version == SSL3_VERSION &&
-      !ssl->s3->send_connection_binding) {
-    return 1;
-  }
-
   CBB extensions;
   if (!CBB_add_u16_length_prefixed(out, &extensions)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return 0;
+    return false;
   }
 
   hs->extensions.sent = 0;
-  hs->custom_extensions.sent = 0;
 
   for (size_t i = 0; i < kNumExtensions; i++) {
     if (kExtensions[i].init != NULL) {
@@ -2999,7 +3084,7 @@
     if (!CBB_add_u16(&extensions, grease_ext1) ||
         !CBB_add_u16(&extensions, 0 /* zero length */)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return 0;
+      return false;
     }
   }
 
@@ -3008,7 +3093,7 @@
     if (!kExtensions[i].add_clienthello(hs, &extensions)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_ADDING_EXTENSION);
       ERR_add_error_dataf("extension %u", (unsigned)kExtensions[i].value);
-      return 0;
+      return false;
     }
 
     if (CBB_len(&extensions) != len_before) {
@@ -3016,11 +3101,6 @@
     }
   }
 
-  if (!custom_ext_add_clienthello(hs, &extensions)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return 0;
-  }
-
   if (ssl->ctx->grease_enabled) {
     // Add a fake non-empty extension. See draft-davidben-tls-grease-01.
     uint16_t grease_ext2 = ssl_get_grease_value(hs, ssl_grease_extension2);
@@ -3036,7 +3116,7 @@
         !CBB_add_u16(&extensions, 1 /* one byte length */) ||
         !CBB_add_u8(&extensions, 0 /* single zero byte as contents */)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return 0;
+      return false;
     }
   }
 
@@ -3064,7 +3144,7 @@
           !CBB_add_u16(&extensions, padding_len) ||
           !CBB_add_space(&extensions, &padding_bytes, padding_len)) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-        return 0;
+        return false;
       }
 
       OPENSSL_memset(padding_bytes, 0, padding_len);
@@ -3074,7 +3154,7 @@
   // The PSK extension must be last, including after the padding.
   if (!ext_pre_shared_key_add_clienthello(hs, &extensions)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return 0;
+    return false;
   }
 
   // Discard empty extensions blocks.
@@ -3085,7 +3165,7 @@
   return CBB_flush(out);
 }
 
-int ssl_add_serverhello_tlsext(SSL_HANDSHAKE *hs, CBB *out) {
+bool ssl_add_serverhello_tlsext(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
   CBB extensions;
   if (!CBB_add_u16_length_prefixed(out, &extensions)) {
@@ -3105,10 +3185,6 @@
     }
   }
 
-  if (!custom_ext_add_serverhello(hs, &extensions)) {
-    goto err;
-  }
-
   // Discard empty extensions blocks before TLS 1.3.
   if (ssl_protocol_version(ssl) < TLS1_3_VERSION &&
       CBB_len(&extensions) == 0) {
@@ -3119,13 +3195,12 @@
 
 err:
   OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-  return 0;
+  return false;
 }
 
-static int ssl_scan_clienthello_tlsext(SSL_HANDSHAKE *hs,
-                                       const SSL_CLIENT_HELLO *client_hello,
-                                       int *out_alert) {
-  SSL *const ssl = hs->ssl;
+static bool ssl_scan_clienthello_tlsext(SSL_HANDSHAKE *hs,
+                                        const SSL_CLIENT_HELLO *client_hello,
+                                        int *out_alert) {
   for (size_t i = 0; i < kNumExtensions; i++) {
     if (kExtensions[i].init != NULL) {
       kExtensions[i].init(hs);
@@ -3133,7 +3208,6 @@
   }
 
   hs->extensions.received = 0;
-  hs->custom_extensions.received = 0;
   CBS extensions;
   CBS_init(&extensions, client_hello->extensions, client_hello->extensions_len);
   while (CBS_len(&extensions) != 0) {
@@ -3144,24 +3218,13 @@
     if (!CBS_get_u16(&extensions, &type) ||
         !CBS_get_u16_length_prefixed(&extensions, &extension)) {
       *out_alert = SSL_AD_DECODE_ERROR;
-      return 0;
-    }
-
-    // RFC 5746 made the existence of extensions in SSL 3.0 somewhat
-    // ambiguous. Ignore all but the renegotiation_info extension.
-    if (ssl->version == SSL3_VERSION && type != TLSEXT_TYPE_renegotiate) {
-      continue;
+      return false;
     }
 
     unsigned ext_index;
     const struct tls_extension *const ext =
         tls_extension_find(&ext_index, type);
-
     if (ext == NULL) {
-      if (!custom_ext_parse_clienthello(hs, out_alert, type, &extension)) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_PARSING_EXTENSION);
-        return 0;
-      }
       continue;
     }
 
@@ -3171,7 +3234,7 @@
       *out_alert = alert;
       OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_PARSING_EXTENSION);
       ERR_add_error_dataf("extension %u", (unsigned)type);
-      return 0;
+      return false;
     }
   }
 
@@ -3200,36 +3263,36 @@
       OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_EXTENSION);
       ERR_add_error_dataf("extension %u", (unsigned)kExtensions[i].value);
       *out_alert = alert;
-      return 0;
+      return false;
     }
   }
 
-  return 1;
+  return true;
 }
 
-int ssl_parse_clienthello_tlsext(SSL_HANDSHAKE *hs,
-                                 const SSL_CLIENT_HELLO *client_hello) {
+bool ssl_parse_clienthello_tlsext(SSL_HANDSHAKE *hs,
+                                  const SSL_CLIENT_HELLO *client_hello) {
   SSL *const ssl = hs->ssl;
   int alert = SSL_AD_DECODE_ERROR;
-  if (ssl_scan_clienthello_tlsext(hs, client_hello, &alert) <= 0) {
+  if (!ssl_scan_clienthello_tlsext(hs, client_hello, &alert)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
-    return 0;
+    return false;
   }
 
-  if (ssl_check_clienthello_tlsext(hs) <= 0) {
+  if (!ssl_check_clienthello_tlsext(hs)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_CLIENTHELLO_TLSEXT);
-    return 0;
+    return false;
   }
 
-  return 1;
+  return true;
 }
 
-static int ssl_scan_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs,
-                                       int *out_alert) {
+static bool ssl_scan_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs,
+                                        int *out_alert) {
   SSL *const ssl = hs->ssl;
   // Before TLS 1.3, ServerHello extensions blocks may be omitted if empty.
   if (CBS_len(cbs) == 0 && ssl_protocol_version(ssl) < TLS1_3_VERSION) {
-    return 1;
+    return true;
   }
 
   // Decode the extensions block and check it is valid.
@@ -3237,7 +3300,7 @@
   if (!CBS_get_u16_length_prefixed(cbs, &extensions) ||
       !tls1_check_duplicate_extensions(&extensions)) {
     *out_alert = SSL_AD_DECODE_ERROR;
-    return 0;
+    return false;
   }
 
   uint32_t received = 0;
@@ -3249,7 +3312,7 @@
     if (!CBS_get_u16(&extensions, &type) ||
         !CBS_get_u16_length_prefixed(&extensions, &extension)) {
       *out_alert = SSL_AD_DECODE_ERROR;
-      return 0;
+      return false;
     }
 
     unsigned ext_index;
@@ -3257,24 +3320,21 @@
         tls_extension_find(&ext_index, type);
 
     if (ext == NULL) {
-      hs->received_custom_extension = true;
-      if (!custom_ext_parse_serverhello(hs, out_alert, type, &extension)) {
-        return 0;
-      }
-      continue;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
+      ERR_add_error_dataf("extension %u", (unsigned)type);
+      *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
+      return false;
     }
 
     static_assert(kNumExtensions <= sizeof(hs->extensions.sent) * 8,
                   "too many bits");
 
-    if (!(hs->extensions.sent & (1u << ext_index)) &&
-        type != TLSEXT_TYPE_renegotiate) {
-      // If the extension was never sent then it is illegal, except for the
-      // renegotiation extension which, in SSL 3.0, is signaled via SCSV.
+    if (!(hs->extensions.sent & (1u << ext_index))) {
+      // If the extension was never sent then it is illegal.
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
       ERR_add_error_dataf("extension :%u", (unsigned)type);
       *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
-      return 0;
+      return false;
     }
 
     received |= (1u << ext_index);
@@ -3284,7 +3344,7 @@
       OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_PARSING_EXTENSION);
       ERR_add_error_dataf("extension %u", (unsigned)type);
       *out_alert = alert;
-      return 0;
+      return false;
     }
   }
 
@@ -3297,15 +3357,15 @@
         OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_EXTENSION);
         ERR_add_error_dataf("extension %u", (unsigned)kExtensions[i].value);
         *out_alert = alert;
-        return 0;
+        return false;
       }
     }
   }
 
-  return 1;
+  return true;
 }
 
-static int ssl_check_clienthello_tlsext(SSL_HANDSHAKE *hs) {
+static bool ssl_check_clienthello_tlsext(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
   if (ssl->s3->token_binding_negotiated &&
@@ -3313,43 +3373,42 @@
         SSL_get_extms_support(ssl))) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NEGOTIATED_TB_WITHOUT_EMS_OR_RI);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
-    return -1;
+    return false;
   }
 
   int ret = SSL_TLSEXT_ERR_NOACK;
   int al = SSL_AD_UNRECOGNIZED_NAME;
 
-  if (ssl->ctx->tlsext_servername_callback != 0) {
-    ret = ssl->ctx->tlsext_servername_callback(ssl, &al,
-                                               ssl->ctx->tlsext_servername_arg);
-  } else if (ssl->session_ctx->tlsext_servername_callback != 0) {
-    ret = ssl->session_ctx->tlsext_servername_callback(
-        ssl, &al, ssl->session_ctx->tlsext_servername_arg);
+  if (ssl->ctx->servername_callback != 0) {
+    ret = ssl->ctx->servername_callback(ssl, &al, ssl->ctx->servername_arg);
+  } else if (ssl->session_ctx->servername_callback != 0) {
+    ret = ssl->session_ctx->servername_callback(
+        ssl, &al, ssl->session_ctx->servername_arg);
   }
 
   switch (ret) {
     case SSL_TLSEXT_ERR_ALERT_FATAL:
       ssl_send_alert(ssl, SSL3_AL_FATAL, al);
-      return -1;
+      return false;
 
     case SSL_TLSEXT_ERR_NOACK:
       hs->should_ack_sni = false;
-      return 1;
+      return true;
 
     default:
-      return 1;
+      return true;
   }
 }
 
-int ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs) {
+bool ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs) {
   SSL *const ssl = hs->ssl;
   int alert = SSL_AD_DECODE_ERROR;
-  if (ssl_scan_serverhello_tlsext(hs, cbs, &alert) <= 0) {
+  if (!ssl_scan_serverhello_tlsext(hs, cbs, &alert)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
-    return 0;
+    return false;
   }
 
-  return 1;
+  return true;
 }
 
 static enum ssl_ticket_aead_result_t decrypt_ticket_with_cipher_ctx(
@@ -3366,10 +3425,10 @@
   }
   HMAC_Update(hmac_ctx, ticket, ticket_len - mac_len);
   HMAC_Final(hmac_ctx, mac, NULL);
-  int mac_ok =
+  bool mac_ok =
       CRYPTO_memcmp(mac, ticket + (ticket_len - mac_len), mac_len) == 0;
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  mac_ok = 1;
+  mac_ok = true;
 #endif
   if (!mac_ok) {
     return ssl_ticket_aead_ignore_ticket;
@@ -3407,14 +3466,14 @@
 }
 
 static enum ssl_ticket_aead_result_t ssl_decrypt_ticket_with_cb(
-    SSL *ssl, uint8_t **out, size_t *out_len, bool *out_renew_ticket,
+    SSL_HANDSHAKE *hs, uint8_t **out, size_t *out_len, bool *out_renew_ticket,
     const uint8_t *ticket, size_t ticket_len) {
   assert(ticket_len >= SSL_TICKET_KEY_NAME_LEN + EVP_MAX_IV_LENGTH);
   ScopedEVP_CIPHER_CTX cipher_ctx;
   ScopedHMAC_CTX hmac_ctx;
   const uint8_t *iv = ticket + SSL_TICKET_KEY_NAME_LEN;
-  int cb_ret = ssl->session_ctx->tlsext_ticket_key_cb(
-      ssl, (uint8_t *)ticket /* name */, (uint8_t *)iv, cipher_ctx.get(),
+  int cb_ret = hs->ssl->session_ctx->ticket_key_cb(
+      hs->ssl, (uint8_t *)ticket /* name */, (uint8_t *)iv, cipher_ctx.get(),
       hmac_ctx.get(), 0 /* decrypt */);
   if (cb_ret < 0) {
     return ssl_ticket_aead_error;
@@ -3430,10 +3489,10 @@
 }
 
 static enum ssl_ticket_aead_result_t ssl_decrypt_ticket_with_ticket_keys(
-    SSL *ssl, uint8_t **out, size_t *out_len, const uint8_t *ticket,
+    SSL_HANDSHAKE *hs, uint8_t **out, size_t *out_len, const uint8_t *ticket,
     size_t ticket_len) {
   assert(ticket_len >= SSL_TICKET_KEY_NAME_LEN + EVP_MAX_IV_LENGTH);
-  SSL_CTX *ctx = ssl->session_ctx;
+  SSL_CTX *ctx = hs->ssl->session_ctx.get();
 
   // Rotate the ticket key if necessary.
   if (!ssl_ctx_rotate_ticket_encryption_key(ctx)) {
@@ -3445,15 +3504,15 @@
   ScopedHMAC_CTX hmac_ctx;
   {
     MutexReadLock lock(&ctx->lock);
-    const tlsext_ticket_key *key;
-    if (ctx->tlsext_ticket_key_current &&
-        !OPENSSL_memcmp(ctx->tlsext_ticket_key_current->name, ticket,
+    const TicketKey *key;
+    if (ctx->ticket_key_current &&
+        !OPENSSL_memcmp(ctx->ticket_key_current->name, ticket,
                         SSL_TICKET_KEY_NAME_LEN)) {
-      key = ctx->tlsext_ticket_key_current;
-    } else if (ctx->tlsext_ticket_key_prev &&
-               !OPENSSL_memcmp(ctx->tlsext_ticket_key_prev->name, ticket,
+      key = ctx->ticket_key_current.get();
+    } else if (ctx->ticket_key_prev &&
+               !OPENSSL_memcmp(ctx->ticket_key_prev->name, ticket,
                                SSL_TICKET_KEY_NAME_LEN)) {
-      key = ctx->tlsext_ticket_key_prev;
+      key = ctx->ticket_key_prev.get();
     } else {
       return ssl_ticket_aead_ignore_ticket;
     }
@@ -3470,7 +3529,7 @@
 }
 
 static enum ssl_ticket_aead_result_t ssl_decrypt_ticket_with_method(
-    SSL *ssl, uint8_t **out, size_t *out_len, bool *out_renew_ticket,
+    SSL_HANDSHAKE *hs, uint8_t **out, size_t *out_len, bool *out_renew_ticket,
     const uint8_t *ticket, size_t ticket_len) {
   uint8_t *plaintext = (uint8_t *)OPENSSL_malloc(ticket_len);
   if (plaintext == NULL) {
@@ -3480,8 +3539,8 @@
 
   size_t plaintext_len;
   const enum ssl_ticket_aead_result_t result =
-      ssl->session_ctx->ticket_aead_method->open(
-          ssl, plaintext, &plaintext_len, ticket_len, ticket, ticket_len);
+      hs->ssl->session_ctx->ticket_aead_method->open(
+          hs->ssl, plaintext, &plaintext_len, ticket_len, ticket, ticket_len);
 
   if (result == ssl_ticket_aead_success) {
     *out = plaintext;
@@ -3494,13 +3553,13 @@
 }
 
 enum ssl_ticket_aead_result_t ssl_process_ticket(
-    SSL *ssl, UniquePtr<SSL_SESSION> *out_session, bool *out_renew_ticket,
-    const uint8_t *ticket, size_t ticket_len, const uint8_t *session_id,
-    size_t session_id_len) {
+    SSL_HANDSHAKE *hs, UniquePtr<SSL_SESSION> *out_session,
+    bool *out_renew_ticket, const uint8_t *ticket, size_t ticket_len,
+    const uint8_t *session_id, size_t session_id_len) {
   *out_renew_ticket = false;
   out_session->reset();
 
-  if ((SSL_get_options(ssl) & SSL_OP_NO_TICKET) ||
+  if ((SSL_get_options(hs->ssl) & SSL_OP_NO_TICKET) ||
       session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH) {
     return ssl_ticket_aead_ignore_ticket;
   }
@@ -3508,23 +3567,23 @@
   uint8_t *plaintext = NULL;
   size_t plaintext_len;
   enum ssl_ticket_aead_result_t result;
-  if (ssl->session_ctx->ticket_aead_method != NULL) {
+  if (hs->ssl->session_ctx->ticket_aead_method != NULL) {
     result = ssl_decrypt_ticket_with_method(
-        ssl, &plaintext, &plaintext_len, out_renew_ticket, ticket, ticket_len);
+        hs, &plaintext, &plaintext_len, out_renew_ticket, ticket, ticket_len);
   } else {
-    // Ensure there is room for the key name and the largest IV
-    // |tlsext_ticket_key_cb| may try to consume. The real limit may be lower,
-    // but the maximum IV length should be well under the minimum size for the
-    // session material and HMAC.
+    // Ensure there is room for the key name and the largest IV |ticket_key_cb|
+    // may try to consume. The real limit may be lower, but the maximum IV
+    // length should be well under the minimum size for the session material and
+    // HMAC.
     if (ticket_len < SSL_TICKET_KEY_NAME_LEN + EVP_MAX_IV_LENGTH) {
       return ssl_ticket_aead_ignore_ticket;
     }
-    if (ssl->session_ctx->tlsext_ticket_key_cb != NULL) {
-      result = ssl_decrypt_ticket_with_cb(ssl, &plaintext, &plaintext_len,
+    if (hs->ssl->session_ctx->ticket_key_cb != NULL) {
+      result = ssl_decrypt_ticket_with_cb(hs, &plaintext, &plaintext_len,
                                           out_renew_ticket, ticket, ticket_len);
     } else {
       result = ssl_decrypt_ticket_with_ticket_keys(
-          ssl, &plaintext, &plaintext_len, ticket, ticket_len);
+          hs, &plaintext, &plaintext_len, ticket, ticket_len);
     }
   }
 
@@ -3534,7 +3593,7 @@
 
   // Decode the session.
   UniquePtr<SSL_SESSION> session(
-      SSL_SESSION_from_bytes(plaintext, plaintext_len, ssl->ctx));
+      SSL_SESSION_from_bytes(plaintext, plaintext_len, hs->ssl->ctx.get()));
   OPENSSL_free(plaintext);
 
   if (!session) {
@@ -3575,7 +3634,7 @@
 
 bool tls1_choose_signature_algorithm(SSL_HANDSHAKE *hs, uint16_t *out) {
   SSL *const ssl = hs->ssl;
-  CERT *cert = ssl->cert;
+  CERT *cert = hs->config->cert.get();
 
   // Before TLS 1.2, the signature algorithm isn't negotiated as part of the
   // handshake.
@@ -3622,7 +3681,7 @@
   return false;
 }
 
-int tls1_verify_channel_id(SSL_HANDSHAKE *hs, const SSLMessage &msg) {
+bool tls1_verify_channel_id(SSL_HANDSHAKE *hs, const SSLMessage &msg) {
   SSL *const ssl = hs->ssl;
   // A Channel ID handshake message is structured to contain multiple
   // extensions, but the only one that can be present is Channel ID.
@@ -3635,19 +3694,19 @@
       CBS_len(&extension) != TLSEXT_CHANNEL_ID_SIZE) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return 0;
+    return false;
   }
 
   UniquePtr<EC_GROUP> p256(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
   if (!p256) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NO_P256_SUPPORT);
-    return 0;
+    return false;
   }
 
   UniquePtr<ECDSA_SIG> sig(ECDSA_SIG_new());
   UniquePtr<BIGNUM> x(BN_new()), y(BN_new());
   if (!sig || !x || !y) {
-    return 0;
+    return false;
   }
 
   const uint8_t *p = CBS_data(&extension);
@@ -3655,7 +3714,7 @@
       BN_bin2bn(p + 32, 32, y.get()) == NULL ||
       BN_bin2bn(p + 64, 32, sig->r) == NULL ||
       BN_bin2bn(p + 96, 32, sig->s) == NULL) {
-    return 0;
+    return false;
   }
 
   UniquePtr<EC_KEY> key(EC_KEY_new());
@@ -3665,40 +3724,39 @@
                                            y.get(), nullptr) ||
       !EC_KEY_set_group(key.get(), p256.get()) ||
       !EC_KEY_set_public_key(key.get(), point.get())) {
-    return 0;
+    return false;
   }
 
   uint8_t digest[EVP_MAX_MD_SIZE];
   size_t digest_len;
   if (!tls1_channel_id_hash(hs, digest, &digest_len)) {
-    return 0;
-  }
-
-  int sig_ok = ECDSA_do_verify(digest, digest_len, sig.get(), key.get());
-#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  sig_ok = 1;
-  ERR_clear_error();
-#endif
-  if (!sig_ok) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_CHANNEL_ID_SIGNATURE_INVALID);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
-    ssl->s3->tlsext_channel_id_valid = false;
-    return 0;
-  }
-
-  OPENSSL_memcpy(ssl->s3->tlsext_channel_id, p, 64);
-  return 1;
-}
-
-bool tls1_write_channel_id(SSL_HANDSHAKE *hs, CBB *cbb) {
-  SSL *const ssl = hs->ssl;
-  uint8_t digest[EVP_MAX_MD_SIZE];
-  size_t digest_len;
-  if (!tls1_channel_id_hash(hs, digest, &digest_len)) {
     return false;
   }
 
-  EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(ssl->tlsext_channel_id_private);
+  bool sig_ok = ECDSA_do_verify(digest, digest_len, sig.get(), key.get());
+#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
+  sig_ok = true;
+  ERR_clear_error();
+#endif
+  if (!sig_ok) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_CHANNEL_ID_SIGNATURE_INVALID);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
+    ssl->s3->channel_id_valid = false;
+    return false;
+  }
+
+  OPENSSL_memcpy(ssl->s3->channel_id, p, 64);
+  return true;
+}
+
+bool tls1_write_channel_id(SSL_HANDSHAKE *hs, CBB *cbb) {
+  uint8_t digest[EVP_MAX_MD_SIZE];
+  size_t digest_len;
+  if (!tls1_channel_id_hash(hs, digest, &digest_len)) {
+    return false;
+  }
+
+  EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(hs->config->channel_id_private.get());
   if (ec_key == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return false;
@@ -3731,17 +3789,17 @@
   return true;
 }
 
-int tls1_channel_id_hash(SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len) {
+bool tls1_channel_id_hash(SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len) {
   SSL *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     Array<uint8_t> msg;
     if (!tls13_get_cert_verify_signature_input(hs, &msg,
                                                ssl_cert_verify_channel_id)) {
-      return 0;
+      return false;
     }
     SHA256(msg.data(), msg.size(), out);
     *out_len = SHA256_DIGEST_LENGTH;
-    return 1;
+    return true;
   }
 
   SHA256_CTX ctx;
@@ -3755,7 +3813,7 @@
     SHA256_Update(&ctx, kResumptionMagic, sizeof(kResumptionMagic));
     if (ssl->session->original_handshake_hash_len == 0) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return 0;
+      return false;
     }
     SHA256_Update(&ctx, ssl->session->original_handshake_hash,
                   ssl->session->original_handshake_hash_len);
@@ -3764,24 +3822,21 @@
   uint8_t hs_hash[EVP_MAX_MD_SIZE];
   size_t hs_hash_len;
   if (!hs->transcript.GetHash(hs_hash, &hs_hash_len)) {
-    return 0;
+    return false;
   }
   SHA256_Update(&ctx, hs_hash, (size_t)hs_hash_len);
   SHA256_Final(out, &ctx);
   *out_len = SHA256_DIGEST_LENGTH;
-  return 1;
+  return true;
 }
 
-// tls1_record_handshake_hashes_for_channel_id records the current handshake
-// hashes in |hs->new_session| so that Channel ID resumptions can sign that
-// data.
-int tls1_record_handshake_hashes_for_channel_id(SSL_HANDSHAKE *hs) {
+bool tls1_record_handshake_hashes_for_channel_id(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   // This function should never be called for a resumed session because the
   // handshake hashes that we wish to record are for the original, full
   // handshake.
   if (ssl->session != NULL) {
-    return 0;
+    return false;
   }
 
   static_assert(
@@ -3791,35 +3846,34 @@
   size_t digest_len;
   if (!hs->transcript.GetHash(hs->new_session->original_handshake_hash,
                               &digest_len)) {
-    return 0;
+    return false;
   }
 
   static_assert(EVP_MAX_MD_SIZE <= 0xff,
                 "EVP_MAX_MD_SIZE does not fit in uint8_t");
   hs->new_session->original_handshake_hash_len = (uint8_t)digest_len;
 
-  return 1;
+  return true;
 }
 
-int ssl_do_channel_id_callback(SSL *ssl) {
-  if (ssl->tlsext_channel_id_private != NULL ||
-      ssl->ctx->channel_id_cb == NULL) {
-    return 1;
+bool ssl_do_channel_id_callback(SSL_HANDSHAKE *hs) {
+  if (hs->config->channel_id_private != NULL ||
+      hs->ssl->ctx->channel_id_cb == NULL) {
+    return true;
   }
 
   EVP_PKEY *key = NULL;
-  ssl->ctx->channel_id_cb(ssl, &key);
+  hs->ssl->ctx->channel_id_cb(hs->ssl, &key);
   if (key == NULL) {
     // The caller should try again later.
-    return 1;
+    return true;
   }
 
-  int ret = SSL_set1_tls_channel_id(ssl, key);
-  EVP_PKEY_free(key);
-  return ret;
+  UniquePtr<EVP_PKEY> free_key(key);
+  return SSL_set1_tls_channel_id(hs->ssl, key);
 }
 
-int ssl_is_sct_list_valid(const CBS *contents) {
+bool ssl_is_sct_list_valid(const CBS *contents) {
   // Shallow parse the SCT list for sanity. By the RFC
   // (https://tools.ietf.org/html/rfc6962#section-3.3) neither the list nor any
   // of the SCTs may be empty.
@@ -3828,18 +3882,18 @@
   if (!CBS_get_u16_length_prefixed(&copy, &sct_list) ||
       CBS_len(&copy) != 0 ||
       CBS_len(&sct_list) == 0) {
-    return 0;
+    return false;
   }
 
   while (CBS_len(&sct_list) > 0) {
     CBS sct;
     if (!CBS_get_u16_length_prefixed(&sct_list, &sct) ||
         CBS_len(&sct) == 0) {
-      return 0;
+      return false;
     }
   }
 
-  return 1;
+  return true;
 }
 
 }  // namespace bssl
@@ -3867,9 +3921,3 @@
 void SSL_CTX_set_rsa_pss_rsae_certs_enabled(SSL_CTX *ctx, int enabled) {
   ctx->rsa_pss_rsae_certs_enabled = !!enabled;
 }
-
-int SSL_extension_supported(unsigned extension_value) {
-  uint32_t index;
-  return extension_value == TLSEXT_TYPE_padding ||
-         tls_extension_find(&index, extension_value) != NULL;
-}
diff --git a/src/ssl/test/CMakeLists.txt b/src/ssl/test/CMakeLists.txt
index 3b07903..425b43b 100644
--- a/src/ssl/test/CMakeLists.txt
+++ b/src/ssl/test/CMakeLists.txt
@@ -5,10 +5,34 @@
 
   async_bio.cc
   bssl_shim.cc
+  handshake_util.cc
   packeted_bio.cc
+  settings_writer.cc
   test_config.cc
+  test_state.cc
 
   $<TARGET_OBJECTS:test_support>
 )
 
 target_link_libraries(bssl_shim ssl crypto)
+
+if(UNIX AND NOT APPLE AND NOT ANDROID)
+  add_executable(
+    handshaker
+
+    async_bio.cc
+    handshake_util.cc
+    handshaker.cc
+    packeted_bio.cc
+    settings_writer.cc
+    test_config.cc
+    test_state.cc
+
+    $<TARGET_OBJECTS:test_support>
+  )
+
+  target_link_libraries(handshaker ssl crypto)
+else()
+  # Declare a dummy target for run_tests to depend on.
+  add_custom_target(handshaker)
+endif()
diff --git a/src/ssl/test/bssl_shim.cc b/src/ssl/test/bssl_shim.cc
index 824dc94..009bdeb 100644
--- a/src/ssl/test/bssl_shim.cc
+++ b/src/ssl/test/bssl_shim.cc
@@ -64,12 +64,16 @@
 #include "../../crypto/internal.h"
 #include "../internal.h"
 #include "async_bio.h"
-#include "fuzzer_tags.h"
+#include "handshake_util.h"
 #include "packeted_bio.h"
+#include "settings_writer.h"
 #include "test_config.h"
+#include "test_state.h"
 
+#if defined(OPENSSL_LINUX) && !defined(OPENSSL_ANDROID)
+#define HANDSHAKER_SUPPORTED
+#endif
 
-static CRYPTO_BUFFER_POOL *g_pool = nullptr;
 
 #if !defined(OPENSSL_WINDOWS)
 static int closesocket(int sock) {
@@ -90,350 +94,6 @@
   return 1;
 }
 
-struct TestState {
-  // async_bio is async BIO which pauses reads and writes.
-  BIO *async_bio = nullptr;
-  // packeted_bio is the packeted BIO which simulates read timeouts.
-  BIO *packeted_bio = nullptr;
-  bssl::UniquePtr<EVP_PKEY> channel_id;
-  bool cert_ready = false;
-  bssl::UniquePtr<SSL_SESSION> session;
-  bssl::UniquePtr<SSL_SESSION> pending_session;
-  bool early_callback_called = false;
-  bool handshake_done = false;
-  // private_key is the underlying private key used when testing custom keys.
-  bssl::UniquePtr<EVP_PKEY> private_key;
-  std::vector<uint8_t> private_key_result;
-  // private_key_retries is the number of times an asynchronous private key
-  // operation has been retried.
-  unsigned private_key_retries = 0;
-  bool got_new_session = false;
-  bssl::UniquePtr<SSL_SESSION> new_session;
-  bool ticket_decrypt_done = false;
-  bool alpn_select_done = false;
-  bool is_resume = false;
-  bool early_callback_ready = false;
-  bool custom_verify_ready = false;
-  std::string msg_callback_text;
-  bool msg_callback_ok = true;
-  // cert_verified is true if certificate verification has been driven to
-  // completion. This tests that the callback is not called again after this.
-  bool cert_verified = false;
-};
-
-static void TestStateExFree(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
-                            int index, long argl, void *argp) {
-  delete ((TestState *)ptr);
-}
-
-static int g_config_index = 0;
-static int g_state_index = 0;
-
-static bool SetTestConfig(SSL *ssl, const TestConfig *config) {
-  return SSL_set_ex_data(ssl, g_config_index, (void *)config) == 1;
-}
-
-static const TestConfig *GetTestConfig(const SSL *ssl) {
-  return (const TestConfig *)SSL_get_ex_data(ssl, g_config_index);
-}
-
-static bool SetTestState(SSL *ssl, std::unique_ptr<TestState> state) {
-  // |SSL_set_ex_data| takes ownership of |state| only on success.
-  if (SSL_set_ex_data(ssl, g_state_index, state.get()) == 1) {
-    state.release();
-    return true;
-  }
-  return false;
-}
-
-static TestState *GetTestState(const SSL *ssl) {
-  return (TestState *)SSL_get_ex_data(ssl, g_state_index);
-}
-
-static bool MoveExData(SSL *dest, SSL *src) {
-  TestState *state = GetTestState(src);
-  const TestConfig *config = GetTestConfig(src);
-  if (!SSL_set_ex_data(src, g_state_index, nullptr) ||
-      !SSL_set_ex_data(dest, g_state_index, state) ||
-      !SSL_set_ex_data(src, g_config_index, nullptr) ||
-      !SSL_set_ex_data(dest, g_config_index, (void *) config)) {
-    return false;
-  }
-
-  return true;
-}
-
-static void MoveBIOs(SSL *dest, SSL *src) {
-  BIO *rbio = SSL_get_rbio(src);
-  BIO_up_ref(rbio);
-  SSL_set0_rbio(dest, rbio);
-
-  BIO *wbio = SSL_get_wbio(src);
-  BIO_up_ref(wbio);
-  SSL_set0_wbio(dest, wbio);
-
-  SSL_set0_rbio(src, nullptr);
-  SSL_set0_wbio(src, nullptr);
-}
-
-static bool LoadCertificate(bssl::UniquePtr<X509> *out_x509,
-                            bssl::UniquePtr<STACK_OF(X509)> *out_chain,
-                            const std::string &file) {
-  bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_file()));
-  if (!bio || !BIO_read_filename(bio.get(), file.c_str())) {
-    return false;
-  }
-
-  out_x509->reset(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
-  if (!*out_x509) {
-    return false;
-  }
-
-  out_chain->reset(sk_X509_new_null());
-  if (!*out_chain) {
-    return false;
-  }
-
-  // Keep reading the certificate chain.
-  for (;;) {
-    bssl::UniquePtr<X509> cert(
-        PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
-    if (!cert) {
-      break;
-    }
-
-    if (!sk_X509_push(out_chain->get(), cert.get())) {
-      return false;
-    }
-    cert.release();  // sk_X509_push takes ownership.
-  }
-
-  uint32_t err = ERR_peek_last_error();
-  if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
-      ERR_GET_REASON(err) != PEM_R_NO_START_LINE) {
-    return false;
-}
-
-  ERR_clear_error();
-  return true;
-}
-
-static bssl::UniquePtr<EVP_PKEY> LoadPrivateKey(const std::string &file) {
-  bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_file()));
-  if (!bio || !BIO_read_filename(bio.get(), file.c_str())) {
-    return nullptr;
-  }
-  return bssl::UniquePtr<EVP_PKEY>(
-      PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, NULL));
-}
-
-static bool FromHexDigit(uint8_t *out, char c) {
-  if ('0' <= c && c <= '9') {
-    *out = c - '0';
-    return true;
-  }
-  if ('a' <= c && c <= 'f') {
-    *out = c - 'a' + 10;
-    return true;
-  }
-  if ('A' <= c && c <= 'F') {
-    *out = c - 'A' + 10;
-    return true;
-  }
-  return false;
-}
-
-static bool HexDecode(std::string *out, const std::string &in) {
-  if ((in.size() & 1) != 0) {
-    return false;
-  }
-
-  std::unique_ptr<uint8_t[]> buf(new uint8_t[in.size() / 2]);
-  for (size_t i = 0; i < in.size() / 2; i++) {
-    uint8_t high, low;
-    if (!FromHexDigit(&high, in[i*2]) ||
-        !FromHexDigit(&low, in[i*2+1])) {
-      return false;
-    }
-    buf[i] = (high << 4) | low;
-  }
-
-  out->assign(reinterpret_cast<const char *>(buf.get()), in.size() / 2);
-  return true;
-}
-
-static std::vector<std::string> SplitParts(const std::string &in,
-                                           const char delim) {
-  std::vector<std::string> ret;
-  size_t start = 0;
-
-  for (size_t i = 0; i < in.size(); i++) {
-    if (in[i] == delim) {
-      ret.push_back(in.substr(start, i - start));
-      start = i + 1;
-    }
-  }
-
-  ret.push_back(in.substr(start, std::string::npos));
-  return ret;
-}
-
-static std::vector<std::string> DecodeHexStrings(
-    const std::string &hex_strings) {
-  std::vector<std::string> ret;
-  const std::vector<std::string> parts = SplitParts(hex_strings, ',');
-
-  for (const auto &part : parts) {
-    std::string binary;
-    if (!HexDecode(&binary, part)) {
-      fprintf(stderr, "Bad hex string: %s\n", part.c_str());
-      return ret;
-    }
-
-    ret.push_back(binary);
-  }
-
-  return ret;
-}
-
-static bssl::UniquePtr<STACK_OF(X509_NAME)> DecodeHexX509Names(
-    const std::string &hex_names) {
-  const std::vector<std::string> der_names = DecodeHexStrings(hex_names);
-  bssl::UniquePtr<STACK_OF(X509_NAME)> ret(sk_X509_NAME_new_null());
-  if (!ret) {
-    return nullptr;
-  }
-
-  for (const auto &der_name : der_names) {
-    const uint8_t *const data =
-        reinterpret_cast<const uint8_t *>(der_name.data());
-    const uint8_t *derp = data;
-    bssl::UniquePtr<X509_NAME> name(
-        d2i_X509_NAME(nullptr, &derp, der_name.size()));
-    if (!name || derp != data + der_name.size()) {
-      fprintf(stderr, "Failed to parse X509_NAME.\n");
-      return nullptr;
-    }
-
-    if (!sk_X509_NAME_push(ret.get(), name.get())) {
-      return nullptr;
-    }
-    name.release();
-  }
-
-  return ret;
-}
-
-static ssl_private_key_result_t AsyncPrivateKeySign(
-    SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
-    uint16_t signature_algorithm, const uint8_t *in, size_t in_len) {
-  TestState *test_state = GetTestState(ssl);
-  if (!test_state->private_key_result.empty()) {
-    fprintf(stderr, "AsyncPrivateKeySign called with operation pending.\n");
-    abort();
-  }
-
-  if (EVP_PKEY_id(test_state->private_key.get()) !=
-      SSL_get_signature_algorithm_key_type(signature_algorithm)) {
-    fprintf(stderr, "Key type does not match signature algorithm.\n");
-    abort();
-  }
-
-  // Determine the hash.
-  const EVP_MD *md = SSL_get_signature_algorithm_digest(signature_algorithm);
-  bssl::ScopedEVP_MD_CTX ctx;
-  EVP_PKEY_CTX *pctx;
-  if (!EVP_DigestSignInit(ctx.get(), &pctx, md, nullptr,
-                          test_state->private_key.get())) {
-    return ssl_private_key_failure;
-  }
-
-  // Configure additional signature parameters.
-  if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) {
-    if (!EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) ||
-        !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1 /* salt len = hash len */)) {
-      return ssl_private_key_failure;
-    }
-  }
-
-  // Write the signature into |test_state|.
-  size_t len = 0;
-  if (!EVP_DigestSign(ctx.get(), nullptr, &len, in, in_len)) {
-    return ssl_private_key_failure;
-  }
-  test_state->private_key_result.resize(len);
-  if (!EVP_DigestSign(ctx.get(), test_state->private_key_result.data(), &len,
-                      in, in_len)) {
-    return ssl_private_key_failure;
-  }
-  test_state->private_key_result.resize(len);
-
-  // The signature will be released asynchronously in |AsyncPrivateKeyComplete|.
-  return ssl_private_key_retry;
-}
-
-static ssl_private_key_result_t AsyncPrivateKeyDecrypt(
-    SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
-    const uint8_t *in, size_t in_len) {
-  TestState *test_state = GetTestState(ssl);
-  if (!test_state->private_key_result.empty()) {
-    fprintf(stderr,
-            "AsyncPrivateKeyDecrypt called with operation pending.\n");
-    abort();
-  }
-
-  RSA *rsa = EVP_PKEY_get0_RSA(test_state->private_key.get());
-  if (rsa == NULL) {
-    fprintf(stderr,
-            "AsyncPrivateKeyDecrypt called with incorrect key type.\n");
-    abort();
-  }
-  test_state->private_key_result.resize(RSA_size(rsa));
-  if (!RSA_decrypt(rsa, out_len, test_state->private_key_result.data(),
-                   RSA_size(rsa), in, in_len, RSA_NO_PADDING)) {
-    return ssl_private_key_failure;
-  }
-
-  test_state->private_key_result.resize(*out_len);
-
-  // The decryption will be released asynchronously in |AsyncPrivateComplete|.
-  return ssl_private_key_retry;
-}
-
-static ssl_private_key_result_t AsyncPrivateKeyComplete(
-    SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out) {
-  TestState *test_state = GetTestState(ssl);
-  if (test_state->private_key_result.empty()) {
-    fprintf(stderr,
-            "AsyncPrivateKeyComplete called without operation pending.\n");
-    abort();
-  }
-
-  if (test_state->private_key_retries < 2) {
-    // Only return the decryption on the second attempt, to test both incomplete
-    // |decrypt| and |decrypt_complete|.
-    return ssl_private_key_retry;
-  }
-
-  if (max_out < test_state->private_key_result.size()) {
-    fprintf(stderr, "Output buffer too small.\n");
-    return ssl_private_key_failure;
-  }
-  OPENSSL_memcpy(out, test_state->private_key_result.data(),
-                 test_state->private_key_result.size());
-  *out_len = test_state->private_key_result.size();
-
-  test_state->private_key_result.clear();
-  test_state->private_key_retries = 0;
-  return ssl_private_key_success;
-}
-
-static const SSL_PRIVATE_KEY_METHOD g_async_private_key_method = {
-    AsyncPrivateKeySign,
-    AsyncPrivateKeyDecrypt,
-    AsyncPrivateKeyComplete,
-};
-
 template<typename T>
 struct Free {
   void operator()(T *buf) {
@@ -441,635 +101,6 @@
   }
 };
 
-static bool GetCertificate(SSL *ssl, bssl::UniquePtr<X509> *out_x509,
-                           bssl::UniquePtr<STACK_OF(X509)> *out_chain,
-                           bssl::UniquePtr<EVP_PKEY> *out_pkey) {
-  const TestConfig *config = GetTestConfig(ssl);
-
-  if (!config->signing_prefs.empty()) {
-    std::vector<uint16_t> u16s(config->signing_prefs.begin(),
-                               config->signing_prefs.end());
-    if (!SSL_set_signing_algorithm_prefs(ssl, u16s.data(), u16s.size())) {
-      return false;
-    }
-  }
-
-  if (!config->key_file.empty()) {
-    *out_pkey = LoadPrivateKey(config->key_file.c_str());
-    if (!*out_pkey) {
-      return false;
-    }
-  }
-  if (!config->cert_file.empty() &&
-      !LoadCertificate(out_x509, out_chain, config->cert_file.c_str())) {
-    return false;
-  }
-  if (!config->ocsp_response.empty() &&
-      !SSL_set_ocsp_response(ssl, (const uint8_t *)config->ocsp_response.data(),
-                             config->ocsp_response.size())) {
-    return false;
-  }
-  return true;
-}
-
-static bool InstallCertificate(SSL *ssl) {
-  bssl::UniquePtr<X509> x509;
-  bssl::UniquePtr<STACK_OF(X509)> chain;
-  bssl::UniquePtr<EVP_PKEY> pkey;
-  if (!GetCertificate(ssl, &x509, &chain, &pkey)) {
-    return false;
-  }
-
-  if (pkey) {
-    TestState *test_state = GetTestState(ssl);
-    const TestConfig *config = GetTestConfig(ssl);
-    if (config->async) {
-      test_state->private_key = std::move(pkey);
-      SSL_set_private_key_method(ssl, &g_async_private_key_method);
-    } else if (!SSL_use_PrivateKey(ssl, pkey.get())) {
-      return false;
-    }
-  }
-
-  if (x509 && !SSL_use_certificate(ssl, x509.get())) {
-    return false;
-  }
-
-  if (sk_X509_num(chain.get()) > 0 &&
-      !SSL_set1_chain(ssl, chain.get())) {
-    return false;
-  }
-
-  return true;
-}
-
-static enum ssl_select_cert_result_t SelectCertificateCallback(
-    const SSL_CLIENT_HELLO *client_hello) {
-  const TestConfig *config = GetTestConfig(client_hello->ssl);
-  GetTestState(client_hello->ssl)->early_callback_called = true;
-
-  if (!config->expected_server_name.empty()) {
-    const uint8_t *extension_data;
-    size_t extension_len;
-    CBS extension, server_name_list, host_name;
-    uint8_t name_type;
-
-    if (!SSL_early_callback_ctx_extension_get(
-            client_hello, TLSEXT_TYPE_server_name, &extension_data,
-            &extension_len)) {
-      fprintf(stderr, "Could not find server_name extension.\n");
-      return ssl_select_cert_error;
-    }
-
-    CBS_init(&extension, extension_data, extension_len);
-    if (!CBS_get_u16_length_prefixed(&extension, &server_name_list) ||
-        CBS_len(&extension) != 0 ||
-        !CBS_get_u8(&server_name_list, &name_type) ||
-        name_type != TLSEXT_NAMETYPE_host_name ||
-        !CBS_get_u16_length_prefixed(&server_name_list, &host_name) ||
-        CBS_len(&server_name_list) != 0) {
-      fprintf(stderr, "Could not decode server_name extension.\n");
-      return ssl_select_cert_error;
-    }
-
-    if (!CBS_mem_equal(&host_name,
-                       (const uint8_t*)config->expected_server_name.data(),
-                       config->expected_server_name.size())) {
-      fprintf(stderr, "Server name mismatch.\n");
-    }
-  }
-
-  if (config->fail_early_callback) {
-    return ssl_select_cert_error;
-  }
-
-  // Install the certificate in the early callback.
-  if (config->use_early_callback) {
-    bool early_callback_ready =
-        GetTestState(client_hello->ssl)->early_callback_ready;
-    if (config->async && !early_callback_ready) {
-      // Install the certificate asynchronously.
-      return ssl_select_cert_retry;
-    }
-    if (!InstallCertificate(client_hello->ssl)) {
-      return ssl_select_cert_error;
-    }
-  }
-  return ssl_select_cert_success;
-}
-
-static bool CheckCertificateRequest(SSL *ssl) {
-  const TestConfig *config = GetTestConfig(ssl);
-
-  if (!config->expected_certificate_types.empty()) {
-    const uint8_t *certificate_types;
-    size_t certificate_types_len =
-        SSL_get0_certificate_types(ssl, &certificate_types);
-    if (certificate_types_len != config->expected_certificate_types.size() ||
-        OPENSSL_memcmp(certificate_types,
-                       config->expected_certificate_types.data(),
-                       certificate_types_len) != 0) {
-      fprintf(stderr, "certificate types mismatch\n");
-      return false;
-    }
-  }
-
-  if (!config->expected_client_ca_list.empty()) {
-    bssl::UniquePtr<STACK_OF(X509_NAME)> expected =
-        DecodeHexX509Names(config->expected_client_ca_list);
-    const size_t num_expected = sk_X509_NAME_num(expected.get());
-
-    const STACK_OF(X509_NAME) *received = SSL_get_client_CA_list(ssl);
-    const size_t num_received = sk_X509_NAME_num(received);
-
-    if (num_received != num_expected) {
-      fprintf(stderr, "expected %u names in CertificateRequest but got %u\n",
-              static_cast<unsigned>(num_expected),
-              static_cast<unsigned>(num_received));
-      return false;
-    }
-
-    for (size_t i = 0; i < num_received; i++) {
-      if (X509_NAME_cmp(sk_X509_NAME_value(received, i),
-                        sk_X509_NAME_value(expected.get(), i)) != 0) {
-        fprintf(stderr, "names in CertificateRequest differ at index #%d\n",
-                static_cast<unsigned>(i));
-        return false;
-      }
-    }
-
-    STACK_OF(CRYPTO_BUFFER) *buffers = SSL_get0_server_requested_CAs(ssl);
-    if (sk_CRYPTO_BUFFER_num(buffers) != num_received) {
-      fprintf(stderr,
-              "Mismatch between SSL_get_server_requested_CAs and "
-              "SSL_get_client_CA_list.\n");
-      return false;
-    }
-  }
-
-  return true;
-}
-
-static int ClientCertCallback(SSL *ssl, X509 **out_x509, EVP_PKEY **out_pkey) {
-  if (!CheckCertificateRequest(ssl)) {
-    return -1;
-  }
-
-  if (GetTestConfig(ssl)->async && !GetTestState(ssl)->cert_ready) {
-    return -1;
-  }
-
-  bssl::UniquePtr<X509> x509;
-  bssl::UniquePtr<STACK_OF(X509)> chain;
-  bssl::UniquePtr<EVP_PKEY> pkey;
-  if (!GetCertificate(ssl, &x509, &chain, &pkey)) {
-    return -1;
-  }
-
-  // Return zero for no certificate.
-  if (!x509) {
-    return 0;
-  }
-
-  // Chains and asynchronous private keys are not supported with client_cert_cb.
-  *out_x509 = x509.release();
-  *out_pkey = pkey.release();
-  return 1;
-}
-
-static int CertCallback(SSL *ssl, void *arg) {
-  const TestConfig *config = GetTestConfig(ssl);
-
-  // Check the CertificateRequest metadata is as expected.
-  if (!SSL_is_server(ssl) && !CheckCertificateRequest(ssl)) {
-    return -1;
-  }
-
-  if (config->fail_cert_callback) {
-    return 0;
-  }
-
-  // The certificate will be installed via other means.
-  if (!config->async || config->use_early_callback) {
-    return 1;
-  }
-
-  if (!GetTestState(ssl)->cert_ready) {
-    return -1;
-  }
-  if (!InstallCertificate(ssl)) {
-    return 0;
-  }
-  return 1;
-}
-
-static bool CheckVerifyCallback(SSL *ssl) {
-  const TestConfig *config = GetTestConfig(ssl);
-  if (!config->expected_ocsp_response.empty()) {
-    const uint8_t *data;
-    size_t len;
-    SSL_get0_ocsp_response(ssl, &data, &len);
-    if (len == 0) {
-      fprintf(stderr, "OCSP response not available in verify callback\n");
-      return false;
-    }
-  }
-
-  if (GetTestState(ssl)->cert_verified) {
-    fprintf(stderr, "Certificate verified twice.\n");
-    return false;
-  }
-
-  return true;
-}
-
-static int CertVerifyCallback(X509_STORE_CTX *store_ctx, void *arg) {
-  SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(store_ctx,
-      SSL_get_ex_data_X509_STORE_CTX_idx());
-  const TestConfig *config = GetTestConfig(ssl);
-  if (!CheckVerifyCallback(ssl)) {
-    return 0;
-  }
-
-  GetTestState(ssl)->cert_verified = true;
-  if (config->verify_fail) {
-    store_ctx->error = X509_V_ERR_APPLICATION_VERIFICATION;
-    return 0;
-  }
-
-  return 1;
-}
-
-static ssl_verify_result_t CustomVerifyCallback(SSL *ssl, uint8_t *out_alert) {
-  const TestConfig *config = GetTestConfig(ssl);
-  if (!CheckVerifyCallback(ssl)) {
-    return ssl_verify_invalid;
-  }
-
-  if (config->async && !GetTestState(ssl)->custom_verify_ready) {
-    return ssl_verify_retry;
-  }
-
-  GetTestState(ssl)->cert_verified = true;
-  if (config->verify_fail) {
-    return ssl_verify_invalid;
-  }
-
-  return ssl_verify_ok;
-}
-
-static int NextProtosAdvertisedCallback(SSL *ssl, const uint8_t **out,
-                                        unsigned int *out_len, void *arg) {
-  const TestConfig *config = GetTestConfig(ssl);
-  if (config->advertise_npn.empty()) {
-    return SSL_TLSEXT_ERR_NOACK;
-  }
-
-  *out = (const uint8_t*)config->advertise_npn.data();
-  *out_len = config->advertise_npn.size();
-  return SSL_TLSEXT_ERR_OK;
-}
-
-static int NextProtoSelectCallback(SSL* ssl, uint8_t** out, uint8_t* outlen,
-                                   const uint8_t* in, unsigned inlen, void* arg) {
-  const TestConfig *config = GetTestConfig(ssl);
-  if (config->select_next_proto.empty()) {
-    return SSL_TLSEXT_ERR_NOACK;
-  }
-
-  *out = (uint8_t*)config->select_next_proto.data();
-  *outlen = config->select_next_proto.size();
-  return SSL_TLSEXT_ERR_OK;
-}
-
-static int AlpnSelectCallback(SSL* ssl, const uint8_t** out, uint8_t* outlen,
-                              const uint8_t* in, unsigned inlen, void* arg) {
-  if (GetTestState(ssl)->alpn_select_done) {
-    fprintf(stderr, "AlpnSelectCallback called after completion.\n");
-    exit(1);
-  }
-
-  GetTestState(ssl)->alpn_select_done = true;
-
-  const TestConfig *config = GetTestConfig(ssl);
-  if (config->decline_alpn) {
-    return SSL_TLSEXT_ERR_NOACK;
-  }
-
-  if (!config->expected_advertised_alpn.empty() &&
-      (config->expected_advertised_alpn.size() != inlen ||
-       OPENSSL_memcmp(config->expected_advertised_alpn.data(), in, inlen) !=
-           0)) {
-    fprintf(stderr, "bad ALPN select callback inputs\n");
-    exit(1);
-  }
-
-  *out = (const uint8_t*)config->select_alpn.data();
-  *outlen = config->select_alpn.size();
-  return SSL_TLSEXT_ERR_OK;
-}
-
-static unsigned PskClientCallback(SSL *ssl, const char *hint,
-                                  char *out_identity,
-                                  unsigned max_identity_len,
-                                  uint8_t *out_psk, unsigned max_psk_len) {
-  const TestConfig *config = GetTestConfig(ssl);
-
-  if (config->psk_identity.empty()) {
-    if (hint != nullptr) {
-      fprintf(stderr, "Server PSK hint was non-null.\n");
-      return 0;
-    }
-  } else if (hint == nullptr ||
-             strcmp(hint, config->psk_identity.c_str()) != 0) {
-    fprintf(stderr, "Server PSK hint did not match.\n");
-    return 0;
-  }
-
-  // Account for the trailing '\0' for the identity.
-  if (config->psk_identity.size() >= max_identity_len ||
-      config->psk.size() > max_psk_len) {
-    fprintf(stderr, "PSK buffers too small\n");
-    return 0;
-  }
-
-  BUF_strlcpy(out_identity, config->psk_identity.c_str(),
-              max_identity_len);
-  OPENSSL_memcpy(out_psk, config->psk.data(), config->psk.size());
-  return config->psk.size();
-}
-
-static unsigned PskServerCallback(SSL *ssl, const char *identity,
-                                  uint8_t *out_psk, unsigned max_psk_len) {
-  const TestConfig *config = GetTestConfig(ssl);
-
-  if (strcmp(identity, config->psk_identity.c_str()) != 0) {
-    fprintf(stderr, "Client PSK identity did not match.\n");
-    return 0;
-  }
-
-  if (config->psk.size() > max_psk_len) {
-    fprintf(stderr, "PSK buffers too small\n");
-    return 0;
-  }
-
-  OPENSSL_memcpy(out_psk, config->psk.data(), config->psk.size());
-  return config->psk.size();
-}
-
-static timeval g_clock;
-
-static void CurrentTimeCallback(const SSL *ssl, timeval *out_clock) {
-  *out_clock = g_clock;
-}
-
-static void ChannelIdCallback(SSL *ssl, EVP_PKEY **out_pkey) {
-  *out_pkey = GetTestState(ssl)->channel_id.release();
-}
-
-static SSL_SESSION *GetSessionCallback(SSL *ssl, const uint8_t *data, int len,
-                                       int *copy) {
-  TestState *async_state = GetTestState(ssl);
-  if (async_state->session) {
-    *copy = 0;
-    return async_state->session.release();
-  } else if (async_state->pending_session) {
-    return SSL_magic_pending_session_ptr();
-  } else {
-    return NULL;
-  }
-}
-
-static int DDoSCallback(const SSL_CLIENT_HELLO *client_hello) {
-  const TestConfig *config = GetTestConfig(client_hello->ssl);
-  static int callback_num = 0;
-
-  callback_num++;
-  if (config->fail_ddos_callback ||
-      (config->fail_second_ddos_callback && callback_num == 2)) {
-    return 0;
-  }
-  return 1;
-}
-
-static void InfoCallback(const SSL *ssl, int type, int val) {
-  if (type == SSL_CB_HANDSHAKE_DONE) {
-    if (GetTestConfig(ssl)->handshake_never_done) {
-      fprintf(stderr, "Handshake unexpectedly completed.\n");
-      // Abort before any expected error code is printed, to ensure the overall
-      // test fails.
-      abort();
-    }
-    // This callback is called when the handshake completes. |SSL_get_session|
-    // must continue to work and |SSL_in_init| must return false.
-    if (SSL_in_init(ssl) || SSL_get_session(ssl) == nullptr) {
-      fprintf(stderr, "Invalid state for SSL_CB_HANDSHAKE_DONE.\n");
-      abort();
-    }
-    GetTestState(ssl)->handshake_done = true;
-
-    // Callbacks may be called again on a new handshake.
-    GetTestState(ssl)->ticket_decrypt_done = false;
-    GetTestState(ssl)->alpn_select_done = false;
-  }
-}
-
-static int NewSessionCallback(SSL *ssl, SSL_SESSION *session) {
-  // This callback is called as the handshake completes. |SSL_get_session|
-  // must continue to work and, historically, |SSL_in_init| returned false at
-  // this point.
-  if (SSL_in_init(ssl) || SSL_get_session(ssl) == nullptr) {
-    fprintf(stderr, "Invalid state for NewSessionCallback.\n");
-    abort();
-  }
-
-  GetTestState(ssl)->got_new_session = true;
-  GetTestState(ssl)->new_session.reset(session);
-  return 1;
-}
-
-static int TicketKeyCallback(SSL *ssl, uint8_t *key_name, uint8_t *iv,
-                             EVP_CIPHER_CTX *ctx, HMAC_CTX *hmac_ctx,
-                             int encrypt) {
-  if (!encrypt) {
-    if (GetTestState(ssl)->ticket_decrypt_done) {
-      fprintf(stderr, "TicketKeyCallback called after completion.\n");
-      return -1;
-    }
-
-    GetTestState(ssl)->ticket_decrypt_done = true;
-  }
-
-  // This is just test code, so use the all-zeros key.
-  static const uint8_t kZeros[16] = {0};
-
-  if (encrypt) {
-    OPENSSL_memcpy(key_name, kZeros, sizeof(kZeros));
-    RAND_bytes(iv, 16);
-  } else if (OPENSSL_memcmp(key_name, kZeros, 16) != 0) {
-    return 0;
-  }
-
-  if (!HMAC_Init_ex(hmac_ctx, kZeros, sizeof(kZeros), EVP_sha256(), NULL) ||
-      !EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, kZeros, iv, encrypt)) {
-    return -1;
-  }
-
-  if (!encrypt) {
-    return GetTestConfig(ssl)->renew_ticket ? 2 : 1;
-  }
-  return 1;
-}
-
-// kCustomExtensionValue is the extension value that the custom extension
-// callbacks will add.
-static const uint16_t kCustomExtensionValue = 1234;
-static void *const kCustomExtensionAddArg =
-    reinterpret_cast<void *>(kCustomExtensionValue);
-static void *const kCustomExtensionParseArg =
-    reinterpret_cast<void *>(kCustomExtensionValue + 1);
-static const char kCustomExtensionContents[] = "custom extension";
-
-static int CustomExtensionAddCallback(SSL *ssl, unsigned extension_value,
-                                      const uint8_t **out, size_t *out_len,
-                                      int *out_alert_value, void *add_arg) {
-  if (extension_value != kCustomExtensionValue ||
-      add_arg != kCustomExtensionAddArg) {
-    abort();
-  }
-
-  if (GetTestConfig(ssl)->custom_extension_skip) {
-    return 0;
-  }
-  if (GetTestConfig(ssl)->custom_extension_fail_add) {
-    return -1;
-  }
-
-  *out = reinterpret_cast<const uint8_t*>(kCustomExtensionContents);
-  *out_len = sizeof(kCustomExtensionContents) - 1;
-
-  return 1;
-}
-
-static void CustomExtensionFreeCallback(SSL *ssl, unsigned extension_value,
-                                        const uint8_t *out, void *add_arg) {
-  if (extension_value != kCustomExtensionValue ||
-      add_arg != kCustomExtensionAddArg ||
-      out != reinterpret_cast<const uint8_t *>(kCustomExtensionContents)) {
-    abort();
-  }
-}
-
-static int CustomExtensionParseCallback(SSL *ssl, unsigned extension_value,
-                                        const uint8_t *contents,
-                                        size_t contents_len,
-                                        int *out_alert_value, void *parse_arg) {
-  if (extension_value != kCustomExtensionValue ||
-      parse_arg != kCustomExtensionParseArg) {
-    abort();
-  }
-
-  if (contents_len != sizeof(kCustomExtensionContents) - 1 ||
-      OPENSSL_memcmp(contents, kCustomExtensionContents, contents_len) != 0) {
-    *out_alert_value = SSL_AD_DECODE_ERROR;
-    return 0;
-  }
-
-  return 1;
-}
-
-static int ServerNameCallback(SSL *ssl, int *out_alert, void *arg) {
-  // SNI must be accessible from the SNI callback.
-  const TestConfig *config = GetTestConfig(ssl);
-  const char *server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
-  if (server_name == nullptr ||
-      std::string(server_name) != config->expected_server_name) {
-    fprintf(stderr, "servername mismatch (got %s; want %s)\n", server_name,
-            config->expected_server_name.c_str());
-    return SSL_TLSEXT_ERR_ALERT_FATAL;
-  }
-
-  return SSL_TLSEXT_ERR_OK;
-}
-
-static void MessageCallback(int is_write, int version, int content_type,
-                            const void *buf, size_t len, SSL *ssl, void *arg) {
-  const uint8_t *buf_u8 = reinterpret_cast<const uint8_t *>(buf);
-  const TestConfig *config = GetTestConfig(ssl);
-  TestState *state = GetTestState(ssl);
-  if (!state->msg_callback_ok) {
-    return;
-  }
-
-  if (content_type == SSL3_RT_HEADER) {
-    if (len !=
-        (config->is_dtls ? DTLS1_RT_HEADER_LENGTH : SSL3_RT_HEADER_LENGTH)) {
-      fprintf(stderr, "Incorrect length for record header: %zu\n", len);
-      state->msg_callback_ok = false;
-    }
-    return;
-  }
-
-  state->msg_callback_text += is_write ? "write " : "read ";
-  switch (content_type) {
-    case 0:
-      if (version != SSL2_VERSION) {
-        fprintf(stderr, "Incorrect version for V2ClientHello: %x\n", version);
-        state->msg_callback_ok = false;
-        return;
-      }
-      state->msg_callback_text += "v2clienthello\n";
-      return;
-
-    case SSL3_RT_HANDSHAKE: {
-      CBS cbs;
-      CBS_init(&cbs, buf_u8, len);
-      uint8_t type;
-      uint32_t msg_len;
-      if (!CBS_get_u8(&cbs, &type) ||
-          // TODO(davidben): Reporting on entire messages would be more
-          // consistent than fragments.
-          (config->is_dtls &&
-           !CBS_skip(&cbs, 3 /* total */ + 2 /* seq */ + 3 /* frag_off */)) ||
-          !CBS_get_u24(&cbs, &msg_len) ||
-          !CBS_skip(&cbs, msg_len) ||
-          CBS_len(&cbs) != 0) {
-        fprintf(stderr, "Could not parse handshake message.\n");
-        state->msg_callback_ok = false;
-        return;
-      }
-      char text[16];
-      snprintf(text, sizeof(text), "hs %d\n", type);
-      state->msg_callback_text += text;
-      return;
-    }
-
-    case SSL3_RT_CHANGE_CIPHER_SPEC:
-      if (len != 1 || buf_u8[0] != 1) {
-        fprintf(stderr, "Invalid ChangeCipherSpec.\n");
-        state->msg_callback_ok = false;
-        return;
-      }
-      state->msg_callback_text += "ccs\n";
-      return;
-
-    case SSL3_RT_ALERT:
-      if (len != 2) {
-        fprintf(stderr, "Invalid alert.\n");
-        state->msg_callback_ok = false;
-        return;
-      }
-      char text[16];
-      snprintf(text, sizeof(text), "alert %d %d\n", buf_u8[0], buf_u8[1]);
-      state->msg_callback_text += text;
-      return;
-
-    default:
-      fprintf(stderr, "Invalid content_type: %d\n", content_type);
-      state->msg_callback_ok = false;
-  }
-}
-
 // Connect returns a new socket connected to localhost on |port| or -1 on
 // error.
 static int Connect(uint16_t port) {
@@ -1147,270 +178,6 @@
   const int sock_;
 };
 
-static void ssl_ctx_add_session(SSL_SESSION *session, void *void_param) {
-  SSL_CTX *ctx = reinterpret_cast<SSL_CTX *>(void_param);
-  bssl::UniquePtr<SSL_SESSION> new_session = bssl::SSL_SESSION_dup(
-      session, SSL_SESSION_INCLUDE_NONAUTH | SSL_SESSION_INCLUDE_TICKET);
-  if (new_session != nullptr) {
-    SSL_CTX_add_session(ctx, new_session.get());
-  }
-}
-
-static bssl::UniquePtr<SSL_CTX> SetupCtx(SSL_CTX *old_ctx,
-                                         const TestConfig *config) {
-  bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(
-      config->is_dtls ? DTLS_method() : TLS_method()));
-  if (!ssl_ctx) {
-    return nullptr;
-  }
-
-  SSL_CTX_set0_buffer_pool(ssl_ctx.get(), g_pool);
-
-  // Enable SSL 3.0 and TLS 1.3 for tests.
-  if (!config->is_dtls &&
-      (!SSL_CTX_set_min_proto_version(ssl_ctx.get(), SSL3_VERSION) ||
-       !SSL_CTX_set_max_proto_version(ssl_ctx.get(), TLS1_3_VERSION))) {
-    return nullptr;
-  }
-
-  std::string cipher_list = "ALL";
-  if (!config->cipher.empty()) {
-    cipher_list = config->cipher;
-    SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_CIPHER_SERVER_PREFERENCE);
-  }
-  if (!SSL_CTX_set_strict_cipher_list(ssl_ctx.get(), cipher_list.c_str())) {
-    return nullptr;
-  }
-
-  if (config->async && config->is_server) {
-    // Disable the internal session cache. To test asynchronous session lookup,
-    // we use an external session cache.
-    SSL_CTX_set_session_cache_mode(
-        ssl_ctx.get(), SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL);
-    SSL_CTX_sess_set_get_cb(ssl_ctx.get(), GetSessionCallback);
-  } else {
-    SSL_CTX_set_session_cache_mode(ssl_ctx.get(), SSL_SESS_CACHE_BOTH);
-  }
-
-  SSL_CTX_set_select_certificate_cb(ssl_ctx.get(), SelectCertificateCallback);
-
-  if (config->use_old_client_cert_callback) {
-    SSL_CTX_set_client_cert_cb(ssl_ctx.get(), ClientCertCallback);
-  }
-
-  SSL_CTX_set_next_protos_advertised_cb(
-      ssl_ctx.get(), NextProtosAdvertisedCallback, NULL);
-  if (!config->select_next_proto.empty()) {
-    SSL_CTX_set_next_proto_select_cb(ssl_ctx.get(), NextProtoSelectCallback,
-                                     NULL);
-  }
-
-  if (!config->select_alpn.empty() || config->decline_alpn) {
-    SSL_CTX_set_alpn_select_cb(ssl_ctx.get(), AlpnSelectCallback, NULL);
-  }
-
-  SSL_CTX_set_channel_id_cb(ssl_ctx.get(), ChannelIdCallback);
-
-  SSL_CTX_set_current_time_cb(ssl_ctx.get(), CurrentTimeCallback);
-
-  SSL_CTX_set_info_callback(ssl_ctx.get(), InfoCallback);
-  SSL_CTX_sess_set_new_cb(ssl_ctx.get(), NewSessionCallback);
-
-  if (config->use_ticket_callback) {
-    SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx.get(), TicketKeyCallback);
-  }
-
-  if (config->enable_client_custom_extension &&
-      !SSL_CTX_add_client_custom_ext(
-          ssl_ctx.get(), kCustomExtensionValue, CustomExtensionAddCallback,
-          CustomExtensionFreeCallback, kCustomExtensionAddArg,
-          CustomExtensionParseCallback, kCustomExtensionParseArg)) {
-    return nullptr;
-  }
-
-  if (config->enable_server_custom_extension &&
-      !SSL_CTX_add_server_custom_ext(
-          ssl_ctx.get(), kCustomExtensionValue, CustomExtensionAddCallback,
-          CustomExtensionFreeCallback, kCustomExtensionAddArg,
-          CustomExtensionParseCallback, kCustomExtensionParseArg)) {
-    return nullptr;
-  }
-
-  if (!config->use_custom_verify_callback) {
-    SSL_CTX_set_cert_verify_callback(ssl_ctx.get(), CertVerifyCallback, NULL);
-  }
-
-  if (!config->signed_cert_timestamps.empty() &&
-      !SSL_CTX_set_signed_cert_timestamp_list(
-          ssl_ctx.get(), (const uint8_t *)config->signed_cert_timestamps.data(),
-          config->signed_cert_timestamps.size())) {
-    return nullptr;
-  }
-
-  if (!config->use_client_ca_list.empty()) {
-    if (config->use_client_ca_list == "<NULL>") {
-      SSL_CTX_set_client_CA_list(ssl_ctx.get(), nullptr);
-    } else if (config->use_client_ca_list == "<EMPTY>") {
-      bssl::UniquePtr<STACK_OF(X509_NAME)> names;
-      SSL_CTX_set_client_CA_list(ssl_ctx.get(), names.release());
-    } else {
-      bssl::UniquePtr<STACK_OF(X509_NAME)> names =
-          DecodeHexX509Names(config->use_client_ca_list);
-      SSL_CTX_set_client_CA_list(ssl_ctx.get(), names.release());
-    }
-  }
-
-  if (config->enable_grease) {
-    SSL_CTX_set_grease_enabled(ssl_ctx.get(), 1);
-  }
-
-  if (!config->expected_server_name.empty()) {
-    SSL_CTX_set_tlsext_servername_callback(ssl_ctx.get(), ServerNameCallback);
-  }
-
-  if (!config->ticket_key.empty() &&
-      !SSL_CTX_set_tlsext_ticket_keys(ssl_ctx.get(), config->ticket_key.data(),
-                                      config->ticket_key.size())) {
-    return nullptr;
-  }
-
-  if (config->enable_early_data) {
-    SSL_CTX_set_early_data_enabled(ssl_ctx.get(), 1);
-  }
-
-  SSL_CTX_set_tls13_variant(
-      ssl_ctx.get(), static_cast<enum tls13_variant_t>(config->tls13_variant));
-
-  if (config->allow_unknown_alpn_protos) {
-    SSL_CTX_set_allow_unknown_alpn_protos(ssl_ctx.get(), 1);
-  }
-
-  if (config->enable_ed25519) {
-    SSL_CTX_set_ed25519_enabled(ssl_ctx.get(), 1);
-  }
-  if (config->no_rsa_pss_rsae_certs) {
-    SSL_CTX_set_rsa_pss_rsae_certs_enabled(ssl_ctx.get(), 0);
-  }
-
-  if (!config->verify_prefs.empty()) {
-    std::vector<uint16_t> u16s(config->verify_prefs.begin(),
-                               config->verify_prefs.end());
-    if (!SSL_CTX_set_verify_algorithm_prefs(ssl_ctx.get(), u16s.data(),
-                                            u16s.size())) {
-      return nullptr;
-    }
-  }
-
-  SSL_CTX_set_msg_callback(ssl_ctx.get(), MessageCallback);
-
-  if (config->allow_false_start_without_alpn) {
-    SSL_CTX_set_false_start_allowed_without_alpn(ssl_ctx.get(), 1);
-  }
-
-  if (old_ctx) {
-    uint8_t keys[48];
-    if (!SSL_CTX_get_tlsext_ticket_keys(old_ctx, &keys, sizeof(keys)) ||
-        !SSL_CTX_set_tlsext_ticket_keys(ssl_ctx.get(), keys, sizeof(keys))) {
-      return nullptr;
-    }
-    lh_SSL_SESSION_doall_arg(old_ctx->sessions, ssl_ctx_add_session,
-                             ssl_ctx.get());
-  }
-
-  return ssl_ctx;
-}
-
-// RetryAsync is called after a failed operation on |ssl| with return code
-// |ret|. If the operation should be retried, it simulates one asynchronous
-// event and returns true. Otherwise it returns false.
-static bool RetryAsync(SSL *ssl, int ret) {
-  // No error; don't retry.
-  if (ret >= 0) {
-    return false;
-  }
-
-  TestState *test_state = GetTestState(ssl);
-  assert(GetTestConfig(ssl)->async);
-
-  if (test_state->packeted_bio != nullptr &&
-      PacketedBioAdvanceClock(test_state->packeted_bio)) {
-    // The DTLS retransmit logic silently ignores write failures. So the test
-    // may progress, allow writes through synchronously.
-    AsyncBioEnforceWriteQuota(test_state->async_bio, false);
-    int timeout_ret = DTLSv1_handle_timeout(ssl);
-    AsyncBioEnforceWriteQuota(test_state->async_bio, true);
-
-    if (timeout_ret < 0) {
-      fprintf(stderr, "Error retransmitting.\n");
-      return false;
-    }
-    return true;
-  }
-
-  // See if we needed to read or write more. If so, allow one byte through on
-  // the appropriate end to maximally stress the state machine.
-  switch (SSL_get_error(ssl, ret)) {
-    case SSL_ERROR_WANT_READ:
-      AsyncBioAllowRead(test_state->async_bio, 1);
-      return true;
-    case SSL_ERROR_WANT_WRITE:
-      AsyncBioAllowWrite(test_state->async_bio, 1);
-      return true;
-    case SSL_ERROR_WANT_CHANNEL_ID_LOOKUP: {
-      bssl::UniquePtr<EVP_PKEY> pkey =
-          LoadPrivateKey(GetTestConfig(ssl)->send_channel_id);
-      if (!pkey) {
-        return false;
-      }
-      test_state->channel_id = std::move(pkey);
-      return true;
-    }
-    case SSL_ERROR_WANT_X509_LOOKUP:
-      test_state->cert_ready = true;
-      return true;
-    case SSL_ERROR_PENDING_SESSION:
-      test_state->session = std::move(test_state->pending_session);
-      return true;
-    case SSL_ERROR_PENDING_CERTIFICATE:
-      test_state->early_callback_ready = true;
-      return true;
-    case SSL_ERROR_WANT_PRIVATE_KEY_OPERATION:
-      test_state->private_key_retries++;
-      return true;
-    case SSL_ERROR_WANT_CERTIFICATE_VERIFY:
-      test_state->custom_verify_ready = true;
-      return true;
-    default:
-      return false;
-  }
-}
-
-// CheckIdempotentError runs |func|, an operation on |ssl|, ensuring that
-// errors are idempotent.
-static int CheckIdempotentError(const char *name, SSL *ssl,
-                                std::function<int()> func) {
-  int ret = func();
-  int ssl_err = SSL_get_error(ssl, ret);
-  uint32_t err = ERR_peek_error();
-  if (ssl_err == SSL_ERROR_SSL || ssl_err == SSL_ERROR_ZERO_RETURN) {
-    int ret2 = func();
-    int ssl_err2 = SSL_get_error(ssl, ret2);
-    uint32_t err2 = ERR_peek_error();
-    if (ret != ret2 || ssl_err != ssl_err2 || err != err2) {
-      fprintf(stderr, "Repeating %s did not replay the error.\n", name);
-      char buf[256];
-      ERR_error_string_n(err, buf, sizeof(buf));
-      fprintf(stderr, "Wanted: %d %d %s\n", ret, ssl_err, buf);
-      ERR_error_string_n(err2, buf, sizeof(buf));
-      fprintf(stderr, "Got:    %d %d %s\n", ret2, ssl_err2, buf);
-      // runner treats exit code 90 as always failing. Otherwise, it may
-      // accidentally consider the result an expected protocol failure.
-      exit(90);
-    }
-  }
-  return ret;
-}
-
 // DoRead reads from |ssl|, resolving any asynchronous operations. It returns
 // the result value of the final |SSL_read| call.
 static int DoRead(SSL *ssl, uint8_t *out, size_t max_out) {
@@ -1564,7 +331,7 @@
       if (!sk_X509_insert(expect_chain.get(), expect_leaf.get(), 0)) {
         return false;
       }
-      X509_up_ref(expect_leaf.get());  // sk_X509_push takes ownership.
+      X509_up_ref(expect_leaf.get());  // sk_X509_insert takes ownership.
     }
 
     bssl::UniquePtr<X509> leaf(SSL_get_peer_certificate(ssl));
@@ -1590,7 +357,7 @@
     }
   }
 
-  if (SSL_get_session(ssl)->peer_sha256_valid !=
+  if (!!SSL_SESSION_has_peer_sha256(SSL_get_session(ssl)) !=
       config->expect_sha256_client_cert) {
     fprintf(stderr,
             "Unexpected SHA-256 client cert state: expected:%d is_resume:%d.\n",
@@ -1599,12 +366,30 @@
   }
 
   if (config->expect_sha256_client_cert &&
-      SSL_get_session(ssl)->certs != nullptr) {
+      SSL_SESSION_get0_peer_certificates(SSL_get_session(ssl)) != nullptr) {
     fprintf(stderr, "Have both client cert and SHA-256 hash: is_resume:%d.\n",
             is_resume);
     return false;
   }
 
+  const uint8_t *peer_sha256;
+  size_t peer_sha256_len;
+  SSL_SESSION_get0_peer_sha256(SSL_get_session(ssl), &peer_sha256,
+                               &peer_sha256_len);
+  if (SSL_SESSION_has_peer_sha256(SSL_get_session(ssl))) {
+    if (peer_sha256_len != 32) {
+      fprintf(stderr, "Peer SHA-256 hash had length %zu instead of 32\n",
+              peer_sha256_len);
+      return false;
+    }
+  } else {
+    if (peer_sha256_len != 0) {
+      fprintf(stderr, "Unexpected peer SHA-256 hash of length %zu\n",
+              peer_sha256_len);
+      return false;
+    }
+  }
+
   return true;
 }
 
@@ -1670,7 +455,10 @@
     }
   }
 
-  if (config->is_server && !GetTestState(ssl)->early_callback_called) {
+  // early_callback_called is updated in the handshaker, so we don't see it
+  // here.
+  if (!config->handoff && config->is_server &&
+      !GetTestState(ssl)->early_callback_called) {
     fprintf(stderr, "early callback not called\n");
     return false;
   }
@@ -1849,283 +637,10 @@
   return true;
 }
 
-static bool WriteSettings(int i, const TestConfig *config,
-                          const SSL_SESSION *session) {
-  if (config->write_settings.empty()) {
-    return true;
-  }
-
-  // Treat write_settings as a path prefix for each connection in the run.
-  char buf[DECIMAL_SIZE(int)];
-  snprintf(buf, sizeof(buf), "%d", i);
-  std::string path = config->write_settings + buf;
-
-  bssl::ScopedCBB cbb;
-  if (!CBB_init(cbb.get(), 64)) {
-    return false;
-  }
-
-  if (session != nullptr) {
-    uint8_t *data;
-    size_t len;
-    if (!SSL_SESSION_to_bytes(session, &data, &len)) {
-      return false;
-    }
-    bssl::UniquePtr<uint8_t> free_data(data);
-    CBB child;
-    if (!CBB_add_u16(cbb.get(), kSessionTag) ||
-        !CBB_add_u24_length_prefixed(cbb.get(), &child) ||
-        !CBB_add_bytes(&child, data, len) ||
-        !CBB_flush(cbb.get())) {
-      return false;
-    }
-  }
-
-  if (config->is_server &&
-      (config->require_any_client_certificate || config->verify_peer) &&
-      !CBB_add_u16(cbb.get(), kRequestClientCert)) {
-    return false;
-  }
-
-  if (config->tls13_variant != 0 &&
-      (!CBB_add_u16(cbb.get(), kTLS13Variant) ||
-       !CBB_add_u8(cbb.get(), static_cast<uint8_t>(config->tls13_variant)))) {
-    return false;
-  }
-
-  uint8_t *settings;
-  size_t settings_len;
-  if (!CBB_add_u16(cbb.get(), kDataTag) ||
-      !CBB_finish(cbb.get(), &settings, &settings_len)) {
-    return false;
-  }
-  bssl::UniquePtr<uint8_t> free_settings(settings);
-
-  using ScopedFILE = std::unique_ptr<FILE, decltype(&fclose)>;
-  ScopedFILE file(fopen(path.c_str(), "w"), fclose);
-  if (!file) {
-    return false;
-  }
-
-  return fwrite(settings, settings_len, 1, file.get()) == 1;
-}
-
-static bssl::UniquePtr<SSL> NewSSL(SSL_CTX *ssl_ctx, const TestConfig *config,
-                                   SSL_SESSION *session, bool is_resume,
-                                   std::unique_ptr<TestState> test_state) {
-  bssl::UniquePtr<SSL> ssl(SSL_new(ssl_ctx));
-  if (!ssl) {
-    return nullptr;
-  }
-
-  if (!SetTestConfig(ssl.get(), config)) {
-    return nullptr;
-  }
-  if (test_state != nullptr) {
-    if (!SetTestState(ssl.get(), std::move(test_state))) {
-      return nullptr;
-    }
-    GetTestState(ssl.get())->is_resume = is_resume;
-  }
-
-  if (config->fallback_scsv &&
-      !SSL_set_mode(ssl.get(), SSL_MODE_SEND_FALLBACK_SCSV)) {
-    return nullptr;
-  }
-  // Install the certificate synchronously if nothing else will handle it.
-  if (!config->use_early_callback &&
-      !config->use_old_client_cert_callback &&
-      !config->async &&
-      !InstallCertificate(ssl.get())) {
-    return nullptr;
-  }
-  if (!config->use_old_client_cert_callback) {
-    SSL_set_cert_cb(ssl.get(), CertCallback, nullptr);
-  }
-  int mode = SSL_VERIFY_NONE;
-  if (config->require_any_client_certificate) {
-    mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
-  }
-  if (config->verify_peer) {
-    mode = SSL_VERIFY_PEER;
-  }
-  if (config->verify_peer_if_no_obc) {
-    // Set SSL_VERIFY_FAIL_IF_NO_PEER_CERT so testing whether client
-    // certificates were requested is easy.
-    mode = SSL_VERIFY_PEER | SSL_VERIFY_PEER_IF_NO_OBC |
-           SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
-  }
-  if (config->use_custom_verify_callback) {
-    SSL_set_custom_verify(ssl.get(), mode, CustomVerifyCallback);
-  } else if (mode != SSL_VERIFY_NONE) {
-    SSL_set_verify(ssl.get(), mode, NULL);
-  }
-  if (config->false_start) {
-    SSL_set_mode(ssl.get(), SSL_MODE_ENABLE_FALSE_START);
-  }
-  if (config->cbc_record_splitting) {
-    SSL_set_mode(ssl.get(), SSL_MODE_CBC_RECORD_SPLITTING);
-  }
-  if (config->partial_write) {
-    SSL_set_mode(ssl.get(), SSL_MODE_ENABLE_PARTIAL_WRITE);
-  }
-  if (config->no_tls13) {
-    SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_3);
-  }
-  if (config->no_tls12) {
-    SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_2);
-  }
-  if (config->no_tls11) {
-    SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_1);
-  }
-  if (config->no_tls1) {
-    SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1);
-  }
-  if (config->no_ssl3) {
-    SSL_set_options(ssl.get(), SSL_OP_NO_SSLv3);
-  }
-  if (!config->expected_channel_id.empty() ||
-      config->enable_channel_id) {
-    SSL_set_tls_channel_id_enabled(ssl.get(), 1);
-  }
-  if (!config->send_channel_id.empty()) {
-    SSL_set_tls_channel_id_enabled(ssl.get(), 1);
-    if (!config->async) {
-      // The async case will be supplied by |ChannelIdCallback|.
-      bssl::UniquePtr<EVP_PKEY> pkey = LoadPrivateKey(config->send_channel_id);
-      if (!pkey || !SSL_set1_tls_channel_id(ssl.get(), pkey.get())) {
-        return nullptr;
-      }
-    }
-  }
-  if (!config->send_token_binding_params.empty()) {
-    SSL_set_token_binding_params(ssl.get(),
-                                 reinterpret_cast<const uint8_t *>(
-                                     config->send_token_binding_params.data()),
-                                 config->send_token_binding_params.length());
-  }
-  if (!config->host_name.empty() &&
-      !SSL_set_tlsext_host_name(ssl.get(), config->host_name.c_str())) {
-    return nullptr;
-  }
-  if (!config->advertise_alpn.empty() &&
-      SSL_set_alpn_protos(ssl.get(),
-                          (const uint8_t *)config->advertise_alpn.data(),
-                          config->advertise_alpn.size()) != 0) {
-    return nullptr;
-  }
-  if (!config->psk.empty()) {
-    SSL_set_psk_client_callback(ssl.get(), PskClientCallback);
-    SSL_set_psk_server_callback(ssl.get(), PskServerCallback);
-  }
-  if (!config->psk_identity.empty() &&
-      !SSL_use_psk_identity_hint(ssl.get(), config->psk_identity.c_str())) {
-    return nullptr;
-  }
-  if (!config->srtp_profiles.empty() &&
-      !SSL_set_srtp_profiles(ssl.get(), config->srtp_profiles.c_str())) {
-    return nullptr;
-  }
-  if (config->enable_ocsp_stapling) {
-    SSL_enable_ocsp_stapling(ssl.get());
-  }
-  if (config->enable_signed_cert_timestamps) {
-    SSL_enable_signed_cert_timestamps(ssl.get());
-  }
-  if (config->min_version != 0 &&
-      !SSL_set_min_proto_version(ssl.get(), (uint16_t)config->min_version)) {
-    return nullptr;
-  }
-  if (config->max_version != 0 &&
-      !SSL_set_max_proto_version(ssl.get(), (uint16_t)config->max_version)) {
-    return nullptr;
-  }
-  if (config->mtu != 0) {
-    SSL_set_options(ssl.get(), SSL_OP_NO_QUERY_MTU);
-    SSL_set_mtu(ssl.get(), config->mtu);
-  }
-  if (config->install_ddos_callback) {
-    SSL_CTX_set_dos_protection_cb(ssl_ctx, DDoSCallback);
-  }
-  if (config->renegotiate_once) {
-    SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_once);
-  }
-  if (config->renegotiate_freely) {
-    SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely);
-  }
-  if (config->renegotiate_ignore) {
-    SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_ignore);
-  }
-  if (!config->check_close_notify) {
-    SSL_set_quiet_shutdown(ssl.get(), 1);
-  }
-  if (config->p384_only) {
-    int nid = NID_secp384r1;
-    if (!SSL_set1_curves(ssl.get(), &nid, 1)) {
-      return nullptr;
-    }
-  }
-  if (config->enable_all_curves) {
-    static const int kAllCurves[] = {
-        NID_secp224r1, NID_X9_62_prime256v1, NID_secp384r1,
-        NID_secp521r1, NID_X25519,
-    };
-    if (!SSL_set1_curves(ssl.get(), kAllCurves,
-                         OPENSSL_ARRAY_SIZE(kAllCurves))) {
-      return nullptr;
-    }
-  }
-  if (config->initial_timeout_duration_ms > 0) {
-    DTLSv1_set_initial_timeout_duration(ssl.get(),
-                                        config->initial_timeout_duration_ms);
-  }
-  if (config->max_cert_list > 0) {
-    SSL_set_max_cert_list(ssl.get(), config->max_cert_list);
-  }
-  if (config->retain_only_sha256_client_cert) {
-    SSL_set_retain_only_sha256_of_client_certs(ssl.get(), 1);
-  }
-  if (config->max_send_fragment > 0) {
-    SSL_set_max_send_fragment(ssl.get(), config->max_send_fragment);
-  }
-  if (config->dummy_pq_padding_len > 0 &&
-      !SSL_set_dummy_pq_padding_size(ssl.get(), config->dummy_pq_padding_len)) {
-    return nullptr;
-  }
-  if (!config->quic_transport_params.empty()) {
-    if (!SSL_set_quic_transport_params(
-            ssl.get(),
-            reinterpret_cast<const uint8_t *>(
-                config->quic_transport_params.data()),
-            config->quic_transport_params.size())) {
-      return nullptr;
-    }
-  }
-
-  if (session != NULL) {
-    if (!config->is_server) {
-      if (SSL_set_session(ssl.get(), session) != 1) {
-        return nullptr;
-      }
-    } else if (config->async) {
-      // The internal session cache is disabled, so install the session
-      // manually.
-      SSL_SESSION_up_ref(session);
-      GetTestState(ssl.get())->pending_session.reset(session);
-    }
-  }
-
-  if (SSL_get_current_cipher(ssl.get()) != nullptr) {
-    fprintf(stderr, "non-null cipher before handshake\n");
-    return nullptr;
-  }
-
-  return ssl;
-}
-
 static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session,
                        bssl::UniquePtr<SSL> *ssl_uniqueptr,
-                       const TestConfig *config, bool is_resume, bool is_retry);
+                       const TestConfig *config, bool is_resume, bool is_retry,
+                       SettingsWriter *writer);
 
 // DoConnection tests an SSL connection against the peer. On success, it returns
 // true and sets |*out_session| to the negotiated SSL session. If the test is a
@@ -2134,9 +649,9 @@
 static bool DoConnection(bssl::UniquePtr<SSL_SESSION> *out_session,
                          SSL_CTX *ssl_ctx, const TestConfig *config,
                          const TestConfig *retry_config, bool is_resume,
-                         SSL_SESSION *session) {
-  bssl::UniquePtr<SSL> ssl = NewSSL(ssl_ctx, config, session, is_resume,
-                                    std::unique_ptr<TestState>(new TestState));
+                         SSL_SESSION *session, SettingsWriter *writer) {
+  bssl::UniquePtr<SSL> ssl = config->NewSSL(
+      ssl_ctx, session, is_resume, std::unique_ptr<TestState>(new TestState));
   if (!ssl) {
     return false;
   }
@@ -2158,7 +673,7 @@
     return false;
   }
   if (config->is_dtls) {
-    bssl::UniquePtr<BIO> packeted = PacketedBioCreate(&g_clock);
+    bssl::UniquePtr<BIO> packeted = PacketedBioCreate(GetClock());
     if (!packeted) {
       return false;
     }
@@ -2179,7 +694,7 @@
   SSL_set_bio(ssl.get(), bio.get(), bio.get());
   bio.release();  // SSL_set_bio takes ownership.
 
-  bool ret = DoExchange(out_session, &ssl, config, is_resume, false);
+  bool ret = DoExchange(out_session, &ssl, config, is_resume, false, writer);
   if (!config->is_server && is_resume && config->expect_reject_early_data) {
     // We must have failed due to an early data rejection.
     if (ret) {
@@ -2214,7 +729,7 @@
     }
 
     assert(!config->handoff);
-    ret = DoExchange(out_session, &ssl, retry_config, is_resume, true);
+    ret = DoExchange(out_session, &ssl, retry_config, is_resume, true, writer);
   }
 
   if (!ret) {
@@ -2237,73 +752,25 @@
   return true;
 }
 
-static bool HandoffReady(SSL *ssl, int ret) {
-  return ret < 0 && SSL_get_error(ssl, ret) == SSL_ERROR_HANDOFF;
-}
-
-static bool HandbackReady(SSL *ssl, int ret) {
-  return ret < 0 && SSL_get_error(ssl, ret) == SSL_ERROR_HANDBACK;
-}
-
 static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session,
                        bssl::UniquePtr<SSL> *ssl_uniqueptr,
-                       const TestConfig *config, bool is_resume,
-                       bool is_retry) {
+                       const TestConfig *config, bool is_resume, bool is_retry,
+                       SettingsWriter *writer) {
   int ret;
   SSL *ssl = ssl_uniqueptr->get();
-  SSL_CTX *session_ctx = ssl->ctx;
+  SSL_CTX *session_ctx = SSL_get_SSL_CTX(ssl);
 
   if (!config->implicit_handshake) {
     if (config->handoff) {
-      bssl::UniquePtr<SSL_CTX> ctx_handoff = SetupCtx(ssl->ctx, config);
-      if (!ctx_handoff) {
+#if defined(HANDSHAKER_SUPPORTED)
+      if (!DoSplitHandshake(ssl_uniqueptr, writer, is_resume)) {
         return false;
       }
-      SSL_CTX_set_handoff_mode(ctx_handoff.get(), 1);
-
-      bssl::UniquePtr<SSL> ssl_handoff =
-          NewSSL(ctx_handoff.get(), config, nullptr, false, nullptr);
-      if (!ssl_handoff) {
-        return false;
-      }
-      SSL_set_accept_state(ssl_handoff.get());
-      if (!MoveExData(ssl_handoff.get(), ssl)) {
-        return false;
-      }
-      MoveBIOs(ssl_handoff.get(), ssl);
-
-      do {
-        ret = CheckIdempotentError("SSL_do_handshake", ssl_handoff.get(),
-                                   [&]() -> int {
-          return SSL_do_handshake(ssl_handoff.get());
-        });
-      } while (!HandoffReady(ssl_handoff.get(), ret) &&
-               config->async &&
-               RetryAsync(ssl_handoff.get(), ret));
-
-      if (!HandoffReady(ssl_handoff.get(), ret)) {
-        fprintf(stderr, "Handshake failed while waiting for handoff.\n");
-        return false;
-      }
-
-      bssl::ScopedCBB cbb;
-      bssl::Array<uint8_t> handoff;
-      if (!CBB_init(cbb.get(), 512) ||
-          !SSL_serialize_handoff(ssl_handoff.get(), cbb.get()) ||
-          !CBBFinishArray(cbb.get(), &handoff)) {
-        fprintf(stderr, "Handoff serialisation failed.\n");
-        return false;
-      }
-
-      MoveBIOs(ssl, ssl_handoff.get());
-      if (!MoveExData(ssl, ssl_handoff.get())) {
-        return false;
-      }
-
-      if (!SSL_apply_handoff(ssl, handoff)) {
-        fprintf(stderr, "Handoff application failed.\n");
-        return false;
-      }
+      ssl = ssl_uniqueptr->get();
+#else
+      fprintf(stderr, "The external handshaker can only be used on Linux\n");
+      return false;
+#endif
     }
 
     do {
@@ -2312,56 +779,15 @@
       });
     } while (config->async && RetryAsync(ssl, ret));
 
-    if (config->handoff) {
-      if (!HandbackReady(ssl, ret)) {
-        fprintf(stderr, "Connection failed to handback.\n");
-        return false;
-      }
-
-      bssl::ScopedCBB cbb;
-      bssl::Array<uint8_t> handback;
-      if (!CBB_init(cbb.get(), 512) ||
-          !SSL_serialize_handback(ssl, cbb.get()) ||
-          !CBBFinishArray(cbb.get(), &handback)) {
-        fprintf(stderr, "Handback serialisation failed.\n");
-        return false;
-      }
-
-      bssl::UniquePtr<SSL_CTX> ctx_handback = SetupCtx(ssl->ctx, config);
-      if (!ctx_handback) {
-        return false;
-      }
-      bssl::UniquePtr<SSL> ssl_handback =
-          NewSSL(ctx_handback.get(), config, nullptr, false, nullptr);
-      if (!ssl_handback) {
-        return false;
-      }
-      MoveBIOs(ssl_handback.get(), ssl);
-      if (!MoveExData(ssl_handback.get(), ssl)) {
-        return false;
-      }
-
-      if (!SSL_apply_handback(ssl_handback.get(), handback)) {
-        fprintf(stderr, "Applying handback failed.\n");
-        return false;
-      }
-
-      *ssl_uniqueptr = std::move(ssl_handback);
-      ssl = ssl_uniqueptr->get();
-
-      do {
-        ret = CheckIdempotentError("SSL_do_handshake", ssl, [&]() -> int {
-          return SSL_do_handshake(ssl);
-        });
-      } while (config->async && RetryAsync(ssl, ret));
+    if (config->forbid_renegotiation_after_handshake) {
+      SSL_set_renegotiate_mode(ssl, ssl_renegotiate_never);
     }
 
     if (ret != 1 || !CheckHandshakeProperties(ssl, is_resume, config)) {
       return false;
     }
 
-    lh_SSL_SESSION_doall_arg(ssl->ctx->sessions, ssl_ctx_add_session,
-                             session_ctx);
+    CopySessions(session_ctx, SSL_get_SSL_CTX(ssl));
 
     if (is_resume && !is_retry && !config->is_server &&
         config->expect_no_offer_early_data && SSL_in_early_data(ssl)) {
@@ -2671,11 +1097,6 @@
 #endif
 
   CRYPTO_library_init();
-  g_config_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
-  g_state_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, TestStateExFree);
-  if (g_config_index < 0 || g_state_index < 0) {
-    return 1;
-  }
 
   TestConfig initial_config, resume_config, retry_config;
   if (!ParseConfig(argc - 1, argv + 1, &initial_config, &resume_config,
@@ -2683,12 +1104,14 @@
     return Usage(argv[0]);
   }
 
-  g_pool = CRYPTO_BUFFER_POOL_new();
-
-  // Some code treats the zero time special, so initialize the clock to a
-  // non-zero time.
-  g_clock.tv_sec = 1234;
-  g_clock.tv_usec = 1234;
+  if (initial_config.is_handshaker_supported) {
+#if defined(HANDSHAKER_SUPPORTED)
+    printf("Yes\n");
+#else
+    printf("No\n");
+#endif
+    return 0;
+  }
 
   bssl::UniquePtr<SSL_CTX> ssl_ctx;
 
@@ -2696,7 +1119,7 @@
   for (int i = 0; i < initial_config.resume_count + 1; i++) {
     bool is_resume = i > 0;
     TestConfig *config = is_resume ? &resume_config : &initial_config;
-    ssl_ctx = SetupCtx(ssl_ctx.get(), config);
+    ssl_ctx = config->SetupCtx(ssl_ctx.get());
     if (!ssl_ctx) {
       ERR_print_errors_fp(stderr);
       return 1;
@@ -2708,19 +1131,25 @@
     }
 
     bssl::UniquePtr<SSL_SESSION> offer_session = std::move(session);
-    if (!WriteSettings(i, config, offer_session.get())) {
+    SettingsWriter writer;
+    if (!writer.Init(i, config, offer_session.get())) {
       fprintf(stderr, "Error writing settings.\n");
       return 1;
     }
-    if (!DoConnection(&session, ssl_ctx.get(), config, &retry_config, is_resume,
-                      offer_session.get())) {
+    bool ok = DoConnection(&session, ssl_ctx.get(), config, &retry_config,
+                           is_resume, offer_session.get(), &writer);
+    if (!writer.Commit()) {
+      fprintf(stderr, "Error writing settings.\n");
+      return 1;
+    }
+    if (!ok) {
       fprintf(stderr, "Connection %d failed.\n", i + 1);
       ERR_print_errors_fp(stderr);
       return 1;
     }
 
     if (config->resumption_delay != 0) {
-      g_clock.tv_sec += config->resumption_delay;
+      AdvanceClock(config->resumption_delay);
     }
   }
 
diff --git a/src/ssl/test/fuzzer.h b/src/ssl/test/fuzzer.h
index c794c4c..1ca970d 100644
--- a/src/ssl/test/fuzzer.h
+++ b/src/ssl/test/fuzzer.h
@@ -30,9 +30,9 @@
 #include <openssl/ssl.h>
 #include <openssl/x509.h>
 
+#include "../internal.h"
 #include "./fuzzer_tags.h"
 
-
 namespace {
 
 const uint8_t kP256KeyPKCS8[] = {
@@ -276,6 +276,19 @@
     }
   }
 
+  static void MoveBIOs(SSL *dest, SSL *src) {
+    BIO *rbio = SSL_get_rbio(src);
+    BIO_up_ref(rbio);
+    SSL_set0_rbio(dest, rbio);
+
+    BIO *wbio = SSL_get_wbio(src);
+    BIO_up_ref(wbio);
+    SSL_set0_wbio(dest, wbio);
+
+    SSL_set0_rbio(src, nullptr);
+    SSL_set0_wbio(src, nullptr);
+  }
+
   int TestOneInput(const uint8_t *buf, size_t len) {
     RAND_reset_for_fuzzing();
 
@@ -294,19 +307,63 @@
       SSL_set_tlsext_host_name(ssl.get(), "hostname");
     }
 
+    // ssl_handoff may or may not be used.
+    bssl::UniquePtr<SSL> ssl_handoff(SSL_new(ctx_.get()));
+    bssl::UniquePtr<SSL> ssl_handback(SSL_new(ctx_.get()));
+    SSL_set_accept_state(ssl_handoff.get());
+
     SSL_set0_rbio(ssl.get(), MakeBIO(CBS_data(&cbs), CBS_len(&cbs)).release());
     SSL_set0_wbio(ssl.get(), BIO_new(BIO_s_mem()));
 
-    if (SSL_do_handshake(ssl.get()) == 1) {
+    SSL *ssl_handshake = ssl.get();
+    bool handshake_successful = false;
+    bool handback_successful = false;
+    for (;;) {
+      int ret = SSL_do_handshake(ssl_handshake);
+      if (ret < 0 && SSL_get_error(ssl_handshake, ret) == SSL_ERROR_HANDOFF) {
+        MoveBIOs(ssl_handoff.get(), ssl.get());
+        // Ordinarily we would call SSL_serialize_handoff(ssl.get().  But for
+        // fuzzing, use the serialized handoff that's getting fuzzed.
+        if (!SSL_apply_handoff(ssl_handoff.get(), handoff_)) {
+          if (debug_) {
+            fprintf(stderr, "Handoff failed.\n");
+          }
+          break;
+        }
+        ssl_handshake = ssl_handoff.get();
+      } else if (ret < 0 &&
+                 SSL_get_error(ssl_handshake, ret) == SSL_ERROR_HANDBACK) {
+        MoveBIOs(ssl_handback.get(), ssl_handoff.get());
+        if (!SSL_apply_handback(ssl_handback.get(), handback_)) {
+          if (debug_) {
+            fprintf(stderr, "Handback failed.\n");
+          }
+          break;
+        }
+        handback_successful = true;
+        ssl_handshake = ssl_handback.get();
+      } else {
+        handshake_successful = ret == 1;
+        break;
+      }
+    }
+
+    if (debug_) {
+      if (!handshake_successful) {
+        fprintf(stderr, "Handshake failed.\n");
+      } else if (handback_successful) {
+        fprintf(stderr, "Handback successful.\n");
+      }
+    }
+
+    if (handshake_successful) {
       // Keep reading application data until error or EOF.
       uint8_t tmp[1024];
       for (;;) {
-        if (SSL_read(ssl.get(), tmp, sizeof(tmp)) <= 0) {
+        if (SSL_read(ssl_handshake, tmp, sizeof(tmp)) <= 0) {
           break;
         }
       }
-    } else if (debug_) {
-      fprintf(stderr, "Handshake failed.\n");
     }
 
     if (debug_) {
@@ -352,11 +409,9 @@
     if (!SSL_CTX_set_strict_cipher_list(ctx_.get(), "ALL:NULL-SHA")) {
       return false;
     }
-    if (protocol_ == kTLS) {
-      if (!SSL_CTX_set_max_proto_version(ctx_.get(), TLS1_3_VERSION) ||
-          !SSL_CTX_set_min_proto_version(ctx_.get(), SSL3_VERSION)) {
-        return false;
-      }
+    if (protocol_ == kTLS &&
+        !SSL_CTX_set_max_proto_version(ctx_.get(), TLS1_3_VERSION)) {
+      return false;
     }
 
     SSL_CTX_set_early_data_enabled(ctx_.get(), 1);
@@ -389,6 +444,8 @@
     // |ctx| is shared between runs, so we must clear any modifications to it
     // made later on in this function.
     SSL_CTX_flush_sessions(ctx_.get(), 0);
+    handoff_ = {};
+    handback_ = {};
 
     bssl::UniquePtr<SSL> ssl(SSL_new(ctx_.get()));
     if (role_ == kServer) {
@@ -442,6 +499,26 @@
           break;
         }
 
+        case kHandoffTag: {
+          CBS handoff;
+          if (!CBS_get_u24_length_prefixed(cbs, &handoff)) {
+            return nullptr;
+          }
+          handoff_.CopyFrom(handoff);
+          bssl::SSL_set_handoff_mode(ssl.get(), 1);
+          break;
+        }
+
+        case kHandbackTag: {
+          CBS handback;
+          if (!CBS_get_u24_length_prefixed(cbs, &handback)) {
+            return nullptr;
+          }
+          handback_.CopyFrom(handback);
+          bssl::SSL_set_handoff_mode(ssl.get(), 1);
+          break;
+        }
+
         default:
           return nullptr;
       }
@@ -497,6 +574,7 @@
   Protocol protocol_;
   Role role_;
   bssl::UniquePtr<SSL_CTX> ctx_;
+  bssl::Array<uint8_t> handoff_, handback_;
 };
 
 const BIO_METHOD TLSFuzzer::kBIOMethod = {
diff --git a/src/ssl/test/fuzzer_tags.h b/src/ssl/test/fuzzer_tags.h
index b161d80..c21aca3 100644
--- a/src/ssl/test/fuzzer_tags.h
+++ b/src/ssl/test/fuzzer_tags.h
@@ -42,4 +42,10 @@
 // kTLS13Variant is followed by a u8 denoting the TLS 1.3 variant to configure.
 static const uint16_t kTLS13Variant = 3;
 
+// kHandoffTag is followed by the output of |SSL_serialize_handoff|.
+static const uint16_t kHandoffTag = 4;
+
+// kHandbackTag is followed by te output of |SSL_serialize_handback|.
+static const uint16_t kHandbackTag = 5;
+
 #endif  // HEADER_SSL_TEST_FUZZER_TAGS
diff --git a/src/ssl/test/handshake_util.cc b/src/ssl/test/handshake_util.cc
new file mode 100644
index 0000000..f839653
--- /dev/null
+++ b/src/ssl/test/handshake_util.cc
@@ -0,0 +1,473 @@
+/* Copyright (c) 2018, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include "handshake_util.h"
+
+#include <assert.h>
+#if defined(OPENSSL_LINUX) && !defined(OPENSSL_ANDROID)
+#include <errno.h>
+#include <fcntl.h>
+#include <spawn.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
+#include <functional>
+
+#include "async_bio.h"
+#include "packeted_bio.h"
+#include "test_config.h"
+#include "test_state.h"
+
+#include <openssl/ssl.h>
+
+using namespace bssl;
+
+bool RetryAsync(SSL *ssl, int ret) {
+  // No error; don't retry.
+  if (ret >= 0) {
+    return false;
+  }
+
+  TestState *test_state = GetTestState(ssl);
+  assert(GetTestConfig(ssl)->async);
+
+  if (test_state->packeted_bio != nullptr &&
+      PacketedBioAdvanceClock(test_state->packeted_bio)) {
+    // The DTLS retransmit logic silently ignores write failures. So the test
+    // may progress, allow writes through synchronously.
+    AsyncBioEnforceWriteQuota(test_state->async_bio, false);
+    int timeout_ret = DTLSv1_handle_timeout(ssl);
+    AsyncBioEnforceWriteQuota(test_state->async_bio, true);
+
+    if (timeout_ret < 0) {
+      fprintf(stderr, "Error retransmitting.\n");
+      return false;
+    }
+    return true;
+  }
+
+  // See if we needed to read or write more. If so, allow one byte through on
+  // the appropriate end to maximally stress the state machine.
+  switch (SSL_get_error(ssl, ret)) {
+    case SSL_ERROR_WANT_READ:
+      AsyncBioAllowRead(test_state->async_bio, 1);
+      return true;
+    case SSL_ERROR_WANT_WRITE:
+      AsyncBioAllowWrite(test_state->async_bio, 1);
+      return true;
+    case SSL_ERROR_WANT_CHANNEL_ID_LOOKUP: {
+      UniquePtr<EVP_PKEY> pkey =
+          LoadPrivateKey(GetTestConfig(ssl)->send_channel_id);
+      if (!pkey) {
+        return false;
+      }
+      test_state->channel_id = std::move(pkey);
+      return true;
+    }
+    case SSL_ERROR_WANT_X509_LOOKUP:
+      test_state->cert_ready = true;
+      return true;
+    case SSL_ERROR_PENDING_SESSION:
+      test_state->session = std::move(test_state->pending_session);
+      return true;
+    case SSL_ERROR_PENDING_CERTIFICATE:
+      test_state->early_callback_ready = true;
+      return true;
+    case SSL_ERROR_WANT_PRIVATE_KEY_OPERATION:
+      test_state->private_key_retries++;
+      return true;
+    case SSL_ERROR_WANT_CERTIFICATE_VERIFY:
+      test_state->custom_verify_ready = true;
+      return true;
+    default:
+      return false;
+  }
+}
+
+int CheckIdempotentError(const char *name, SSL *ssl,
+                         std::function<int()> func) {
+  int ret = func();
+  int ssl_err = SSL_get_error(ssl, ret);
+  uint32_t err = ERR_peek_error();
+  if (ssl_err == SSL_ERROR_SSL || ssl_err == SSL_ERROR_ZERO_RETURN) {
+    int ret2 = func();
+    int ssl_err2 = SSL_get_error(ssl, ret2);
+    uint32_t err2 = ERR_peek_error();
+    if (ret != ret2 || ssl_err != ssl_err2 || err != err2) {
+      fprintf(stderr, "Repeating %s did not replay the error.\n", name);
+      char buf[256];
+      ERR_error_string_n(err, buf, sizeof(buf));
+      fprintf(stderr, "Wanted: %d %d %s\n", ret, ssl_err, buf);
+      ERR_error_string_n(err2, buf, sizeof(buf));
+      fprintf(stderr, "Got:    %d %d %s\n", ret2, ssl_err2, buf);
+      // runner treats exit code 90 as always failing. Otherwise, it may
+      // accidentally consider the result an expected protocol failure.
+      exit(90);
+    }
+  }
+  return ret;
+}
+
+#if defined(OPENSSL_LINUX) && !defined(OPENSSL_ANDROID)
+
+// MoveBIOs moves the |BIO|s of |src| to |dst|.  It is used for handoff.
+static void MoveBIOs(SSL *dest, SSL *src) {
+  BIO *rbio = SSL_get_rbio(src);
+  BIO_up_ref(rbio);
+  SSL_set0_rbio(dest, rbio);
+
+  BIO *wbio = SSL_get_wbio(src);
+  BIO_up_ref(wbio);
+  SSL_set0_wbio(dest, wbio);
+
+  SSL_set0_rbio(src, nullptr);
+  SSL_set0_wbio(src, nullptr);
+}
+
+static bool HandoffReady(SSL *ssl, int ret) {
+  return ret < 0 && SSL_get_error(ssl, ret) == SSL_ERROR_HANDOFF;
+}
+
+static ssize_t read_eintr(int fd, void *out, size_t len) {
+  ssize_t ret;
+  do {
+    ret = read(fd, out, len);
+  } while (ret < 0 && errno == EINTR);
+  return ret;
+}
+
+static ssize_t write_eintr(int fd, const void *in, size_t len) {
+  ssize_t ret;
+  do {
+    ret = write(fd, in, len);
+  } while (ret < 0 && errno == EINTR);
+  return ret;
+}
+
+static ssize_t waitpid_eintr(pid_t pid, int *wstatus, int options) {
+  pid_t ret;
+  do {
+    ret = waitpid(pid, wstatus, options);
+  } while (ret < 0 && errno == EINTR);
+  return ret;
+}
+
+// Proxy relays data between |socket|, which is connected to the client, and the
+// handshaker, which is connected to the numerically specified file descriptors,
+// until the handshaker returns control.
+static bool Proxy(BIO *socket, bool async, int control, int rfd, int wfd) {
+  for (;;) {
+    fd_set rfds;
+    FD_ZERO(&rfds);
+    FD_SET(wfd, &rfds);
+    FD_SET(control, &rfds);
+    int fd_max = wfd > control ? wfd : control;
+    if (select(fd_max + 1, &rfds, nullptr, nullptr, nullptr) == -1) {
+      perror("select");
+      return false;
+    }
+
+    char buf[64];
+    ssize_t bytes;
+    if (FD_ISSET(wfd, &rfds) &&
+        (bytes = read_eintr(wfd, buf, sizeof(buf))) > 0) {
+      char *b = buf;
+      while (bytes) {
+        int written = BIO_write(socket, b, bytes);
+        if (!written) {
+          fprintf(stderr, "BIO_write wrote nothing\n");
+          return false;
+        }
+        if (written < 0) {
+          if (async) {
+            AsyncBioAllowWrite(socket, 1);
+            continue;
+          }
+          fprintf(stderr, "BIO_write failed\n");
+          return false;
+        }
+        b += written;
+        bytes -= written;
+      }
+      // Flush all pending data from the handshaker to the client before
+      // considering control messages.
+      continue;
+    }
+
+    if (!FD_ISSET(control, &rfds)) {
+      continue;
+    }
+
+    char msg;
+    if (read_eintr(control, &msg, 1) != 1) {
+      perror("read");
+      return false;
+    }
+    switch (msg) {
+      case kControlMsgHandback:
+        return true;
+      case kControlMsgError:
+        return false;
+      case kControlMsgWantRead:
+        break;
+      default:
+        fprintf(stderr, "Unknown control message from handshaker: %c\n", msg);
+        return false;
+    }
+
+    char readbuf[64];
+    if (async) {
+      AsyncBioAllowRead(socket, 1);
+    }
+    int read = BIO_read(socket, readbuf, sizeof(readbuf));
+    if (read < 1) {
+      fprintf(stderr, "BIO_read failed\n");
+      return false;
+    }
+    ssize_t written = write_eintr(rfd, readbuf, read);
+    if (written == -1) {
+      perror("write");
+      return false;
+    }
+    if (written != read) {
+      fprintf(stderr, "short write (%zu of %d bytes)\n", written, read);
+      return false;
+    }
+    // The handshaker blocks on the control channel, so we have to signal
+    // it that the data have been written.
+    msg = kControlMsgWriteCompleted;
+    if (write_eintr(control, &msg, 1) != 1) {
+      perror("write");
+      return false;
+    }
+  }
+}
+
+class ScopedFD {
+ public:
+  explicit ScopedFD(int fd): fd_(fd) {}
+  ~ScopedFD() { close(fd_); }
+ private:
+  const int fd_;
+};
+
+// RunHandshaker forks and execs the handshaker binary, handing off |input|,
+// and, after proxying some amount of handshake traffic, handing back |out|.
+static bool RunHandshaker(BIO *bio, const TestConfig *config, bool is_resume,
+                          const Array<uint8_t> &input,
+                          Array<uint8_t> *out) {
+  if (config->handshaker_path.empty()) {
+    fprintf(stderr, "no -handshaker-path specified\n");
+    return false;
+  }
+  struct stat dummy;
+  if (stat(config->handshaker_path.c_str(), &dummy) == -1) {
+    perror(config->handshaker_path.c_str());
+    return false;
+  }
+
+  // A datagram socket guarantees that writes are all-or-nothing.
+  int control[2];
+  if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, control) != 0) {
+    perror("socketpair");
+    return false;
+  }
+  int rfd[2], wfd[2];
+  // We use pipes, rather than some other mechanism, for their buffers.  During
+  // the handshake, this process acts as a dumb proxy until receiving the
+  // handback signal, which arrives asynchronously.  The race condition means
+  // that this process could incorrectly proxy post-handshake data from the
+  // client to the handshaker.
+  //
+  // To avoid this, this process never proxies data to the handshaker that the
+  // handshaker has not explicitly requested as a result of hitting
+  // |SSL_ERROR_WANT_READ|.  Pipes allow the data to sit in a buffer while the
+  // two processes synchronize over the |control| channel.
+  if (pipe(rfd) != 0 || pipe(wfd) != 0) {
+    perror("pipe2");
+    return false;
+  }
+
+  fflush(stdout);
+  fflush(stderr);
+
+  std::vector<char *> args;
+  bssl::UniquePtr<char> handshaker_path(
+      OPENSSL_strdup(config->handshaker_path.c_str()));
+  args.push_back(handshaker_path.get());
+  char resume[] = "-handshaker-resume";
+  if (is_resume) {
+    args.push_back(resume);
+  }
+  // config->argv omits argv[0].
+  for (int j = 0; j < config->argc; ++j) {
+    args.push_back(config->argv[j]);
+  }
+  args.push_back(nullptr);
+
+  posix_spawn_file_actions_t actions;
+  if (posix_spawn_file_actions_init(&actions) != 0 ||
+      posix_spawn_file_actions_addclose(&actions, control[0]) ||
+      posix_spawn_file_actions_addclose(&actions, rfd[1]) ||
+      posix_spawn_file_actions_addclose(&actions, wfd[0])) {
+    return false;
+  }
+  assert(kFdControl != rfd[0]);
+  assert(kFdControl != wfd[1]);
+  if (control[1] != kFdControl &&
+      posix_spawn_file_actions_adddup2(&actions, control[1], kFdControl) != 0) {
+    return false;
+  }
+  assert(kFdProxyToHandshaker != wfd[1]);
+  if (rfd[0] != kFdProxyToHandshaker &&
+      posix_spawn_file_actions_adddup2(&actions, rfd[0],
+                                       kFdProxyToHandshaker) != 0) {
+    return false;
+  }
+  if (wfd[1] != kFdHandshakerToProxy &&
+      posix_spawn_file_actions_adddup2(&actions, wfd[1],
+                                       kFdHandshakerToProxy) != 0) {
+      return false;
+  }
+
+  // MSan doesn't know that |posix_spawn| initializes its output, so initialize
+  // it to -1.
+  pid_t handshaker_pid = -1;
+  int ret = posix_spawn(&handshaker_pid, args[0], &actions, nullptr,
+                        args.data(), nullptr);
+  if (posix_spawn_file_actions_destroy(&actions) != 0 ||
+      ret != 0) {
+    return false;
+  }
+
+  close(control[1]);
+  close(rfd[0]);
+  close(wfd[1]);
+  ScopedFD rfd_closer(rfd[1]);
+  ScopedFD wfd_closer(wfd[0]);
+  ScopedFD control_closer(control[0]);
+
+  if (write_eintr(control[0], input.data(), input.size()) == -1) {
+    perror("write");
+    return false;
+  }
+  bool ok = Proxy(bio, config->async, control[0], rfd[1], wfd[0]);
+  int wstatus;
+  if (waitpid_eintr(handshaker_pid, &wstatus, 0) != handshaker_pid) {
+    perror("waitpid");
+    return false;
+  }
+  if (ok && wstatus) {
+    fprintf(stderr, "handshaker exited irregularly\n");
+    return false;
+  }
+  if (!ok) {
+    return false;  // This is a "good", i.e. expected, error.
+  }
+
+  constexpr size_t kBufSize = 1024 * 1024;
+  bssl::UniquePtr<uint8_t> buf((uint8_t *) OPENSSL_malloc(kBufSize));
+  int len = read_eintr(control[0], buf.get(), kBufSize);
+  if (len == -1) {
+    perror("read");
+    return false;
+  }
+  out->CopyFrom({buf.get(), (size_t)len});
+  return true;
+}
+
+// PrepareHandoff accepts the |ClientHello| from |ssl| and serializes state to
+// be passed to the handshaker.  The serialized state includes both the SSL
+// handoff, as well test-related state.
+static bool PrepareHandoff(SSL *ssl, SettingsWriter *writer,
+                           Array<uint8_t> *out_handoff) {
+  SSL_set_handoff_mode(ssl, 1);
+
+  const TestConfig *config = GetTestConfig(ssl);
+  int ret = -1;
+  do {
+    ret = CheckIdempotentError(
+        "SSL_do_handshake", ssl,
+        [&]() -> int { return SSL_do_handshake(ssl); });
+  } while (!HandoffReady(ssl, ret) &&
+           config->async &&
+           RetryAsync(ssl, ret));
+  if (!HandoffReady(ssl, ret)) {
+    fprintf(stderr, "Handshake failed while waiting for handoff.\n");
+    return false;
+  }
+
+  ScopedCBB cbb;
+  if (!CBB_init(cbb.get(), 512) ||
+      !SSL_serialize_handoff(ssl, cbb.get()) ||
+      !writer->WriteHandoff({CBB_data(cbb.get()), CBB_len(cbb.get())}) ||
+      !SerializeContextState(ssl->ctx.get(), cbb.get()) ||
+      !GetTestState(ssl)->Serialize(cbb.get())) {
+    fprintf(stderr, "Handoff serialisation failed.\n");
+    return false;
+  }
+  return CBBFinishArray(cbb.get(), out_handoff);
+}
+
+// DoSplitHandshake delegates the SSL handshake to a separate process, called
+// the handshaker.  This process proxies I/O between the handshaker and the
+// client, using the |BIO| from |ssl|.  After a successful handshake, |ssl| is
+// replaced with a new |SSL| object, in a way that is intended to be invisible
+// to the caller.
+bool DoSplitHandshake(UniquePtr<SSL> *ssl, SettingsWriter *writer,
+                      bool is_resume) {
+  assert(SSL_get_rbio(ssl->get()) == SSL_get_wbio(ssl->get()));
+  Array<uint8_t> handshaker_input;
+  const TestConfig *config = GetTestConfig(ssl->get());
+  // out is the response from the handshaker, which includes a serialized
+  // handback message, but also serialized updates to the |TestState|.
+  Array<uint8_t> out;
+  if (!PrepareHandoff(ssl->get(), writer, &handshaker_input) ||
+      !RunHandshaker(SSL_get_rbio(ssl->get()), config, is_resume,
+                     handshaker_input, &out)) {
+    fprintf(stderr, "Handoff failed.\n");
+    return false;
+  }
+
+  UniquePtr<SSL> ssl_handback =
+      config->NewSSL((*ssl)->ctx.get(), nullptr, false, nullptr);
+  if (!ssl_handback) {
+    return false;
+  }
+  CBS output, handback;
+  CBS_init(&output, out.data(), out.size());
+  if (!CBS_get_u24_length_prefixed(&output, &handback) ||
+      !DeserializeContextState(&output, ssl_handback->ctx.get()) ||
+      !SetTestState(ssl_handback.get(), TestState::Deserialize(
+          &output, ssl_handback->ctx.get())) ||
+      !GetTestState(ssl_handback.get()) ||
+      !writer->WriteHandback(handback) ||
+      !SSL_apply_handback(ssl_handback.get(), handback)) {
+    fprintf(stderr, "Handback failed.\n");
+    return false;
+  }
+  MoveBIOs(ssl_handback.get(), ssl->get());
+  GetTestState(ssl_handback.get())->async_bio =
+      GetTestState(ssl->get())->async_bio;
+  GetTestState(ssl->get())->async_bio = nullptr;
+
+  *ssl = std::move(ssl_handback);
+  return true;
+}
+
+#endif  // defined(OPENSSL_LINUX) && !defined(OPENSSL_ANDROID)
diff --git a/src/ssl/test/handshake_util.h b/src/ssl/test/handshake_util.h
new file mode 100644
index 0000000..4fb46db
--- /dev/null
+++ b/src/ssl/test/handshake_util.h
@@ -0,0 +1,53 @@
+/* Copyright (c) 2018, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#ifndef HEADER_TEST_HANDSHAKE
+#define HEADER_TEST_HANDSHAKE
+
+#include <functional>
+
+#include <openssl/base.h>
+
+#include "settings_writer.h"
+
+// RetryAsync is called after a failed operation on |ssl| with return code
+// |ret|. If the operation should be retried, it simulates one asynchronous
+// event and returns true. Otherwise it returns false.
+bool RetryAsync(SSL *ssl, int ret);
+
+// CheckIdempotentError runs |func|, an operation on |ssl|, ensuring that
+// errors are idempotent.
+int CheckIdempotentError(const char *name, SSL *ssl, std::function<int()> func);
+
+// DoSplitHandshake delegates the SSL handshake to a separate process, called
+// the handshaker.  This process proxies I/O between the handshaker and the
+// client, using the |BIO| from |ssl|.  After a successful handshake, |ssl| is
+// replaced with a new |SSL| object, in a way that is intended to be invisible
+// to the caller.
+bool DoSplitHandshake(bssl::UniquePtr<SSL> *ssl, SettingsWriter *writer,
+                      bool is_resume);
+
+// The protocol between the proxy and the handshaker is defined by these
+// single-character prefixes.
+constexpr char kControlMsgWantRead = 'R';        // Handshaker wants data
+constexpr char kControlMsgWriteCompleted = 'W';  // Proxy has sent data
+constexpr char kControlMsgHandback = 'H';        // Proxy should resume control
+constexpr char kControlMsgError = 'E';           // Handshaker hit an error
+
+// The protocol between the proxy and handshaker uses these file descriptors.
+constexpr int kFdControl = 3;                    // Bi-directional dgram socket.
+constexpr int kFdProxyToHandshaker = 4;          // Uni-directional pipe.
+constexpr int kFdHandshakerToProxy = 5;          // Uni-directional pipe.
+
+#endif  // HEADER_TEST_HANDSHAKE
diff --git a/src/ssl/test/handshaker.cc b/src/ssl/test/handshaker.cc
new file mode 100644
index 0000000..9888876
--- /dev/null
+++ b/src/ssl/test/handshaker.cc
@@ -0,0 +1,170 @@
+/* Copyright (c) 2018, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <openssl/bytestring.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+
+#include "../internal.h"
+#include "handshake_util.h"
+#include "test_config.h"
+#include "test_state.h"
+
+using namespace bssl;
+
+namespace {
+
+bool HandbackReady(SSL *ssl, int ret) {
+  return ret < 0 && SSL_get_error(ssl, ret) == SSL_ERROR_HANDBACK;
+}
+
+bool Handshaker(const TestConfig *config, int rfd, int wfd,
+                       Span<const uint8_t> input, int control) {
+  UniquePtr<SSL_CTX> ctx = config->SetupCtx(/*old_ctx=*/nullptr);
+  if (!ctx) {
+    return false;
+  }
+  UniquePtr<SSL> ssl = config->NewSSL(ctx.get(), nullptr, false, nullptr);
+
+  // Set |O_NONBLOCK| in order to break out of the loop when we hit
+  // |SSL_ERROR_WANT_READ|, so that we can send |kControlMsgWantRead| to the
+  // proxy.
+  if (fcntl(rfd, F_SETFL, O_NONBLOCK) != 0) {
+    perror("fcntl");
+    return false;
+  }
+  SSL_set_rfd(ssl.get(), rfd);
+  SSL_set_wfd(ssl.get(), wfd);
+
+  CBS cbs, handoff;
+  CBS_init(&cbs, input.data(), input.size());
+  if (!CBS_get_asn1_element(&cbs, &handoff, CBS_ASN1_SEQUENCE) ||
+      !DeserializeContextState(&cbs, ctx.get()) ||
+      !SetTestState(ssl.get(), TestState::Deserialize(&cbs, ctx.get())) ||
+      !GetTestState(ssl.get()) ||
+      !SSL_apply_handoff(ssl.get(), handoff)) {
+    fprintf(stderr, "Handoff application failed.\n");
+    return false;
+  }
+
+  int ret = 0;
+  for (;;) {
+    ret = CheckIdempotentError(
+        "SSL_do_handshake", ssl.get(),
+        [&]() -> int { return SSL_do_handshake(ssl.get()); });
+    if (SSL_get_error(ssl.get(), ret) == SSL_ERROR_WANT_READ) {
+      // Synchronize with the proxy, i.e. don't let the handshake continue until
+      // the proxy has sent more data.
+      char msg = kControlMsgWantRead;
+      if (write(control, &msg, 1) != 1 ||
+          read(control, &msg, 1) != 1 ||
+          msg != kControlMsgWriteCompleted) {
+        fprintf(stderr, "read via proxy failed\n");
+        return false;
+      }
+      continue;
+    }
+    if (!config->async || !RetryAsync(ssl.get(), ret)) {
+      break;
+    }
+  }
+  if (!HandbackReady(ssl.get(), ret)) {
+    ERR_print_errors_fp(stderr);
+    return false;
+  }
+
+  ScopedCBB output;
+  CBB handback;
+  Array<uint8_t> bytes;
+  if (!CBB_init(output.get(), 1024) ||
+      !CBB_add_u24_length_prefixed(output.get(), &handback) ||
+      !SSL_serialize_handback(ssl.get(), &handback) ||
+      !SerializeContextState(ssl->ctx.get(), output.get()) ||
+      !GetTestState(ssl.get())->Serialize(output.get()) ||
+      !CBBFinishArray(output.get(), &bytes)) {
+    fprintf(stderr, "Handback serialisation failed.\n");
+    return false;
+  }
+
+  char msg = kControlMsgHandback;
+  if (write(control, &msg, 1) == -1 ||
+      write(control, bytes.data(), bytes.size()) == -1) {
+    perror("write");
+    return false;
+  }
+  return true;
+}
+
+ssize_t read_eintr(int fd, void *out, size_t len) {
+  ssize_t ret;
+  do {
+    ret = read(fd, out, len);
+  } while (ret < 0 && errno == EINTR);
+  return ret;
+}
+
+ssize_t write_eintr(int fd, const void *in, size_t len) {
+  ssize_t ret;
+  do {
+    ret = write(fd, in, len);
+  } while (ret < 0 && errno == EINTR);
+  return ret;
+}
+
+}  // namespace
+
+int main(int argc, char **argv) {
+  TestConfig initial_config, resume_config, retry_config;
+  if (!ParseConfig(argc - 1, argv + 1, &initial_config, &resume_config,
+                   &retry_config)) {
+    return 2;
+  }
+  const TestConfig *config = initial_config.handshaker_resume
+      ? &resume_config : &initial_config;
+#if defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE)
+  if (initial_config.handshaker_resume) {
+    // If the PRNG returns exactly the same values when trying to resume then a
+    // "random" session ID will happen to exactly match the session ID
+    // "randomly" generated on the initial connection. The client will thus
+    // incorrectly believe that the server is resuming.
+    uint8_t byte;
+    RAND_bytes(&byte, 1);
+  }
+#endif  // BORINGSSL_UNSAFE_DETERMINISTIC_MODE
+
+  // read() will return the entire message in one go, because it's a datagram
+  // socket.
+  constexpr size_t kBufSize = 1024 * 1024;
+  bssl::UniquePtr<uint8_t> buf((uint8_t *) OPENSSL_malloc(kBufSize));
+  ssize_t len = read_eintr(kFdControl, buf.get(), kBufSize);
+  if (len == -1) {
+    perror("read");
+    return 2;
+  }
+  Span<uint8_t> handoff(buf.get(), len);
+  if (!Handshaker(config, kFdProxyToHandshaker, kFdHandshakerToProxy, handoff,
+                  kFdControl)) {
+    char msg = kControlMsgError;
+    if (write_eintr(kFdControl, &msg, 1) != 1) {
+      return 3;
+    }
+    return 1;
+  }
+  return 0;
+}
diff --git a/src/ssl/test/runner/alert.go b/src/ssl/test/runner/alert.go
index 652e9ee..c79725e 100644
--- a/src/ssl/test/runner/alert.go
+++ b/src/ssl/test/runner/alert.go
@@ -15,69 +15,71 @@
 )
 
 const (
-	alertCloseNotify            alert = 0
-	alertEndOfEarlyData         alert = 1
-	alertUnexpectedMessage      alert = 10
-	alertBadRecordMAC           alert = 20
-	alertDecryptionFailed       alert = 21
-	alertRecordOverflow         alert = 22
-	alertDecompressionFailure   alert = 30
-	alertHandshakeFailure       alert = 40
-	alertNoCertificate          alert = 41
-	alertBadCertificate         alert = 42
-	alertUnsupportedCertificate alert = 43
-	alertCertificateRevoked     alert = 44
-	alertCertificateExpired     alert = 45
-	alertCertificateUnknown     alert = 46
-	alertIllegalParameter       alert = 47
-	alertUnknownCA              alert = 48
-	alertAccessDenied           alert = 49
-	alertDecodeError            alert = 50
-	alertDecryptError           alert = 51
-	alertProtocolVersion        alert = 70
-	alertInsufficientSecurity   alert = 71
-	alertInternalError          alert = 80
-	alertInappropriateFallback  alert = 86
-	alertUserCanceled           alert = 90
-	alertNoRenegotiation        alert = 100
-	alertMissingExtension       alert = 109
-	alertUnsupportedExtension   alert = 110
-	alertUnrecognizedName       alert = 112
-	alertUnknownPSKIdentity     alert = 115
-	alertCertificateRequired    alert = 116
+	alertCloseNotify                  alert = 0
+	alertEndOfEarlyData               alert = 1
+	alertUnexpectedMessage            alert = 10
+	alertBadRecordMAC                 alert = 20
+	alertDecryptionFailed             alert = 21
+	alertRecordOverflow               alert = 22
+	alertDecompressionFailure         alert = 30
+	alertHandshakeFailure             alert = 40
+	alertNoCertificate                alert = 41
+	alertBadCertificate               alert = 42
+	alertUnsupportedCertificate       alert = 43
+	alertCertificateRevoked           alert = 44
+	alertCertificateExpired           alert = 45
+	alertCertificateUnknown           alert = 46
+	alertIllegalParameter             alert = 47
+	alertUnknownCA                    alert = 48
+	alertAccessDenied                 alert = 49
+	alertDecodeError                  alert = 50
+	alertDecryptError                 alert = 51
+	alertProtocolVersion              alert = 70
+	alertInsufficientSecurity         alert = 71
+	alertInternalError                alert = 80
+	alertInappropriateFallback        alert = 86
+	alertUserCanceled                 alert = 90
+	alertNoRenegotiation              alert = 100
+	alertMissingExtension             alert = 109
+	alertUnsupportedExtension         alert = 110
+	alertUnrecognizedName             alert = 112
+	alertBadCertificateStatusResponse alert = 113
+	alertUnknownPSKIdentity           alert = 115
+	alertCertificateRequired          alert = 116
 )
 
 var alertText = map[alert]string{
-	alertCloseNotify:            "close notify",
-	alertEndOfEarlyData:         "end of early data",
-	alertUnexpectedMessage:      "unexpected message",
-	alertBadRecordMAC:           "bad record MAC",
-	alertDecryptionFailed:       "decryption failed",
-	alertRecordOverflow:         "record overflow",
-	alertDecompressionFailure:   "decompression failure",
-	alertHandshakeFailure:       "handshake failure",
-	alertNoCertificate:          "no certificate",
-	alertBadCertificate:         "bad certificate",
-	alertUnsupportedCertificate: "unsupported certificate",
-	alertCertificateRevoked:     "revoked certificate",
-	alertCertificateExpired:     "expired certificate",
-	alertCertificateUnknown:     "unknown certificate",
-	alertIllegalParameter:       "illegal parameter",
-	alertUnknownCA:              "unknown certificate authority",
-	alertAccessDenied:           "access denied",
-	alertDecodeError:            "error decoding message",
-	alertDecryptError:           "error decrypting message",
-	alertProtocolVersion:        "protocol version not supported",
-	alertInsufficientSecurity:   "insufficient security level",
-	alertInternalError:          "internal error",
-	alertInappropriateFallback:  "inappropriate fallback",
-	alertUserCanceled:           "user canceled",
-	alertNoRenegotiation:        "no renegotiation",
-	alertMissingExtension:       "missing extension",
-	alertUnsupportedExtension:   "unsupported extension",
-	alertUnrecognizedName:       "unrecognized name",
-	alertUnknownPSKIdentity:     "unknown PSK identity",
-	alertCertificateRequired:    "certificate required",
+	alertCloseNotify:                  "close notify",
+	alertEndOfEarlyData:               "end of early data",
+	alertUnexpectedMessage:            "unexpected message",
+	alertBadRecordMAC:                 "bad record MAC",
+	alertDecryptionFailed:             "decryption failed",
+	alertRecordOverflow:               "record overflow",
+	alertDecompressionFailure:         "decompression failure",
+	alertHandshakeFailure:             "handshake failure",
+	alertNoCertificate:                "no certificate",
+	alertBadCertificate:               "bad certificate",
+	alertUnsupportedCertificate:       "unsupported certificate",
+	alertCertificateRevoked:           "revoked certificate",
+	alertCertificateExpired:           "expired certificate",
+	alertCertificateUnknown:           "unknown certificate",
+	alertIllegalParameter:             "illegal parameter",
+	alertUnknownCA:                    "unknown certificate authority",
+	alertAccessDenied:                 "access denied",
+	alertDecodeError:                  "error decoding message",
+	alertDecryptError:                 "error decrypting message",
+	alertProtocolVersion:              "protocol version not supported",
+	alertInsufficientSecurity:         "insufficient security level",
+	alertInternalError:                "internal error",
+	alertInappropriateFallback:        "inappropriate fallback",
+	alertUserCanceled:                 "user canceled",
+	alertNoRenegotiation:              "no renegotiation",
+	alertMissingExtension:             "missing extension",
+	alertUnsupportedExtension:         "unsupported extension",
+	alertBadCertificateStatusResponse: "bad certificate status response",
+	alertUnrecognizedName:             "unrecognized name",
+	alertUnknownPSKIdentity:           "unknown PSK identity",
+	alertCertificateRequired:          "certificate required",
 }
 
 func (e alert) String() string {
diff --git a/src/ssl/test/runner/common.go b/src/ssl/test/runner/common.go
index 7115b4d..aa17350 100644
--- a/src/ssl/test/runner/common.go
+++ b/src/ssl/test/runner/common.go
@@ -39,8 +39,9 @@
 )
 
 const (
-	TLS13Draft23 = 0
-	TLS13Draft28 = 1
+	TLS13Default = 0
+	TLS13Draft23 = 1
+	TLS13Draft28 = 2
 )
 
 var allTLSWireVersions = []uint16{
@@ -81,26 +82,27 @@
 
 // TLS handshake message types.
 const (
-	typeHelloRequest        uint8 = 0
-	typeClientHello         uint8 = 1
-	typeServerHello         uint8 = 2
-	typeHelloVerifyRequest  uint8 = 3
-	typeNewSessionTicket    uint8 = 4
-	typeEndOfEarlyData      uint8 = 5 // draft-ietf-tls-tls13-21
-	typeHelloRetryRequest   uint8 = 6 // draft-ietf-tls-tls13-16
-	typeEncryptedExtensions uint8 = 8 // draft-ietf-tls-tls13-16
-	typeCertificate         uint8 = 11
-	typeServerKeyExchange   uint8 = 12
-	typeCertificateRequest  uint8 = 13
-	typeServerHelloDone     uint8 = 14
-	typeCertificateVerify   uint8 = 15
-	typeClientKeyExchange   uint8 = 16
-	typeFinished            uint8 = 20
-	typeCertificateStatus   uint8 = 22
-	typeKeyUpdate           uint8 = 24  // draft-ietf-tls-tls13-16
-	typeNextProtocol        uint8 = 67  // Not IANA assigned
-	typeChannelID           uint8 = 203 // Not IANA assigned
-	typeMessageHash         uint8 = 254 // draft-ietf-tls-tls13-21
+	typeHelloRequest          uint8 = 0
+	typeClientHello           uint8 = 1
+	typeServerHello           uint8 = 2
+	typeHelloVerifyRequest    uint8 = 3
+	typeNewSessionTicket      uint8 = 4
+	typeEndOfEarlyData        uint8 = 5 // draft-ietf-tls-tls13-21
+	typeHelloRetryRequest     uint8 = 6 // draft-ietf-tls-tls13-16
+	typeEncryptedExtensions   uint8 = 8 // draft-ietf-tls-tls13-16
+	typeCertificate           uint8 = 11
+	typeServerKeyExchange     uint8 = 12
+	typeCertificateRequest    uint8 = 13
+	typeServerHelloDone       uint8 = 14
+	typeCertificateVerify     uint8 = 15
+	typeClientKeyExchange     uint8 = 16
+	typeFinished              uint8 = 20
+	typeCertificateStatus     uint8 = 22
+	typeKeyUpdate             uint8 = 24  // draft-ietf-tls-tls13-16
+	typeCompressedCertificate uint8 = 25  // Not IANA assigned
+	typeNextProtocol          uint8 = 67  // Not IANA assigned
+	typeChannelID             uint8 = 203 // Not IANA assigned
+	typeMessageHash           uint8 = 254 // draft-ietf-tls-tls13-21
 )
 
 // TLS compression types.
@@ -121,7 +123,7 @@
 	extensionPadding                    uint16 = 21
 	extensionExtendedMasterSecret       uint16 = 23
 	extensionTokenBinding               uint16 = 24
-	extensionQUICTransportParams        uint16 = 26
+	extensionCompressedCertAlgs         uint16 = 27
 	extensionSessionTicket              uint16 = 35
 	extensionPreSharedKey               uint16 = 41    // draft-ietf-tls-tls13-23
 	extensionEarlyData                  uint16 = 42    // draft-ietf-tls-tls13-23
@@ -134,8 +136,9 @@
 	extensionCustom                     uint16 = 1234  // not IANA assigned
 	extensionNextProtoNeg               uint16 = 13172 // not IANA assigned
 	extensionRenegotiationInfo          uint16 = 0xff01
-	extensionChannelID                  uint16 = 30032 // not IANA assigned
-	extensionDummyPQPadding             uint16 = 54537 // not IANA assigned
+	extensionQUICTransportParams        uint16 = 0xffa5 // draft-ietf-quic-tls-13
+	extensionChannelID                  uint16 = 30032  // not IANA assigned
+	extensionDummyPQPadding             uint16 = 54537  // not IANA assigned
 )
 
 // TLS signaling cipher suite values
@@ -329,6 +332,16 @@
 	Put(sessionId string, session *sessionState)
 }
 
+// CertCompressionAlg is a certificate compression algorithm, specified as a
+// pair of functions for compressing and decompressing certificates.
+type CertCompressionAlg struct {
+	// Compress returns a compressed representation of the input.
+	Compress func([]byte) []byte
+	// Decompress depresses the contents of in and writes the result to out, which
+	// will be the correct size. It returns true on success and false otherwise.
+	Decompress func(out, in []byte) bool
+}
+
 // A Config structure is used to configure a TLS client or server.
 // After one has been passed to a TLS function it must not be
 // modified. A Config may be reused; the tls package will also not
@@ -499,6 +512,8 @@
 	// transport parameters extension.
 	QUICTransportParams []byte
 
+	CertCompressionAlgs map[uint16]CertCompressionAlg
+
 	// Bugs specifies optional misbehaviour to be used for testing other
 	// implementations.
 	Bugs ProtocolBugs
@@ -1153,6 +1168,10 @@
 	// sessions use session tickets instead of session IDs.
 	RequireSessionTickets bool
 
+	// RequireSessionIDs, if true, causes the client to require new sessions use
+	// session IDs instead of session tickets.
+	RequireSessionIDs bool
+
 	// NullAllCiphers, if true, causes every cipher to behave like the null
 	// cipher.
 	NullAllCiphers bool
@@ -1395,9 +1414,15 @@
 	// specified value in ServerHello version field.
 	SendServerHelloVersion uint16
 
-	// SendServerSupportedExtensionVersion, if non-zero, causes the server to send
-	// the specified value in supported_versions extension in the ServerHello.
-	SendServerSupportedExtensionVersion uint16
+	// SendServerSupportedVersionExtension, if non-zero, causes the server to send
+	// the specified value in supported_versions extension in the ServerHello (but
+	// not the HelloRetryRequest).
+	SendServerSupportedVersionExtension uint16
+
+	// OmitServerSupportedVersionExtension, if true, causes the server to
+	// omit the supported_versions extension in the ServerHello (but not the
+	// HelloRetryRequest)
+	OmitServerSupportedVersionExtension bool
 
 	// SkipHelloRetryRequest, if true, causes the TLS 1.3 server to not send
 	// HelloRetryRequest.
@@ -1499,6 +1524,15 @@
 	// length accepted from the peer.
 	MaxReceivePlaintext int
 
+	// ExpectPackedEncryptedHandshake, if non-zero, requires that the peer maximally
+	// pack their encrypted handshake messages, fitting at most the
+	// specified number of plaintext bytes per record.
+	ExpectPackedEncryptedHandshake int
+
+	// ForbidHandshakePacking, if true, requires the peer place a record
+	// boundary after every handshake message.
+	ForbidHandshakePacking bool
+
 	// SendTicketLifetime, if non-zero, is the ticket lifetime to send in
 	// NewSessionTicket messages.
 	SendTicketLifetime time.Duration
@@ -1580,6 +1614,22 @@
 	// SetX25519HighBit, if true, causes X25519 key shares to set their
 	// high-order bit.
 	SetX25519HighBit bool
+
+	// DuplicateCompressedCertAlgs, if true, causes two, equal, certificate
+	// compression algorithm IDs to be sent.
+	DuplicateCompressedCertAlgs bool
+
+	// ExpectedCompressedCert specifies the compression algorithm ID that must be
+	// used on this connection, or zero if there are no special requirements.
+	ExpectedCompressedCert uint16
+
+	// SendCertCompressionAlgId, if not zero, sets the algorithm ID that will be
+	// sent in the compressed certificate message.
+	SendCertCompressionAlgId uint16
+
+	// SendCertUncompressedLength, if not zero, sets the uncompressed length that
+	// will be sent in the compressed certificate message.
+	SendCertUncompressedLength uint32
 }
 
 func (c *Config) serverInit() {
@@ -1708,8 +1758,8 @@
 // it returns true and the corresponding protocol version. Otherwise, it returns
 // false.
 func (c *Config) isSupportedVersion(wireVers uint16, isDTLS bool) (uint16, bool) {
-	if (c.TLS13Variant != TLS13Draft23 && wireVers == tls13Draft23Version) ||
-		(c.TLS13Variant != TLS13Draft28 && wireVers == tls13Draft28Version) {
+	if (c.TLS13Variant == TLS13Draft23 && wireVers == tls13Draft28Version) ||
+		(c.TLS13Variant == TLS13Draft28 && wireVers == tls13Draft23Version) {
 		return 0, false
 	}
 
diff --git a/src/ssl/test/runner/conn.go b/src/ssl/test/runner/conn.go
index 9cd61eb..b6b6ffa 100644
--- a/src/ssl/test/runner/conn.go
+++ b/src/ssl/test/runner/conn.go
@@ -111,6 +111,11 @@
 
 	expectTLS13ChangeCipherSpec bool
 
+	// seenHandshakePackEnd is whether the most recent handshake record was
+	// not full for ExpectPackedEncryptedHandshake. If true, no more
+	// handshake data may be received until the next flight or epoch change.
+	seenHandshakePackEnd bool
+
 	tmp [16]byte
 }
 
@@ -756,6 +761,7 @@
 		side = clientWrite
 	}
 	c.in.useTrafficSecret(version, suite, secret, side)
+	c.seenHandshakePackEnd = false
 	return nil
 }
 
@@ -975,6 +981,13 @@
 		return c.in.setErrorLocked(err)
 	}
 
+	if typ != recordTypeHandshake {
+		c.seenHandshakePackEnd = false
+	} else if c.seenHandshakePackEnd {
+		c.in.freeBlock(b)
+		return c.in.setErrorLocked(errors.New("tls: peer violated ExpectPackedEncryptedHandshake"))
+	}
+
 	switch typ {
 	default:
 		c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
@@ -1037,6 +1050,9 @@
 			return c.in.setErrorLocked(c.sendAlert(alertNoRenegotiation))
 		}
 		c.hand.Write(data)
+		if pack := c.config.Bugs.ExpectPackedEncryptedHandshake; pack > 0 && len(data) < pack && c.out.cipher != nil {
+			c.seenHandshakePackEnd = true
+		}
 	}
 
 	if b != nil {
@@ -1095,6 +1111,7 @@
 // to the connection and updates the record layer state.
 // c.out.Mutex <= L.
 func (c *Conn) writeRecord(typ recordType, data []byte) (n int, err error) {
+	c.seenHandshakePackEnd = false
 	if typ == recordTypeHandshake {
 		msgType := data[0]
 		if c.config.Bugs.SendWrongMessageType != 0 && msgType == c.config.Bugs.SendWrongMessageType {
@@ -1304,6 +1321,9 @@
 			return nil, err
 		}
 	}
+	if c.hand.Len() > 4+n && c.config.Bugs.ForbidHandshakePacking {
+		return nil, errors.New("tls: forbidden trailing data after a handshake message")
+	}
 	return c.hand.Next(4 + n), nil
 }
 
@@ -1348,9 +1368,11 @@
 		m = &certificateMsg{
 			hasRequestContext: c.vers >= VersionTLS13,
 		}
+	case typeCompressedCertificate:
+		m = new(compressedCertificateMsg)
 	case typeCertificateRequest:
 		m = &certificateRequestMsg{
-			vers: c.wireVersion,
+			vers:                  c.wireVersion,
 			hasSignatureAlgorithm: c.vers >= VersionTLS12,
 			hasRequestContext:     c.vers >= VersionTLS13,
 		}
@@ -1786,7 +1808,7 @@
 	if c.isDTLS && c.config.Bugs.SendSplitAlert {
 		c.conn.Write([]byte{
 			byte(recordTypeAlert), // type
-			0xfe, 0xff, // version
+			0xfe, 0xff,            // version
 			0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // sequence
 			0x0, 0x2, // length
 		})
diff --git a/src/ssl/test/runner/handshake_client.go b/src/ssl/test/runner/handshake_client.go
index 4178dc1..3e20d87 100644
--- a/src/ssl/test/runner/handshake_client.go
+++ b/src/ssl/test/runner/handshake_client.go
@@ -155,6 +155,15 @@
 		}
 	}
 
+	if c.config.Bugs.DuplicateCompressedCertAlgs {
+		hello.compressedCertAlgs = []uint16{1, 1}
+	} else if len(c.config.CertCompressionAlgs) > 0 {
+		hello.compressedCertAlgs = make([]uint16, 0, len(c.config.CertCompressionAlgs))
+		for id, _ := range c.config.CertCompressionAlgs {
+			hello.compressedCertAlgs = append(hello.compressedCertAlgs, uint16(id))
+		}
+	}
+
 	if c.noRenegotiationInfo() {
 		hello.secureRenegotiation = nil
 	}
@@ -590,7 +599,7 @@
 	}
 
 	if serverWireVersion != serverHello.vers {
-		c.sendAlert(alertProtocolVersion)
+		c.sendAlert(alertIllegalParameter)
 		return fmt.Errorf("tls: server sent non-matching version %x vs %x", serverWireVersion, serverHello.vers)
 	}
 
@@ -720,6 +729,9 @@
 			if c.config.Bugs.RequireSessionTickets && len(hs.session.sessionTicket) == 0 {
 				return errors.New("tls: new session used session IDs instead of tickets")
 			}
+			if c.config.Bugs.RequireSessionIDs && len(hs.session.sessionId) == 0 {
+				return errors.New("tls: new session used session tickets instead of IDs")
+			}
 			sessionCache.Put(cacheKey, hs.session)
 		}
 
@@ -864,12 +876,46 @@
 			}
 		}
 
-		certMsg, ok := msg.(*certificateMsg)
-		if !ok {
-			c.sendAlert(alertUnexpectedMessage)
-			return unexpectedMessageError(certMsg, msg)
+		var certMsg *certificateMsg
+
+		if compressedCertMsg, ok := msg.(*compressedCertificateMsg); ok {
+			hs.writeServerHash(compressedCertMsg.marshal())
+
+			alg, ok := c.config.CertCompressionAlgs[compressedCertMsg.algID]
+			if !ok {
+				c.sendAlert(alertBadCertificate)
+				return fmt.Errorf("tls: received certificate compressed with unknown algorithm %x", compressedCertMsg.algID)
+			}
+
+			decompressed := make([]byte, 4+int(compressedCertMsg.uncompressedLength))
+			if !alg.Decompress(decompressed[4:], compressedCertMsg.compressed) {
+				c.sendAlert(alertBadCertificate)
+				return fmt.Errorf("tls: failed to decompress certificate with algorithm %x", compressedCertMsg.algID)
+			}
+
+			certMsg = &certificateMsg{
+				hasRequestContext: true,
+			}
+
+			if !certMsg.unmarshal(decompressed) {
+				c.sendAlert(alertBadCertificate)
+				return errors.New("tls: failed to parse decompressed certificate")
+			}
+
+			if expected := c.config.Bugs.ExpectedCompressedCert; expected != 0 && expected != compressedCertMsg.algID {
+				return fmt.Errorf("tls: expected certificate compressed with algorithm %x, but message used %x", expected, compressedCertMsg.algID)
+			}
+		} else {
+			if certMsg, ok = msg.(*certificateMsg); !ok {
+				c.sendAlert(alertUnexpectedMessage)
+				return unexpectedMessageError(certMsg, msg)
+			}
+			hs.writeServerHash(certMsg.marshal())
+
+			if c.config.Bugs.ExpectedCompressedCert != 0 {
+				return errors.New("tls: uncompressed certificate received")
+			}
 		}
-		hs.writeServerHash(certMsg.marshal())
 
 		// Check for unsolicited extensions.
 		for i, cert := range certMsg.certificates {
@@ -1629,6 +1675,9 @@
 	if c.vers == VersionSSL30 {
 		return errors.New("tls: negotiated session tickets in SSL 3.0")
 	}
+	if c.config.Bugs.ExpectNoNewSessionTicket {
+		return errors.New("tls: received unexpected NewSessionTicket")
+	}
 
 	msg, err := c.readHandshake()
 	if err != nil {
diff --git a/src/ssl/test/runner/handshake_messages.go b/src/ssl/test/runner/handshake_messages.go
index 64936b4..5324d74 100644
--- a/src/ssl/test/runner/handshake_messages.go
+++ b/src/ssl/test/runner/handshake_messages.go
@@ -296,6 +296,7 @@
 	emptyExtensions         bool
 	pad                     int
 	dummyPQPaddingLen       int
+	compressedCertAlgs      []uint16
 }
 
 func (m *clientHelloMsg) equal(i interface{}) bool {
@@ -349,7 +350,8 @@
 		m.omitExtensions == m1.omitExtensions &&
 		m.emptyExtensions == m1.emptyExtensions &&
 		m.pad == m1.pad &&
-		m.dummyPQPaddingLen == m1.dummyPQPaddingLen
+		m.dummyPQPaddingLen == m1.dummyPQPaddingLen &&
+		eqUint16s(m.compressedCertAlgs, m1.compressedCertAlgs)
 }
 
 func (m *clientHelloMsg) marshal() []byte {
@@ -586,6 +588,14 @@
 		body := extensions.addU16LengthPrefixed()
 		body.addBytes(make([]byte, l))
 	}
+	if len(m.compressedCertAlgs) > 0 {
+		extensions.addU16(extensionCompressedCertAlgs)
+		body := extensions.addU16LengthPrefixed()
+		algIDs := body.addU8LengthPrefixed()
+		for _, v := range m.compressedCertAlgs {
+			algIDs.addU16(v)
+		}
+	}
 	// The PSK extension must be last (draft-ietf-tls-tls13-18 section 4.2.6).
 	if len(m.pskIdentities) > 0 && !m.pskBinderFirst {
 		extensions.addU16(extensionPreSharedKey)
@@ -903,6 +913,24 @@
 				return false
 			}
 			m.dummyPQPaddingLen = len(body)
+		case extensionCompressedCertAlgs:
+			var algIDs byteReader
+			if !body.readU8LengthPrefixed(&algIDs) {
+				return false
+			}
+
+			seen := make(map[uint16]struct{})
+			for len(algIDs) > 0 {
+				var algID uint16
+				if !algIDs.readU16(&algID) {
+					return false
+				}
+				if _, ok := seen[algID]; ok {
+					return false
+				}
+				seen[algID] = struct{}{}
+				m.compressedCertAlgs = append(m.compressedCertAlgs, algID)
+			}
 		}
 
 		if isGREASEValue(extension) {
@@ -919,6 +947,7 @@
 	vers                  uint16
 	versOverride          uint16
 	supportedVersOverride uint16
+	omitSupportedVers     bool
 	random                []byte
 	sessionId             []byte
 	cipherSuite           uint16
@@ -979,12 +1008,14 @@
 			extensions.addU16(2) // Length
 			extensions.addU16(m.pskIdentity)
 		}
-		extensions.addU16(extensionSupportedVersions)
-		extensions.addU16(2) // Length
-		if m.supportedVersOverride != 0 {
-			extensions.addU16(m.supportedVersOverride)
-		} else {
-			extensions.addU16(m.vers)
+		if !m.omitSupportedVers {
+			extensions.addU16(extensionSupportedVersions)
+			extensions.addU16(2) // Length
+			if m.supportedVersOverride != 0 {
+				extensions.addU16(m.supportedVersOverride)
+			} else {
+				extensions.addU16(m.vers)
+			}
 		}
 		if len(m.customExtension) > 0 {
 			extensions.addU16(extensionCustom)
@@ -1668,6 +1699,48 @@
 	return true
 }
 
+type compressedCertificateMsg struct {
+	raw                []byte
+	algID              uint16
+	uncompressedLength uint32
+	compressed         []byte
+}
+
+func (m *compressedCertificateMsg) marshal() (x []byte) {
+	if m.raw != nil {
+		return m.raw
+	}
+
+	certMsg := newByteBuilder()
+	certMsg.addU8(typeCompressedCertificate)
+	certificate := certMsg.addU24LengthPrefixed()
+	certificate.addU16(m.algID)
+	certificate.addU24(int(m.uncompressedLength))
+	compressed := certificate.addU24LengthPrefixed()
+	compressed.addBytes(m.compressed)
+
+	m.raw = certMsg.finish()
+	return m.raw
+}
+
+func (m *compressedCertificateMsg) unmarshal(data []byte) bool {
+	m.raw = data
+	reader := byteReader(data[4:])
+
+	if !reader.readU16(&m.algID) ||
+		!reader.readU24(&m.uncompressedLength) ||
+		!reader.readU24LengthPrefixedBytes(&m.compressed) ||
+		len(reader) != 0 {
+		return false
+	}
+
+	if m.uncompressedLength >= 1<<17 {
+		return false
+	}
+
+	return true
+}
+
 type serverKeyExchangeMsg struct {
 	raw []byte
 	key []byte
diff --git a/src/ssl/test/runner/handshake_server.go b/src/ssl/test/runner/handshake_server.go
index e5f2944..c0653b1 100644
--- a/src/ssl/test/runner/handshake_server.go
+++ b/src/ssl/test/runner/handshake_server.go
@@ -375,7 +375,8 @@
 		sessionId:             hs.clientHello.sessionId,
 		compressionMethod:     config.Bugs.SendCompressionMethod,
 		versOverride:          config.Bugs.SendServerHelloVersion,
-		supportedVersOverride: config.Bugs.SendServerSupportedExtensionVersion,
+		supportedVersOverride: config.Bugs.SendServerSupportedVersionExtension,
+		omitSupportedVers:     config.Bugs.OmitServerSupportedVersionExtension,
 		customExtension:       config.Bugs.CustomUnencryptedExtension,
 		unencryptedALPN:       config.Bugs.SendUnencryptedALPN,
 	}
@@ -833,7 +834,7 @@
 		if config.ClientAuth >= RequestClientCert {
 			// Request a client certificate
 			certReq := &certificateRequestMsg{
-				vers: c.wireVersion,
+				vers:                  c.wireVersion,
 				hasSignatureAlgorithm: !config.Bugs.OmitCertificateRequestAlgorithms,
 				hasRequestContext:     true,
 				requestContext:        config.Bugs.SendRequestContext,
@@ -884,8 +885,47 @@
 			}
 		}
 		certMsgBytes := certMsg.marshal()
-		hs.writeServerHash(certMsgBytes)
-		c.writeRecord(recordTypeHandshake, certMsgBytes)
+		sentCompressedCertMsg := false
+
+	FindCertCompressionAlg:
+		for candidate, alg := range c.config.CertCompressionAlgs {
+			for _, id := range hs.clientHello.compressedCertAlgs {
+				if id == candidate {
+					if expected := config.Bugs.ExpectedCompressedCert; expected != 0 && expected != id {
+						return fmt.Errorf("expected to send compressed cert with alg %d, but picked %d", expected, id)
+					}
+
+					if override := config.Bugs.SendCertCompressionAlgId; override != 0 {
+						id = override
+					}
+
+					uncompressed := certMsgBytes[4:]
+					uncompressedLen := uint32(len(uncompressed))
+					if override := config.Bugs.SendCertUncompressedLength; override != 0 {
+						uncompressedLen = override
+					}
+
+					compressedCertMsgBytes := (&compressedCertificateMsg{
+						algID:              id,
+						uncompressedLength: uncompressedLen,
+						compressed:         alg.Compress(uncompressed),
+					}).marshal()
+
+					hs.writeServerHash(compressedCertMsgBytes)
+					c.writeRecord(recordTypeHandshake, compressedCertMsgBytes)
+					sentCompressedCertMsg = true
+					break FindCertCompressionAlg
+				}
+			}
+		}
+
+		if !sentCompressedCertMsg {
+			if config.Bugs.ExpectedCompressedCert != 0 {
+				return errors.New("unexpectedly sent uncompressed certificate")
+			}
+			hs.writeServerHash(certMsgBytes)
+			c.writeRecord(recordTypeHandshake, certMsgBytes)
+		}
 
 		certVerify := &certificateVerifyMsg{
 			hasSignatureAlgorithm: true,
@@ -1122,7 +1162,7 @@
 		versOverride:      config.Bugs.SendServerHelloVersion,
 		compressionMethod: config.Bugs.SendCompressionMethod,
 		extensions: serverExtensions{
-			supportedVersion: config.Bugs.SendServerSupportedExtensionVersion,
+			supportedVersion: config.Bugs.SendServerSupportedVersionExtension,
 		},
 		omitExtensions:  config.Bugs.OmitExtensions,
 		emptyExtensions: config.Bugs.EmptyExtensions,
diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go
index ce20d22..d1748dd 100644
--- a/src/ssl/test/runner/runner.go
+++ b/src/ssl/test/runner/runner.go
@@ -56,6 +56,7 @@
 	testToRun          = flag.String("test", "", "The pattern to filter tests to run, or empty to run all tests")
 	numWorkers         = flag.Int("num-workers", runtime.NumCPU(), "The number of workers to run in parallel.")
 	shimPath           = flag.String("shim-path", "../../../build/ssl/test/bssl_shim", "The location of the shim binary.")
+	handshakerPath     = flag.String("handshaker-path", "../../../build/ssl/test/handshaker", "The location of the handshaker binary.")
 	resourceDir        = flag.String("resource-dir", ".", "The directory in which to find certificate and key files.")
 	fuzzer             = flag.Bool("fuzzer", false, "If true, tests against a BoringSSL built in fuzzer mode.")
 	transcriptDir      = flag.String("transcript-dir", "", "The directory in which to write transcripts.")
@@ -414,7 +415,8 @@
 	readWithUnfinishedWrite bool
 	// shimShutsDown, if true, runs a test where the shim shuts down the
 	// connection immediately after the handshake rather than echoing
-	// messages from the runner.
+	// messages from the runner. The runner will default to not sending
+	// application data.
 	shimShutsDown bool
 	// renegotiate indicates the number of times the connection should be
 	// renegotiated during the exchange.
@@ -482,21 +484,23 @@
 
 var testCases []testCase
 
-func writeTranscript(test *testCase, path string, data []byte) {
+func appendTranscript(path string, data []byte) error {
 	if len(data) == 0 {
-		return
+		return nil
 	}
 
 	settings, err := ioutil.ReadFile(path)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error reading %s: %s.\n", path, err)
-		return
+		if !os.IsNotExist(err) {
+			return err
+		}
+		// If the shim aborted before writing a file, use a default
+		// settings block, so the transcript is still somewhat valid.
+		settings = []byte{0, 0} // kDataTag
 	}
 
 	settings = append(settings, data...)
-	if err := ioutil.WriteFile(path, settings, 0644); err != nil {
-		fmt.Fprintf(os.Stderr, "Error writing %s: %s\n", path, err)
-	}
+	return ioutil.WriteFile(path, settings, 0644)
 }
 
 // A timeoutConn implements an idle timeout on each Read and Write operation.
@@ -523,7 +527,7 @@
 	return t.Conn.Write(b)
 }
 
-func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, transcriptPrefix string, num int) error {
+func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, transcripts *[][]byte, num int) error {
 	if !test.noSessionCache {
 		if config.ClientSessionCache == nil {
 			config.ClientSessionCache = NewLRUClientSessionCache(1)
@@ -575,10 +579,13 @@
 		if *flagDebug {
 			defer connDebug.WriteTo(os.Stdout)
 		}
-		if len(transcriptPrefix) != 0 {
+		if len(*transcriptDir) != 0 {
 			defer func() {
-				path := transcriptPrefix + strconv.Itoa(num)
-				writeTranscript(test, path, connDebug.Transcript())
+				if num == len(*transcripts) {
+					*transcripts = append(*transcripts, connDebug.Transcript())
+				} else {
+					panic("transcripts are out of sync")
+				}
 			}()
 		}
 
@@ -820,7 +827,8 @@
 	}
 
 	messageCount := test.messageCount
-	if messageCount == 0 {
+	// shimShutsDown sets the default message count to zero.
+	if messageCount == 0 && !test.shimShutsDown {
 		messageCount = 1
 	}
 
@@ -1117,7 +1125,10 @@
 		flags = append(flags, "-tls13-variant", strconv.Itoa(test.tls13Variant))
 	}
 
+	flags = append(flags, "-handshaker-path", *handshakerPath)
+
 	var transcriptPrefix string
+	var transcripts [][]byte
 	if len(*transcriptDir) != 0 {
 		protocol := "tls"
 		if test.protocol == dtls {
@@ -1176,7 +1187,7 @@
 
 	conn, err := acceptOrWait(listener, waitChan)
 	if err == nil {
-		err = doExchange(test, &config, conn, false /* not a resumption */, transcriptPrefix, 0)
+		err = doExchange(test, &config, conn, false /* not a resumption */, &transcripts, 0)
 		conn.Close()
 	}
 
@@ -1196,7 +1207,7 @@
 		var connResume net.Conn
 		connResume, err = acceptOrWait(listener, waitChan)
 		if err == nil {
-			err = doExchange(test, &resumeConfig, connResume, true /* resumption */, transcriptPrefix, i+1)
+			err = doExchange(test, &resumeConfig, connResume, true /* resumption */, &transcripts, i+1)
 			connResume.Close()
 		}
 	}
@@ -1217,6 +1228,14 @@
 		waitTimeout.Stop()
 	}
 
+	// Now that the shim has exitted, all the settings files have been
+	// written. Append the saved transcripts.
+	for i, transcript := range transcripts {
+		if err := appendTranscript(transcriptPrefix+strconv.Itoa(i), transcript); err != nil {
+			return err
+		}
+	}
+
 	var isValgrindError, mustFail bool
 	if exitError, ok := childErr.(*exec.ExitError); ok {
 		switch exitError.Sys().(syscall.WaitStatus).ExitStatus() {
@@ -1338,11 +1357,6 @@
 
 var tlsVersions = []tlsVersion{
 	{
-		name:        "SSL3",
-		version:     VersionSSL30,
-		excludeFlag: "-no-ssl3",
-	},
-	{
 		name:        "TLS1",
 		version:     VersionTLS10,
 		excludeFlag: "-no-tls1",
@@ -1391,6 +1405,23 @@
 	return ret
 }
 
+func allShimVersions(protocol protocol) []tlsVersion {
+	if protocol == dtls {
+		return allVersions(protocol)
+	}
+	tls13Default := tlsVersion{
+		name:         "TLS13Default",
+		version:      VersionTLS13,
+		excludeFlag:  "-no-tls13",
+		versionWire:  0,
+		tls13Variant: TLS13Default,
+	}
+
+	var shimVersions []tlsVersion
+	shimVersions = append(shimVersions, allVersions(protocol)...)
+	return append(shimVersions, tls13Default)
+}
+
 type testCipherSuite struct {
 	name string
 	id   uint16
@@ -1400,23 +1431,17 @@
 	{"3DES-SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
 	{"AES128-GCM", TLS_RSA_WITH_AES_128_GCM_SHA256},
 	{"AES128-SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
-	{"AES128-SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
 	{"AES256-GCM", TLS_RSA_WITH_AES_256_GCM_SHA384},
 	{"AES256-SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
-	{"AES256-SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
 	{"ECDHE-ECDSA-AES128-GCM", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
 	{"ECDHE-ECDSA-AES128-SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
-	{"ECDHE-ECDSA-AES128-SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256},
 	{"ECDHE-ECDSA-AES256-GCM", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
 	{"ECDHE-ECDSA-AES256-SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA},
-	{"ECDHE-ECDSA-AES256-SHA384", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384},
 	{"ECDHE-ECDSA-CHACHA20-POLY1305", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256},
 	{"ECDHE-RSA-AES128-GCM", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
 	{"ECDHE-RSA-AES128-SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
-	{"ECDHE-RSA-AES128-SHA256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256},
 	{"ECDHE-RSA-AES256-GCM", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
 	{"ECDHE-RSA-AES256-SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
-	{"ECDHE-RSA-AES256-SHA384", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384},
 	{"ECDHE-RSA-CHACHA20-POLY1305", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
 	{"PSK-AES128-CBC-SHA", TLS_PSK_WITH_AES_128_CBC_SHA},
 	{"PSK-AES256-CBC-SHA", TLS_PSK_WITH_AES_256_CBC_SHA},
@@ -1453,6 +1478,22 @@
 }
 
 func convertToSplitHandshakeTests(tests []testCase) (splitHandshakeTests []testCase) {
+	var stdout bytes.Buffer
+	shim := exec.Command(*shimPath, "-is-handshaker-supported")
+	shim.Stdout = &stdout;
+	if err := shim.Run(); err != nil {
+		panic(err)
+	}
+
+	switch strings.TrimSpace(string(stdout.Bytes())) {
+	case "No":
+		return
+	case "Yes":
+		break
+	default:
+		panic("Unknown output from shim: 0x" + hex.EncodeToString(stdout.Bytes()))
+	}
+
 NextTest:
 	for _, test := range tests {
 		if test.protocol != tls ||
@@ -1811,7 +1852,7 @@
 		},
 		{
 			name:          "DisableEverything",
-			flags:         []string{"-no-tls13", "-no-tls12", "-no-tls11", "-no-tls1", "-no-ssl3"},
+			flags:         []string{"-no-tls13", "-no-tls12", "-no-tls11", "-no-tls1"},
 			shouldFail:    true,
 			expectedError: ":NO_SUPPORTED_VERSIONS_ENABLED:",
 		},
@@ -2973,10 +3014,12 @@
 			config: Config{
 				MaxVersion: VersionTLS13,
 				Bugs: ProtocolBugs{
-					MaxReceivePlaintext: 512,
+					MaxReceivePlaintext:            512,
+					ExpectPackedEncryptedHandshake: 512,
 				},
 			},
-			messageLen: 1024,
+			tls13Variant: TLS13Draft28,
+			messageLen:   1024,
 			flags: []string{
 				"-max-send-fragment", "512",
 				"-read-size", "1024",
@@ -3004,6 +3047,32 @@
 			expectedLocalError: "local error: record overflow",
 		},
 		{
+			// Test that handshake data is not packed in TLS 1.3
+			// draft-23.
+			testType: serverTest,
+			name:     "ForbidHandshakePacking-TLS13Draft23",
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					ForbidHandshakePacking: true,
+				},
+			},
+			tls13Variant: TLS13Draft23,
+		},
+		{
+			// Test that handshake data is tightly packed in TLS 1.3
+			// draft-28.
+			testType: serverTest,
+			name:     "PackedEncryptedHandshake-TLS13Draft28",
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					ExpectPackedEncryptedHandshake: 16384,
+				},
+			},
+			tls13Variant: TLS13Draft28,
+		},
+		{
 			// Test that DTLS can handle multiple application data
 			// records in a single packet.
 			protocol: dtls,
@@ -3119,33 +3188,22 @@
 		flags = append(flags, "-cipher", "DEFAULT:NULL-SHA")
 	}
 
-	var shouldServerFail, shouldClientFail bool
-	if hasComponent(suite.name, "ECDHE") && ver.version == VersionSSL30 {
-		// BoringSSL clients accept ECDHE on SSLv3, but
-		// a BoringSSL server will never select it
-		// because the extension is missing.
-		shouldServerFail = true
-	}
+	var shouldFail bool
 	if isTLS12Only(suite.name) && ver.version < VersionTLS12 {
-		shouldClientFail = true
-		shouldServerFail = true
+		shouldFail = true
 	}
 	if !isTLS13Suite(suite.name) && ver.version >= VersionTLS13 {
-		shouldClientFail = true
-		shouldServerFail = true
+		shouldFail = true
 	}
 	if isTLS13Suite(suite.name) && ver.version < VersionTLS13 {
-		shouldClientFail = true
-		shouldServerFail = true
+		shouldFail = true
 	}
 
 	var sendCipherSuite uint16
 	var expectedServerError, expectedClientError string
 	serverCipherSuites := []uint16{suite.id}
-	if shouldServerFail {
+	if shouldFail {
 		expectedServerError = ":NO_SHARED_CIPHER:"
-	}
-	if shouldClientFail {
 		expectedClientError = ":WRONG_CIPHER_RETURNED:"
 		// Configure the server to select ciphers as normal but
 		// select an incompatible cipher in ServerHello.
@@ -3153,12 +3211,8 @@
 		sendCipherSuite = suite.id
 	}
 
-	// For cipher suites and versions where exporters are defined, verify
-	// that they interoperate.
-	var exportKeyingMaterial int
-	if ver.version > VersionSSL30 {
-		exportKeyingMaterial = 1024
-	}
+	// Verify exporters interoperate.
+	exportKeyingMaterial := 1024
 
 	testCases = append(testCases, testCase{
 		testType: serverTest,
@@ -3180,7 +3234,7 @@
 		keyFile:              keyFile,
 		flags:                flags,
 		resumeSession:        true,
-		shouldFail:           shouldServerFail,
+		shouldFail:           shouldFail,
 		expectedError:        expectedServerError,
 		exportKeyingMaterial: exportKeyingMaterial,
 	})
@@ -3197,19 +3251,19 @@
 			PreSharedKey:         []byte(psk),
 			PreSharedKeyIdentity: pskIdentity,
 			Bugs: ProtocolBugs{
-				IgnorePeerCipherPreferences: shouldClientFail,
+				IgnorePeerCipherPreferences: shouldFail,
 				SendCipherSuite:             sendCipherSuite,
 			},
 		},
 		tls13Variant:         ver.tls13Variant,
 		flags:                flags,
 		resumeSession:        true,
-		shouldFail:           shouldClientFail,
+		shouldFail:           shouldFail,
 		expectedError:        expectedClientError,
 		exportKeyingMaterial: exportKeyingMaterial,
 	})
 
-	if shouldClientFail {
+	if shouldFail {
 		return
 	}
 
@@ -3232,10 +3286,9 @@
 
 	// Test bad records for all ciphers. Bad records are fatal in TLS
 	// and ignored in DTLS.
-	var shouldFail bool
+	shouldFail = protocol == tls
 	var expectedError string
-	if protocol == tls {
-		shouldFail = true
+	if shouldFail {
 		expectedError = ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"
 	}
 
@@ -3741,34 +3794,32 @@
 			tls13Variant: ver.tls13Variant,
 			flags:        []string{"-require-any-client-certificate"},
 		})
-		if ver.version != VersionSSL30 {
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     ver.name + "-Server-ClientAuth-ECDSA",
-				config: Config{
-					MinVersion:   ver.version,
-					MaxVersion:   ver.version,
-					Certificates: []Certificate{ecdsaP256Certificate},
-				},
-				tls13Variant: ver.tls13Variant,
-				flags:        []string{"-require-any-client-certificate"},
-			})
-			testCases = append(testCases, testCase{
-				testType: clientTest,
-				name:     ver.name + "-Client-ClientAuth-ECDSA",
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-					ClientAuth: RequireAnyClientCert,
-					ClientCAs:  certPool,
-				},
-				tls13Variant: ver.tls13Variant,
-				flags: []string{
-					"-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile),
-					"-key-file", path.Join(*resourceDir, ecdsaP256KeyFile),
-				},
-			})
-		}
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     ver.name + "-Server-ClientAuth-ECDSA",
+			config: Config{
+				MinVersion:   ver.version,
+				MaxVersion:   ver.version,
+				Certificates: []Certificate{ecdsaP256Certificate},
+			},
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-require-any-client-certificate"},
+		})
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     ver.name + "-Client-ClientAuth-ECDSA",
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				ClientAuth: RequireAnyClientCert,
+				ClientCAs:  certPool,
+			},
+			tls13Variant: ver.tls13Variant,
+			flags: []string{
+				"-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile),
+				"-key-file", path.Join(*resourceDir, ecdsaP256KeyFile),
+			},
+		})
 
 		testCases = append(testCases, testCase{
 			name: "NoClientCertificate-" + ver.name,
@@ -3835,57 +3886,55 @@
 			expectedLocalError: certificateRequired,
 		})
 
-		if ver.version != VersionSSL30 {
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "SkipClientCertificate-" + ver.name,
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-					Bugs: ProtocolBugs{
-						SkipClientCertificate: true,
-					},
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipClientCertificate-" + ver.name,
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				Bugs: ProtocolBugs{
+					SkipClientCertificate: true,
 				},
-				// Setting SSL_VERIFY_PEER allows anonymous clients.
-				flags:         []string{"-verify-peer"},
-				tls13Variant:  ver.tls13Variant,
-				shouldFail:    true,
-				expectedError: ":UNEXPECTED_MESSAGE:",
-			})
+			},
+			// Setting SSL_VERIFY_PEER allows anonymous clients.
+			flags:         []string{"-verify-peer"},
+			tls13Variant:  ver.tls13Variant,
+			shouldFail:    true,
+			expectedError: ":UNEXPECTED_MESSAGE:",
+		})
 
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "VerifyPeerIfNoOBC-NoChannelID-" + ver.name,
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-				},
-				flags: []string{
-					"-enable-channel-id",
-					"-verify-peer-if-no-obc",
-				},
-				tls13Variant:       ver.tls13Variant,
-				shouldFail:         true,
-				expectedError:      ":PEER_DID_NOT_RETURN_A_CERTIFICATE:",
-				expectedLocalError: certificateRequired,
-			})
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "VerifyPeerIfNoOBC-NoChannelID-" + ver.name,
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+			},
+			flags: []string{
+				"-enable-channel-id",
+				"-verify-peer-if-no-obc",
+			},
+			tls13Variant:       ver.tls13Variant,
+			shouldFail:         true,
+			expectedError:      ":PEER_DID_NOT_RETURN_A_CERTIFICATE:",
+			expectedLocalError: certificateRequired,
+		})
 
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "VerifyPeerIfNoOBC-ChannelID-" + ver.name,
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-					ChannelID:  channelIDKey,
-				},
-				expectChannelID: true,
-				tls13Variant:    ver.tls13Variant,
-				flags: []string{
-					"-enable-channel-id",
-					"-verify-peer-if-no-obc",
-				},
-			})
-		}
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "VerifyPeerIfNoOBC-ChannelID-" + ver.name,
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				ChannelID:  channelIDKey,
+			},
+			expectChannelID: true,
+			tls13Variant:    ver.tls13Variant,
+			flags: []string{
+				"-enable-channel-id",
+				"-verify-peer-if-no-obc",
+			},
+		})
 
 		testCases = append(testCases, testCase{
 			testType: serverTest,
@@ -4023,7 +4072,7 @@
 					flags = []string{expectEMSFlag}
 				}
 
-				test := testCase{
+				testCases = append(testCases, testCase{
 					testType: testType,
 					name:     prefix + "ExtendedMasterSecret-" + ver.name + suffix,
 					config: Config{
@@ -4036,12 +4085,7 @@
 					},
 					tls13Variant: ver.tls13Variant,
 					flags:        flags,
-					shouldFail:   ver.version == VersionSSL30 && with,
-				}
-				if test.shouldFail {
-					test.expectedLocalError = "extended master secret required but not supported by peer"
-				}
-				testCases = append(testCases, test)
+				})
 			}
 		}
 	}
@@ -4506,23 +4550,6 @@
 	if config.protocol == tls {
 		tests = append(tests, testCase{
 			testType: clientTest,
-			name:     "ClientAuth-NoCertificate-Client-SSL3",
-			config: Config{
-				MaxVersion: VersionSSL30,
-				ClientAuth: RequestClientCert,
-			},
-		})
-		tests = append(tests, testCase{
-			testType: serverTest,
-			name:     "ClientAuth-NoCertificate-Server-SSL3",
-			config: Config{
-				MaxVersion: VersionSSL30,
-			},
-			// Setting SSL_VERIFY_PEER allows anonymous clients.
-			flags: []string{"-verify-peer"},
-		})
-		tests = append(tests, testCase{
-			testType: clientTest,
 			name:     "ClientAuth-NoCertificate-Client-TLS13",
 			config: Config{
 				MaxVersion: VersionTLS13,
@@ -4741,60 +4768,157 @@
 	})
 
 	// OCSP stapling tests.
-	tests = append(tests, testCase{
-		testType: clientTest,
-		name:     "OCSPStapling-Client",
-		config: Config{
-			MaxVersion: VersionTLS12,
-		},
-		flags: []string{
-			"-enable-ocsp-stapling",
-			"-expect-ocsp-response",
-			base64.StdEncoding.EncodeToString(testOCSPResponse),
-			"-verify-peer",
-		},
-		resumeSession: true,
-	})
-	tests = append(tests, testCase{
-		testType: serverTest,
-		name:     "OCSPStapling-Server",
-		config: Config{
-			MaxVersion: VersionTLS12,
-		},
-		expectedOCSPResponse: testOCSPResponse,
-		flags: []string{
-			"-ocsp-response",
-			base64.StdEncoding.EncodeToString(testOCSPResponse),
-		},
-		resumeSession: true,
-	})
-	tests = append(tests, testCase{
-		testType: clientTest,
-		name:     "OCSPStapling-Client-TLS13",
-		config: Config{
-			MaxVersion: VersionTLS13,
-		},
-		flags: []string{
-			"-enable-ocsp-stapling",
-			"-expect-ocsp-response",
-			base64.StdEncoding.EncodeToString(testOCSPResponse),
-			"-verify-peer",
-		},
-		resumeSession: true,
-	})
-	tests = append(tests, testCase{
-		testType: serverTest,
-		name:     "OCSPStapling-Server-TLS13",
-		config: Config{
-			MaxVersion: VersionTLS13,
-		},
-		expectedOCSPResponse: testOCSPResponse,
-		flags: []string{
-			"-ocsp-response",
-			base64.StdEncoding.EncodeToString(testOCSPResponse),
-		},
-		resumeSession: true,
-	})
+	for _, vers := range tlsVersions {
+		if config.protocol == dtls && !vers.hasDTLS {
+			continue
+		}
+		tests = append(tests, testCase{
+			testType: clientTest,
+			name:     "OCSPStapling-Client-" + vers.name,
+			config: Config{
+				MaxVersion: vers.version,
+			},
+			tls13Variant: vers.tls13Variant,
+			flags: []string{
+				"-enable-ocsp-stapling",
+				"-expect-ocsp-response",
+				base64.StdEncoding.EncodeToString(testOCSPResponse),
+				"-verify-peer",
+			},
+			resumeSession: true,
+		})
+		tests = append(tests, testCase{
+			testType: serverTest,
+			name:     "OCSPStapling-Server-" + vers.name,
+			config: Config{
+				MaxVersion: vers.version,
+			},
+			tls13Variant:         vers.tls13Variant,
+			expectedOCSPResponse: testOCSPResponse,
+			flags: []string{
+				"-ocsp-response",
+				base64.StdEncoding.EncodeToString(testOCSPResponse),
+			},
+			resumeSession: true,
+		})
+
+		// The client OCSP callback is an alternate certificate
+		// verification callback.
+		tests = append(tests, testCase{
+			testType: clientTest,
+			name:     "ClientOCSPCallback-Pass-" + vers.name,
+			config: Config{
+				MaxVersion:   vers.version,
+				Certificates: []Certificate{rsaCertificate},
+			},
+			tls13Variant: vers.tls13Variant,
+			flags: []string{
+				"-enable-ocsp-stapling",
+				"-use-ocsp-callback",
+			},
+		})
+		var expectedLocalError string
+		if !config.async {
+			// TODO(davidben): Asynchronous fatal alerts are never
+			// sent. https://crbug.com/boringssl/130.
+			expectedLocalError = "remote error: bad certificate status response"
+		}
+		tests = append(tests, testCase{
+			testType: clientTest,
+			name:     "ClientOCSPCallback-Fail-" + vers.name,
+			config: Config{
+				MaxVersion:   vers.version,
+				Certificates: []Certificate{rsaCertificate},
+			},
+			tls13Variant: vers.tls13Variant,
+			flags: []string{
+				"-enable-ocsp-stapling",
+				"-use-ocsp-callback",
+				"-fail-ocsp-callback",
+			},
+			shouldFail:         true,
+			expectedLocalError: expectedLocalError,
+			expectedError:      ":OCSP_CB_ERROR:",
+		})
+		// The callback still runs if the server does not send an OCSP
+		// response.
+		certNoStaple := rsaCertificate
+		certNoStaple.OCSPStaple = nil
+		tests = append(tests, testCase{
+			testType: clientTest,
+			name:     "ClientOCSPCallback-FailNoStaple-" + vers.name,
+			config: Config{
+				MaxVersion:   vers.version,
+				Certificates: []Certificate{certNoStaple},
+			},
+			tls13Variant: vers.tls13Variant,
+			flags: []string{
+				"-enable-ocsp-stapling",
+				"-use-ocsp-callback",
+				"-fail-ocsp-callback",
+			},
+			shouldFail:         true,
+			expectedLocalError: expectedLocalError,
+			expectedError:      ":OCSP_CB_ERROR:",
+		})
+
+		// The server OCSP callback is a legacy mechanism for
+		// configuring OCSP, used by unreliable server software.
+		tests = append(tests, testCase{
+			testType: serverTest,
+			name:     "ServerOCSPCallback-SetInCallback-" + vers.name,
+			config: Config{
+				MaxVersion: vers.version,
+			},
+			tls13Variant:         vers.tls13Variant,
+			expectedOCSPResponse: testOCSPResponse,
+			flags: []string{
+				"-use-ocsp-callback",
+				"-set-ocsp-in-callback",
+				"-ocsp-response",
+				base64.StdEncoding.EncodeToString(testOCSPResponse),
+			},
+			resumeSession: true,
+		})
+
+		// The callback may decline OCSP, in which case  we act as if
+		// the client did not support it, even if a response was
+		// configured.
+		tests = append(tests, testCase{
+			testType: serverTest,
+			name:     "ServerOCSPCallback-Decline-" + vers.name,
+			config: Config{
+				MaxVersion: vers.version,
+			},
+			tls13Variant:         vers.tls13Variant,
+			expectedOCSPResponse: []byte{},
+			flags: []string{
+				"-use-ocsp-callback",
+				"-decline-ocsp-callback",
+				"-ocsp-response",
+				base64.StdEncoding.EncodeToString(testOCSPResponse),
+			},
+			resumeSession: true,
+		})
+
+		// The callback may also signal an internal error.
+		tests = append(tests, testCase{
+			testType: serverTest,
+			name:     "ServerOCSPCallback-Fail-" + vers.name,
+			config: Config{
+				MaxVersion: vers.version,
+			},
+			tls13Variant: vers.tls13Variant,
+			flags: []string{
+				"-use-ocsp-callback",
+				"-fail-ocsp-callback",
+				"-ocsp-response",
+				base64.StdEncoding.EncodeToString(testOCSPResponse),
+			},
+			shouldFail:    true,
+			expectedError: ":OCSP_CB_ERROR:",
+		})
+	}
 
 	// Certificate verification tests.
 	for _, vers := range tlsVersions {
@@ -5165,6 +5289,25 @@
 				flags:             []string{"-check-close-notify"},
 			})
 
+			// The shim should reject unexpected application data
+			// when shutting down.
+			tests = append(tests, testCase{
+				name: "Shutdown-Shim-ApplicationData",
+				config: Config{
+					MaxVersion: VersionTLS12,
+					Bugs: ProtocolBugs{
+						ExpectCloseNotify: true,
+					},
+				},
+				shimShutsDown:     true,
+				messageCount:      1,
+				sendEmptyRecords:  1,
+				sendWarningAlerts: 1,
+				flags:             []string{"-check-close-notify"},
+				shouldFail:        true,
+				expectedError:     ":APPLICATION_DATA_ON_SHUTDOWN:",
+			})
+
 			// Test that SSL_shutdown still processes KeyUpdate.
 			tests = append(tests, testCase{
 				name: "Shutdown-Shim-KeyUpdate",
@@ -5205,11 +5348,11 @@
 					MinVersion: VersionTLS12,
 					MaxVersion: VersionTLS12,
 					Bugs: ProtocolBugs{
-						SendHelloRequestBeforeEveryAppDataRecord: true,
-						ExpectCloseNotify:                        true,
+						ExpectCloseNotify: true,
 					},
 				},
 				shimShutsDown: true,
+				renegotiate:   1,
 				shouldFail:    true,
 				expectedError: ":NO_RENEGOTIATION:",
 				flags:         []string{"-check-close-notify"},
@@ -5220,11 +5363,11 @@
 					MinVersion: VersionTLS12,
 					MaxVersion: VersionTLS12,
 					Bugs: ProtocolBugs{
-						SendHelloRequestBeforeEveryAppDataRecord: true,
-						ExpectCloseNotify:                        true,
+						ExpectCloseNotify: true,
 					},
 				},
 				shimShutsDown: true,
+				renegotiate:   1,
 				shouldFail:    true,
 				expectedError: ":NO_RENEGOTIATION:",
 				flags: []string{
@@ -5332,7 +5475,7 @@
 
 		failFlag := "-fail-ddos-callback"
 		if resume {
-			failFlag = "-fail-second-ddos-callback"
+			failFlag = "-on-resume-fail-ddos-callback"
 		}
 		testCases = append(testCases, testCase{
 			testType: serverTest,
@@ -5363,7 +5506,7 @@
 
 func addVersionNegotiationTests() {
 	for _, protocol := range []protocol{tls, dtls} {
-		for _, shimVers := range allVersions(protocol) {
+		for _, shimVers := range allShimVersions(protocol) {
 			// Assemble flags to disable all newer versions on the shim.
 			var flags []string
 			for _, vers := range allVersions(protocol) {
@@ -5385,13 +5528,12 @@
 				if runnerVers.version < shimVers.version {
 					expectedVersion = runnerVers.version
 				}
-				// When running and shim have different TLS 1.3 variants enabled,
-				// shim peers are expected to fall back to TLS 1.2.
+
 				if expectedVersion == VersionTLS13 && runnerVers.tls13Variant != shimVers.tls13Variant {
-					expectedVersion = VersionTLS12
+					if shimVers.tls13Variant != TLS13Default {
+						expectedVersion = VersionTLS12
+					}
 				}
-				expectedClientVersion := expectedVersion
-				expectedServerVersion := expectedVersion
 
 				suffix := shimVers.name + "-" + runnerVers.name
 				if protocol == dtls {
@@ -5404,8 +5546,8 @@
 					clientVers = VersionTLS10
 				}
 				clientVers = recordVersionToWire(clientVers, protocol)
-				serverVers := expectedServerVersion
-				if expectedServerVersion >= VersionTLS13 {
+				serverVers := expectedVersion
+				if expectedVersion >= VersionTLS13 {
 					serverVers = VersionTLS12
 				}
 				serverVers = recordVersionToWire(serverVers, protocol)
@@ -5422,7 +5564,7 @@
 						},
 					},
 					flags:           flags,
-					expectedVersion: expectedClientVersion,
+					expectedVersion: expectedVersion,
 				})
 				testCases = append(testCases, testCase{
 					protocol: protocol,
@@ -5436,7 +5578,7 @@
 						},
 					},
 					flags:           flags2,
-					expectedVersion: expectedClientVersion,
+					expectedVersion: expectedVersion,
 				})
 
 				testCases = append(testCases, testCase{
@@ -5451,7 +5593,7 @@
 						},
 					},
 					flags:           flags,
-					expectedVersion: expectedServerVersion,
+					expectedVersion: expectedVersion,
 				})
 				testCases = append(testCases, testCase{
 					protocol: protocol,
@@ -5465,7 +5607,7 @@
 						},
 					},
 					flags:           flags2,
-					expectedVersion: expectedServerVersion,
+					expectedVersion: expectedVersion,
 				})
 			}
 		}
@@ -5584,7 +5726,7 @@
 		config: Config{
 			MaxVersion: VersionTLS12,
 			Bugs: ProtocolBugs{
-				SendServerSupportedExtensionVersion: VersionTLS12,
+				SendServerSupportedVersionExtension: VersionTLS12,
 			},
 		},
 		shouldFail:    true,
@@ -5750,6 +5892,46 @@
 			},
 		},
 	})
+
+	// SSL 3.0 support has been removed. Test that the shim does not
+	// support it.
+	testCases = append(testCases, testCase{
+		name: "NoSSL3-Client",
+		config: Config{
+			MinVersion: VersionSSL30,
+			MaxVersion: VersionSSL30,
+		},
+		shouldFail:         true,
+		expectedLocalError: "tls: client did not offer any supported protocol versions",
+	})
+	testCases = append(testCases, testCase{
+		name: "NoSSL3-Client-Unsolicited",
+		config: Config{
+			MinVersion: VersionSSL30,
+			MaxVersion: VersionSSL30,
+			Bugs: ProtocolBugs{
+				// The above test asserts the client does not
+				// offer SSL 3.0 in the supported_versions
+				// list. Additionally assert that it rejects an
+				// unsolicited SSL 3.0 ServerHello.
+				NegotiateVersion: VersionSSL30,
+			},
+		},
+		shouldFail:         true,
+		expectedError:      ":UNSUPPORTED_PROTOCOL:",
+		expectedLocalError: "remote error: protocol version not supported",
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "NoSSL3-Server",
+		config: Config{
+			MinVersion: VersionSSL30,
+			MaxVersion: VersionSSL30,
+		},
+		shouldFail:         true,
+		expectedError:      ":UNSUPPORTED_PROTOCOL:",
+		expectedLocalError: "remote error: protocol version not supported",
+	})
 }
 
 func addMinimumVersionTests() {
@@ -5878,12 +6060,8 @@
 	// halves to EncryptedExtensions in TLS 1.3. Duplicate each of these
 	// tests for both. Also test interaction with 0-RTT when implemented.
 
-	// Repeat extensions tests all versions except SSL 3.0.
+	// Repeat extensions tests at all versions.
 	for _, ver := range tlsVersions {
-		if ver.version == VersionSSL30 {
-			continue
-		}
-
 		// Test that duplicate extensions are rejected.
 		testCases = append(testCases, testCase{
 			testType: clientTest,
@@ -6069,6 +6247,24 @@
 			expectNoNextProto: true,
 			resumeSession:     true,
 		})
+		// Test that the server implementation catches itself if the
+		// callback tries to return an invalid empty ALPN protocol.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "ALPNServer-SelectEmpty-" + ver.name,
+			config: Config{
+				MaxVersion: ver.version,
+				NextProtos: []string{"foo", "bar", "baz"},
+			},
+			flags: []string{
+				"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+				"-select-empty-alpn",
+			},
+			tls13Variant:       ver.tls13Variant,
+			shouldFail:         true,
+			expectedLocalError: "remote error: internal error",
+			expectedError:      ":INVALID_ALPN_PROTOCOL:",
+		})
 
 		// Test ALPN in async mode as well to ensure that extensions callbacks are only
 		// called once.
@@ -7036,70 +7232,6 @@
 		flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com"},
 	})
 
-	// Extensions should not function in SSL 3.0.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SSLv3Extensions-NoALPN",
-		config: Config{
-			MaxVersion: VersionSSL30,
-			NextProtos: []string{"foo", "bar", "baz"},
-		},
-		flags: []string{
-			"-select-alpn", "foo",
-		},
-		expectNoNextProto: true,
-	})
-
-	// Test session tickets separately as they follow a different codepath.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SSLv3Extensions-NoTickets",
-		config: Config{
-			MaxVersion: VersionSSL30,
-			Bugs: ProtocolBugs{
-				// Historically, session tickets in SSL 3.0
-				// failed in different ways depending on whether
-				// the client supported renegotiation_info.
-				NoRenegotiationInfo: true,
-			},
-		},
-		resumeSession: true,
-	})
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SSLv3Extensions-NoTickets2",
-		config: Config{
-			MaxVersion: VersionSSL30,
-		},
-		resumeSession: true,
-	})
-
-	// But SSL 3.0 does send and process renegotiation_info.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SSLv3Extensions-RenegotiationInfo",
-		config: Config{
-			MaxVersion: VersionSSL30,
-			Bugs: ProtocolBugs{
-				RequireRenegotiationInfo: true,
-			},
-		},
-		flags: []string{"-expect-secure-renegotiation"},
-	})
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SSLv3Extensions-RenegotiationInfo-SCSV",
-		config: Config{
-			MaxVersion: VersionSSL30,
-			Bugs: ProtocolBugs{
-				NoRenegotiationInfo:      true,
-				SendRenegotiationSCSV:    true,
-				RequireRenegotiationInfo: true,
-			},
-		},
-		flags: []string{"-expect-secure-renegotiation"},
-	})
-
 	// Test that illegal extensions in TLS 1.3 are rejected by the client if
 	// in ServerHello.
 	testCases = append(testCases, testCase{
@@ -7378,14 +7510,6 @@
 func addResumptionVersionTests() {
 	for _, sessionVers := range tlsVersions {
 		for _, resumeVers := range tlsVersions {
-			// SSL 3.0 does not have tickets and TLS 1.3 does not
-			// have session IDs, so skip their cross-resumption
-			// tests.
-			if (sessionVers.version >= VersionTLS13 && resumeVers.version == VersionSSL30) ||
-				(resumeVers.version >= VersionTLS13 && sessionVers.version == VersionSSL30) {
-				continue
-			}
-
 			protocols := []protocol{tls}
 			if sessionVers.hasDTLS && resumeVers.hasDTLS {
 				protocols = append(protocols, dtls)
@@ -7504,6 +7628,27 @@
 						"-on-resume-tls13-variant", strconv.Itoa(resumeVers.tls13Variant),
 					},
 				})
+
+				// Repeat the test using session IDs, rather than tickets.
+				if sessionVers.version < VersionTLS13 && resumeVers.version < VersionTLS13 {
+					testCases = append(testCases, testCase{
+						protocol:      protocol,
+						testType:      serverTest,
+						name:          "Resume-Server-NoTickets" + suffix,
+						resumeSession: true,
+						config: Config{
+							MaxVersion:             sessionVers.version,
+							SessionTicketsDisabled: true,
+						},
+						expectedVersion:      sessionVers.version,
+						expectResumeRejected: sessionVers != resumeVers,
+						resumeConfig: &Config{
+							MaxVersion:             resumeVers.version,
+							SessionTicketsDisabled: true,
+						},
+						expectedResumeVersion: resumeVers.version,
+					})
+				}
 			}
 		}
 	}
@@ -8195,17 +8340,15 @@
 		},
 	})
 
-	// Renegotiation is not allowed at SSL 3.0.
+	// Renegotiation may be enabled and then disabled immediately after the
+	// handshake.
 	testCases = append(testCases, testCase{
-		name: "Renegotiate-Client-SSL3",
+		name: "Renegotiate-ForbidAfterHandshake",
 		config: Config{
-			MaxVersion: VersionSSL30,
+			MaxVersion: VersionTLS12,
 		},
-		renegotiate: 1,
-		flags: []string{
-			"-renegotiate-freely",
-			"-expect-total-renegotiations", "1",
-		},
+		renegotiate:        1,
+		flags:              []string{"-forbid-renegotiation-after-handshake"},
 		shouldFail:         true,
 		expectedError:      ":NO_RENEGOTIATION:",
 		expectedLocalError: "remote error: no renegotiation",
@@ -8497,12 +8640,6 @@
 				continue
 			}
 
-			// TODO(davidben): Support ECDSA in SSL 3.0 in Go for testing
-			// or remove it in C.
-			if ver.version == VersionSSL30 && alg.cert != testCertRSA {
-				continue
-			}
-
 			var shouldSignFail, shouldVerifyFail bool
 			// ecdsa_sha1 does not exist in TLS 1.3.
 			if ver.version >= VersionTLS13 && alg.id == signatureECDSAWithSHA1 {
@@ -8526,12 +8663,14 @@
 				shouldVerifyFail = true
 			}
 
-			var signError, verifyError string
+			var signError, signLocalError, verifyError, verifyLocalError string
 			if shouldSignFail {
 				signError = ":NO_COMMON_SIGNATURE_ALGORITHMS:"
+				signLocalError = "remote error: handshake failure"
 			}
 			if shouldVerifyFail {
 				verifyError = ":WRONG_SIGNATURE_TYPE:"
+				verifyLocalError = "remote error"
 			}
 
 			suffix := "-" + alg.name + "-" + ver.name
@@ -8556,6 +8695,7 @@
 				tls13Variant:                   ver.tls13Variant,
 				shouldFail:                     shouldSignFail,
 				expectedError:                  signError,
+				expectedLocalError:             signLocalError,
 				expectedPeerSignatureAlgorithm: alg.id,
 			})
 
@@ -8584,37 +8724,36 @@
 				},
 				// Resume the session to assert the peer signature
 				// algorithm is reported on both handshakes.
-				resumeSession: !shouldVerifyFail,
-				shouldFail:    shouldVerifyFail,
-				expectedError: verifyError,
+				resumeSession:      !shouldVerifyFail,
+				shouldFail:         shouldVerifyFail,
+				expectedError:      verifyError,
+				expectedLocalError: verifyLocalError,
 			})
 
-			// No signing cipher for SSL 3.0.
-			if ver.version > VersionSSL30 {
-				testCases = append(testCases, testCase{
-					testType: serverTest,
-					name:     "ServerAuth-Sign" + suffix,
-					config: Config{
-						MaxVersion:   ver.version,
-						CipherSuites: signingCiphers,
-						VerifySignatureAlgorithms: []signatureAlgorithm{
-							fakeSigAlg1,
-							alg.id,
-							fakeSigAlg2,
-						},
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "ServerAuth-Sign" + suffix,
+				config: Config{
+					MaxVersion:   ver.version,
+					CipherSuites: signingCiphers,
+					VerifySignatureAlgorithms: []signatureAlgorithm{
+						fakeSigAlg1,
+						alg.id,
+						fakeSigAlg2,
 					},
-					tls13Variant: ver.tls13Variant,
-					flags: []string{
-						"-cert-file", path.Join(*resourceDir, getShimCertificate(alg.cert)),
-						"-key-file", path.Join(*resourceDir, getShimKey(alg.cert)),
-						"-enable-all-curves",
-						"-enable-ed25519",
-					},
-					shouldFail:                     shouldSignFail,
-					expectedError:                  signError,
-					expectedPeerSignatureAlgorithm: alg.id,
-				})
-			}
+				},
+				tls13Variant: ver.tls13Variant,
+				flags: []string{
+					"-cert-file", path.Join(*resourceDir, getShimCertificate(alg.cert)),
+					"-key-file", path.Join(*resourceDir, getShimKey(alg.cert)),
+					"-enable-all-curves",
+					"-enable-ed25519",
+				},
+				shouldFail:                     shouldSignFail,
+				expectedError:                  signError,
+				expectedLocalError:             signLocalError,
+				expectedPeerSignatureAlgorithm: alg.id,
+			})
 
 			testCases = append(testCases, testCase{
 				name: "ServerAuth-Verify" + suffix,
@@ -8640,9 +8779,10 @@
 				},
 				// Resume the session to assert the peer signature
 				// algorithm is reported on both handshakes.
-				resumeSession: !shouldVerifyFail,
-				shouldFail:    shouldVerifyFail,
-				expectedError: verifyError,
+				resumeSession:      !shouldVerifyFail,
+				shouldFail:         shouldVerifyFail,
+				expectedError:      verifyError,
+				expectedLocalError: verifyLocalError,
 			})
 
 			if !shouldVerifyFail {
@@ -8733,6 +8873,55 @@
 		}
 	}
 
+	// Test the peer's verify preferences are available.
+	for _, ver := range tlsVersions {
+		if ver.version < VersionTLS12 {
+			continue
+		}
+		testCases = append(testCases, testCase{
+			name: "ClientAuth-PeerVerifyPrefs-" + ver.name,
+			config: Config{
+				MaxVersion: ver.version,
+				ClientAuth: RequireAnyClientCert,
+				VerifySignatureAlgorithms: []signatureAlgorithm{
+					signatureRSAPSSWithSHA256,
+					signatureEd25519,
+					signatureECDSAWithP256AndSHA256,
+				},
+			},
+			tls13Variant: ver.tls13Variant,
+			flags: []string{
+				"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+				"-key-file", path.Join(*resourceDir, rsaKeyFile),
+				"-expect-peer-verify-pref", strconv.Itoa(int(signatureRSAPSSWithSHA256)),
+				"-expect-peer-verify-pref", strconv.Itoa(int(signatureEd25519)),
+				"-expect-peer-verify-pref", strconv.Itoa(int(signatureECDSAWithP256AndSHA256)),
+			},
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "ServerAuth-PeerVerifyPrefs-" + ver.name,
+			config: Config{
+				MaxVersion: ver.version,
+				VerifySignatureAlgorithms: []signatureAlgorithm{
+					signatureRSAPSSWithSHA256,
+					signatureEd25519,
+					signatureECDSAWithP256AndSHA256,
+				},
+			},
+			tls13Variant: ver.tls13Variant,
+			flags: []string{
+				"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+				"-key-file", path.Join(*resourceDir, rsaKeyFile),
+				"-expect-peer-verify-pref", strconv.Itoa(int(signatureRSAPSSWithSHA256)),
+				"-expect-peer-verify-pref", strconv.Itoa(int(signatureEd25519)),
+				"-expect-peer-verify-pref", strconv.Itoa(int(signatureECDSAWithP256AndSHA256)),
+			},
+		})
+
+	}
+
 	// Test that algorithm selection takes the key type into account.
 	testCases = append(testCases, testCase{
 		name: "ClientAuth-SignatureType",
@@ -9772,9 +9961,6 @@
 
 func addExportKeyingMaterialTests() {
 	for _, vers := range tlsVersions {
-		if vers.version == VersionSSL30 {
-			continue
-		}
 		testCases = append(testCases, testCase{
 			name: "ExportKeyingMaterial-" + vers.name,
 			config: Config{
@@ -10042,19 +10228,6 @@
 		}
 	}
 
-	testCases = append(testCases, testCase{
-		name: "ExportKeyingMaterial-SSL3",
-		config: Config{
-			MaxVersion: VersionSSL30,
-		},
-		exportKeyingMaterial: 1024,
-		exportLabel:          "label",
-		exportContext:        "context",
-		useExportContext:     true,
-		shouldFail:           true,
-		expectedError:        "failed to export keying material",
-	})
-
 	// Exporters work during a False Start.
 	testCases = append(testCases, testCase{
 		name: "ExportKeyingMaterial-FalseStart",
@@ -10154,278 +10327,6 @@
 }
 
 func addCustomExtensionTests() {
-	expectedContents := "custom extension"
-	emptyString := ""
-
-	for _, isClient := range []bool{false, true} {
-		suffix := "Server"
-		flag := "-enable-server-custom-extension"
-		testType := serverTest
-		if isClient {
-			suffix = "Client"
-			flag = "-enable-client-custom-extension"
-			testType = clientTest
-		}
-
-		testCases = append(testCases, testCase{
-			testType: testType,
-			name:     "CustomExtensions-" + suffix,
-			config: Config{
-				MaxVersion: VersionTLS12,
-				Bugs: ProtocolBugs{
-					CustomExtension:         expectedContents,
-					ExpectedCustomExtension: &expectedContents,
-				},
-			},
-			flags: []string{flag},
-		})
-		testCases = append(testCases, testCase{
-			testType: testType,
-			name:     "CustomExtensions-" + suffix + "-TLS13",
-			config: Config{
-				MaxVersion: VersionTLS13,
-				Bugs: ProtocolBugs{
-					CustomExtension:         expectedContents,
-					ExpectedCustomExtension: &expectedContents,
-				},
-			},
-			flags: []string{flag},
-		})
-
-		// If the parse callback fails, the handshake should also fail.
-		testCases = append(testCases, testCase{
-			testType: testType,
-			name:     "CustomExtensions-ParseError-" + suffix,
-			config: Config{
-				MaxVersion: VersionTLS12,
-				Bugs: ProtocolBugs{
-					CustomExtension:         expectedContents + "foo",
-					ExpectedCustomExtension: &expectedContents,
-				},
-			},
-			flags:         []string{flag},
-			shouldFail:    true,
-			expectedError: ":CUSTOM_EXTENSION_ERROR:",
-		})
-		testCases = append(testCases, testCase{
-			testType: testType,
-			name:     "CustomExtensions-ParseError-" + suffix + "-TLS13",
-			config: Config{
-				MaxVersion: VersionTLS13,
-				Bugs: ProtocolBugs{
-					CustomExtension:         expectedContents + "foo",
-					ExpectedCustomExtension: &expectedContents,
-				},
-			},
-			flags:         []string{flag},
-			shouldFail:    true,
-			expectedError: ":CUSTOM_EXTENSION_ERROR:",
-		})
-
-		// If the add callback fails, the handshake should also fail.
-		testCases = append(testCases, testCase{
-			testType: testType,
-			name:     "CustomExtensions-FailAdd-" + suffix,
-			config: Config{
-				MaxVersion: VersionTLS12,
-				Bugs: ProtocolBugs{
-					CustomExtension:         expectedContents,
-					ExpectedCustomExtension: &expectedContents,
-				},
-			},
-			flags:         []string{flag, "-custom-extension-fail-add"},
-			shouldFail:    true,
-			expectedError: ":CUSTOM_EXTENSION_ERROR:",
-		})
-		testCases = append(testCases, testCase{
-			testType: testType,
-			name:     "CustomExtensions-FailAdd-" + suffix + "-TLS13",
-			config: Config{
-				MaxVersion: VersionTLS13,
-				Bugs: ProtocolBugs{
-					CustomExtension:         expectedContents,
-					ExpectedCustomExtension: &expectedContents,
-				},
-			},
-			flags:         []string{flag, "-custom-extension-fail-add"},
-			shouldFail:    true,
-			expectedError: ":CUSTOM_EXTENSION_ERROR:",
-		})
-
-		// If the add callback returns zero, no extension should be
-		// added.
-		skipCustomExtension := expectedContents
-		if isClient {
-			// For the case where the client skips sending the
-			// custom extension, the server must not “echo” it.
-			skipCustomExtension = ""
-		}
-		testCases = append(testCases, testCase{
-			testType: testType,
-			name:     "CustomExtensions-Skip-" + suffix,
-			config: Config{
-				MaxVersion: VersionTLS12,
-				Bugs: ProtocolBugs{
-					CustomExtension:         skipCustomExtension,
-					ExpectedCustomExtension: &emptyString,
-				},
-			},
-			flags: []string{flag, "-custom-extension-skip"},
-		})
-		testCases = append(testCases, testCase{
-			testType: testType,
-			name:     "CustomExtensions-Skip-" + suffix + "-TLS13",
-			config: Config{
-				MaxVersion: VersionTLS13,
-				Bugs: ProtocolBugs{
-					CustomExtension:         skipCustomExtension,
-					ExpectedCustomExtension: &emptyString,
-				},
-			},
-			flags: []string{flag, "-custom-extension-skip"},
-		})
-	}
-
-	// If the client sends both early data and custom extension, the handshake
-	// should succeed as long as both the extensions aren't returned by the
-	// server.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "CustomExtensions-Client-EarlyData-None",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				ExpectedCustomExtension: &expectedContents,
-				AlwaysRejectEarlyData:   true,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-client-custom-extension",
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-reject-early-data",
-		},
-	})
-
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "CustomExtensions-Client-EarlyData-EarlyDataAccepted",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				ExpectedCustomExtension: &expectedContents,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-client-custom-extension",
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-accept-early-data",
-		},
-	})
-
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "CustomExtensions-Client-EarlyData-CustomExtensionAccepted",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				AlwaysRejectEarlyData:   true,
-				CustomExtension:         expectedContents,
-				ExpectedCustomExtension: &expectedContents,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-client-custom-extension",
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-reject-early-data",
-		},
-	})
-
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "CustomExtensions-Client-EarlyDataAndCustomExtensions",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				CustomExtension:         expectedContents,
-				ExpectedCustomExtension: &expectedContents,
-			},
-		},
-		resumeConfig: &Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				CustomExtension:         expectedContents,
-				ExpectedCustomExtension: &expectedContents,
-				SendEarlyDataExtension:  true,
-			},
-		},
-		resumeSession: true,
-		shouldFail:    true,
-		expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
-		flags: []string{
-			"-enable-client-custom-extension",
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-		},
-	})
-
-	// If the server receives both early data and custom extension, only the
-	// custom extension should be accepted.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "CustomExtensions-Server-EarlyDataOffered",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				CustomExtension:         expectedContents,
-				ExpectedCustomExtension: &expectedContents,
-				ExpectEarlyDataAccepted: false,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-server-custom-extension",
-			"-enable-early-data",
-		},
-	})
-
-	// The custom extension add callback should not be called if the client
-	// doesn't send the extension.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "CustomExtensions-NotCalled-Server",
-		config: Config{
-			MaxVersion: VersionTLS12,
-			Bugs: ProtocolBugs{
-				ExpectedCustomExtension: &emptyString,
-			},
-		},
-		flags: []string{"-enable-server-custom-extension", "-custom-extension-fail-add"},
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "CustomExtensions-NotCalled-Server-TLS13",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				ExpectedCustomExtension: &emptyString,
-			},
-		},
-		flags: []string{"-enable-server-custom-extension", "-custom-extension-fail-add"},
-	})
-
 	// Test an unknown extension from the server.
 	testCases = append(testCases, testCase{
 		testType: clientTest,
@@ -10433,7 +10334,7 @@
 		config: Config{
 			MaxVersion: VersionTLS12,
 			Bugs: ProtocolBugs{
-				CustomExtension: expectedContents,
+				CustomExtension: "custom extension",
 			},
 		},
 		shouldFail:         true,
@@ -10446,7 +10347,7 @@
 		config: Config{
 			MaxVersion: VersionTLS13,
 			Bugs: ProtocolBugs{
-				CustomExtension: expectedContents,
+				CustomExtension: "custom extension",
 			},
 		},
 		shouldFail:         true,
@@ -10459,7 +10360,7 @@
 		config: Config{
 			MaxVersion: VersionTLS13,
 			Bugs: ProtocolBugs{
-				CustomUnencryptedExtension: expectedContents,
+				CustomUnencryptedExtension: "custom extension",
 			},
 		},
 		shouldFail:    true,
@@ -10567,11 +10468,6 @@
 func addCurveTests() {
 	for _, curve := range testCurves {
 		for _, ver := range tlsVersions {
-			// SSL 3.0 cannot reliably negotiate curves.
-			if ver.version == VersionSSL30 {
-				continue
-			}
-
 			suffix := curve.name + "-" + ver.name
 
 			testCases = append(testCases, testCase{
@@ -11286,6 +11182,44 @@
 		resumeSession:        true,
 		expectResumeRejected: true,
 	})
+
+	for _, ver := range tlsVersions {
+		// Prior to TLS 1.3, disabling session tickets enables session IDs.
+		useStatefulResumption := ver.version < VersionTLS13
+
+		// SSL_OP_NO_TICKET implies the server must not mint any tickets.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     ver.name + "-NoTicket-NoMint",
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				Bugs: ProtocolBugs{
+					ExpectNoNewSessionTicket: true,
+					RequireSessionIDs:        useStatefulResumption,
+				},
+			},
+			resumeSession: useStatefulResumption,
+			tls13Variant:  ver.tls13Variant,
+			flags:         []string{"-no-ticket"},
+		})
+
+		// SSL_OP_NO_TICKET implies the server must not accept any tickets.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     ver.name + "-NoTicket-NoAccept",
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+			},
+			tls13Variant:         ver.tls13Variant,
+			resumeSession:        true,
+			expectResumeRejected: true,
+			// Set SSL_OP_NO_TICKET on the second connection, after the first
+			// has established tickets.
+			flags: []string{"-on-resume-no-ticket"},
+		})
+	}
 }
 
 func addChangeCipherSpecTests() {
@@ -12735,6 +12669,39 @@
 			expectedError: ":WRONG_CURVE:",
 		})
 
+		// Test that the supported_versions extension is enforced in the
+		// second ServerHello. Note we only enforce this starting draft 28.
+		if isDraft28(version.versionWire) {
+			testCases = append(testCases, testCase{
+				name: "SecondServerHelloNoVersion-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					// P-384 requires HelloRetryRequest in BoringSSL.
+					CurvePreferences: []CurveID{CurveP384},
+					Bugs: ProtocolBugs{
+						OmitServerSupportedVersionExtension: true,
+					},
+				},
+				tls13Variant:  variant,
+				shouldFail:    true,
+				expectedError: ":SECOND_SERVERHELLO_VERSION_MISMATCH:",
+			})
+			testCases = append(testCases, testCase{
+				name: "SecondServerHelloWrongVersion-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					// P-384 requires HelloRetryRequest in BoringSSL.
+					CurvePreferences: []CurveID{CurveP384},
+					Bugs: ProtocolBugs{
+						SendServerSupportedVersionExtension: 0x1234,
+					},
+				},
+				tls13Variant:  variant,
+				shouldFail:    true,
+				expectedError: ":SECOND_SERVERHELLO_VERSION_MISMATCH:",
+			})
+		}
+
 		testCases = append(testCases, testCase{
 			name: "RequestContextInHandshake-" + name,
 			config: Config{
@@ -14179,6 +14146,252 @@
 	}
 }
 
+func addCertCompressionTests() {
+	// shrinkingPrefix is the first two bytes of a Certificate message.
+	shrinkingPrefix := []byte{0, 0}
+	// expandingPrefix is just some arbitrary byte string. This has to match the
+	// value in the shim.
+	expandingPrefix := []byte{1, 2, 3, 4}
+
+	shrinking := CertCompressionAlg{
+		Compress: func(uncompressed []byte) []byte {
+			if !bytes.HasPrefix(uncompressed, shrinkingPrefix) {
+				panic(fmt.Sprintf("cannot compress certificate message %x", uncompressed))
+			}
+			return uncompressed[len(shrinkingPrefix):]
+		},
+		Decompress: func(out []byte, compressed []byte) bool {
+			if len(out) != len(shrinkingPrefix)+len(compressed) {
+				return false
+			}
+
+			copy(out, shrinkingPrefix)
+			copy(out[len(shrinkingPrefix):], compressed)
+			return true
+		},
+	}
+
+	expanding := CertCompressionAlg{
+		Compress: func(uncompressed []byte) []byte {
+			ret := make([]byte, 0, len(expandingPrefix)+len(uncompressed))
+			ret = append(ret, expandingPrefix...)
+			return append(ret, uncompressed...)
+		},
+		Decompress: func(out []byte, compressed []byte) bool {
+			if !bytes.HasPrefix(compressed, expandingPrefix) {
+				return false
+			}
+			copy(out, compressed[len(expandingPrefix):])
+			return true
+		},
+	}
+
+	const (
+		shrinkingAlgId = 0xff01
+		expandingAlgId = 0xff02
+	)
+
+	for _, ver := range tlsVersions {
+		if ver.version < VersionTLS12 {
+			continue
+		}
+
+		// Duplicate compression algorithms is an error, even if nothing is
+		// configured.
+		testCases = append(testCases, testCase{
+			testType:     serverTest,
+			name:         "DuplicateCertCompressionExt-" + ver.name,
+			tls13Variant: ver.tls13Variant,
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				Bugs: ProtocolBugs{
+					DuplicateCompressedCertAlgs: true,
+				},
+			},
+			shouldFail:    true,
+			expectedError: ":ERROR_PARSING_EXTENSION:",
+		})
+
+		// With compression algorithms configured, an duplicate values should still
+		// be an error.
+		testCases = append(testCases, testCase{
+			testType:     serverTest,
+			name:         "DuplicateCertCompressionExt2-" + ver.name,
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-install-cert-compression-algs"},
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				Bugs: ProtocolBugs{
+					DuplicateCompressedCertAlgs: true,
+				},
+			},
+			shouldFail:    true,
+			expectedError: ":ERROR_PARSING_EXTENSION:",
+		})
+
+		if ver.version < VersionTLS13 {
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "CertCompressionIgnoredBefore13-" + ver.name,
+				flags:    []string{"-install-cert-compression-algs"},
+				config: Config{
+					MinVersion:          ver.version,
+					MaxVersion:          ver.version,
+					CertCompressionAlgs: map[uint16]CertCompressionAlg{expandingAlgId: expanding},
+				},
+			})
+
+			continue
+		}
+
+		testCases = append(testCases, testCase{
+			testType:     serverTest,
+			name:         "CertCompressionExpands-" + ver.name,
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-install-cert-compression-algs"},
+			config: Config{
+				MinVersion:          ver.version,
+				MaxVersion:          ver.version,
+				CertCompressionAlgs: map[uint16]CertCompressionAlg{expandingAlgId: expanding},
+				Bugs: ProtocolBugs{
+					ExpectedCompressedCert: expandingAlgId,
+				},
+			},
+		})
+
+		testCases = append(testCases, testCase{
+			testType:     serverTest,
+			name:         "CertCompressionShrinks-" + ver.name,
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-install-cert-compression-algs"},
+			config: Config{
+				MinVersion:          ver.version,
+				MaxVersion:          ver.version,
+				CertCompressionAlgs: map[uint16]CertCompressionAlg{shrinkingAlgId: shrinking},
+				Bugs: ProtocolBugs{
+					ExpectedCompressedCert: shrinkingAlgId,
+				},
+			},
+		})
+
+		// With both algorithms configured, the server should pick its most
+		// preferable. (Which is expandingAlgId.)
+		testCases = append(testCases, testCase{
+			testType:     serverTest,
+			name:         "CertCompressionPriority-" + ver.name,
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-install-cert-compression-algs"},
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				CertCompressionAlgs: map[uint16]CertCompressionAlg{
+					shrinkingAlgId: shrinking,
+					expandingAlgId: expanding,
+				},
+				Bugs: ProtocolBugs{
+					ExpectedCompressedCert: expandingAlgId,
+				},
+			},
+		})
+
+		testCases = append(testCases, testCase{
+			testType:     clientTest,
+			name:         "CertCompressionExpandsClient-" + ver.name,
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-install-cert-compression-algs"},
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				CertCompressionAlgs: map[uint16]CertCompressionAlg{
+					expandingAlgId: expanding,
+				},
+				Bugs: ProtocolBugs{
+					ExpectedCompressedCert: expandingAlgId,
+				},
+			},
+		})
+
+		testCases = append(testCases, testCase{
+			testType:     clientTest,
+			name:         "CertCompressionShrinksClient-" + ver.name,
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-install-cert-compression-algs"},
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				CertCompressionAlgs: map[uint16]CertCompressionAlg{
+					shrinkingAlgId: shrinking,
+				},
+				Bugs: ProtocolBugs{
+					ExpectedCompressedCert: shrinkingAlgId,
+				},
+			},
+		})
+
+		testCases = append(testCases, testCase{
+			testType:     clientTest,
+			name:         "CertCompressionBadAlgIdClient-" + ver.name,
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-install-cert-compression-algs"},
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				CertCompressionAlgs: map[uint16]CertCompressionAlg{
+					shrinkingAlgId: shrinking,
+				},
+				Bugs: ProtocolBugs{
+					ExpectedCompressedCert:   shrinkingAlgId,
+					SendCertCompressionAlgId: 1234,
+				},
+			},
+			shouldFail:    true,
+			expectedError: ":UNKNOWN_CERT_COMPRESSION_ALG:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType:     clientTest,
+			name:         "CertCompressionTooSmallClient-" + ver.name,
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-install-cert-compression-algs"},
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				CertCompressionAlgs: map[uint16]CertCompressionAlg{
+					shrinkingAlgId: shrinking,
+				},
+				Bugs: ProtocolBugs{
+					ExpectedCompressedCert:     shrinkingAlgId,
+					SendCertUncompressedLength: 12,
+				},
+			},
+			shouldFail:    true,
+			expectedError: ":CERT_DECOMPRESSION_FAILED:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType:     clientTest,
+			name:         "CertCompressionTooLargeClient-" + ver.name,
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-install-cert-compression-algs"},
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				CertCompressionAlgs: map[uint16]CertCompressionAlg{
+					shrinkingAlgId: shrinking,
+				},
+				Bugs: ProtocolBugs{
+					ExpectedCompressedCert:     shrinkingAlgId,
+					SendCertUncompressedLength: 1 << 20,
+				},
+			},
+			shouldFail:    true,
+			expectedError: ":UNCOMPRESSED_CERT_TOO_LARGE:",
+		})
+	}
+}
+
 func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -14307,6 +14520,7 @@
 	addECDSAKeyUsageTests()
 	addExtraHandshakeTests()
 	addOmitExtensionsTests()
+	addCertCompressionTests()
 
 	testCases = append(testCases, convertToSplitHandshakeTests(testCases)...)
 
diff --git a/src/ssl/test/settings_writer.cc b/src/ssl/test/settings_writer.cc
new file mode 100644
index 0000000..66025f6
--- /dev/null
+++ b/src/ssl/test/settings_writer.cc
@@ -0,0 +1,121 @@
+/* Copyright (c) 2018, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include "settings_writer.h"
+
+#include <stdio.h>
+
+#include <openssl/ssl.h>
+
+#include "fuzzer_tags.h"
+#include "test_config.h"
+
+
+SettingsWriter::SettingsWriter() {}
+
+bool SettingsWriter::Init(int i, const TestConfig *config,
+                          SSL_SESSION *session) {
+  if (config->write_settings.empty()) {
+    return true;
+  }
+  // Treat write_settings as a path prefix for each connection in the run.
+  char buf[DECIMAL_SIZE(int)];
+  snprintf(buf, sizeof(buf), "%d", i);
+  path_ = config->write_settings + buf;
+
+  if (!CBB_init(cbb_.get(), 64)) {
+    return false;
+  }
+
+  if (session != nullptr) {
+    uint8_t *data;
+    size_t len;
+    if (!SSL_SESSION_to_bytes(session, &data, &len)) {
+      return false;
+    }
+    bssl::UniquePtr<uint8_t> free_data(data);
+    CBB child;
+    if (!CBB_add_u16(cbb_.get(), kSessionTag) ||
+        !CBB_add_u24_length_prefixed(cbb_.get(), &child) ||
+        !CBB_add_bytes(&child, data, len) || !CBB_flush(cbb_.get())) {
+      return false;
+    }
+  }
+
+  if (config->is_server &&
+      (config->require_any_client_certificate || config->verify_peer) &&
+      !CBB_add_u16(cbb_.get(), kRequestClientCert)) {
+    return false;
+  }
+
+  if (config->tls13_variant != 0 &&
+      (!CBB_add_u16(cbb_.get(), kTLS13Variant) ||
+       !CBB_add_u8(cbb_.get(), static_cast<uint8_t>(config->tls13_variant)))) {
+    return false;
+  }
+
+  return true;
+}
+
+bool SettingsWriter::Commit() {
+  if (path_.empty()) {
+    return true;
+  }
+
+  uint8_t *settings;
+  size_t settings_len;
+  if (!CBB_add_u16(cbb_.get(), kDataTag) ||
+      !CBB_finish(cbb_.get(), &settings, &settings_len)) {
+    return false;
+  }
+  bssl::UniquePtr<uint8_t> free_settings(settings);
+
+  using ScopedFILE = std::unique_ptr<FILE, decltype(&fclose)>;
+  ScopedFILE file(fopen(path_.c_str(), "w"), fclose);
+  if (!file) {
+    return false;
+  }
+
+  return fwrite(settings, settings_len, 1, file.get()) == 1;
+}
+
+bool SettingsWriter::WriteHandoff(bssl::Span<const uint8_t> handoff) {
+  if (path_.empty()) {
+    return true;
+  }
+
+  CBB child;
+  if (!CBB_add_u16(cbb_.get(), kHandoffTag) ||
+      !CBB_add_u24_length_prefixed(cbb_.get(), &child) ||
+      !CBB_add_bytes(&child, handoff.data(), handoff.size()) ||
+      !CBB_flush(cbb_.get())) {
+    return false;
+  }
+  return true;
+}
+
+bool SettingsWriter::WriteHandback(bssl::Span<const uint8_t> handback) {
+  if (path_.empty()) {
+    return true;
+  }
+
+  CBB child;
+  if (!CBB_add_u16(cbb_.get(), kHandbackTag) ||
+      !CBB_add_u24_length_prefixed(cbb_.get(), &child) ||
+      !CBB_add_bytes(&child, handback.data(), handback.size()) ||
+      !CBB_flush(cbb_.get())) {
+    return false;
+  }
+  return true;
+}
diff --git a/src/ssl/test/settings_writer.h b/src/ssl/test/settings_writer.h
new file mode 100644
index 0000000..322850d
--- /dev/null
+++ b/src/ssl/test/settings_writer.h
@@ -0,0 +1,46 @@
+/* Copyright (c) 2018, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#ifndef HEADER_SETTINGS_WRITER
+#define HEADER_SETTINGS_WRITER
+
+#include <string>
+
+#include <openssl/bytestring.h>
+#include <openssl/ssl.h>
+
+#include "../internal.h"
+#include "test_config.h"
+
+struct SettingsWriter {
+ public:
+  SettingsWriter();
+
+  // Init initializes the writer for a new connection, given by |i|.  Each
+  // connection gets a unique output file.
+  bool Init(int i, const TestConfig *config, SSL_SESSION *session);
+
+  // Commit writes the buffered data to disk.
+  bool Commit();
+
+  bool WriteHandoff(bssl::Span<const uint8_t> handoff);
+
+  bool WriteHandback(bssl::Span<const uint8_t> handback);
+
+ private:
+  std::string path_;
+  bssl::ScopedCBB cbb_;
+};
+
+#endif  // HEADER_SETTINGS_WRITER
diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc
index f74267d..d92cf72 100644
--- a/src/ssl/test/test_config.cc
+++ b/src/ssl/test/test_config.cc
@@ -14,6 +14,7 @@
 
 #include "test_config.h"
 
+#include <assert.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -21,6 +22,12 @@
 #include <memory>
 
 #include <openssl/base64.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+
+#include "../../crypto/internal.h"
+#include "../internal.h"
+#include "test_state.h"
 
 namespace {
 
@@ -59,11 +66,12 @@
   { "-no-tls12", &TestConfig::no_tls12 },
   { "-no-tls11", &TestConfig::no_tls11 },
   { "-no-tls1", &TestConfig::no_tls1 },
-  { "-no-ssl3", &TestConfig::no_ssl3 },
+  { "-no-ticket", &TestConfig::no_ticket },
   { "-enable-channel-id", &TestConfig::enable_channel_id },
   { "-shim-writes-first", &TestConfig::shim_writes_first },
   { "-expect-session-miss", &TestConfig::expect_session_miss },
   { "-decline-alpn", &TestConfig::decline_alpn },
+  { "-select-empty-alpn", &TestConfig::select_empty_alpn },
   { "-expect-extended-master-secret",
     &TestConfig::expect_extended_master_secret },
   { "-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling },
@@ -74,7 +82,6 @@
   { "-fail-early-callback", &TestConfig::fail_early_callback },
   { "-install-ddos-callback", &TestConfig::install_ddos_callback },
   { "-fail-ddos-callback", &TestConfig::fail_ddos_callback },
-  { "-fail-second-ddos-callback", &TestConfig::fail_second_ddos_callback },
   { "-fail-cert-callback", &TestConfig::fail_cert_callback },
   { "-handshake-never-done", &TestConfig::handshake_never_done },
   { "-use-export-context", &TestConfig::use_export_context },
@@ -86,12 +93,6 @@
   { "-use-ticket-callback", &TestConfig::use_ticket_callback },
   { "-renew-ticket", &TestConfig::renew_ticket },
   { "-enable-early-data", &TestConfig::enable_early_data },
-  { "-enable-client-custom-extension",
-    &TestConfig::enable_client_custom_extension },
-  { "-enable-server-custom-extension",
-    &TestConfig::enable_server_custom_extension },
-  { "-custom-extension-skip", &TestConfig::custom_extension_skip },
-  { "-custom-extension-fail-add", &TestConfig::custom_extension_fail_add },
   { "-check-close-notify", &TestConfig::check_close_notify },
   { "-shim-shuts-down", &TestConfig::shim_shuts_down },
   { "-verify-fail", &TestConfig::verify_fail },
@@ -101,6 +102,8 @@
   { "-renegotiate-once", &TestConfig::renegotiate_once },
   { "-renegotiate-freely", &TestConfig::renegotiate_freely },
   { "-renegotiate-ignore", &TestConfig::renegotiate_ignore },
+  { "-forbid-renegotiation-after-handshake",
+    &TestConfig::forbid_renegotiation_after_handshake },
   { "-p384-only", &TestConfig::p384_only },
   { "-enable-all-curves", &TestConfig::enable_all_curves },
   { "-use-old-client-cert-callback",
@@ -134,6 +137,14 @@
   { "-handoff", &TestConfig::handoff },
   { "-expect-dummy-pq-padding", &TestConfig::expect_dummy_pq_padding },
   { "-no-rsa-pss-rsae-certs", &TestConfig::no_rsa_pss_rsae_certs },
+  { "-use-ocsp-callback", &TestConfig::use_ocsp_callback },
+  { "-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback },
+  { "-decline-ocsp-callback", &TestConfig::decline_ocsp_callback },
+  { "-fail-ocsp-callback", &TestConfig::fail_ocsp_callback },
+  { "-install-cert-compression-algs",
+    &TestConfig::install_cert_compression_algs },
+  { "-is-handshaker-supported", &TestConfig::is_handshaker_supported },
+  { "-handshaker-resume", &TestConfig::handshaker_resume },
 };
 
 const Flag<std::string> kStringFlags[] = {
@@ -161,6 +172,7 @@
   { "-use-client-ca-list", &TestConfig::use_client_ca_list },
   { "-expect-client-ca-list", &TestConfig::expected_client_ca_list },
   { "-expect-msg-callback", &TestConfig::expect_msg_callback },
+  { "-handshaker-path", &TestConfig::handshaker_path },
 };
 
 const Flag<std::string> kBase64Flags[] = {
@@ -209,6 +221,8 @@
 const Flag<std::vector<int>> kIntVectorFlags[] = {
   { "-signing-prefs", &TestConfig::signing_prefs },
   { "-verify-prefs", &TestConfig::verify_prefs },
+  { "-expect-peer-verify-pref",
+    &TestConfig::expected_peer_verify_prefs },
 };
 
 bool ParseFlag(char *flag, int argc, char **argv, int *i,
@@ -303,6 +317,8 @@
                  TestConfig *out_initial,
                  TestConfig *out_resume,
                  TestConfig *out_retry) {
+  out_initial->argc = out_resume->argc = out_retry->argc = argc;
+  out_initial->argv = out_resume->argv = out_retry->argv = argv;
   for (int i = 0; i < argc; i++) {
     bool skip = false;
     char *flag = argv[i];
@@ -332,3 +348,1297 @@
 
   return true;
 }
+
+static CRYPTO_once_t once = CRYPTO_ONCE_INIT;
+static int g_config_index = 0;
+static CRYPTO_BUFFER_POOL *g_pool = nullptr;
+
+static void init_once() {
+  g_config_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+  if (g_config_index < 0) {
+    abort();
+  }
+  g_pool = CRYPTO_BUFFER_POOL_new();
+  if (!g_pool) {
+    abort();
+  }
+}
+
+bool SetTestConfig(SSL *ssl, const TestConfig *config) {
+  CRYPTO_once(&once, init_once);
+  return SSL_set_ex_data(ssl, g_config_index, (void *)config) == 1;
+}
+
+const TestConfig *GetTestConfig(const SSL *ssl) {
+  CRYPTO_once(&once, init_once);
+  return (const TestConfig *)SSL_get_ex_data(ssl, g_config_index);
+}
+
+static int LegacyOCSPCallback(SSL *ssl, void *arg) {
+  const TestConfig *config = GetTestConfig(ssl);
+  if (!SSL_is_server(ssl)) {
+    return !config->fail_ocsp_callback;
+  }
+
+  if (!config->ocsp_response.empty() && config->set_ocsp_in_callback &&
+      !SSL_set_ocsp_response(ssl, (const uint8_t *)config->ocsp_response.data(),
+                             config->ocsp_response.size())) {
+    return SSL_TLSEXT_ERR_ALERT_FATAL;
+  }
+  if (config->fail_ocsp_callback) {
+    return SSL_TLSEXT_ERR_ALERT_FATAL;
+  }
+  if (config->decline_ocsp_callback) {
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+  return SSL_TLSEXT_ERR_OK;
+}
+
+static int ServerNameCallback(SSL *ssl, int *out_alert, void *arg) {
+  // SNI must be accessible from the SNI callback.
+  const TestConfig *config = GetTestConfig(ssl);
+  const char *server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+  if (server_name == nullptr ||
+      std::string(server_name) != config->expected_server_name) {
+    fprintf(stderr, "servername mismatch (got %s; want %s)\n", server_name,
+            config->expected_server_name.c_str());
+    return SSL_TLSEXT_ERR_ALERT_FATAL;
+  }
+
+  return SSL_TLSEXT_ERR_OK;
+}
+
+static int NextProtoSelectCallback(SSL *ssl, uint8_t **out, uint8_t *outlen,
+                                   const uint8_t *in, unsigned inlen,
+                                   void *arg) {
+  const TestConfig *config = GetTestConfig(ssl);
+  if (config->select_next_proto.empty()) {
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+
+  *out = (uint8_t *)config->select_next_proto.data();
+  *outlen = config->select_next_proto.size();
+  return SSL_TLSEXT_ERR_OK;
+}
+
+static int NextProtosAdvertisedCallback(SSL *ssl, const uint8_t **out,
+                                        unsigned int *out_len, void *arg) {
+  const TestConfig *config = GetTestConfig(ssl);
+  if (config->advertise_npn.empty()) {
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+
+  *out = (const uint8_t *)config->advertise_npn.data();
+  *out_len = config->advertise_npn.size();
+  return SSL_TLSEXT_ERR_OK;
+}
+
+static void MessageCallback(int is_write, int version, int content_type,
+                            const void *buf, size_t len, SSL *ssl, void *arg) {
+  const uint8_t *buf_u8 = reinterpret_cast<const uint8_t *>(buf);
+  const TestConfig *config = GetTestConfig(ssl);
+  TestState *state = GetTestState(ssl);
+  if (!state->msg_callback_ok) {
+    return;
+  }
+
+  if (content_type == SSL3_RT_HEADER) {
+    if (len !=
+        (config->is_dtls ? DTLS1_RT_HEADER_LENGTH : SSL3_RT_HEADER_LENGTH)) {
+      fprintf(stderr, "Incorrect length for record header: %zu\n", len);
+      state->msg_callback_ok = false;
+    }
+    return;
+  }
+
+  state->msg_callback_text += is_write ? "write " : "read ";
+  switch (content_type) {
+    case 0:
+      if (version != SSL2_VERSION) {
+        fprintf(stderr, "Incorrect version for V2ClientHello: %x\n", version);
+        state->msg_callback_ok = false;
+        return;
+      }
+      state->msg_callback_text += "v2clienthello\n";
+      return;
+
+    case SSL3_RT_HANDSHAKE: {
+      CBS cbs;
+      CBS_init(&cbs, buf_u8, len);
+      uint8_t type;
+      uint32_t msg_len;
+      if (!CBS_get_u8(&cbs, &type) ||
+          // TODO(davidben): Reporting on entire messages would be more
+          // consistent than fragments.
+          (config->is_dtls &&
+           !CBS_skip(&cbs, 3 /* total */ + 2 /* seq */ + 3 /* frag_off */)) ||
+          !CBS_get_u24(&cbs, &msg_len) || !CBS_skip(&cbs, msg_len) ||
+          CBS_len(&cbs) != 0) {
+        fprintf(stderr, "Could not parse handshake message.\n");
+        state->msg_callback_ok = false;
+        return;
+      }
+      char text[16];
+      snprintf(text, sizeof(text), "hs %d\n", type);
+      state->msg_callback_text += text;
+      return;
+    }
+
+    case SSL3_RT_CHANGE_CIPHER_SPEC:
+      if (len != 1 || buf_u8[0] != 1) {
+        fprintf(stderr, "Invalid ChangeCipherSpec.\n");
+        state->msg_callback_ok = false;
+        return;
+      }
+      state->msg_callback_text += "ccs\n";
+      return;
+
+    case SSL3_RT_ALERT:
+      if (len != 2) {
+        fprintf(stderr, "Invalid alert.\n");
+        state->msg_callback_ok = false;
+        return;
+      }
+      char text[16];
+      snprintf(text, sizeof(text), "alert %d %d\n", buf_u8[0], buf_u8[1]);
+      state->msg_callback_text += text;
+      return;
+
+    default:
+      fprintf(stderr, "Invalid content_type: %d\n", content_type);
+      state->msg_callback_ok = false;
+  }
+}
+
+static int TicketKeyCallback(SSL *ssl, uint8_t *key_name, uint8_t *iv,
+                             EVP_CIPHER_CTX *ctx, HMAC_CTX *hmac_ctx,
+                             int encrypt) {
+  if (!encrypt) {
+    if (GetTestState(ssl)->ticket_decrypt_done) {
+      fprintf(stderr, "TicketKeyCallback called after completion.\n");
+      return -1;
+    }
+
+    GetTestState(ssl)->ticket_decrypt_done = true;
+  }
+
+  // This is just test code, so use the all-zeros key.
+  static const uint8_t kZeros[16] = {0};
+
+  if (encrypt) {
+    OPENSSL_memcpy(key_name, kZeros, sizeof(kZeros));
+    RAND_bytes(iv, 16);
+  } else if (OPENSSL_memcmp(key_name, kZeros, 16) != 0) {
+    return 0;
+  }
+
+  if (!HMAC_Init_ex(hmac_ctx, kZeros, sizeof(kZeros), EVP_sha256(), NULL) ||
+      !EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, kZeros, iv, encrypt)) {
+    return -1;
+  }
+
+  if (!encrypt) {
+    return GetTestConfig(ssl)->renew_ticket ? 2 : 1;
+  }
+  return 1;
+}
+
+static int NewSessionCallback(SSL *ssl, SSL_SESSION *session) {
+  // This callback is called as the handshake completes. |SSL_get_session|
+  // must continue to work and, historically, |SSL_in_init| returned false at
+  // this point.
+  if (SSL_in_init(ssl) || SSL_get_session(ssl) == nullptr) {
+    fprintf(stderr, "Invalid state for NewSessionCallback.\n");
+    abort();
+  }
+
+  GetTestState(ssl)->got_new_session = true;
+  GetTestState(ssl)->new_session.reset(session);
+  return 1;
+}
+
+static void InfoCallback(const SSL *ssl, int type, int val) {
+  if (type == SSL_CB_HANDSHAKE_DONE) {
+    if (GetTestConfig(ssl)->handshake_never_done) {
+      fprintf(stderr, "Handshake unexpectedly completed.\n");
+      // Abort before any expected error code is printed, to ensure the overall
+      // test fails.
+      abort();
+    }
+    // This callback is called when the handshake completes. |SSL_get_session|
+    // must continue to work and |SSL_in_init| must return false.
+    if (SSL_in_init(ssl) || SSL_get_session(ssl) == nullptr) {
+      fprintf(stderr, "Invalid state for SSL_CB_HANDSHAKE_DONE.\n");
+      abort();
+    }
+    GetTestState(ssl)->handshake_done = true;
+
+    // Callbacks may be called again on a new handshake.
+    GetTestState(ssl)->ticket_decrypt_done = false;
+    GetTestState(ssl)->alpn_select_done = false;
+  }
+}
+
+static void ChannelIdCallback(SSL *ssl, EVP_PKEY **out_pkey) {
+  *out_pkey = GetTestState(ssl)->channel_id.release();
+}
+
+static SSL_SESSION *GetSessionCallback(SSL *ssl, const uint8_t *data, int len,
+                                       int *copy) {
+  TestState *async_state = GetTestState(ssl);
+  if (async_state->session) {
+    *copy = 0;
+    return async_state->session.release();
+  } else if (async_state->pending_session) {
+    return SSL_magic_pending_session_ptr();
+  } else {
+    return NULL;
+  }
+}
+
+static void CurrentTimeCallback(const SSL *ssl, timeval *out_clock) {
+  *out_clock = *GetClock();
+}
+
+static int AlpnSelectCallback(SSL *ssl, const uint8_t **out, uint8_t *outlen,
+                              const uint8_t *in, unsigned inlen, void *arg) {
+  if (GetTestState(ssl)->alpn_select_done) {
+    fprintf(stderr, "AlpnSelectCallback called after completion.\n");
+    exit(1);
+  }
+
+  GetTestState(ssl)->alpn_select_done = true;
+
+  const TestConfig *config = GetTestConfig(ssl);
+  if (config->decline_alpn) {
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+
+  if (!config->expected_advertised_alpn.empty() &&
+      (config->expected_advertised_alpn.size() != inlen ||
+       OPENSSL_memcmp(config->expected_advertised_alpn.data(), in, inlen) !=
+           0)) {
+    fprintf(stderr, "bad ALPN select callback inputs\n");
+    exit(1);
+  }
+
+  assert(config->select_alpn.empty() || !config->select_empty_alpn);
+  *out = (const uint8_t *)config->select_alpn.data();
+  *outlen = config->select_alpn.size();
+  return SSL_TLSEXT_ERR_OK;
+}
+
+static bool CheckVerifyCallback(SSL *ssl) {
+  const TestConfig *config = GetTestConfig(ssl);
+  if (!config->expected_ocsp_response.empty()) {
+    const uint8_t *data;
+    size_t len;
+    SSL_get0_ocsp_response(ssl, &data, &len);
+    if (len == 0) {
+      fprintf(stderr, "OCSP response not available in verify callback\n");
+      return false;
+    }
+  }
+
+  if (GetTestState(ssl)->cert_verified) {
+    fprintf(stderr, "Certificate verified twice.\n");
+    return false;
+  }
+
+  return true;
+}
+
+static int CertVerifyCallback(X509_STORE_CTX *store_ctx, void *arg) {
+  SSL *ssl = (SSL *)X509_STORE_CTX_get_ex_data(
+      store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+  const TestConfig *config = GetTestConfig(ssl);
+  if (!CheckVerifyCallback(ssl)) {
+    return 0;
+  }
+
+  GetTestState(ssl)->cert_verified = true;
+  if (config->verify_fail) {
+    store_ctx->error = X509_V_ERR_APPLICATION_VERIFICATION;
+    return 0;
+  }
+
+  return 1;
+}
+
+bool LoadCertificate(bssl::UniquePtr<X509> *out_x509,
+                     bssl::UniquePtr<STACK_OF(X509)> *out_chain,
+                     const std::string &file) {
+  bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_file()));
+  if (!bio || !BIO_read_filename(bio.get(), file.c_str())) {
+    return false;
+  }
+
+  out_x509->reset(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
+  if (!*out_x509) {
+    return false;
+  }
+
+  out_chain->reset(sk_X509_new_null());
+  if (!*out_chain) {
+    return false;
+  }
+
+  // Keep reading the certificate chain.
+  for (;;) {
+    bssl::UniquePtr<X509> cert(
+        PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
+    if (!cert) {
+      break;
+    }
+
+    if (!bssl::PushToStack(out_chain->get(), std::move(cert))) {
+      return false;
+    }
+  }
+
+  uint32_t err = ERR_peek_last_error();
+  if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
+      ERR_GET_REASON(err) != PEM_R_NO_START_LINE) {
+    return false;
+  }
+
+  ERR_clear_error();
+  return true;
+}
+
+bssl::UniquePtr<EVP_PKEY> LoadPrivateKey(const std::string &file) {
+  bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_file()));
+  if (!bio || !BIO_read_filename(bio.get(), file.c_str())) {
+    return nullptr;
+  }
+  return bssl::UniquePtr<EVP_PKEY>(
+      PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, NULL));
+}
+
+static bool GetCertificate(SSL *ssl, bssl::UniquePtr<X509> *out_x509,
+                           bssl::UniquePtr<STACK_OF(X509)> *out_chain,
+                           bssl::UniquePtr<EVP_PKEY> *out_pkey) {
+  const TestConfig *config = GetTestConfig(ssl);
+
+  if (!config->signing_prefs.empty()) {
+    std::vector<uint16_t> u16s(config->signing_prefs.begin(),
+                               config->signing_prefs.end());
+    if (!SSL_set_signing_algorithm_prefs(ssl, u16s.data(), u16s.size())) {
+      return false;
+    }
+  }
+
+  if (!config->key_file.empty()) {
+    *out_pkey = LoadPrivateKey(config->key_file.c_str());
+    if (!*out_pkey) {
+      return false;
+    }
+  }
+  if (!config->cert_file.empty() &&
+      !LoadCertificate(out_x509, out_chain, config->cert_file.c_str())) {
+    return false;
+  }
+  if (!config->ocsp_response.empty() && !config->set_ocsp_in_callback &&
+      !SSL_set_ocsp_response(ssl, (const uint8_t *)config->ocsp_response.data(),
+                             config->ocsp_response.size())) {
+    return false;
+  }
+  return true;
+}
+
+static bool FromHexDigit(uint8_t *out, char c) {
+  if ('0' <= c && c <= '9') {
+    *out = c - '0';
+    return true;
+  }
+  if ('a' <= c && c <= 'f') {
+    *out = c - 'a' + 10;
+    return true;
+  }
+  if ('A' <= c && c <= 'F') {
+    *out = c - 'A' + 10;
+    return true;
+  }
+  return false;
+}
+
+static bool HexDecode(std::string *out, const std::string &in) {
+  if ((in.size() & 1) != 0) {
+    return false;
+  }
+
+  std::unique_ptr<uint8_t[]> buf(new uint8_t[in.size() / 2]);
+  for (size_t i = 0; i < in.size() / 2; i++) {
+    uint8_t high, low;
+    if (!FromHexDigit(&high, in[i * 2]) || !FromHexDigit(&low, in[i * 2 + 1])) {
+      return false;
+    }
+    buf[i] = (high << 4) | low;
+  }
+
+  out->assign(reinterpret_cast<const char *>(buf.get()), in.size() / 2);
+  return true;
+}
+
+static std::vector<std::string> SplitParts(const std::string &in,
+                                           const char delim) {
+  std::vector<std::string> ret;
+  size_t start = 0;
+
+  for (size_t i = 0; i < in.size(); i++) {
+    if (in[i] == delim) {
+      ret.push_back(in.substr(start, i - start));
+      start = i + 1;
+    }
+  }
+
+  ret.push_back(in.substr(start, std::string::npos));
+  return ret;
+}
+
+static std::vector<std::string> DecodeHexStrings(
+    const std::string &hex_strings) {
+  std::vector<std::string> ret;
+  const std::vector<std::string> parts = SplitParts(hex_strings, ',');
+
+  for (const auto &part : parts) {
+    std::string binary;
+    if (!HexDecode(&binary, part)) {
+      fprintf(stderr, "Bad hex string: %s\n", part.c_str());
+      return ret;
+    }
+
+    ret.push_back(binary);
+  }
+
+  return ret;
+}
+
+static bssl::UniquePtr<STACK_OF(X509_NAME)> DecodeHexX509Names(
+    const std::string &hex_names) {
+  const std::vector<std::string> der_names = DecodeHexStrings(hex_names);
+  bssl::UniquePtr<STACK_OF(X509_NAME)> ret(sk_X509_NAME_new_null());
+  if (!ret) {
+    return nullptr;
+  }
+
+  for (const auto &der_name : der_names) {
+    const uint8_t *const data =
+        reinterpret_cast<const uint8_t *>(der_name.data());
+    const uint8_t *derp = data;
+    bssl::UniquePtr<X509_NAME> name(
+        d2i_X509_NAME(nullptr, &derp, der_name.size()));
+    if (!name || derp != data + der_name.size()) {
+      fprintf(stderr, "Failed to parse X509_NAME.\n");
+      return nullptr;
+    }
+
+    if (!bssl::PushToStack(ret.get(), std::move(name))) {
+      return nullptr;
+    }
+  }
+
+  return ret;
+}
+
+static bool CheckPeerVerifyPrefs(SSL *ssl) {
+  const TestConfig *config = GetTestConfig(ssl);
+  if (!config->expected_peer_verify_prefs.empty()) {
+    const uint16_t *peer_sigalgs;
+    size_t num_peer_sigalgs =
+        SSL_get0_peer_verify_algorithms(ssl, &peer_sigalgs);
+    if (config->expected_peer_verify_prefs.size() != num_peer_sigalgs) {
+      fprintf(stderr,
+              "peer verify preferences length mismatch (got %zu, wanted %zu)\n",
+              num_peer_sigalgs, config->expected_peer_verify_prefs.size());
+      return false;
+    }
+    for (size_t i = 0; i < num_peer_sigalgs; i++) {
+      if (static_cast<int>(peer_sigalgs[i]) !=
+          config->expected_peer_verify_prefs[i]) {
+        fprintf(stderr,
+                "peer verify preference %zu mismatch (got %04x, wanted %04x\n",
+                i, peer_sigalgs[i], config->expected_peer_verify_prefs[i]);
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+static bool CheckCertificateRequest(SSL *ssl) {
+  const TestConfig *config = GetTestConfig(ssl);
+
+  if (!CheckPeerVerifyPrefs(ssl)) {
+    return false;
+  }
+
+  if (!config->expected_certificate_types.empty()) {
+    const uint8_t *certificate_types;
+    size_t certificate_types_len =
+        SSL_get0_certificate_types(ssl, &certificate_types);
+    if (certificate_types_len != config->expected_certificate_types.size() ||
+        OPENSSL_memcmp(certificate_types,
+                       config->expected_certificate_types.data(),
+                       certificate_types_len) != 0) {
+      fprintf(stderr, "certificate types mismatch\n");
+      return false;
+    }
+  }
+
+  if (!config->expected_client_ca_list.empty()) {
+    bssl::UniquePtr<STACK_OF(X509_NAME)> expected =
+        DecodeHexX509Names(config->expected_client_ca_list);
+    const size_t num_expected = sk_X509_NAME_num(expected.get());
+
+    const STACK_OF(X509_NAME) *received = SSL_get_client_CA_list(ssl);
+    const size_t num_received = sk_X509_NAME_num(received);
+
+    if (num_received != num_expected) {
+      fprintf(stderr, "expected %u names in CertificateRequest but got %u\n",
+              static_cast<unsigned>(num_expected),
+              static_cast<unsigned>(num_received));
+      return false;
+    }
+
+    for (size_t i = 0; i < num_received; i++) {
+      if (X509_NAME_cmp(sk_X509_NAME_value(received, i),
+                        sk_X509_NAME_value(expected.get(), i)) != 0) {
+        fprintf(stderr, "names in CertificateRequest differ at index #%d\n",
+                static_cast<unsigned>(i));
+        return false;
+      }
+    }
+
+    const STACK_OF(CRYPTO_BUFFER) *buffers = SSL_get0_server_requested_CAs(ssl);
+    if (sk_CRYPTO_BUFFER_num(buffers) != num_received) {
+      fprintf(stderr,
+              "Mismatch between SSL_get_server_requested_CAs and "
+              "SSL_get_client_CA_list.\n");
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static int ClientCertCallback(SSL *ssl, X509 **out_x509, EVP_PKEY **out_pkey) {
+  if (!CheckCertificateRequest(ssl)) {
+    return -1;
+  }
+
+  if (GetTestConfig(ssl)->async && !GetTestState(ssl)->cert_ready) {
+    return -1;
+  }
+
+  bssl::UniquePtr<X509> x509;
+  bssl::UniquePtr<STACK_OF(X509)> chain;
+  bssl::UniquePtr<EVP_PKEY> pkey;
+  if (!GetCertificate(ssl, &x509, &chain, &pkey)) {
+    return -1;
+  }
+
+  // Return zero for no certificate.
+  if (!x509) {
+    return 0;
+  }
+
+  // Chains and asynchronous private keys are not supported with client_cert_cb.
+  *out_x509 = x509.release();
+  *out_pkey = pkey.release();
+  return 1;
+}
+
+static ssl_private_key_result_t AsyncPrivateKeySign(
+    SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
+    uint16_t signature_algorithm, const uint8_t *in, size_t in_len) {
+  TestState *test_state = GetTestState(ssl);
+  if (!test_state->private_key_result.empty()) {
+    fprintf(stderr, "AsyncPrivateKeySign called with operation pending.\n");
+    abort();
+  }
+
+  if (EVP_PKEY_id(test_state->private_key.get()) !=
+      SSL_get_signature_algorithm_key_type(signature_algorithm)) {
+    fprintf(stderr, "Key type does not match signature algorithm.\n");
+    abort();
+  }
+
+  // Determine the hash.
+  const EVP_MD *md = SSL_get_signature_algorithm_digest(signature_algorithm);
+  bssl::ScopedEVP_MD_CTX ctx;
+  EVP_PKEY_CTX *pctx;
+  if (!EVP_DigestSignInit(ctx.get(), &pctx, md, nullptr,
+                          test_state->private_key.get())) {
+    return ssl_private_key_failure;
+  }
+
+  // Configure additional signature parameters.
+  if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) {
+    if (!EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) ||
+        !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1 /* salt len = hash len */)) {
+      return ssl_private_key_failure;
+    }
+  }
+
+  // Write the signature into |test_state|.
+  size_t len = 0;
+  if (!EVP_DigestSign(ctx.get(), nullptr, &len, in, in_len)) {
+    return ssl_private_key_failure;
+  }
+  test_state->private_key_result.resize(len);
+  if (!EVP_DigestSign(ctx.get(), test_state->private_key_result.data(), &len,
+                      in, in_len)) {
+    return ssl_private_key_failure;
+  }
+  test_state->private_key_result.resize(len);
+
+  // The signature will be released asynchronously in |AsyncPrivateKeyComplete|.
+  return ssl_private_key_retry;
+}
+
+static ssl_private_key_result_t AsyncPrivateKeyDecrypt(SSL *ssl, uint8_t *out,
+                                                       size_t *out_len,
+                                                       size_t max_out,
+                                                       const uint8_t *in,
+                                                       size_t in_len) {
+  TestState *test_state = GetTestState(ssl);
+  if (!test_state->private_key_result.empty()) {
+    fprintf(stderr, "AsyncPrivateKeyDecrypt called with operation pending.\n");
+    abort();
+  }
+
+  RSA *rsa = EVP_PKEY_get0_RSA(test_state->private_key.get());
+  if (rsa == NULL) {
+    fprintf(stderr, "AsyncPrivateKeyDecrypt called with incorrect key type.\n");
+    abort();
+  }
+  test_state->private_key_result.resize(RSA_size(rsa));
+  if (!RSA_decrypt(rsa, out_len, test_state->private_key_result.data(),
+                   RSA_size(rsa), in, in_len, RSA_NO_PADDING)) {
+    return ssl_private_key_failure;
+  }
+
+  test_state->private_key_result.resize(*out_len);
+
+  // The decryption will be released asynchronously in |AsyncPrivateComplete|.
+  return ssl_private_key_retry;
+}
+
+static ssl_private_key_result_t AsyncPrivateKeyComplete(SSL *ssl, uint8_t *out,
+                                                        size_t *out_len,
+                                                        size_t max_out) {
+  TestState *test_state = GetTestState(ssl);
+  if (test_state->private_key_result.empty()) {
+    fprintf(stderr,
+            "AsyncPrivateKeyComplete called without operation pending.\n");
+    abort();
+  }
+
+  if (test_state->private_key_retries < 2) {
+    // Only return the decryption on the second attempt, to test both incomplete
+    // |decrypt| and |decrypt_complete|.
+    return ssl_private_key_retry;
+  }
+
+  if (max_out < test_state->private_key_result.size()) {
+    fprintf(stderr, "Output buffer too small.\n");
+    return ssl_private_key_failure;
+  }
+  OPENSSL_memcpy(out, test_state->private_key_result.data(),
+                 test_state->private_key_result.size());
+  *out_len = test_state->private_key_result.size();
+
+  test_state->private_key_result.clear();
+  test_state->private_key_retries = 0;
+  return ssl_private_key_success;
+}
+
+static const SSL_PRIVATE_KEY_METHOD g_async_private_key_method = {
+    AsyncPrivateKeySign,
+    AsyncPrivateKeyDecrypt,
+    AsyncPrivateKeyComplete,
+};
+
+static bool InstallCertificate(SSL *ssl) {
+  bssl::UniquePtr<X509> x509;
+  bssl::UniquePtr<STACK_OF(X509)> chain;
+  bssl::UniquePtr<EVP_PKEY> pkey;
+  if (!GetCertificate(ssl, &x509, &chain, &pkey)) {
+    return false;
+  }
+
+  if (pkey) {
+    TestState *test_state = GetTestState(ssl);
+    const TestConfig *config = GetTestConfig(ssl);
+    if (config->async) {
+      test_state->private_key = std::move(pkey);
+      SSL_set_private_key_method(ssl, &g_async_private_key_method);
+    } else if (!SSL_use_PrivateKey(ssl, pkey.get())) {
+      return false;
+    }
+  }
+
+  if (x509 && !SSL_use_certificate(ssl, x509.get())) {
+    return false;
+  }
+
+  if (sk_X509_num(chain.get()) > 0 && !SSL_set1_chain(ssl, chain.get())) {
+    return false;
+  }
+
+  return true;
+}
+
+static enum ssl_select_cert_result_t SelectCertificateCallback(
+    const SSL_CLIENT_HELLO *client_hello) {
+  const TestConfig *config = GetTestConfig(client_hello->ssl);
+  GetTestState(client_hello->ssl)->early_callback_called = true;
+
+  if (!config->expected_server_name.empty()) {
+    const uint8_t *extension_data;
+    size_t extension_len;
+    CBS extension, server_name_list, host_name;
+    uint8_t name_type;
+
+    if (!SSL_early_callback_ctx_extension_get(
+            client_hello, TLSEXT_TYPE_server_name, &extension_data,
+            &extension_len)) {
+      fprintf(stderr, "Could not find server_name extension.\n");
+      return ssl_select_cert_error;
+    }
+
+    CBS_init(&extension, extension_data, extension_len);
+    if (!CBS_get_u16_length_prefixed(&extension, &server_name_list) ||
+        CBS_len(&extension) != 0 ||
+        !CBS_get_u8(&server_name_list, &name_type) ||
+        name_type != TLSEXT_NAMETYPE_host_name ||
+        !CBS_get_u16_length_prefixed(&server_name_list, &host_name) ||
+        CBS_len(&server_name_list) != 0) {
+      fprintf(stderr, "Could not decode server_name extension.\n");
+      return ssl_select_cert_error;
+    }
+
+    if (!CBS_mem_equal(&host_name,
+                       (const uint8_t *)config->expected_server_name.data(),
+                       config->expected_server_name.size())) {
+      fprintf(stderr, "Server name mismatch.\n");
+    }
+  }
+
+  if (config->fail_early_callback) {
+    return ssl_select_cert_error;
+  }
+
+  // Install the certificate in the early callback.
+  if (config->use_early_callback) {
+    bool early_callback_ready =
+        GetTestState(client_hello->ssl)->early_callback_ready;
+    if (config->async && !early_callback_ready) {
+      // Install the certificate asynchronously.
+      return ssl_select_cert_retry;
+    }
+    if (!InstallCertificate(client_hello->ssl)) {
+      return ssl_select_cert_error;
+    }
+  }
+  return ssl_select_cert_success;
+}
+
+bssl::UniquePtr<SSL_CTX> TestConfig::SetupCtx(SSL_CTX *old_ctx) const {
+  bssl::UniquePtr<SSL_CTX> ssl_ctx(
+      SSL_CTX_new(is_dtls ? DTLS_method() : TLS_method()));
+  if (!ssl_ctx) {
+    return nullptr;
+  }
+
+  CRYPTO_once(&once, init_once);
+  SSL_CTX_set0_buffer_pool(ssl_ctx.get(), g_pool);
+
+  // Enable TLS 1.3 for tests.
+  if (!is_dtls &&
+      !SSL_CTX_set_max_proto_version(ssl_ctx.get(), TLS1_3_VERSION)) {
+    return nullptr;
+  }
+
+  std::string cipher_list = "ALL";
+  if (!cipher.empty()) {
+    cipher_list = cipher;
+    SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_CIPHER_SERVER_PREFERENCE);
+  }
+  if (!SSL_CTX_set_strict_cipher_list(ssl_ctx.get(), cipher_list.c_str())) {
+    return nullptr;
+  }
+
+  if (async && is_server) {
+    // Disable the internal session cache. To test asynchronous session lookup,
+    // we use an external session cache.
+    SSL_CTX_set_session_cache_mode(
+        ssl_ctx.get(), SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL);
+    SSL_CTX_sess_set_get_cb(ssl_ctx.get(), GetSessionCallback);
+  } else {
+    SSL_CTX_set_session_cache_mode(ssl_ctx.get(), SSL_SESS_CACHE_BOTH);
+  }
+
+  SSL_CTX_set_select_certificate_cb(ssl_ctx.get(), SelectCertificateCallback);
+
+  if (use_old_client_cert_callback) {
+    SSL_CTX_set_client_cert_cb(ssl_ctx.get(), ClientCertCallback);
+  }
+
+  SSL_CTX_set_next_protos_advertised_cb(ssl_ctx.get(),
+                                        NextProtosAdvertisedCallback, NULL);
+  if (!select_next_proto.empty()) {
+    SSL_CTX_set_next_proto_select_cb(ssl_ctx.get(), NextProtoSelectCallback,
+                                     NULL);
+  }
+
+  if (!select_alpn.empty() || decline_alpn || select_empty_alpn) {
+    SSL_CTX_set_alpn_select_cb(ssl_ctx.get(), AlpnSelectCallback, NULL);
+  }
+
+  SSL_CTX_set_channel_id_cb(ssl_ctx.get(), ChannelIdCallback);
+
+  SSL_CTX_set_current_time_cb(ssl_ctx.get(), CurrentTimeCallback);
+
+  SSL_CTX_set_info_callback(ssl_ctx.get(), InfoCallback);
+  SSL_CTX_sess_set_new_cb(ssl_ctx.get(), NewSessionCallback);
+
+  if (use_ticket_callback) {
+    SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx.get(), TicketKeyCallback);
+  }
+
+  if (!use_custom_verify_callback) {
+    SSL_CTX_set_cert_verify_callback(ssl_ctx.get(), CertVerifyCallback, NULL);
+  }
+
+  if (!signed_cert_timestamps.empty() &&
+      !SSL_CTX_set_signed_cert_timestamp_list(
+          ssl_ctx.get(), (const uint8_t *)signed_cert_timestamps.data(),
+          signed_cert_timestamps.size())) {
+    return nullptr;
+  }
+
+  if (!use_client_ca_list.empty()) {
+    if (use_client_ca_list == "<NULL>") {
+      SSL_CTX_set_client_CA_list(ssl_ctx.get(), nullptr);
+    } else if (use_client_ca_list == "<EMPTY>") {
+      bssl::UniquePtr<STACK_OF(X509_NAME)> names;
+      SSL_CTX_set_client_CA_list(ssl_ctx.get(), names.release());
+    } else {
+      bssl::UniquePtr<STACK_OF(X509_NAME)> names =
+          DecodeHexX509Names(use_client_ca_list);
+      SSL_CTX_set_client_CA_list(ssl_ctx.get(), names.release());
+    }
+  }
+
+  if (enable_grease) {
+    SSL_CTX_set_grease_enabled(ssl_ctx.get(), 1);
+  }
+
+  if (!expected_server_name.empty()) {
+    SSL_CTX_set_tlsext_servername_callback(ssl_ctx.get(), ServerNameCallback);
+  }
+
+  if (enable_early_data) {
+    SSL_CTX_set_early_data_enabled(ssl_ctx.get(), 1);
+  }
+
+  SSL_CTX_set_tls13_variant(ssl_ctx.get(),
+                            static_cast<enum tls13_variant_t>(tls13_variant));
+
+  if (allow_unknown_alpn_protos) {
+    SSL_CTX_set_allow_unknown_alpn_protos(ssl_ctx.get(), 1);
+  }
+
+  if (enable_ed25519) {
+    SSL_CTX_set_ed25519_enabled(ssl_ctx.get(), 1);
+  }
+  if (no_rsa_pss_rsae_certs) {
+    SSL_CTX_set_rsa_pss_rsae_certs_enabled(ssl_ctx.get(), 0);
+  }
+
+  if (!verify_prefs.empty()) {
+    std::vector<uint16_t> u16s(verify_prefs.begin(), verify_prefs.end());
+    if (!SSL_CTX_set_verify_algorithm_prefs(ssl_ctx.get(), u16s.data(),
+                                            u16s.size())) {
+      return nullptr;
+    }
+  }
+
+  SSL_CTX_set_msg_callback(ssl_ctx.get(), MessageCallback);
+
+  if (allow_false_start_without_alpn) {
+    SSL_CTX_set_false_start_allowed_without_alpn(ssl_ctx.get(), 1);
+  }
+
+  if (use_ocsp_callback) {
+    SSL_CTX_set_tlsext_status_cb(ssl_ctx.get(), LegacyOCSPCallback);
+  }
+
+  if (old_ctx) {
+    uint8_t keys[48];
+    if (!SSL_CTX_get_tlsext_ticket_keys(old_ctx, &keys, sizeof(keys)) ||
+        !SSL_CTX_set_tlsext_ticket_keys(ssl_ctx.get(), keys, sizeof(keys))) {
+      return nullptr;
+    }
+    CopySessions(ssl_ctx.get(), old_ctx);
+  } else if (!ticket_key.empty() &&
+             !SSL_CTX_set_tlsext_ticket_keys(ssl_ctx.get(), ticket_key.data(),
+                                             ticket_key.size())) {
+    return nullptr;
+  }
+
+
+  if (install_cert_compression_algs &&
+      (!SSL_CTX_add_cert_compression_alg(
+           ssl_ctx.get(), 0xff02,
+           [](SSL *ssl, CBB *out, const uint8_t *in, size_t in_len) -> int {
+             if (!CBB_add_u8(out, 1) || !CBB_add_u8(out, 2) ||
+                 !CBB_add_u8(out, 3) || !CBB_add_u8(out, 4) ||
+                 !CBB_add_bytes(out, in, in_len)) {
+               return 0;
+             }
+             return 1;
+           },
+           [](SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len,
+              const uint8_t *in, size_t in_len) -> int {
+             if (in_len < 4 || in[0] != 1 || in[1] != 2 || in[2] != 3 ||
+                 in[3] != 4 || uncompressed_len != in_len - 4) {
+               return 0;
+             }
+             const bssl::Span<const uint8_t> uncompressed(in + 4, in_len - 4);
+             *out = CRYPTO_BUFFER_new(uncompressed.data(), uncompressed.size(),
+                                      nullptr);
+             return 1;
+           }) ||
+       !SSL_CTX_add_cert_compression_alg(
+           ssl_ctx.get(), 0xff01,
+           [](SSL *ssl, CBB *out, const uint8_t *in, size_t in_len) -> int {
+             if (in_len < 2 || in[0] != 0 || in[1] != 0) {
+               return 0;
+             }
+             return CBB_add_bytes(out, in + 2, in_len - 2);
+           },
+           [](SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len,
+              const uint8_t *in, size_t in_len) -> int {
+             if (uncompressed_len != 2 + in_len) {
+               return 0;
+             }
+             std::unique_ptr<uint8_t[]> buf(new uint8_t[2 + in_len]);
+             buf[0] = 0;
+             buf[1] = 0;
+             OPENSSL_memcpy(&buf[2], in, in_len);
+             *out = CRYPTO_BUFFER_new(buf.get(), 2 + in_len, nullptr);
+             return 1;
+           }))) {
+    fprintf(stderr, "SSL_CTX_add_cert_compression_alg failed.\n");
+    abort();
+  }
+
+  return ssl_ctx;
+}
+
+static int DDoSCallback(const SSL_CLIENT_HELLO *client_hello) {
+  const TestConfig *config = GetTestConfig(client_hello->ssl);
+  return config->fail_ddos_callback ? 0 : 1;
+}
+
+static unsigned PskClientCallback(SSL *ssl, const char *hint,
+                                  char *out_identity, unsigned max_identity_len,
+                                  uint8_t *out_psk, unsigned max_psk_len) {
+  const TestConfig *config = GetTestConfig(ssl);
+
+  if (config->psk_identity.empty()) {
+    if (hint != nullptr) {
+      fprintf(stderr, "Server PSK hint was non-null.\n");
+      return 0;
+    }
+  } else if (hint == nullptr ||
+             strcmp(hint, config->psk_identity.c_str()) != 0) {
+    fprintf(stderr, "Server PSK hint did not match.\n");
+    return 0;
+  }
+
+  // Account for the trailing '\0' for the identity.
+  if (config->psk_identity.size() >= max_identity_len ||
+      config->psk.size() > max_psk_len) {
+    fprintf(stderr, "PSK buffers too small\n");
+    return 0;
+  }
+
+  BUF_strlcpy(out_identity, config->psk_identity.c_str(), max_identity_len);
+  OPENSSL_memcpy(out_psk, config->psk.data(), config->psk.size());
+  return config->psk.size();
+}
+
+static unsigned PskServerCallback(SSL *ssl, const char *identity,
+                                  uint8_t *out_psk, unsigned max_psk_len) {
+  const TestConfig *config = GetTestConfig(ssl);
+
+  if (strcmp(identity, config->psk_identity.c_str()) != 0) {
+    fprintf(stderr, "Client PSK identity did not match.\n");
+    return 0;
+  }
+
+  if (config->psk.size() > max_psk_len) {
+    fprintf(stderr, "PSK buffers too small\n");
+    return 0;
+  }
+
+  OPENSSL_memcpy(out_psk, config->psk.data(), config->psk.size());
+  return config->psk.size();
+}
+
+static ssl_verify_result_t CustomVerifyCallback(SSL *ssl, uint8_t *out_alert) {
+  const TestConfig *config = GetTestConfig(ssl);
+  if (!CheckVerifyCallback(ssl)) {
+    return ssl_verify_invalid;
+  }
+
+  if (config->async && !GetTestState(ssl)->custom_verify_ready) {
+    return ssl_verify_retry;
+  }
+
+  GetTestState(ssl)->cert_verified = true;
+  if (config->verify_fail) {
+    return ssl_verify_invalid;
+  }
+
+  return ssl_verify_ok;
+}
+
+static int CertCallback(SSL *ssl, void *arg) {
+  const TestConfig *config = GetTestConfig(ssl);
+
+  // Check the peer certificate metadata is as expected.
+  if ((!SSL_is_server(ssl) && !CheckCertificateRequest(ssl)) ||
+      !CheckPeerVerifyPrefs(ssl)) {
+    return -1;
+  }
+
+  if (config->fail_cert_callback) {
+    return 0;
+  }
+
+  // The certificate will be installed via other means.
+  if (!config->async || config->use_early_callback) {
+    return 1;
+  }
+
+  if (!GetTestState(ssl)->cert_ready) {
+    return -1;
+  }
+  if (!InstallCertificate(ssl)) {
+    return 0;
+  }
+  return 1;
+}
+
+bssl::UniquePtr<SSL> TestConfig::NewSSL(
+    SSL_CTX *ssl_ctx, SSL_SESSION *session, bool is_resume,
+    std::unique_ptr<TestState> test_state) const {
+  bssl::UniquePtr<SSL> ssl(SSL_new(ssl_ctx));
+  if (!ssl) {
+    return nullptr;
+  }
+
+  if (!SetTestConfig(ssl.get(), this)) {
+    return nullptr;
+  }
+  if (test_state != nullptr) {
+    if (!SetTestState(ssl.get(), std::move(test_state))) {
+      return nullptr;
+    }
+    GetTestState(ssl.get())->is_resume = is_resume;
+  }
+
+  if (fallback_scsv && !SSL_set_mode(ssl.get(), SSL_MODE_SEND_FALLBACK_SCSV)) {
+    return nullptr;
+  }
+  // Install the certificate synchronously if nothing else will handle it.
+  if (!use_early_callback && !use_old_client_cert_callback && !async &&
+      !InstallCertificate(ssl.get())) {
+    return nullptr;
+  }
+  if (!use_old_client_cert_callback) {
+    SSL_set_cert_cb(ssl.get(), CertCallback, nullptr);
+  }
+  int mode = SSL_VERIFY_NONE;
+  if (require_any_client_certificate) {
+    mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+  }
+  if (verify_peer) {
+    mode = SSL_VERIFY_PEER;
+  }
+  if (verify_peer_if_no_obc) {
+    // Set SSL_VERIFY_FAIL_IF_NO_PEER_CERT so testing whether client
+    // certificates were requested is easy.
+    mode = SSL_VERIFY_PEER | SSL_VERIFY_PEER_IF_NO_OBC |
+           SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+  }
+  if (use_custom_verify_callback) {
+    SSL_set_custom_verify(ssl.get(), mode, CustomVerifyCallback);
+  } else if (mode != SSL_VERIFY_NONE) {
+    SSL_set_verify(ssl.get(), mode, NULL);
+  }
+  if (false_start) {
+    SSL_set_mode(ssl.get(), SSL_MODE_ENABLE_FALSE_START);
+  }
+  if (cbc_record_splitting) {
+    SSL_set_mode(ssl.get(), SSL_MODE_CBC_RECORD_SPLITTING);
+  }
+  if (partial_write) {
+    SSL_set_mode(ssl.get(), SSL_MODE_ENABLE_PARTIAL_WRITE);
+  }
+  if (no_tls13) {
+    SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_3);
+  }
+  if (no_tls12) {
+    SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_2);
+  }
+  if (no_tls11) {
+    SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_1);
+  }
+  if (no_tls1) {
+    SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1);
+  }
+  if (no_ticket) {
+    SSL_set_options(ssl.get(), SSL_OP_NO_TICKET);
+  }
+  if (!expected_channel_id.empty() || enable_channel_id) {
+    SSL_set_tls_channel_id_enabled(ssl.get(), 1);
+  }
+  if (!send_channel_id.empty()) {
+    SSL_set_tls_channel_id_enabled(ssl.get(), 1);
+    if (!async) {
+      // The async case will be supplied by |ChannelIdCallback|.
+      bssl::UniquePtr<EVP_PKEY> pkey = LoadPrivateKey(send_channel_id);
+      if (!pkey || !SSL_set1_tls_channel_id(ssl.get(), pkey.get())) {
+        return nullptr;
+      }
+    }
+  }
+  if (!send_token_binding_params.empty()) {
+    SSL_set_token_binding_params(
+        ssl.get(),
+        reinterpret_cast<const uint8_t *>(send_token_binding_params.data()),
+        send_token_binding_params.length());
+  }
+  if (!host_name.empty() &&
+      !SSL_set_tlsext_host_name(ssl.get(), host_name.c_str())) {
+    return nullptr;
+  }
+  if (!advertise_alpn.empty() &&
+      SSL_set_alpn_protos(ssl.get(), (const uint8_t *)advertise_alpn.data(),
+                          advertise_alpn.size()) != 0) {
+    return nullptr;
+  }
+  if (!psk.empty()) {
+    SSL_set_psk_client_callback(ssl.get(), PskClientCallback);
+    SSL_set_psk_server_callback(ssl.get(), PskServerCallback);
+  }
+  if (!psk_identity.empty() &&
+      !SSL_use_psk_identity_hint(ssl.get(), psk_identity.c_str())) {
+    return nullptr;
+  }
+  if (!srtp_profiles.empty() &&
+      !SSL_set_srtp_profiles(ssl.get(), srtp_profiles.c_str())) {
+    return nullptr;
+  }
+  if (enable_ocsp_stapling) {
+    SSL_enable_ocsp_stapling(ssl.get());
+  }
+  if (enable_signed_cert_timestamps) {
+    SSL_enable_signed_cert_timestamps(ssl.get());
+  }
+  if (min_version != 0 &&
+      !SSL_set_min_proto_version(ssl.get(), (uint16_t)min_version)) {
+    return nullptr;
+  }
+  if (max_version != 0 &&
+      !SSL_set_max_proto_version(ssl.get(), (uint16_t)max_version)) {
+    return nullptr;
+  }
+  if (mtu != 0) {
+    SSL_set_options(ssl.get(), SSL_OP_NO_QUERY_MTU);
+    SSL_set_mtu(ssl.get(), mtu);
+  }
+  if (install_ddos_callback) {
+    SSL_CTX_set_dos_protection_cb(ssl_ctx, DDoSCallback);
+  }
+  SSL_set_shed_handshake_config(ssl.get(), true);
+  if (renegotiate_once) {
+    SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_once);
+  }
+  if (renegotiate_freely || forbid_renegotiation_after_handshake) {
+    // |forbid_renegotiation_after_handshake| will disable renegotiation later.
+    SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely);
+  }
+  if (renegotiate_ignore) {
+    SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_ignore);
+  }
+  if (!check_close_notify) {
+    SSL_set_quiet_shutdown(ssl.get(), 1);
+  }
+  if (p384_only) {
+    int nid = NID_secp384r1;
+    if (!SSL_set1_curves(ssl.get(), &nid, 1)) {
+      return nullptr;
+    }
+  }
+  if (enable_all_curves) {
+    static const int kAllCurves[] = {
+        NID_secp224r1, NID_X9_62_prime256v1, NID_secp384r1,
+        NID_secp521r1, NID_X25519,
+    };
+    if (!SSL_set1_curves(ssl.get(), kAllCurves,
+                         OPENSSL_ARRAY_SIZE(kAllCurves))) {
+      return nullptr;
+    }
+  }
+  if (initial_timeout_duration_ms > 0) {
+    DTLSv1_set_initial_timeout_duration(ssl.get(), initial_timeout_duration_ms);
+  }
+  if (max_cert_list > 0) {
+    SSL_set_max_cert_list(ssl.get(), max_cert_list);
+  }
+  if (retain_only_sha256_client_cert) {
+    SSL_set_retain_only_sha256_of_client_certs(ssl.get(), 1);
+  }
+  if (max_send_fragment > 0) {
+    SSL_set_max_send_fragment(ssl.get(), max_send_fragment);
+  }
+  if (dummy_pq_padding_len > 0 &&
+      !SSL_set_dummy_pq_padding_size(ssl.get(), dummy_pq_padding_len)) {
+    return nullptr;
+  }
+  if (!quic_transport_params.empty()) {
+    if (!SSL_set_quic_transport_params(
+            ssl.get(),
+            reinterpret_cast<const uint8_t *>(quic_transport_params.data()),
+            quic_transport_params.size())) {
+      return nullptr;
+    }
+  }
+
+  if (session != NULL) {
+    if (!is_server) {
+      if (SSL_set_session(ssl.get(), session) != 1) {
+        return nullptr;
+      }
+    } else if (async) {
+      // The internal session cache is disabled, so install the session
+      // manually.
+      SSL_SESSION_up_ref(session);
+      GetTestState(ssl.get())->pending_session.reset(session);
+    }
+  }
+
+  if (SSL_get_current_cipher(ssl.get()) != nullptr) {
+    fprintf(stderr, "non-null cipher before handshake\n");
+    return nullptr;
+  }
+
+  return ssl;
+}
diff --git a/src/ssl/test/test_config.h b/src/ssl/test/test_config.h
index 95f38e0..6c9ac3e 100644
--- a/src/ssl/test/test_config.h
+++ b/src/ssl/test/test_config.h
@@ -18,6 +18,10 @@
 #include <string>
 #include <vector>
 
+#include <openssl/base.h>
+#include <openssl/x509.h>
+
+#include "test_state.h"
 
 struct TestConfig {
   int port = 0;
@@ -28,6 +32,7 @@
   bool fallback_scsv = false;
   std::vector<int> signing_prefs;
   std::vector<int> verify_prefs;
+  std::vector<int> expected_peer_verify_prefs;
   std::string key_file;
   std::string cert_file;
   std::string expected_server_name;
@@ -45,7 +50,7 @@
   bool no_tls12 = false;
   bool no_tls11 = false;
   bool no_tls1 = false;
-  bool no_ssl3 = false;
+  bool no_ticket = false;
   std::string expected_channel_id;
   bool enable_channel_id = false;
   std::string send_channel_id;
@@ -59,6 +64,7 @@
   std::string expected_advertised_alpn;
   std::string select_alpn;
   bool decline_alpn = false;
+  bool select_empty_alpn = false;
   std::string quic_transport_params;
   std::string expected_quic_transport_params;
   bool expect_session_miss = false;
@@ -79,7 +85,6 @@
   bool fail_early_callback = false;
   bool install_ddos_callback = false;
   bool fail_ddos_callback = false;
-  bool fail_second_ddos_callback = false;
   bool fail_cert_callback = false;
   std::string cipher;
   bool handshake_never_done = false;
@@ -115,6 +120,7 @@
   bool renegotiate_once = false;
   bool renegotiate_freely = false;
   bool renegotiate_ignore = false;
+  bool forbid_renegotiation_after_handshake = false;
   int expect_peer_signature_algorithm = 0;
   bool p384_only = false;
   bool enable_all_curves = false;
@@ -155,10 +161,36 @@
   bool handoff = false;
   bool expect_dummy_pq_padding = false;
   bool no_rsa_pss_rsae_certs = false;
+  bool use_ocsp_callback = false;
+  bool set_ocsp_in_callback = false;
+  bool decline_ocsp_callback = false;
+  bool fail_ocsp_callback = false;
+  bool install_cert_compression_algs = false;
+  bool is_handshaker_supported = false;
+  bool handshaker_resume = false;
+  std::string handshaker_path;
+
+  int argc;
+  char **argv;
+
+  bssl::UniquePtr<SSL_CTX> SetupCtx(SSL_CTX *old_ctx) const;
+
+  bssl::UniquePtr<SSL> NewSSL(SSL_CTX *ssl_ctx, SSL_SESSION *session,
+                              bool is_resume,
+                              std::unique_ptr<TestState> test_state) const;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_initial,
                  TestConfig *out_resume, TestConfig *out_retry);
 
+bool SetTestConfig(SSL *ssl, const TestConfig *config);
+
+const TestConfig *GetTestConfig(const SSL *ssl);
+
+bool LoadCertificate(bssl::UniquePtr<X509> *out_x509,
+                     bssl::UniquePtr<STACK_OF(X509)> *out_chain,
+                     const std::string &file);
+
+bssl::UniquePtr<EVP_PKEY> LoadPrivateKey(const std::string &file);
 
 #endif  // HEADER_TEST_CONFIG
diff --git a/src/ssl/test/test_state.cc b/src/ssl/test/test_state.cc
new file mode 100644
index 0000000..b9767e0
--- /dev/null
+++ b/src/ssl/test/test_state.cc
@@ -0,0 +1,169 @@
+/* Copyright (c) 2018, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include "test_state.h"
+
+#include <openssl/ssl.h>
+
+#include "../../crypto/internal.h"
+#include "../internal.h"
+
+using namespace bssl;
+
+static CRYPTO_once_t g_once = CRYPTO_ONCE_INIT;
+static int g_state_index = 0;
+// Some code treats the zero time special, so initialize the clock to a
+// non-zero time.
+static timeval g_clock = { 1234, 1234 };
+
+static void TestStateExFree(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
+                            int index, long argl, void *argp) {
+  delete ((TestState *)ptr);
+}
+
+static void init_once() {
+  g_state_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, TestStateExFree);
+  if (g_state_index < 0) {
+    abort();
+  }
+}
+
+struct timeval *GetClock() {
+  CRYPTO_once(&g_once, init_once);
+  return &g_clock;
+}
+
+void AdvanceClock(unsigned seconds) {
+  CRYPTO_once(&g_once, init_once);
+  g_clock.tv_sec += seconds;
+}
+
+bool SetTestState(SSL *ssl, std::unique_ptr<TestState> state) {
+  CRYPTO_once(&g_once, init_once);
+  // |SSL_set_ex_data| takes ownership of |state| only on success.
+  if (SSL_set_ex_data(ssl, g_state_index, state.get()) == 1) {
+    state.release();
+    return true;
+  }
+  return false;
+}
+
+TestState *GetTestState(const SSL *ssl) {
+  CRYPTO_once(&g_once, init_once);
+  return (TestState *)SSL_get_ex_data(ssl, g_state_index);
+}
+
+static void ssl_ctx_add_session(SSL_SESSION *session, void *void_param) {
+  SSL_CTX *ctx = reinterpret_cast<SSL_CTX *>(void_param);
+  UniquePtr<SSL_SESSION> new_session = SSL_SESSION_dup(
+      session, SSL_SESSION_INCLUDE_NONAUTH | SSL_SESSION_INCLUDE_TICKET);
+  if (new_session != nullptr) {
+    SSL_CTX_add_session(ctx, new_session.get());
+  }
+}
+
+void CopySessions(SSL_CTX *dst, const SSL_CTX *src) {
+  lh_SSL_SESSION_doall_arg(src->sessions, ssl_ctx_add_session, dst);
+}
+
+static void push_session(SSL_SESSION *session, void *arg) {
+  auto s = reinterpret_cast<std::vector<SSL_SESSION *> *>(arg);
+  s->push_back(session);
+}
+
+bool SerializeContextState(SSL_CTX *ctx, CBB *cbb) {
+  CBB out, ctx_sessions, ticket_keys;
+  uint8_t keys[48];
+  if (!CBB_add_u24_length_prefixed(cbb, &out) ||
+      !CBB_add_u16(&out, 0 /* version */) ||
+      !SSL_CTX_get_tlsext_ticket_keys(ctx, &keys, sizeof(keys)) ||
+      !CBB_add_u8_length_prefixed(&out, &ticket_keys) ||
+      !CBB_add_bytes(&ticket_keys, keys, sizeof(keys)) ||
+      !CBB_add_asn1(&out, &ctx_sessions, CBS_ASN1_SEQUENCE)) {
+    return false;
+  }
+  std::vector<SSL_SESSION *> sessions;
+  lh_SSL_SESSION_doall_arg(ctx->sessions, push_session, &sessions);
+  for (const auto &sess : sessions) {
+    if (!ssl_session_serialize(sess, &ctx_sessions)) {
+      return false;
+    }
+  }
+  return CBB_flush(cbb);
+}
+
+bool DeserializeContextState(CBS *cbs, SSL_CTX *ctx) {
+  CBS in, sessions, ticket_keys;
+  uint16_t version;
+  constexpr uint16_t kVersion = 0;
+  if (!CBS_get_u24_length_prefixed(cbs, &in) ||
+      !CBS_get_u16(&in, &version) ||
+      version > kVersion ||
+      !CBS_get_u8_length_prefixed(&in, &ticket_keys) ||
+      !SSL_CTX_set_tlsext_ticket_keys(ctx, CBS_data(&ticket_keys),
+                                      CBS_len(&ticket_keys)) ||
+      !CBS_get_asn1(&in, &sessions, CBS_ASN1_SEQUENCE)) {
+    return false;
+  }
+  while (CBS_len(&sessions)) {
+    UniquePtr<SSL_SESSION> session =
+        SSL_SESSION_parse(&sessions, ctx->x509_method, ctx->pool);
+    if (!session) {
+      return false;
+    }
+    SSL_CTX_add_session(ctx, session.get());
+  }
+  return true;
+}
+
+bool TestState::Serialize(CBB *cbb) const {
+  CBB out, pending, text;
+  if (!CBB_add_u24_length_prefixed(cbb, &out) ||
+      !CBB_add_u16(&out, 0 /* version */) ||
+      !CBB_add_u24_length_prefixed(&out, &pending) ||
+      (pending_session &&
+       !ssl_session_serialize(pending_session.get(), &pending)) ||
+      !CBB_add_u16_length_prefixed(&out, &text) ||
+      !CBB_add_bytes(
+          &text, reinterpret_cast<const uint8_t *>(msg_callback_text.data()),
+          msg_callback_text.length()) ||
+      !CBB_flush(cbb)) {
+    return false;
+  }
+  return true;
+}
+
+std::unique_ptr<TestState> TestState::Deserialize(CBS *cbs, SSL_CTX *ctx) {
+  CBS in, pending_session, text;
+  std::unique_ptr<TestState> out_state(new TestState());
+  uint16_t version;
+  constexpr uint16_t kVersion = 0;
+  if (!CBS_get_u24_length_prefixed(cbs, &in) ||
+      !CBS_get_u16(&in, &version) ||
+      version > kVersion ||
+      !CBS_get_u24_length_prefixed(&in, &pending_session) ||
+      !CBS_get_u16_length_prefixed(&in, &text)) {
+    return nullptr;
+  }
+  if (CBS_len(&pending_session)) {
+    out_state->pending_session = SSL_SESSION_parse(
+        &pending_session, ctx->x509_method, ctx->pool);
+    if (!out_state->pending_session) {
+      return nullptr;
+    }
+  }
+  out_state->msg_callback_text = std::string(
+      reinterpret_cast<const char *>(CBS_data(&text)), CBS_len(&text));
+  return out_state;
+}
diff --git a/src/ssl/test/test_state.h b/src/ssl/test/test_state.h
new file mode 100644
index 0000000..2364286
--- /dev/null
+++ b/src/ssl/test/test_state.h
@@ -0,0 +1,84 @@
+/* Copyright (c) 2018, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#ifndef HEADER_TEST_STATE
+#define HEADER_TEST_STATE
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <openssl/base.h>
+
+struct TestState {
+  // Serialize writes |pending_session| and |msg_callback_text| to |out|, for
+  // use in split-handshake tests.  We don't try to serialize every bit of test
+  // state, but serializing |pending_session| is necessary to exercise session
+  // resumption, and |msg_callback_text| is especially useful.  In the general
+  // case, checks of state updated during the handshake can be skipped when
+  // |config->handoff|.
+  bool Serialize(CBB *out) const;
+
+  // Deserialize returns a new |TestState| from data written by |Serialize|.
+  static std::unique_ptr<TestState> Deserialize(CBS *cbs, SSL_CTX *ctx);
+
+  // async_bio is async BIO which pauses reads and writes.
+  BIO *async_bio = nullptr;
+  // packeted_bio is the packeted BIO which simulates read timeouts.
+  BIO *packeted_bio = nullptr;
+  bssl::UniquePtr<EVP_PKEY> channel_id;
+  bool cert_ready = false;
+  bssl::UniquePtr<SSL_SESSION> session;
+  bssl::UniquePtr<SSL_SESSION> pending_session;
+  bool early_callback_called = false;
+  bool handshake_done = false;
+  // private_key is the underlying private key used when testing custom keys.
+  bssl::UniquePtr<EVP_PKEY> private_key;
+  std::vector<uint8_t> private_key_result;
+  // private_key_retries is the number of times an asynchronous private key
+  // operation has been retried.
+  unsigned private_key_retries = 0;
+  bool got_new_session = false;
+  bssl::UniquePtr<SSL_SESSION> new_session;
+  bool ticket_decrypt_done = false;
+  bool alpn_select_done = false;
+  bool is_resume = false;
+  bool early_callback_ready = false;
+  bool custom_verify_ready = false;
+  std::string msg_callback_text;
+  bool msg_callback_ok = true;
+  // cert_verified is true if certificate verification has been driven to
+  // completion. This tests that the callback is not called again after this.
+  bool cert_verified = false;
+};
+
+bool SetTestState(SSL *ssl, std::unique_ptr<TestState> state);
+
+TestState *GetTestState(const SSL *ssl);
+
+struct timeval *GetClock();
+
+void AdvanceClock(unsigned seconds);
+
+void CopySessions(SSL_CTX *dest, const SSL_CTX *src);
+
+// SerializeContextState writes session material (sessions and ticket keys) from
+// |ctx| into |cbb|.
+bool SerializeContextState(SSL_CTX *ctx, CBB *cbb);
+
+// DeserializeContextState updates |out| with material previously serialized by
+// SerializeContextState.
+bool DeserializeContextState(CBS *in, SSL_CTX *out);
+
+#endif  // HEADER_TEST_STATE
diff --git a/src/ssl/tls13_both.cc b/src/ssl/tls13_both.cc
index defdac1..d6ebb4c 100644
--- a/src/ssl/tls13_both.cc
+++ b/src/ssl/tls13_both.cc
@@ -105,7 +105,73 @@
 int tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg,
                               int allow_anonymous) {
   SSL *const ssl = hs->ssl;
-  CBS body = msg.body, context, certificate_list;
+  CBS body = msg.body;
+  bssl::UniquePtr<CRYPTO_BUFFER> decompressed;
+
+  if (msg.type == SSL3_MT_COMPRESSED_CERTIFICATE) {
+    CBS compressed;
+    uint16_t alg_id;
+    uint32_t uncompressed_len;
+
+    if (!CBS_get_u16(&body, &alg_id) ||
+        !CBS_get_u24(&body, &uncompressed_len) ||
+        !CBS_get_u24_length_prefixed(&body, &compressed) ||
+        CBS_len(&body) != 0) {
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      return 0;
+    }
+
+    if (uncompressed_len > ssl->max_cert_list) {
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      OPENSSL_PUT_ERROR(SSL, SSL_R_UNCOMPRESSED_CERT_TOO_LARGE);
+      ERR_add_error_dataf("requested=%u",
+                          static_cast<unsigned>(uncompressed_len));
+      return 0;
+    }
+
+    ssl_cert_decompression_func_t decompress = nullptr;
+    for (const auto* alg : ssl->ctx->cert_compression_algs.get()) {
+      if (alg->alg_id == alg_id) {
+        decompress = alg->decompress;
+        break;
+      }
+    }
+
+    if (decompress == nullptr) {
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERT_COMPRESSION_ALG);
+      ERR_add_error_dataf("alg=%d", static_cast<int>(alg_id));
+      return 0;
+    }
+
+    CRYPTO_BUFFER *decompressed_ptr = nullptr;
+    if (!decompress(ssl, &decompressed_ptr, uncompressed_len,
+                    CBS_data(&compressed), CBS_len(&compressed))) {
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+      OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_DECOMPRESSION_FAILED);
+      ERR_add_error_dataf("alg=%d", static_cast<int>(alg_id));
+      return 0;
+    }
+    decompressed.reset(decompressed_ptr);
+
+    if (CRYPTO_BUFFER_len(decompressed_ptr) != uncompressed_len) {
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+      OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_DECOMPRESSION_FAILED);
+      ERR_add_error_dataf(
+          "alg=%d got=%u expected=%u", static_cast<int>(alg_id),
+          static_cast<unsigned>(CRYPTO_BUFFER_len(decompressed_ptr)),
+          static_cast<unsigned>(uncompressed_len));
+      return 0;
+    }
+
+    CBS_init(&body, CRYPTO_BUFFER_data(decompressed_ptr),
+             CRYPTO_BUFFER_len(decompressed_ptr));
+  } else {
+    assert(msg.type == SSL3_MT_CERTIFICATE);
+  }
+
+  CBS context, certificate_list;
   if (!CBS_get_u8_length_prefixed(&body, &context) ||
       CBS_len(&context) != 0 ||
       !CBS_get_u24_length_prefixed(&body, &certificate_list) ||
@@ -123,7 +189,7 @@
   }
 
   const bool retain_sha256 =
-      ssl->server && ssl->retain_only_sha256_of_client_certs;
+      ssl->server && hs->config->retain_only_sha256_of_client_certs;
   UniquePtr<EVP_PKEY> pkey;
   while (CBS_len(&certificate_list) > 0) {
     CBS certificate, extensions;
@@ -184,7 +250,7 @@
     // All Certificate extensions are parsed, but only the leaf extensions are
     // stored.
     if (have_status_request) {
-      if (ssl->server || !ssl->ocsp_stapling_enabled) {
+      if (ssl->server || !hs->config->ocsp_stapling_enabled) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
         ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
         return 0;
@@ -202,9 +268,8 @@
       }
 
       if (sk_CRYPTO_BUFFER_num(certs.get()) == 1) {
-        CRYPTO_BUFFER_free(hs->new_session->ocsp_response);
-        hs->new_session->ocsp_response =
-            CRYPTO_BUFFER_new_from_CBS(&ocsp_response, ssl->ctx->pool);
+        hs->new_session->ocsp_response.reset(
+            CRYPTO_BUFFER_new_from_CBS(&ocsp_response, ssl->ctx->pool));
         if (hs->new_session->ocsp_response == nullptr) {
           ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
           return 0;
@@ -213,7 +278,7 @@
     }
 
     if (have_sct) {
-      if (ssl->server || !ssl->signed_cert_timestamps_enabled) {
+      if (ssl->server || !hs->config->signed_cert_timestamps_enabled) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
         ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
         return 0;
@@ -226,9 +291,8 @@
       }
 
       if (sk_CRYPTO_BUFFER_num(certs.get()) == 1) {
-        CRYPTO_BUFFER_free(hs->new_session->signed_cert_timestamp_list);
-        hs->new_session->signed_cert_timestamp_list =
-            CRYPTO_BUFFER_new_from_CBS(&sct, ssl->ctx->pool);
+        hs->new_session->signed_cert_timestamp_list.reset(
+            CRYPTO_BUFFER_new_from_CBS(&sct, ssl->ctx->pool));
         if (hs->new_session->signed_cert_timestamp_list == nullptr) {
           ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
           return 0;
@@ -244,9 +308,7 @@
   }
 
   hs->peer_pubkey = std::move(pkey);
-
-  sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
-  hs->new_session->certs = certs.release();
+  hs->new_session->certs = std::move(certs);
 
   if (!ssl->ctx->x509_method->session_cache_objects(hs->new_session.get())) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
@@ -254,7 +316,7 @@
     return 0;
   }
 
-  if (sk_CRYPTO_BUFFER_num(hs->new_session->certs) == 0) {
+  if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) {
     if (!allow_anonymous) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED);
@@ -353,21 +415,34 @@
 
 int tls13_add_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+  CERT *const cert = hs->config->cert.get();
+
   ScopedCBB cbb;
-  CBB body, certificate_list;
-  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CERTIFICATE) ||
-      // The request context is always empty in the handshake.
-      !CBB_add_u8(&body, 0) ||
-      !CBB_add_u24_length_prefixed(&body, &certificate_list)) {
+  CBB *body, body_storage, certificate_list;
+
+  if (hs->cert_compression_negotiated) {
+    if (!CBB_init(cbb.get(), 1024)) {
+      return false;
+    }
+    body = cbb.get();
+  } else {
+    body = &body_storage;
+    if (!ssl->method->init_message(ssl, cbb.get(), body, SSL3_MT_CERTIFICATE)) {
+      return false;
+    }
+  }
+
+  if (// The request context is always empty in the handshake.
+      !CBB_add_u8(body, 0) ||
+      !CBB_add_u24_length_prefixed(body, &certificate_list)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return 0;
   }
 
-  if (!ssl_has_certificate(ssl)) {
+  if (!ssl_has_certificate(hs->config)) {
     return ssl_add_message_cbb(ssl, cbb.get());
   }
 
-  CERT *cert = ssl->cert;
   CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cert->chain.get(), 0);
   CBB leaf, extensions;
   if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) ||
@@ -378,30 +453,29 @@
     return 0;
   }
 
-  if (hs->scts_requested && ssl->cert->signed_cert_timestamp_list != nullptr) {
+  if (hs->scts_requested && cert->signed_cert_timestamp_list != nullptr) {
     CBB contents;
     if (!CBB_add_u16(&extensions, TLSEXT_TYPE_certificate_timestamp) ||
         !CBB_add_u16_length_prefixed(&extensions, &contents) ||
         !CBB_add_bytes(
             &contents,
-            CRYPTO_BUFFER_data(ssl->cert->signed_cert_timestamp_list.get()),
-            CRYPTO_BUFFER_len(ssl->cert->signed_cert_timestamp_list.get())) ||
+            CRYPTO_BUFFER_data(cert->signed_cert_timestamp_list.get()),
+            CRYPTO_BUFFER_len(cert->signed_cert_timestamp_list.get())) ||
         !CBB_flush(&extensions)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       return 0;
     }
   }
 
-  if (hs->ocsp_stapling_requested &&
-      ssl->cert->ocsp_response != NULL) {
+  if (hs->ocsp_stapling_requested && cert->ocsp_response != NULL) {
     CBB contents, ocsp_response;
     if (!CBB_add_u16(&extensions, TLSEXT_TYPE_status_request) ||
         !CBB_add_u16_length_prefixed(&extensions, &contents) ||
         !CBB_add_u8(&contents, TLSEXT_STATUSTYPE_ocsp) ||
         !CBB_add_u24_length_prefixed(&contents, &ocsp_response) ||
         !CBB_add_bytes(&ocsp_response,
-                       CRYPTO_BUFFER_data(ssl->cert->ocsp_response.get()),
-                       CRYPTO_BUFFER_len(ssl->cert->ocsp_response.get())) ||
+                       CRYPTO_BUFFER_data(cert->ocsp_response.get()),
+                       CRYPTO_BUFFER_len(cert->ocsp_response.get())) ||
         !CBB_flush(&extensions)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       return 0;
@@ -420,13 +494,50 @@
     }
   }
 
-  return ssl_add_message_cbb(ssl, cbb.get());
+  if (!hs->cert_compression_negotiated) {
+    return ssl_add_message_cbb(ssl, cbb.get());
+  }
+
+  Array<uint8_t> msg;
+  if (!CBBFinishArray(cbb.get(), &msg)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  const CertCompressionAlg *alg = nullptr;
+  for (const auto *candidate : ssl->ctx->cert_compression_algs.get()) {
+    if (candidate->alg_id == hs->cert_compression_alg_id) {
+      alg = candidate;
+      break;
+    }
+  }
+
+  if (alg == nullptr || alg->compress == nullptr) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  CBB compressed;
+  body = &body_storage;
+  if (!ssl->method->init_message(ssl, cbb.get(), body,
+                                 SSL3_MT_COMPRESSED_CERTIFICATE) ||
+      !CBB_add_u16(body, hs->cert_compression_alg_id) ||
+      !CBB_add_u24(body, msg.size()) ||
+      !CBB_add_u24_length_prefixed(body, &compressed) ||
+      !alg->compress(ssl, &compressed, msg.data(), msg.size()) ||
+      !ssl_add_message_cbb(ssl, cbb.get())) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  return 1;
 }
 
 enum ssl_private_key_result_t tls13_add_certificate_verify(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   uint16_t signature_algorithm;
   if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
     return ssl_private_key_failure;
   }
 
diff --git a/src/ssl/tls13_client.cc b/src/ssl/tls13_client.cc
index 6e328b8..40281a0 100644
--- a/src/ssl/tls13_client.cc
+++ b/src/ssl/tls13_client.cc
@@ -157,7 +157,7 @@
     }
 
     // The group must be supported.
-    if (!tls1_check_group_id(ssl, group_id)) {
+    if (!tls1_check_group_id(hs, group_id)) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
       OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
       return ssl_hs_error;
@@ -290,6 +290,18 @@
     return ssl_hs_error;
   }
 
+  if (ssl_is_draft28(ssl->version)) {
+    // Recheck supported_versions, in case this is the second ServerHello.
+    uint16_t version;
+    if (!have_supported_versions ||
+        !CBS_get_u16(&supported_versions, &version) ||
+        version != ssl->version) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_SECOND_SERVERHELLO_VERSION_MISMATCH);
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      return ssl_hs_error;
+    }
+  }
+
   alert = SSL_AD_DECODE_ERROR;
   if (have_pre_shared_key) {
     if (ssl->session == NULL) {
@@ -316,7 +328,7 @@
       return ssl_hs_error;
     }
 
-    if (!ssl_session_is_context_valid(ssl, ssl->session)) {
+    if (!ssl_session_is_context_valid(hs, ssl->session.get())) {
       // This is actually a client application bug.
       OPENSSL_PUT_ERROR(SSL,
                         SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT);
@@ -326,7 +338,8 @@
 
     ssl->s3->session_reused = true;
     // Only authentication information carries over in TLS 1.3.
-    hs->new_session = SSL_SESSION_dup(ssl->session, SSL_SESSION_DUP_AUTH_ONLY);
+    hs->new_session =
+        SSL_SESSION_dup(ssl->session.get(), SSL_SESSION_DUP_AUTH_ONLY);
     if (!hs->new_session) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       return ssl_hs_error;
@@ -417,26 +430,19 @@
   }
 
   // Store the negotiated ALPN in the session.
-  if (!ssl->s3->alpn_selected.empty()) {
-    hs->new_session->early_alpn = (uint8_t *)BUF_memdup(
-        ssl->s3->alpn_selected.data(), ssl->s3->alpn_selected.size());
-    if (hs->new_session->early_alpn == NULL) {
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return ssl_hs_error;
-    }
-    hs->new_session->early_alpn_len = ssl->s3->alpn_selected.size();
+  if (!hs->new_session->early_alpn.CopyFrom(ssl->s3->alpn_selected)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+    return ssl_hs_error;
   }
 
   if (ssl->s3->early_data_accepted) {
     if (hs->early_session->cipher != hs->new_session->cipher ||
-        MakeConstSpan(hs->early_session->early_alpn,
-                      hs->early_session->early_alpn_len) !=
+        MakeConstSpan(hs->early_session->early_alpn) !=
             ssl->s3->alpn_selected) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_ALPN_MISMATCH_ON_EARLY_DATA);
       return ssl_hs_error;
     }
-    if (ssl->s3->tlsext_channel_id_valid || hs->received_custom_extension ||
-        ssl->s3->token_binding_negotiated) {
+    if (ssl->s3->channel_id_valid || ssl->s3->token_binding_negotiated) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION_ON_EARLY_DATA);
       return ssl_hs_error;
     }
@@ -535,8 +541,13 @@
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
   }
-  if (!ssl_check_message_type(ssl, msg, SSL3_MT_CERTIFICATE) ||
-      !tls13_process_certificate(hs, msg, 0 /* certificate required */) ||
+
+  if (msg.type != SSL3_MT_COMPRESSED_CERTIFICATE &&
+      !ssl_check_message_type(ssl, msg, SSL3_MT_CERTIFICATE)) {
+    return ssl_hs_error;
+  }
+
+  if (!tls13_process_certificate(hs, msg, 0 /* certificate required */) ||
       !ssl_hash_message(hs, msg)) {
     return ssl_hs_error;
   }
@@ -629,8 +640,8 @@
   }
 
   // Call cert_cb to update the certificate.
-  if (ssl->cert->cert_cb != NULL) {
-    int rv = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg);
+  if (hs->config->cert->cert_cb != NULL) {
+    int rv = hs->config->cert->cert_cb(ssl, hs->config->cert->cert_cb_arg);
     if (rv == 0) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR);
@@ -652,9 +663,8 @@
 }
 
 static enum ssl_hs_wait_t do_send_client_certificate_verify(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
   // Don't send CertificateVerify if there is no certificate.
-  if (!ssl_has_certificate(ssl)) {
+  if (!ssl_has_certificate(hs->config)) {
     hs->tls13_state = state_complete_second_flight;
     return ssl_hs_ok;
   }
@@ -680,13 +690,13 @@
   SSL *const ssl = hs->ssl;
 
   // Send a Channel ID assertion if necessary.
-  if (ssl->s3->tlsext_channel_id_valid) {
-    if (!ssl_do_channel_id_callback(ssl)) {
+  if (ssl->s3->channel_id_valid) {
+    if (!ssl_do_channel_id_callback(hs)) {
       hs->tls13_state = state_complete_second_flight;
       return ssl_hs_error;
     }
 
-    if (ssl->tlsext_channel_id_private == NULL) {
+    if (hs->config->channel_id_private == NULL) {
       return ssl_hs_channel_id_lookup;
     }
 
@@ -833,7 +843,7 @@
       !CBS_get_u32(&body, &session->ticket_age_add) ||
       !CBS_get_u8_length_prefixed(&body, &ticket_nonce) ||
       !CBS_get_u16_length_prefixed(&body, &ticket) ||
-      !CBS_stow(&ticket, &session->tlsext_tick, &session->tlsext_ticklen) ||
+      !session->ticket.CopyFrom(ticket) ||
       !CBS_get_u16_length_prefixed(&body, &extensions) ||
       CBS_len(&body) != 0) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
@@ -866,7 +876,7 @@
     return 0;
   }
 
-  if (have_early_data_info && ssl->cert->enable_early_data) {
+  if (have_early_data_info && ssl->enable_early_data) {
     if (!CBS_get_u32(&early_data_info, &session->ticket_max_early_data) ||
         CBS_len(&early_data_info) != 0) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
@@ -875,12 +885,12 @@
     }
   }
 
-  session->ticket_age_add_valid = 1;
-  session->not_resumable = 0;
+  session->ticket_age_add_valid = true;
+  session->not_resumable = false;
 
-  if ((ssl->ctx->session_cache_mode & SSL_SESS_CACHE_CLIENT) &&
-      ssl->ctx->new_session_cb != NULL &&
-      ssl->ctx->new_session_cb(ssl, session.get())) {
+  if ((ssl->session_ctx->session_cache_mode & SSL_SESS_CACHE_CLIENT) &&
+      ssl->session_ctx->new_session_cb != NULL &&
+      ssl->session_ctx->new_session_cb(ssl, session.get())) {
     // |new_session_cb|'s return value signals that it took ownership.
     session.release();
   }
diff --git a/src/ssl/tls13_enc.cc b/src/ssl/tls13_enc.cc
index cc7afb8..84bc5d2 100644
--- a/src/ssl/tls13_enc.cc
+++ b/src/ssl/tls13_enc.cc
@@ -60,7 +60,7 @@
 int tls13_init_early_key_schedule(SSL_HANDSHAKE *hs, const uint8_t *psk,
                                   size_t psk_len) {
   SSL *const ssl = hs->ssl;
-  return init_key_schedule(hs, ssl_session_protocol_version(ssl->session),
+  return init_key_schedule(hs, ssl_session_protocol_version(ssl->session.get()),
                            ssl->session->cipher) &&
          HKDF_extract(hs->secret, &hs->hash_len, hs->transcript.Digest(), psk,
                       psk_len, hs->secret, hs->hash_len);
@@ -74,8 +74,7 @@
 
   ScopedCBB cbb;
   CBB child;
-  uint8_t *hkdf_label;
-  size_t hkdf_label_len;
+  Array<uint8_t> hkdf_label;
   if (!CBB_init(cbb.get(), 2 + 1 + strlen(kTLS13LabelVersion) + label_len + 1 +
                                hash_len) ||
       !CBB_add_u16(cbb.get(), len) ||
@@ -85,14 +84,12 @@
       !CBB_add_bytes(&child, (const uint8_t *)label, label_len) ||
       !CBB_add_u8_length_prefixed(cbb.get(), &child) ||
       !CBB_add_bytes(&child, hash, hash_len) ||
-      !CBB_finish(cbb.get(), &hkdf_label, &hkdf_label_len)) {
+      !CBBFinishArray(cbb.get(), &hkdf_label)) {
     return 0;
   }
 
-  int ret = HKDF_expand(out, len, digest, secret, secret_len, hkdf_label,
-                        hkdf_label_len);
-  OPENSSL_free(hkdf_label);
-  return ret;
+  return HKDF_expand(out, len, digest, secret, secret_len, hkdf_label.data(),
+                     hkdf_label.size());
 }
 
 static const char kTLS13LabelDerived[] = "derived";
@@ -413,7 +410,7 @@
 
 int tls13_write_psk_binder(SSL_HANDSHAKE *hs, uint8_t *msg, size_t len) {
   SSL *const ssl = hs->ssl;
-  const EVP_MD *digest = ssl_session_get_digest(ssl->session);
+  const EVP_MD *digest = ssl_session_get_digest(ssl->session.get());
   size_t hash_len = EVP_MD_size(digest);
 
   if (len < hash_len + 3) {
diff --git a/src/ssl/tls13_server.cc b/src/ssl/tls13_server.cc
index fe0449d..203e704 100644
--- a/src/ssl/tls13_server.cc
+++ b/src/ssl/tls13_server.cc
@@ -148,8 +148,18 @@
   return best;
 }
 
-static int add_new_session_tickets(SSL_HANDSHAKE *hs) {
+static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) {
   SSL *const ssl = hs->ssl;
+  if (// If the client doesn't accept resumption with PSK_DHE_KE, don't send a
+      // session ticket.
+      !hs->accept_psk_mode ||
+      // We only implement stateless resumption in TLS 1.3, so skip sending
+      // tickets if disabled.
+      (SSL_get_options(ssl) & SSL_OP_NO_TICKET)) {
+    *out_sent_tickets = false;
+    return true;
+  }
+
   // TLS 1.3 recommends single-use tickets, so issue multiple tickets in case
   // the client makes several connections before getting a renewal.
   static const int kNumTickets = 2;
@@ -162,14 +172,14 @@
     UniquePtr<SSL_SESSION> session(
         SSL_SESSION_dup(hs->new_session.get(), SSL_SESSION_INCLUDE_NONAUTH));
     if (!session) {
-      return 0;
+      return false;
     }
 
     if (!RAND_bytes((uint8_t *)&session->ticket_age_add, 4)) {
-      return 0;
+      return false;
     }
-    session->ticket_age_add_valid = 1;
-    if (ssl->cert->enable_early_data) {
+    session->ticket_age_add_valid = true;
+    if (ssl->enable_early_data) {
       session->ticket_max_early_data = kMaxEarlyDataAccepted;
     }
 
@@ -186,18 +196,18 @@
         !CBB_add_bytes(&nonce_cbb, nonce, sizeof(nonce)) ||
         !CBB_add_u16_length_prefixed(&body, &ticket) ||
         !tls13_derive_session_psk(session.get(), nonce) ||
-        !ssl_encrypt_ticket(ssl, &ticket, session.get()) ||
+        !ssl_encrypt_ticket(hs, &ticket, session.get()) ||
         !CBB_add_u16_length_prefixed(&body, &extensions)) {
-      return 0;
+      return false;
     }
 
-    if (ssl->cert->enable_early_data) {
+    if (ssl->enable_early_data) {
       CBB early_data_info;
       if (!CBB_add_u16(&extensions, TLSEXT_TYPE_early_data) ||
           !CBB_add_u16_length_prefixed(&extensions, &early_data_info) ||
           !CBB_add_u32(&early_data_info, session->ticket_max_early_data) ||
           !CBB_flush(&extensions)) {
-        return 0;
+        return false;
       }
     }
 
@@ -205,15 +215,16 @@
     if (!CBB_add_u16(&extensions,
                      ssl_get_grease_value(hs, ssl_grease_ticket_extension)) ||
         !CBB_add_u16(&extensions, 0 /* empty */)) {
-      return 0;
+      return false;
     }
 
     if (!ssl_add_message_cbb(ssl, cbb.get())) {
-      return 0;
+      return false;
     }
   }
 
-  return 1;
+  *out_sent_tickets = true;
+  return true;
 }
 
 static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
@@ -302,7 +313,7 @@
   bool unused_renew;
   UniquePtr<SSL_SESSION> session;
   enum ssl_ticket_aead_result_t ret =
-      ssl_process_ticket(ssl, &session, &unused_renew, CBS_data(&ticket),
+      ssl_process_ticket(hs, &session, &unused_renew, CBS_data(&ticket),
                          CBS_len(&ticket), NULL, 0);
   switch (ret) {
     case ssl_ticket_aead_success:
@@ -383,20 +394,17 @@
       hs->new_session =
           SSL_SESSION_dup(session.get(), SSL_SESSION_DUP_AUTH_ONLY);
 
-      if (ssl->cert->enable_early_data &&
+      if (ssl->enable_early_data &&
           // Early data must be acceptable for this ticket.
           session->ticket_max_early_data != 0 &&
           // The client must have offered early data.
           hs->early_data_offered &&
           // Channel ID is incompatible with 0-RTT.
-          !ssl->s3->tlsext_channel_id_valid &&
+          !ssl->s3->channel_id_valid &&
           // If Token Binding is negotiated, reject 0-RTT.
           !ssl->s3->token_binding_negotiated &&
-          // Custom extensions is incompatible with 0-RTT.
-          hs->custom_extensions.received == 0 &&
           // The negotiated ALPN must match the one in the ticket.
-          ssl->s3->alpn_selected ==
-              MakeConstSpan(session->early_alpn, session->early_alpn_len)) {
+          MakeConstSpan(ssl->s3->alpn_selected) == session->early_alpn) {
         ssl->s3->early_data_accepted = true;
       }
 
@@ -425,14 +433,9 @@
   hs->new_session->cipher = hs->new_cipher;
 
   // Store the initial negotiated ALPN in the session.
-  if (!ssl->s3->alpn_selected.empty()) {
-    hs->new_session->early_alpn = (uint8_t *)BUF_memdup(
-        ssl->s3->alpn_selected.data(), ssl->s3->alpn_selected.size());
-    if (hs->new_session->early_alpn == NULL) {
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return ssl_hs_error;
-    }
-    hs->new_session->early_alpn_len = ssl->s3->alpn_selected.size();
+  if (!hs->new_session->early_alpn.CopyFrom(ssl->s3->alpn_selected)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+    return ssl_hs_error;
   }
 
   if (ssl->ctx->dos_protection_cb != NULL &&
@@ -599,10 +602,10 @@
 
   if (!ssl->s3->session_reused) {
     // Determine whether to request a client certificate.
-    hs->cert_request = !!(ssl->verify_mode & SSL_VERIFY_PEER);
+    hs->cert_request = !!(hs->config->verify_mode & SSL_VERIFY_PEER);
     // Only request a certificate if Channel ID isn't negotiated.
-    if ((ssl->verify_mode & SSL_VERIFY_PEER_IF_NO_OBC) &&
-        ssl->s3->tlsext_channel_id_valid) {
+    if ((hs->config->verify_mode & SSL_VERIFY_PEER_IF_NO_OBC) &&
+        ssl->s3->channel_id_valid) {
       hs->cert_request = false;
     }
   }
@@ -635,13 +638,13 @@
       }
     }
 
-    if (ssl_has_client_CAs(ssl)) {
+    if (ssl_has_client_CAs(hs->config)) {
       CBB ca_contents;
       if (!CBB_add_u16(&cert_request_extensions,
                        TLSEXT_TYPE_certificate_authorities) ||
           !CBB_add_u16_length_prefixed(&cert_request_extensions,
                                        &ca_contents) ||
-          !ssl_add_client_CA_list(ssl, &ca_contents) ||
+          !ssl_add_client_CA_list(hs, &ca_contents) ||
           !CBB_flush(&cert_request_extensions)) {
         return ssl_hs_error;
       }
@@ -654,7 +657,7 @@
 
   // Send the server Certificate message, if necessary.
   if (!ssl->s3->session_reused) {
-    if (!ssl_has_certificate(ssl)) {
+    if (!ssl_has_certificate(hs->config)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET);
       return ssl_hs_error;
     }
@@ -731,11 +734,12 @@
     assert(hs->hash_len <= 0xff);
     uint8_t header[4] = {SSL3_MT_FINISHED, 0, 0,
                          static_cast<uint8_t>(hs->hash_len)};
+    bool unused_sent_tickets;
     if (!hs->transcript.Update(header) ||
         !hs->transcript.Update(
             MakeConstSpan(hs->expected_client_finished, hs->hash_len)) ||
         !tls13_derive_resumption_secret(hs) ||
-        !add_new_session_tickets(hs)) {
+        !add_new_session_tickets(hs, &unused_sent_tickets)) {
       return ssl_hs_error;
     }
   }
@@ -805,7 +809,7 @@
   }
 
   const int allow_anonymous =
-      (ssl->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) == 0;
+      (hs->config->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) == 0;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -824,7 +828,7 @@
 static enum ssl_hs_wait_t do_read_client_certificate_verify(
     SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  if (sk_CRYPTO_BUFFER_num(hs->new_session->certs) == 0) {
+  if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) {
     // Skip this state.
     hs->tls13_state = state_read_channel_id;
     return ssl_hs_ok;
@@ -858,7 +862,7 @@
 
 static enum ssl_hs_wait_t do_read_channel_id(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  if (!ssl->s3->tlsext_channel_id_valid) {
+  if (!ssl->s3->channel_id_valid) {
     hs->tls13_state = state_read_client_finished;
     return ssl_hs_ok;
   }
@@ -912,19 +916,13 @@
 }
 
 static enum ssl_hs_wait_t do_send_new_session_ticket(SSL_HANDSHAKE *hs) {
-  // If the client doesn't accept resumption with PSK_DHE_KE, don't send a
-  // session ticket.
-  if (!hs->accept_psk_mode) {
-    hs->tls13_state = state_done;
-    return ssl_hs_ok;
-  }
-
-  if (!add_new_session_tickets(hs)) {
+  bool sent_tickets;
+  if (!add_new_session_tickets(hs, &sent_tickets)) {
     return ssl_hs_error;
   }
 
   hs->tls13_state = state_done;
-  return ssl_hs_flush;
+  return sent_tickets ? ssl_hs_flush : ssl_hs_ok;
 }
 
 enum ssl_hs_wait_t tls13_server_handshake(SSL_HANDSHAKE *hs) {
diff --git a/src/ssl/tls_method.cc b/src/ssl/tls_method.cc
index 2ad2817..2af5171 100644
--- a/src/ssl/tls_method.cc
+++ b/src/ssl/tls_method.cc
@@ -95,6 +95,10 @@
 }
 
 static bool ssl3_set_write_state(SSL *ssl, UniquePtr<SSLAEADContext> aead_ctx) {
+  if (!tls_flush_pending_hs_data(ssl)) {
+    return false;
+  }
+
   OPENSSL_memset(ssl->s3->write_sequence, 0, sizeof(ssl->s3->write_sequence));
   ssl->s3->aead_write_ctx = std::move(aead_ctx);
   return true;
@@ -141,16 +145,18 @@
 }
 static void ssl_noop_x509_session_clear(SSL_SESSION *session) {}
 static int ssl_noop_x509_session_verify_cert_chain(SSL_SESSION *session,
-                                                   SSL *ssl,
+                                                   SSL_HANDSHAKE *hs,
                                                    uint8_t *out_alert) {
   return 0;
 }
 
 static void ssl_noop_x509_hs_flush_cached_ca_names(SSL_HANDSHAKE *hs) {}
-static int ssl_noop_x509_ssl_new(SSL *ctx) { return 1; }
-static void ssl_noop_x509_ssl_free(SSL *ctx) { }
-static void ssl_noop_x509_ssl_flush_cached_client_CA(SSL *ssl) {}
-static int ssl_noop_x509_ssl_auto_chain_if_needed(SSL *ssl) { return 1; }
+static int ssl_noop_x509_ssl_new(SSL_HANDSHAKE *hs) { return 1; }
+static void ssl_noop_x509_ssl_config_free(SSL_CONFIG *cfg) {}
+static void ssl_noop_x509_ssl_flush_cached_client_CA(SSL_CONFIG *cfg) {}
+static int ssl_noop_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) {
+  return 1;
+}
 static int ssl_noop_x509_ssl_ctx_new(SSL_CTX *ctx) { return 1; }
 static void ssl_noop_x509_ssl_ctx_free(SSL_CTX *ctx) { }
 static void ssl_noop_x509_ssl_ctx_flush_cached_client_CA(SSL_CTX *ctx) {}
@@ -168,7 +174,7 @@
   ssl_noop_x509_session_verify_cert_chain,
   ssl_noop_x509_hs_flush_cached_ca_names,
   ssl_noop_x509_ssl_new,
-  ssl_noop_x509_ssl_free,
+  ssl_noop_x509_ssl_config_free,
   ssl_noop_x509_ssl_flush_cached_client_CA,
   ssl_noop_x509_ssl_auto_chain_if_needed,
   ssl_noop_x509_ssl_ctx_new,