external/boringssl: Sync to c642aca28feb7e18f244658559f4042286aed0c8.

This includes the following changes:

https://boringssl.googlesource.com/boringssl/+log/14308731e5446a73ac2258688a9688b524483cb6..c642aca28feb7e18f244658559f4042286aed0c8

Test: BoringSSL CTS Presubmits
Change-Id: Ia0b5b2cdd64eb2b54ec5335d48da9001e9d6dafa
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7b7f934..3c2d556 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -42,7 +42,7 @@
     set(C_CXX_FLAGS "${C_CXX_FLAGS} -Wno-free-nonheap-object")
   endif()
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_CXX_FLAGS} -Wmissing-prototypes -Wold-style-definition -Wstrict-prototypes")
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${C_CXX_FLAGS} -Wmissing-declarations")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${C_CXX_FLAGS} -Wmissing-declarations -fno-exceptions")
   # In GCC, -Wmissing-declarations is the C++ spelling of -Wmissing-prototypes
   # and using the wrong one is an error. In Clang, -Wmissing-prototypes is the
   # spelling for both and -Wmissing-declarations is some other warning.
@@ -51,7 +51,7 @@
   # https://clang.llvm.org/docs/DiagnosticsReference.html#wmissing-prototypes
   # https://clang.llvm.org/docs/DiagnosticsReference.html#wmissing-declarations
   if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmissing-prototypes")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmissing-prototypes -Wimplicit-fallthrough")
   endif()
 elseif(MSVC)
   set(MSVC_DISABLED_WARNINGS_LIST
diff --git a/src/crypto/fipsmodule/FIPS.md b/src/crypto/fipsmodule/FIPS.md
index 30c4487..8378f1b 100644
--- a/src/crypto/fipsmodule/FIPS.md
+++ b/src/crypto/fipsmodule/FIPS.md
@@ -1,6 +1,14 @@
 # FIPS 140-2
 
-BoringSSL as a whole is not FIPS validated. However, there is a core library (called BoringCrypto) that is undergoing validation at time of writing. This document contains some notes about the design of the FIPS module and some documentation on performing FIPS-related tasks. This is not a substitute for reading the offical Security Policy (which, at the time of writing, has not yet been published).
+BoringSSL as a whole is not FIPS validated. However, there is a core library (called BoringCrypto) that has been FIPS validated. This document contains some notes about the design of the FIPS module and some documentation on performing FIPS-related tasks. This is not a substitute for reading the offical Security Policy.
+
+Please note that we cannot answer questions about FIPS, nor about using BoringSSL in a FIPS-compliant manner. Please consult with an [accredited CMVP lab](http://csrc.nist.gov/groups/STM/testing_labs/) on these subjects.
+
+## Validations
+
+BoringCrypto has undergone the following validations:
+
+1. 2017-06-15: certificate [#2964](http://csrc.nist.gov/groups/STM/cmvp/documents/140-1/1401val2017.htm#2964), [security policy](/crypto/fipsmodule/policydocs/BoringCrypto-Security-Policy-20170615.docx) (in docx format).
 
 ## Running CAVP tests
 
diff --git a/src/crypto/fipsmodule/policydocs/BoringCrypto-Security-Policy-20170615.docx b/src/crypto/fipsmodule/policydocs/BoringCrypto-Security-Policy-20170615.docx
new file mode 100644
index 0000000..272713b
--- /dev/null
+++ b/src/crypto/fipsmodule/policydocs/BoringCrypto-Security-Policy-20170615.docx
Binary files differ
diff --git a/src/crypto/fipsmodule/rand/urandom.c b/src/crypto/fipsmodule/rand/urandom.c
index 2ea8730..8cbf727 100644
--- a/src/crypto/fipsmodule/rand/urandom.c
+++ b/src/crypto/fipsmodule/rand/urandom.c
@@ -45,33 +45,33 @@
 #if defined(OPENSSL_LINUX)
 
 #if defined(OPENSSL_X86_64)
-#define EXPECTED_SYS_getrandom 318
+#define EXPECTED_NR_getrandom 318
 #elif defined(OPENSSL_X86)
-#define EXPECTED_SYS_getrandom 355
+#define EXPECTED_NR_getrandom 355
 #elif defined(OPENSSL_AARCH64)
-#define EXPECTED_SYS_getrandom 278
+#define EXPECTED_NR_getrandom 278
 #elif defined(OPENSSL_ARM)
-#define EXPECTED_SYS_getrandom 384
+#define EXPECTED_NR_getrandom 384
 #elif defined(OPENSSL_PPC64LE)
-#define EXPECTED_SYS_getrandom 359
+#define EXPECTED_NR_getrandom 359
 #endif
 
-#if defined(EXPECTED_SYS_getrandom)
-#define USE_SYS_getrandom
+#if defined(EXPECTED_NR_getrandom)
+#define USE_NR_getrandom
 
-#if defined(SYS_getrandom)
+#if defined(__NR_getrandom)
 
-#if SYS_getrandom != EXPECTED_SYS_getrandom
+#if __NR_getrandom != EXPECTED_NR_getrandom
 #error "system call number for getrandom is not the expected value"
 #endif
 
-#else  /* SYS_getrandom */
+#else  /* __NR_getrandom */
 
-#define SYS_getrandom EXPECTED_SYS_getrandom
+#define __NR_getrandom EXPECTED_NR_getrandom
 
-#endif  /* SYS_getrandom */
+#endif  /* __NR_getrandom */
 
-#endif /* EXPECTED_SYS_getrandom */
+#endif /* EXPECTED_NR_getrandom */
 
 #if !defined(GRND_NONBLOCK)
 #define GRND_NONBLOCK 1
@@ -95,7 +95,7 @@
 
 DEFINE_STATIC_ONCE(rand_once);
 
-#if defined(USE_SYS_getrandom) || defined(BORINGSSL_FIPS)
+#if defined(USE_NR_getrandom) || defined(BORINGSSL_FIPS)
 /* message writes |msg| to stderr. We use this because referencing |stderr|
  * with |fprintf| generates relocations, which is a problem inside the FIPS
  * module. */
@@ -116,10 +116,10 @@
   int fd = *urandom_fd_requested_bss_get();
   CRYPTO_STATIC_MUTEX_unlock_read(rand_lock_bss_get());
 
-#if defined(USE_SYS_getrandom)
+#if defined(USE_NR_getrandom)
   uint8_t dummy;
   long getrandom_ret =
-      syscall(SYS_getrandom, &dummy, sizeof(dummy), GRND_NONBLOCK);
+      syscall(__NR_getrandom, &dummy, sizeof(dummy), GRND_NONBLOCK);
 
   if (getrandom_ret == 1) {
     *urandom_fd_bss_get() = kHaveGetrandom;
@@ -132,7 +132,7 @@
 
     do {
       getrandom_ret =
-          syscall(SYS_getrandom, &dummy, sizeof(dummy), 0 /* no flags */);
+          syscall(__NR_getrandom, &dummy, sizeof(dummy), 0 /* no flags */);
     } while (getrandom_ret == -1 && errno == EINTR);
 
     if (getrandom_ret == 1) {
@@ -140,7 +140,7 @@
       return;
     }
   }
-#endif  /* USE_SYS_getrandom */
+#endif  /* USE_NR_getrandom */
 
   if (fd == kUnset) {
     do {
@@ -234,7 +234,7 @@
   }
 }
 
-#if defined(USE_SYS_getrandom) && defined(OPENSSL_MSAN)
+#if defined(USE_NR_getrandom) && defined(OPENSSL_MSAN)
 void __msan_unpoison(void *, size_t);
 #endif
 
@@ -245,9 +245,9 @@
     ssize_t r;
 
     if (*urandom_fd_bss_get() == kHaveGetrandom) {
-#if defined(USE_SYS_getrandom)
+#if defined(USE_NR_getrandom)
       do {
-        r = syscall(SYS_getrandom, out, len, 0 /* no flags */);
+        r = syscall(__NR_getrandom, out, len, 0 /* no flags */);
       } while (r == -1 && errno == EINTR);
 
 #if defined(OPENSSL_MSAN)
@@ -258,7 +258,7 @@
       }
 #endif /* OPENSSL_MSAN */
 
-#else /* USE_SYS_getrandom */
+#else /* USE_NR_getrandom */
       abort();
 #endif
     } else {
diff --git a/src/include/openssl/base.h b/src/include/openssl/base.h
index 460c1e0..42ead4d 100644
--- a/src/include/openssl/base.h
+++ b/src/include/openssl/base.h
@@ -317,7 +317,6 @@
 typedef struct srtp_protection_profile_st SRTP_PROTECTION_PROFILE;
 typedef struct ssl_cipher_st SSL_CIPHER;
 typedef struct ssl_ctx_st SSL_CTX;
-typedef struct ssl_custom_extension SSL_CUSTOM_EXTENSION;
 typedef struct ssl_method_st SSL_METHOD;
 typedef struct ssl_private_key_method_st SSL_PRIVATE_KEY_METHOD;
 typedef struct ssl_session_st SSL_SESSION;
@@ -341,6 +340,9 @@
 
 #if defined(__cplusplus)
 }  /* extern C */
+#elif !defined(BORINGSSL_NO_CXX)
+#define BORINGSSL_NO_CXX
+#endif
 
 // MSVC doesn't set __cplusplus to 201103 to indicate C++11 support (see
 // https://connect.microsoft.com/VisualStudio/feedback/details/763051/a-value-of-predefined-macro-cplusplus-is-still-199711l)
@@ -371,13 +373,13 @@
 
 extern "C++" {
 
-#include <memory>
-
 namespace bssl {
 
 namespace internal {
 
-template <typename T>
+// The Enable parameter is ignored and only exists so specializations can use
+// SFINAE.
+template <typename T, typename Enable = void>
 struct DeleterImpl {};
 
 template <typename T>
@@ -451,6 +453,4 @@
 
 #endif  // !BORINGSSL_NO_CXX
 
-#endif
-
 #endif  /* OPENSSL_HEADER_BASE_H */
diff --git a/src/include/openssl/bn.h b/src/include/openssl/bn.h
index ee33166..bdd41ba 100644
--- a/src/include/openssl/bn.h
+++ b/src/include/openssl/bn.h
@@ -938,8 +938,7 @@
 #if defined(__cplusplus)
 }  /* extern C */
 
-#if (__cplusplus >= 201103L || (__cplusplus < 200000 && __cplusplus > 199711L)) && !defined(OPENSSL_NO_CXX)
-
+#if !defined(BORINGSSL_NO_CXX)
 extern "C++" {
 
 namespace bssl {
diff --git a/src/include/openssl/pool.h b/src/include/openssl/pool.h
index 8a07af5..4972b93 100644
--- a/src/include/openssl/pool.h
+++ b/src/include/openssl/pool.h
@@ -82,6 +82,8 @@
 BORINGSSL_MAKE_DELETER(CRYPTO_BUFFER_POOL, CRYPTO_BUFFER_POOL_free)
 BORINGSSL_MAKE_DELETER(CRYPTO_BUFFER, CRYPTO_BUFFER_free)
 
+BORINGSSL_MAKE_STACK_DELETER(CRYPTO_BUFFER, CRYPTO_BUFFER_free)
+
 }  // namespace bssl
 
 }  /* extern C++ */
diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h
index 16aeaff..04ec4b8 100644
--- a/src/include/openssl/ssl.h
+++ b/src/include/openssl/ssl.h
@@ -517,6 +517,13 @@
  * used to reuse the underlying connection for the retry. */
 #define SSL_ERROR_EARLY_DATA_REJECTED 15
 
+/* SSL_ERROR_WANT_CERTIFICATE_VERIFY indicates the operation failed because
+ * certificate verification was incomplete. The caller may retry the operation
+ * when certificate verification is complete.
+ *
+ * See also |SSL_CTX_set_custom_verify|. */
+#define SSL_ERROR_WANT_CERTIFICATE_VERIFY 16
+
 /* SSL_set_mtu sets the |ssl|'s MTU in DTLS to |mtu|. It returns one on success
  * and zero on failure. */
 OPENSSL_EXPORT int SSL_set_mtu(SSL *ssl, unsigned mtu);
@@ -2201,6 +2208,39 @@
                                    int (*callback)(int ok,
                                                    X509_STORE_CTX *store_ctx));
 
+enum ssl_verify_result_t {
+  ssl_verify_ok,
+  ssl_verify_invalid,
+  ssl_verify_retry,
+};
+
+/* SSL_CTX_set_custom_verify configures certificate verification. |mode| is one
+ * of the |SSL_VERIFY_*| values defined above. |callback| performs the
+ * certificate verification.
+ *
+ * The callback may call |SSL_get0_peer_certificates| for the certificate chain
+ * to validate. The callback should return |ssl_verify_ok| if the certificate is
+ * valid. If the certificate is invalid, the callback should return
+ * |ssl_verify_invalid| and optionally set |*out_alert| to an alert to send to
+ * the peer. Some useful alerts include |SSL_AD_CERTIFICATE_EXPIRED|,
+ * |SSL_AD_CERTIFICATE_REVOKED|, |SSL_AD_UNKNOWN_CA|, |SSL_AD_BAD_CERTIFICATE|,
+ * |SSL_AD_CERTIFICATE_UNKNOWN|, and |SSL_AD_INTERNAL_ERROR|. See RFC 5246
+ * section 7.2.2 for their precise meanings. If unspecified,
+ * |SSL_AD_CERTIFICATE_UNKNOWN| will be sent by default.
+ *
+ * To verify a certificate asynchronously, the callback may return
+ * |ssl_verify_retry|. The handshake will then pause with |SSL_get_error|
+ * returning |SSL_ERROR_WANT_CERTIFICATE_VERIFY|. */
+OPENSSL_EXPORT void SSL_CTX_set_custom_verify(
+    SSL_CTX *ctx, int mode,
+    enum ssl_verify_result_t (*callback)(SSL *ssl, uint8_t *out_alert));
+
+/* SSL_set_custom_verify behaves like |SSL_CTX_set_custom_verify| but configures
+ * an individual |SSL|. */
+OPENSSL_EXPORT void SSL_set_custom_verify(
+    SSL *ssl, int mode,
+    enum ssl_verify_result_t (*callback)(SSL *ssl, uint8_t *out_alert));
+
 /* SSL_CTX_get_verify_mode returns |ctx|'s verify mode, set by
  * |SSL_CTX_set_verify|. */
 OPENSSL_EXPORT int SSL_CTX_get_verify_mode(const SSL_CTX *ctx);
@@ -2320,13 +2360,6 @@
     SSL_CTX *ctx, int (*callback)(X509_STORE_CTX *store_ctx, void *arg),
     void *arg);
 
-/* SSL_CTX_i_promise_to_verify_certs_after_the_handshake indicates that the
- * caller understands that the |CRYPTO_BUFFER|-based methods currently require
- * post-handshake verification of certificates and thus it's ok to accept any
- * certificates during the handshake. */
-OPENSSL_EXPORT void SSL_CTX_i_promise_to_verify_certs_after_the_handshake(
-    SSL_CTX *ctx);
-
 /* SSL_enable_signed_cert_timestamps causes |ssl| (which must be the client end
  * of a connection) to request SCTs from the server. See
  * https://tools.ietf.org/html/rfc6962.
@@ -2407,6 +2440,18 @@
 OPENSSL_EXPORT void SSL_CTX_set_client_CA_list(SSL_CTX *ctx,
                                                STACK_OF(X509_NAME) *name_list);
 
+/* SSL_set0_client_CAs sets |ssl|'s client certificate CA list to |name_list|,
+ * which should contain DER-encoded distinguished names (RFC 5280). It takes
+ * ownership of |name_list|. */
+OPENSSL_EXPORT void SSL_set0_client_CAs(SSL *ssl,
+                                        STACK_OF(CRYPTO_BUFFER) *name_list);
+
+/* SSL_CTX_set0_client_CAs sets |ctx|'s client certificate CA list to
+ * |name_list|, which should contain DER-encoded distinguished names (RFC 5280).
+ * It takes ownership of |name_list|. */
+OPENSSL_EXPORT void SSL_CTX_set0_client_CAs(SSL_CTX *ctx,
+                                            STACK_OF(CRYPTO_BUFFER) *name_list);
+
 /* SSL_get_client_CA_list returns |ssl|'s client certificate CA list. If |ssl|
  * has not been configured as a client, this is the list configured by
  * |SSL_CTX_set_client_CA_list|.
@@ -3141,6 +3186,7 @@
   tls13_default = 0,
   tls13_experiment = 1,
   tls13_record_type_experiment = 2,
+  tls13_no_session_id_experiment = 3,
 };
 
 /* SSL_CTX_set_tls13_variant sets which variant of TLS 1.3 we negotiate. On the
@@ -3748,6 +3794,7 @@
 #define SSL_PRIVATE_KEY_OPERATION 9
 #define SSL_PENDING_TICKET 10
 #define SSL_EARLY_DATA_REJECTED 11
+#define SSL_CERTIFICATE_VERIFY 12
 
 /* SSL_want returns one of the above values to determine what the most recent
  * operation on |ssl| was blocked on. Use |SSL_get_error| instead. */
@@ -3936,9 +3983,13 @@
  * This structures are exposed for historical reasons, but access to them is
  * deprecated. */
 
+/* TODO(davidben): Opaquify most or all of |SSL_CTX| and |SSL_SESSION| so these
+ * forward declarations are not needed. */
 typedef struct ssl_protocol_method_st SSL_PROTOCOL_METHOD;
 typedef struct ssl_x509_method_st SSL_X509_METHOD;
 
+DECLARE_STACK_OF(SSL_CUSTOM_EXTENSION)
+
 struct ssl_cipher_st {
   /* name is the OpenSSL name for the cipher. */
   const char *name;
@@ -4122,8 +4173,6 @@
   uint8_t *in_group_flags;
 };
 
-DECLARE_STACK_OF(SSL_CUSTOM_EXTENSION)
-
 /* ssl_ctx_st (aka |SSL_CTX|) contains configuration common to several SSL
  * connections. */
 struct ssl_ctx_st {
@@ -4194,6 +4243,9 @@
   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;
 
@@ -4232,7 +4284,7 @@
   uint32_t mode;
   uint32_t max_cert_list;
 
-  struct cert_st /* CERT */ *cert;
+  struct cert_st *cert;
 
   /* callback that allows applications to peek at protocol messages */
   void (*msg_callback)(int write_p, int version, int content_type,
@@ -4374,12 +4426,6 @@
    * otherwise. */
   unsigned grease_enabled:1;
 
-  /* i_promise_to_verify_certs_after_the_handshake indicates that the
-   * application is using the |CRYPTO_BUFFER|-based methods and understands
-   * that this currently requires post-handshake verification of
-   * certificates. */
-  unsigned i_promise_to_verify_certs_after_the_handshake:1;
-
   /* allow_unknown_alpn_protos is one if the client allows unsolicited ALPN
    * protocols from the peer. */
   unsigned allow_unknown_alpn_protos:1;
diff --git a/src/include/openssl/ssl3.h b/src/include/openssl/ssl3.h
index 2b241ba..39cd07b 100644
--- a/src/include/openssl/ssl3.h
+++ b/src/include/openssl/ssl3.h
@@ -334,6 +334,7 @@
 /* server */
 /* extra state */
 #define SSL3_ST_SW_FLUSH (0x100 | SSL_ST_ACCEPT)
+#define SSL3_ST_VERIFY_CLIENT_CERT (0x101 | SSL_ST_ACCEPT)
 /* read from client */
 #define SSL3_ST_SR_CLNT_HELLO_A (0x110 | SSL_ST_ACCEPT)
 #define SSL3_ST_SR_CLNT_HELLO_B (0x111 | SSL_ST_ACCEPT)
diff --git a/src/include/openssl/type_check.h b/src/include/openssl/type_check.h
index 7e70918..a6f8284 100644
--- a/src/include/openssl/type_check.h
+++ b/src/include/openssl/type_check.h
@@ -78,13 +78,9 @@
 
 #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
 #define OPENSSL_COMPILE_ASSERT(cond, msg) _Static_assert(cond, #msg)
-#elif defined(__GNUC__)
-#define OPENSSL_COMPILE_ASSERT(cond, msg)                      \
-  typedef char OPENSSL_COMPILE_ASSERT_##msg[((cond) ? 1 : -1)] \
-      __attribute__((unused))
 #else
 #define OPENSSL_COMPILE_ASSERT(cond, msg) \
-  typedef char OPENSSL_COMPILE_ASSERT_##msg[((cond) ? 1 : -1)]
+  typedef char OPENSSL_COMPILE_ASSERT_##msg[((cond) ? 1 : -1)] OPENSSL_UNUSED
 #endif
 
 
diff --git a/src/ssl/CMakeLists.txt b/src/ssl/CMakeLists.txt
index b6f4451..c228f4a 100644
--- a/src/ssl/CMakeLists.txt
+++ b/src/ssl/CMakeLists.txt
@@ -21,8 +21,8 @@
   ssl_buffer.cc
   ssl_cert.cc
   ssl_cipher.cc
-  ssl_ecdh.cc
   ssl_file.cc
+  ssl_key_share.cc
   ssl_lib.cc
   ssl_privkey.cc
   ssl_session.cc
diff --git a/src/ssl/custom_extensions.cc b/src/ssl/custom_extensions.cc
index f438f73..d86bd48 100644
--- a/src/ssl/custom_extensions.cc
+++ b/src/ssl/custom_extensions.cc
@@ -25,6 +25,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 void SSL_CUSTOM_EXTENSION_free(SSL_CUSTOM_EXTENSION *custom_extension) {
   OPENSSL_free(custom_extension);
 }
@@ -246,6 +248,10 @@
   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,
diff --git a/src/ssl/d1_both.cc b/src/ssl/d1_both.cc
index ee0ec4f..50cca83 100644
--- a/src/ssl/d1_both.cc
+++ b/src/ssl/d1_both.cc
@@ -127,6 +127,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 /* TODO(davidben): 28 comes from the size of IP + UDP header. Is this reasonable
  * for these values? Notably, why is kMinMTU a function of the transport
  * protocol's overhead rather than, say, what's needed to hold a minimally-sized
@@ -152,6 +154,7 @@
 }
 
 static hm_fragment *dtls1_hm_fragment_new(const struct hm_header_st *msg_hdr) {
+  ScopedCBB cbb;
   hm_fragment *frag = (hm_fragment *)OPENSSL_malloc(sizeof(hm_fragment));
   if (frag == NULL) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
@@ -170,15 +173,13 @@
     goto err;
   }
 
-  CBB cbb;
-  if (!CBB_init_fixed(&cbb, frag->data, DTLS1_HM_HEADER_LENGTH) ||
-      !CBB_add_u8(&cbb, msg_hdr->type) ||
-      !CBB_add_u24(&cbb, msg_hdr->msg_len) ||
-      !CBB_add_u16(&cbb, msg_hdr->seq) ||
-      !CBB_add_u24(&cbb, 0 /* frag_off */) ||
-      !CBB_add_u24(&cbb, msg_hdr->msg_len) ||
-      !CBB_finish(&cbb, NULL, NULL)) {
-    CBB_cleanup(&cbb);
+  if (!CBB_init_fixed(cbb.get(), frag->data, DTLS1_HM_HEADER_LENGTH) ||
+      !CBB_add_u8(cbb.get(), msg_hdr->type) ||
+      !CBB_add_u24(cbb.get(), msg_hdr->msg_len) ||
+      !CBB_add_u16(cbb.get(), msg_hdr->seq) ||
+      !CBB_add_u24(cbb.get(), 0 /* frag_off */) ||
+      !CBB_add_u24(cbb.get(), msg_hdr->msg_len) ||
+      !CBB_finish(cbb.get(), NULL, NULL)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     goto err;
   }
@@ -320,9 +321,9 @@
    * the unencrypted epoch (we never renegotiate). Other cases fall through and
    * fail with a fatal error. */
   if ((rr->type == SSL3_RT_APPLICATION_DATA &&
-       ssl->s3->aead_read_ctx != NULL) ||
+       !ssl->s3->aead_read_ctx->is_null_cipher()) ||
       (rr->type == SSL3_RT_CHANGE_CIPHER_SPEC &&
-       ssl->s3->aead_read_ctx == NULL)) {
+       ssl->s3->aead_read_ctx->is_null_cipher())) {
     rr->length = 0;
     goto start;
   }
@@ -548,10 +549,10 @@
   }
 
   if (!is_ccs) {
-    /* TODO(svaldez): Move this up a layer to fix abstraction for SSL_TRANSCRIPT
+    /* TODO(svaldez): Move this up a layer to fix abstraction for SSLTranscript
      * on hs. */
     if (ssl->s3->hs != NULL &&
-        !SSL_TRANSCRIPT_update(&ssl->s3->hs->transcript, data, len)) {
+        !ssl->s3->hs->transcript.Update(data, len)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       OPENSSL_free(data);
       return 0;
@@ -624,14 +625,14 @@
   assert(ssl->d1->outgoing_written < ssl->d1->outgoing_messages_len);
   assert(msg == &ssl->d1->outgoing_messages[ssl->d1->outgoing_written]);
 
-  /* DTLS renegotiation is unsupported, so only epochs 0 (NULL cipher) and 1
-   * (negotiated cipher) exist. */
-  assert(ssl->d1->w_epoch == 0 || ssl->d1->w_epoch == 1);
-  assert(msg->epoch <= ssl->d1->w_epoch);
   enum dtls1_use_epoch_t use_epoch = dtls1_use_current_epoch;
-  if (ssl->d1->w_epoch == 1 && msg->epoch == 0) {
+  if (ssl->d1->w_epoch >= 1 && msg->epoch == ssl->d1->w_epoch - 1) {
     use_epoch = dtls1_use_previous_epoch;
+  } else if (msg->epoch != ssl->d1->w_epoch) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return seal_error;
   }
+
   size_t overhead = dtls_max_seal_overhead(ssl, use_epoch);
   size_t prefix = dtls_seal_prefix_len(ssl, use_epoch);
 
@@ -677,18 +678,17 @@
   }
 
   /* Assemble a fragment, to be sealed in-place. */
-  CBB cbb;
+  ScopedCBB cbb;
   uint8_t *frag = out + prefix;
   size_t max_frag = max_out - prefix, frag_len;
-  if (!CBB_init_fixed(&cbb, frag, max_frag) ||
-      !CBB_add_u8(&cbb, hdr.type) ||
-      !CBB_add_u24(&cbb, hdr.msg_len) ||
-      !CBB_add_u16(&cbb, hdr.seq) ||
-      !CBB_add_u24(&cbb, ssl->d1->outgoing_offset) ||
-      !CBB_add_u24(&cbb, todo) ||
-      !CBB_add_bytes(&cbb, CBS_data(&body), todo) ||
-      !CBB_finish(&cbb, NULL, &frag_len)) {
-    CBB_cleanup(&cbb);
+  if (!CBB_init_fixed(cbb.get(), frag, max_frag) ||
+      !CBB_add_u8(cbb.get(), hdr.type) ||
+      !CBB_add_u24(cbb.get(), hdr.msg_len) ||
+      !CBB_add_u16(cbb.get(), hdr.seq) ||
+      !CBB_add_u24(cbb.get(), ssl->d1->outgoing_offset) ||
+      !CBB_add_u24(cbb.get(), todo) ||
+      !CBB_add_bytes(cbb.get(), CBS_data(&body), todo) ||
+      !CBB_finish(cbb.get(), NULL, &frag_len)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return seal_error;
   }
@@ -812,3 +812,5 @@
 unsigned int dtls1_min_mtu(void) {
   return kMinMTU;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/d1_lib.cc b/src/ssl/d1_lib.cc
index 0074855..8ef1aa2 100644
--- a/src/ssl/d1_lib.cc
+++ b/src/ssl/d1_lib.cc
@@ -68,6 +68,7 @@
 #include "internal.h"
 
 
+namespace bssl {
 
 /* DTLS1_MTU_TIMEOUTS is the maximum number of timeouts to expire
  * before starting to decrease the MTU. */
@@ -108,15 +109,12 @@
 
   dtls_clear_incoming_messages(ssl);
   dtls_clear_outgoing_messages(ssl);
+  Delete(ssl->d1->last_aead_write_ctx);
 
   OPENSSL_free(ssl->d1);
   ssl->d1 = NULL;
 }
 
-void DTLSv1_set_initial_timeout_duration(SSL *ssl, unsigned int duration_ms) {
-  ssl->initial_timeout_duration_ms = duration_ms;
-}
-
 void dtls1_start_timer(SSL *ssl) {
   /* If timer is not set, initialize duration (by default, 1 second) */
   if (ssl->d1->next_timeout.tv_sec == 0 && ssl->d1->next_timeout.tv_usec == 0) {
@@ -135,56 +133,6 @@
   }
 }
 
-int DTLSv1_get_timeout(const SSL *ssl, struct timeval *out) {
-  if (!SSL_is_dtls(ssl)) {
-    return 0;
-  }
-
-  /* If no timeout is set, just return NULL */
-  if (ssl->d1->next_timeout.tv_sec == 0 && ssl->d1->next_timeout.tv_usec == 0) {
-    return 0;
-  }
-
-  struct OPENSSL_timeval timenow;
-  ssl_get_current_time(ssl, &timenow);
-
-  /* If timer already expired, set remaining time to 0 */
-  if (ssl->d1->next_timeout.tv_sec < timenow.tv_sec ||
-      (ssl->d1->next_timeout.tv_sec == timenow.tv_sec &&
-       ssl->d1->next_timeout.tv_usec <= timenow.tv_usec)) {
-    OPENSSL_memset(out, 0, sizeof(*out));
-    return 1;
-  }
-
-  /* Calculate time left until timer expires */
-  struct OPENSSL_timeval ret;
-  OPENSSL_memcpy(&ret, &ssl->d1->next_timeout, sizeof(ret));
-  ret.tv_sec -= timenow.tv_sec;
-  if (ret.tv_usec >= timenow.tv_usec) {
-    ret.tv_usec -= timenow.tv_usec;
-  } else {
-    ret.tv_usec = 1000000 + ret.tv_usec - timenow.tv_usec;
-    ret.tv_sec--;
-  }
-
-  /* If remaining time is less than 15 ms, set it to 0 to prevent issues
-   * because of small divergences with socket timeouts. */
-  if (ret.tv_sec == 0 && ret.tv_usec < 15000) {
-    OPENSSL_memset(&ret, 0, sizeof(ret));
-  }
-
-  /* Clamp the result in case of overflow. */
-  if (ret.tv_sec > INT_MAX) {
-    assert(0);
-    out->tv_sec = INT_MAX;
-  } else {
-    out->tv_sec = ret.tv_sec;
-  }
-
-  out->tv_usec = ret.tv_usec;
-  return 1;
-}
-
 int dtls1_is_timer_expired(SSL *ssl) {
   struct timeval timeleft;
 
@@ -241,6 +189,64 @@
   return 0;
 }
 
+}  // namespace bssl
+
+using namespace bssl;
+
+void DTLSv1_set_initial_timeout_duration(SSL *ssl, unsigned int duration_ms) {
+  ssl->initial_timeout_duration_ms = duration_ms;
+}
+
+int DTLSv1_get_timeout(const SSL *ssl, struct timeval *out) {
+  if (!SSL_is_dtls(ssl)) {
+    return 0;
+  }
+
+  /* If no timeout is set, just return NULL */
+  if (ssl->d1->next_timeout.tv_sec == 0 && ssl->d1->next_timeout.tv_usec == 0) {
+    return 0;
+  }
+
+  struct OPENSSL_timeval timenow;
+  ssl_get_current_time(ssl, &timenow);
+
+  /* If timer already expired, set remaining time to 0 */
+  if (ssl->d1->next_timeout.tv_sec < timenow.tv_sec ||
+      (ssl->d1->next_timeout.tv_sec == timenow.tv_sec &&
+       ssl->d1->next_timeout.tv_usec <= timenow.tv_usec)) {
+    OPENSSL_memset(out, 0, sizeof(*out));
+    return 1;
+  }
+
+  /* Calculate time left until timer expires */
+  struct OPENSSL_timeval ret;
+  OPENSSL_memcpy(&ret, &ssl->d1->next_timeout, sizeof(ret));
+  ret.tv_sec -= timenow.tv_sec;
+  if (ret.tv_usec >= timenow.tv_usec) {
+    ret.tv_usec -= timenow.tv_usec;
+  } else {
+    ret.tv_usec = 1000000 + ret.tv_usec - timenow.tv_usec;
+    ret.tv_sec--;
+  }
+
+  /* If remaining time is less than 15 ms, set it to 0 to prevent issues
+   * because of small divergences with socket timeouts. */
+  if (ret.tv_sec == 0 && ret.tv_usec < 15000) {
+    OPENSSL_memset(&ret, 0, sizeof(ret));
+  }
+
+  /* Clamp the result in case of overflow. */
+  if (ret.tv_sec > INT_MAX) {
+    assert(0);
+    out->tv_sec = INT_MAX;
+  } else {
+    out->tv_sec = ret.tv_sec;
+  }
+
+  out->tv_usec = ret.tv_usec;
+  return 1;
+}
+
 int DTLSv1_handle_timeout(SSL *ssl) {
   ssl_reset_error_state(ssl);
 
diff --git a/src/ssl/d1_pkt.cc b/src/ssl/d1_pkt.cc
index 1ae55eb..52e8111 100644
--- a/src/ssl/d1_pkt.cc
+++ b/src/ssl/d1_pkt.cc
@@ -126,6 +126,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 int dtls1_get_record(SSL *ssl) {
 again:
   switch (ssl->s3->recv_shutdown) {
@@ -415,3 +417,5 @@
 
   return 1;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/d1_srtp.cc b/src/ssl/d1_srtp.cc
index 1085377..2d94bd2 100644
--- a/src/ssl/d1_srtp.cc
+++ b/src/ssl/d1_srtp.cc
@@ -124,6 +124,8 @@
 #include "internal.h"
 
 
+using namespace bssl;
+
 static const SRTP_PROTECTION_PROFILE kSRTPProfiles[] = {
     {
         "SRTP_AES128_CM_SHA1_80", SRTP_AES128_CM_SHA1_80,
@@ -143,9 +145,7 @@
 static int find_profile_by_name(const char *profile_name,
                                 const SRTP_PROTECTION_PROFILE **pptr,
                                 size_t len) {
-  const SRTP_PROTECTION_PROFILE *p;
-
-  p = kSRTPProfiles;
+  const SRTP_PROTECTION_PROFILE *p = kSRTPProfiles;
   while (p->name) {
     if (len == strlen(p->name) && !strncmp(p->name, profile_name, len)) {
       *pptr = p;
diff --git a/src/ssl/dtls_method.cc b/src/ssl/dtls_method.cc
index dd8d786..15c4608 100644
--- a/src/ssl/dtls_method.cc
+++ b/src/ssl/dtls_method.cc
@@ -66,6 +66,8 @@
 #include "internal.h"
 
 
+using namespace bssl;
+
 static int dtls1_supports_cipher(const SSL_CIPHER *cipher) {
   return cipher->algorithm_enc != SSL_eNULL;
 }
@@ -74,12 +76,11 @@
 
 static void dtls1_received_flight(SSL *ssl) { dtls1_stop_timer(ssl); }
 
-static int dtls1_set_read_state(SSL *ssl, SSL_AEAD_CTX *aead_ctx) {
+static int dtls1_set_read_state(SSL *ssl, UniquePtr<SSLAEADContext> aead_ctx) {
   /* Cipher changes are illegal when there are buffered incoming messages. */
   if (dtls_has_incoming_messages(ssl)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFERED_MESSAGES_ON_CIPHER_CHANGE);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-    SSL_AEAD_CTX_free(aead_ctx);
     return 0;
   }
 
@@ -87,19 +88,20 @@
   OPENSSL_memset(&ssl->d1->bitmap, 0, sizeof(ssl->d1->bitmap));
   OPENSSL_memset(ssl->s3->read_sequence, 0, sizeof(ssl->s3->read_sequence));
 
-  SSL_AEAD_CTX_free(ssl->s3->aead_read_ctx);
-  ssl->s3->aead_read_ctx = aead_ctx;
+  Delete(ssl->s3->aead_read_ctx);
+  ssl->s3->aead_read_ctx = aead_ctx.release();
   return 1;
 }
 
-static int dtls1_set_write_state(SSL *ssl, SSL_AEAD_CTX *aead_ctx) {
+static int dtls1_set_write_state(SSL *ssl, UniquePtr<SSLAEADContext> aead_ctx) {
   ssl->d1->w_epoch++;
   OPENSSL_memcpy(ssl->d1->last_write_sequence, ssl->s3->write_sequence,
                  sizeof(ssl->s3->write_sequence));
   OPENSSL_memset(ssl->s3->write_sequence, 0, sizeof(ssl->s3->write_sequence));
 
-  SSL_AEAD_CTX_free(ssl->s3->aead_write_ctx);
-  ssl->s3->aead_write_ctx = aead_ctx;
+  Delete(ssl->d1->last_aead_write_ctx);
+  ssl->d1->last_aead_write_ctx = ssl->s3->aead_write_ctx;
+  ssl->s3->aead_write_ctx = aead_ctx.release();
   return 1;
 }
 
diff --git a/src/ssl/dtls_record.cc b/src/ssl/dtls_record.cc
index 879706d..c7ee646 100644
--- a/src/ssl/dtls_record.cc
+++ b/src/ssl/dtls_record.cc
@@ -121,6 +121,8 @@
 #include "../crypto/internal.h"
 
 
+namespace bssl {
+
 /* to_u64_be treats |in| as a 8-byte big-endian integer and returns the value as
  * a |uint64_t|. */
 static uint64_t to_u64_be(const uint8_t in[8]) {
@@ -213,8 +215,9 @@
   }
 
   /* Decrypt the body in-place. */
-  if (!SSL_AEAD_CTX_open(ssl->s3->aead_read_ctx, out, type, version, sequence,
-                         (uint8_t *)CBS_data(&body), CBS_len(&body))) {
+  if (!ssl->s3->aead_read_ctx->Open(out, type, version, sequence,
+                                    (uint8_t *)CBS_data(&body),
+                                    CBS_len(&body))) {
     /* Bad packets are silently dropped in DTLS. See section 4.2.1 of RFC 6347.
      * Clear the error queue of any errors decryption may have added. Drop the
      * entire packet as it must not have come from the peer.
@@ -249,13 +252,11 @@
   return ssl_open_record_success;
 }
 
-static const SSL_AEAD_CTX *get_write_aead(const SSL *ssl,
-                                          enum dtls1_use_epoch_t use_epoch) {
+static const SSLAEADContext *get_write_aead(const SSL *ssl,
+                                            enum dtls1_use_epoch_t use_epoch) {
   if (use_epoch == dtls1_use_previous_epoch) {
-    /* DTLS renegotiation is unsupported, so only epochs 0 (NULL cipher) and 1
-     * (negotiated cipher) exist. */
-    assert(ssl->d1->w_epoch == 1);
-    return NULL;
+    assert(ssl->d1->w_epoch >= 1);
+    return ssl->d1->last_aead_write_ctx;
   }
 
   return ssl->s3->aead_write_ctx;
@@ -263,13 +264,12 @@
 
 size_t dtls_max_seal_overhead(const SSL *ssl,
                               enum dtls1_use_epoch_t use_epoch) {
-  return DTLS1_RT_HEADER_LENGTH +
-         SSL_AEAD_CTX_max_overhead(get_write_aead(ssl, use_epoch));
+  return DTLS1_RT_HEADER_LENGTH + get_write_aead(ssl, use_epoch)->MaxOverhead();
 }
 
 size_t dtls_seal_prefix_len(const SSL *ssl, enum dtls1_use_epoch_t use_epoch) {
   return DTLS1_RT_HEADER_LENGTH +
-         SSL_AEAD_CTX_explicit_nonce_len(get_write_aead(ssl, use_epoch));
+         get_write_aead(ssl, use_epoch)->ExplicitNonceLen();
 }
 
 int dtls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
@@ -284,14 +284,12 @@
 
   /* Determine the parameters for the current epoch. */
   uint16_t epoch = ssl->d1->w_epoch;
-  SSL_AEAD_CTX *aead = ssl->s3->aead_write_ctx;
+  SSLAEADContext *aead = ssl->s3->aead_write_ctx;
   uint8_t *seq = ssl->s3->write_sequence;
   if (use_epoch == dtls1_use_previous_epoch) {
-    /* DTLS renegotiation is unsupported, so only epochs 0 (NULL cipher) and 1
-     * (negotiated cipher) exist. */
-    assert(ssl->d1->w_epoch == 1);
+    assert(ssl->d1->w_epoch >= 1);
     epoch = ssl->d1->w_epoch - 1;
-    aead = NULL;
+    aead = ssl->d1->last_aead_write_ctx;
     seq = ssl->d1->last_write_sequence;
   }
 
@@ -311,9 +309,9 @@
   OPENSSL_memcpy(&out[5], &seq[2], 6);
 
   size_t ciphertext_len;
-  if (!SSL_AEAD_CTX_seal(aead, out + DTLS1_RT_HEADER_LENGTH, &ciphertext_len,
-                         max_out - DTLS1_RT_HEADER_LENGTH, type, wire_version,
-                         &out[3] /* seq */, in, in_len) ||
+  if (!aead->Seal(out + DTLS1_RT_HEADER_LENGTH, &ciphertext_len,
+                  max_out - DTLS1_RT_HEADER_LENGTH, type, wire_version,
+                  &out[3] /* seq */, in, in_len) ||
       !ssl_record_sequence_update(&seq[2], 6)) {
     return 0;
   }
@@ -332,3 +330,5 @@
 
   return 1;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/handshake_client.cc b/src/ssl/handshake_client.cc
index 9efbf0a..260d3cd 100644
--- a/src/ssl/handshake_client.cc
+++ b/src/ssl/handshake_client.cc
@@ -152,6 +152,8 @@
 #include <assert.h>
 #include <string.h>
 
+#include <utility>
+
 #include <openssl/aead.h>
 #include <openssl/bn.h>
 #include <openssl/buf.h>
@@ -168,12 +170,13 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 static int ssl3_send_client_hello(SSL_HANDSHAKE *hs);
 static int dtls1_get_hello_verify_request(SSL_HANDSHAKE *hs);
 static int ssl3_get_server_hello(SSL_HANDSHAKE *hs);
 static int ssl3_get_server_certificate(SSL_HANDSHAKE *hs);
 static int ssl3_get_cert_status(SSL_HANDSHAKE *hs);
-static int ssl3_verify_server_cert(SSL_HANDSHAKE *hs);
 static int ssl3_get_server_key_exchange(SSL_HANDSHAKE *hs);
 static int ssl3_get_certificate_request(SSL_HANDSHAKE *hs);
 static int ssl3_get_server_hello_done(SSL_HANDSHAKE *hs);
@@ -246,8 +249,8 @@
         /* Stash the early data session, so connection properties may be queried
          * out of it. */
         hs->in_early_data = 1;
-        hs->early_session = ssl->session;
         SSL_SESSION_up_ref(ssl->session);
+        hs->early_session.reset(ssl->session);
 
         hs->state = SSL3_ST_CR_SRVR_HELLO_A;
         hs->can_early_write = 1;
@@ -292,9 +295,16 @@
 
       case SSL3_ST_VERIFY_SERVER_CERT:
         if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-          ret = ssl3_verify_server_cert(hs);
-          if (ret <= 0) {
-            goto end;
+          switch (ssl_verify_peer_cert(hs)) {
+            case ssl_verify_ok:
+              break;
+            case ssl_verify_invalid:
+              ret = -1;
+              goto end;
+            case ssl_verify_retry:
+              ssl->rwstate = SSL_CERTIFICATE_VERIFY;
+              ret = -1;
+              goto end;
           }
         }
         hs->state = SSL3_ST_CR_KEY_EXCH_A;
@@ -498,15 +508,15 @@
            * of the new established_session due to False Start. The caller may
            * have taken a reference to the temporary session. */
           ssl->s3->established_session =
-              SSL_SESSION_dup(hs->new_session, SSL_SESSION_DUP_ALL);
+              SSL_SESSION_dup(hs->new_session.get(), SSL_SESSION_DUP_ALL)
+                  .release();
           if (ssl->s3->established_session == NULL) {
             ret = -1;
             goto end;
           }
           ssl->s3->established_session->not_resumable = 0;
 
-          SSL_SESSION_free(hs->new_session);
-          hs->new_session = NULL;
+          hs->new_session.reset();
         }
 
         hs->state = SSL_ST_OK;
@@ -541,18 +551,6 @@
   return ret;
 }
 
-uint16_t ssl_get_grease_value(const SSL *ssl, enum ssl_grease_index_t index) {
-  /* Use the client_random for entropy. This both avoids calling |RAND_bytes| on
-   * a single byte repeatedly and ensures the values are deterministic. This
-   * allows the same ClientHello be sent twice for a HelloRetryRequest or the
-   * same group be advertised in both supported_groups and key_shares. */
-  uint16_t ret = ssl->s3->client_random[index];
-  /* This generates a random value of the form 0xωaωa, for all 0 ≤ ω < 16. */
-  ret = (ret & 0xf0) | 0x0a;
-  ret |= ret << 8;
-  return ret;
-}
-
 /* 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,
@@ -647,7 +645,7 @@
 
 int ssl_write_client_hello(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  bssl::ScopedCBB cbb;
+  ScopedCBB cbb;
   CBB body;
   if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CLIENT_HELLO)) {
     return 0;
@@ -717,7 +715,7 @@
   SSL *const ssl = hs->ssl;
   /* The handshake buffer is reset on every ClientHello. Notably, in DTLS, we
    * may send multiple ClientHellos if we receive HelloVerifyRequest. */
-  if (!SSL_TRANSCRIPT_init(&hs->transcript)) {
+  if (!hs->transcript.Init()) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return -1;
   }
@@ -758,7 +756,8 @@
     return -1;
   }
 
-  /* Initialize a random session ID for the experimental TLS 1.3 variant. */
+  /* Initialize a random session ID for the experimental TLS 1.3 variant
+   * requiring a session id. */
   if (ssl->tls13_variant == tls13_experiment) {
     hs->session_id_len = sizeof(hs->session_id);
     if (!RAND_bytes(hs->session_id, hs->session_id_len)) {
@@ -1023,8 +1022,7 @@
 
   /* Now that the cipher is known, initialize the handshake hash and hash the
    * ServerHello. */
-  if (!SSL_TRANSCRIPT_init_hash(&hs->transcript, ssl3_protocol_version(ssl),
-                                c->algorithm_prf) ||
+  if (!hs->transcript.InitHash(ssl3_protocol_version(ssl), c->algorithm_prf) ||
       !ssl_hash_current_message(hs)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
     return -1;
@@ -1035,7 +1033,7 @@
    * buffer may be released. */
   if (ssl->session != NULL ||
       !ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-    SSL_TRANSCRIPT_free_buffer(&hs->transcript);
+    hs->transcript.FreeBuffer();
   }
 
   /* Only the NULL compression algorithm is supported. */
@@ -1090,10 +1088,10 @@
 
   uint8_t alert = SSL_AD_DECODE_ERROR;
   sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
-  EVP_PKEY_free(hs->peer_pubkey);
-  hs->peer_pubkey = NULL;
-  hs->new_session->certs = ssl_parse_cert_chain(&alert, &hs->peer_pubkey, NULL,
-                                                &cbs, ssl->ctx->pool);
+  hs->peer_pubkey.reset();
+  hs->new_session->certs =
+      ssl_parse_cert_chain(&alert, &hs->peer_pubkey, NULL, &cbs, ssl->ctx->pool)
+          .release();
   if (hs->new_session->certs == NULL) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
     return -1;
@@ -1101,14 +1099,14 @@
 
   if (sk_CRYPTO_BUFFER_num(hs->new_session->certs) == 0 ||
       CBS_len(&cbs) != 0 ||
-      !ssl->ctx->x509_method->session_cache_objects(hs->new_session)) {
+      !ssl->ctx->x509_method->session_cache_objects(hs->new_session.get())) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     return -1;
   }
 
   if (!ssl_check_leaf_certificate(
-          hs, hs->peer_pubkey,
+          hs, hs->peer_pubkey.get(),
           sk_CRYPTO_BUFFER_value(hs->new_session->certs, 0))) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
     return -1;
@@ -1185,20 +1183,8 @@
   return 1;
 }
 
-static int ssl3_verify_server_cert(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  if (!ssl->ctx->x509_method->session_verify_cert_chain(hs->new_session, ssl)) {
-    return -1;
-  }
-
-  return 1;
-}
-
 static int ssl3_get_server_key_exchange(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  EC_KEY *ecdh = NULL;
-  EC_POINT *srvr_ecpoint = NULL;
-
   int ret = ssl->method->ssl_get_message(ssl);
   if (ret <= 0) {
     return ret;
@@ -1236,7 +1222,7 @@
                                      &psk_identity_hint)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      goto err;
+      return -1;
     }
 
     /* Store PSK identity hint for later use, hint is used in
@@ -1250,7 +1236,7 @@
         CBS_contains_zero_byte(&psk_identity_hint)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DATA_LENGTH_TOO_LONG);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-      goto err;
+      return -1;
     }
 
     /* Save non-empty identity hints as a C string. Empty identity hints we
@@ -1258,12 +1244,14 @@
      * (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. */
+    char *raw = nullptr;
     if (CBS_len(&psk_identity_hint) != 0 &&
-        !CBS_strdup(&psk_identity_hint, &hs->peer_psk_identity_hint)) {
+        !CBS_strdup(&psk_identity_hint, &raw)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      goto err;
+      return -1;
     }
+    hs->peer_psk_identity_hint.reset(raw);
   }
 
   if (alg_k & SSL_kECDHE) {
@@ -1277,7 +1265,7 @@
         !CBS_get_u8_length_prefixed(&server_key_exchange, &point)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      goto err;
+      return -1;
     }
     hs->new_session->group_id = group_id;
 
@@ -1285,18 +1273,19 @@
     if (!tls1_check_group_id(ssl, group_id)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      goto err;
+      return -1;
     }
 
     /* Initialize ECDH and save the peer public key for later. */
-    if (!SSL_ECDH_CTX_init(&hs->ecdh_ctx, group_id) ||
+    hs->key_share = SSLKeyShare::Create(group_id);
+    if (!hs->key_share ||
         !CBS_stow(&point, &hs->peer_key, &hs->peer_key_len)) {
-      goto err;
+      return -1;
     }
   } else if (!(alg_k & SSL_kPSK)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-    goto err;
+    return -1;
   }
 
   /* At this point, |server_key_exchange| contains the signature, if any, while
@@ -1313,19 +1302,19 @@
       if (!CBS_get_u16(&server_key_exchange, &signature_algorithm)) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-        goto err;
+        return -1;
       }
       uint8_t alert = SSL_AD_DECODE_ERROR;
       if (!tls12_check_peer_sigalg(ssl, &alert, signature_algorithm)) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-        goto err;
+        return -1;
       }
       hs->new_session->peer_signature_algorithm = signature_algorithm;
     } else if (!tls1_get_legacy_signature_algorithm(&signature_algorithm,
-                                                    hs->peer_pubkey)) {
+                                                    hs->peer_pubkey.get())) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_CERTIFICATE);
-      goto err;
+      return -1;
     }
 
     /* The last field in |server_key_exchange| is the signature. */
@@ -1334,26 +1323,29 @@
         CBS_len(&server_key_exchange) != 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      goto err;
+      return -1;
     }
 
-    CBB transcript;
+    ScopedCBB transcript;
     uint8_t *transcript_data;
     size_t transcript_len;
-    if (!CBB_init(&transcript, 2*SSL3_RANDOM_SIZE + CBS_len(&parameter)) ||
-        !CBB_add_bytes(&transcript, ssl->s3->client_random, SSL3_RANDOM_SIZE) ||
-        !CBB_add_bytes(&transcript, ssl->s3->server_random, SSL3_RANDOM_SIZE) ||
-        !CBB_add_bytes(&transcript, CBS_data(&parameter), CBS_len(&parameter)) ||
-        !CBB_finish(&transcript, &transcript_data, &transcript_len)) {
-      CBB_cleanup(&transcript);
+    if (!CBB_init(transcript.get(),
+                  2 * SSL3_RANDOM_SIZE + CBS_len(&parameter)) ||
+        !CBB_add_bytes(transcript.get(), ssl->s3->client_random,
+                       SSL3_RANDOM_SIZE) ||
+        !CBB_add_bytes(transcript.get(), ssl->s3->server_random,
+                       SSL3_RANDOM_SIZE) ||
+        !CBB_add_bytes(transcript.get(), CBS_data(&parameter),
+                       CBS_len(&parameter)) ||
+        !CBB_finish(transcript.get(), &transcript_data, &transcript_len)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      goto err;
+      return -1;
     }
 
     int sig_ok = ssl_public_key_verify(
         ssl, CBS_data(&signature), CBS_len(&signature), signature_algorithm,
-        hs->peer_pubkey, transcript_data, transcript_len);
+        hs->peer_pubkey.get(), transcript_data, transcript_len);
     OPENSSL_free(transcript_data);
 
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
@@ -1364,7 +1356,7 @@
       /* bad signature */
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SIGNATURE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
-      goto err;
+      return -1;
     }
   } else {
     /* PSK ciphers are the only supported certificate-less ciphers. */
@@ -1373,15 +1365,10 @@
     if (CBS_len(&server_key_exchange) > 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_EXTRA_DATA_IN_MESSAGE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      goto err;
+      return -1;
     }
   }
   return 1;
-
-err:
-  EC_POINT_free(srvr_ecpoint);
-  EC_KEY_free(ecdh);
-  return -1;
 }
 
 static int ssl3_get_certificate_request(SSL_HANDSHAKE *hs) {
@@ -1395,7 +1382,7 @@
     ssl->s3->tmp.reuse_message = 1;
     /* If we get here we don't need the handshake buffer as we won't be doing
      * client auth. */
-    SSL_TRANSCRIPT_free_buffer(&hs->transcript);
+    hs->transcript.FreeBuffer();
     return 1;
   }
 
@@ -1432,23 +1419,21 @@
   }
 
   uint8_t alert = SSL_AD_DECODE_ERROR;
-  STACK_OF(CRYPTO_BUFFER) *ca_names =
+  UniquePtr<STACK_OF(CRYPTO_BUFFER)> ca_names =
       ssl_parse_client_CA_list(ssl, &alert, &cbs);
-  if (ca_names == NULL) {
+  if (!ca_names) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
     return -1;
   }
 
   if (CBS_len(&cbs) != 0) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    sk_CRYPTO_BUFFER_pop_free(ca_names, CRYPTO_BUFFER_free);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     return -1;
   }
 
   hs->cert_request = 1;
-  sk_CRYPTO_BUFFER_pop_free(hs->ca_names, CRYPTO_BUFFER_free);
-  hs->ca_names = ca_names;
+  hs->ca_names = std::move(ca_names);
   ssl->ctx->x509_method->hs_flush_cached_ca_names(hs);
   return 1;
 }
@@ -1493,7 +1478,7 @@
 
   if (!ssl_has_certificate(ssl)) {
     /* Without a client certificate, the handshake buffer may be released. */
-    SSL_TRANSCRIPT_free_buffer(&hs->transcript);
+    hs->transcript.FreeBuffer();
 
     /* In SSL 3.0, the Certificate message is replaced with a warning alert. */
     if (ssl->version == SSL3_VERSION) {
@@ -1517,7 +1502,7 @@
 
 static int ssl3_send_client_key_exchange(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  bssl::ScopedCBB cbb;
+  ScopedCBB cbb;
   CBB body;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                  SSL3_MT_CLIENT_KEY_EXCHANGE)) {
@@ -1541,8 +1526,8 @@
     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, identity,
-                                 sizeof(identity), psk, sizeof(psk));
+        ssl->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);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
@@ -1576,7 +1561,7 @@
       goto err;
     }
 
-    RSA *rsa = EVP_PKEY_get0_RSA(hs->peer_pubkey);
+    RSA *rsa = EVP_PKEY_get0_RSA(hs->peer_pubkey.get());
     if (rsa == NULL) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       goto err;
@@ -1615,8 +1600,8 @@
 
     /* Compute the premaster. */
     uint8_t alert = SSL_AD_DECODE_ERROR;
-    if (!SSL_ECDH_CTX_accept(&hs->ecdh_ctx, &child, &pms, &pms_len, &alert,
-                             hs->peer_key, hs->peer_key_len)) {
+    if (!hs->key_share->Accept(&child, &pms, &pms_len, &alert, hs->peer_key,
+                              hs->peer_key_len)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
       goto err;
     }
@@ -1625,7 +1610,7 @@
     }
 
     /* The key exchange state may now be discarded. */
-    SSL_ECDH_CTX_cleanup(&hs->ecdh_ctx);
+    hs->key_share.reset();
     OPENSSL_free(hs->peer_key);
     hs->peer_key = NULL;
     hs->peer_key_len = 0;
@@ -1648,18 +1633,17 @@
   /* For a PSK cipher suite, other_secret is combined with the pre-shared
    * key. */
   if (alg_a & SSL_aPSK) {
-    CBB pms_cbb, child;
+    ScopedCBB pms_cbb;
+    CBB child;
     uint8_t *new_pms;
     size_t new_pms_len;
 
-    CBB_zero(&pms_cbb);
-    if (!CBB_init(&pms_cbb, 2 + psk_len + 2 + pms_len) ||
-        !CBB_add_u16_length_prefixed(&pms_cbb, &child) ||
+    if (!CBB_init(pms_cbb.get(), 2 + psk_len + 2 + pms_len) ||
+        !CBB_add_u16_length_prefixed(pms_cbb.get(), &child) ||
         !CBB_add_bytes(&child, pms, pms_len) ||
-        !CBB_add_u16_length_prefixed(&pms_cbb, &child) ||
+        !CBB_add_u16_length_prefixed(pms_cbb.get(), &child) ||
         !CBB_add_bytes(&child, psk, psk_len) ||
-        !CBB_finish(&pms_cbb, &new_pms, &new_pms_len)) {
-      CBB_cleanup(&pms_cbb);
+        !CBB_finish(pms_cbb.get(), &new_pms, &new_pms_len)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       goto err;
     }
@@ -1698,7 +1682,7 @@
   SSL *const ssl = hs->ssl;
   assert(ssl_has_private_key(ssl));
 
-  bssl::ScopedCBB cbb;
+  ScopedCBB cbb;
   CBB body, child;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                  SSL3_MT_CERTIFICATE_VERIFY)) {
@@ -1718,7 +1702,7 @@
   }
 
   /* Set aside space for the signature. */
-  const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey);
+  const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey.get());
   uint8_t *ptr;
   if (!CBB_add_u16_length_prefixed(&body, &child) ||
       !CBB_reserve(&child, &ptr, max_sig_len)) {
@@ -1736,25 +1720,21 @@
 
     uint8_t digest[EVP_MAX_MD_SIZE];
     size_t digest_len;
-    if (!SSL_TRANSCRIPT_ssl3_cert_verify_hash(&hs->transcript, digest,
-                                              &digest_len, hs->new_session,
-                                              signature_algorithm)) {
+    if (!hs->transcript.GetSSL3CertVerifyHash(
+            digest, &digest_len, hs->new_session.get(), signature_algorithm)) {
       return -1;
     }
 
-    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(ssl->cert->privatekey, NULL);
-    int ok = pctx != NULL &&
-             EVP_PKEY_sign_init(pctx) &&
-             EVP_PKEY_sign(pctx, ptr, &sig_len, digest, digest_len);
-    EVP_PKEY_CTX_free(pctx);
-    if (!ok) {
+    UniquePtr<EVP_PKEY_CTX> pctx(EVP_PKEY_CTX_new(ssl->cert->privatekey, NULL));
+    if (!pctx ||
+        !EVP_PKEY_sign_init(pctx.get()) ||
+        !EVP_PKEY_sign(pctx.get(), ptr, &sig_len, digest, digest_len)) {
       return -1;
     }
   } else {
-    switch (ssl_private_key_sign(hs, ptr, &sig_len, max_sig_len,
-                                 signature_algorithm,
-                                 (const uint8_t *)hs->transcript.buffer->data,
-                                 hs->transcript.buffer->length)) {
+    switch (ssl_private_key_sign(
+        hs, ptr, &sig_len, max_sig_len, signature_algorithm,
+        hs->transcript.buffer_data(), hs->transcript.buffer_len())) {
       case ssl_private_key_success:
         break;
       case ssl_private_key_failure:
@@ -1771,7 +1751,7 @@
   }
 
   /* The handshake buffer is no longer necessary. */
-  SSL_TRANSCRIPT_free_buffer(&hs->transcript);
+  hs->transcript.FreeBuffer();
   return 1;
 }
 
@@ -1780,16 +1760,16 @@
   static const uint8_t kZero[32] = {0};
   size_t padding_len = 32 - ((ssl->s3->next_proto_negotiated_len + 2) % 32);
 
-  CBB cbb, body, child;
-  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_NEXT_PROTO) ||
+  ScopedCBB cbb;
+  CBB body, child;
+  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_NEXT_PROTO) ||
       !CBB_add_u8_length_prefixed(&body, &child) ||
       !CBB_add_bytes(&child, ssl->s3->next_proto_negotiated,
                      ssl->s3->next_proto_negotiated_len) ||
       !CBB_add_u8_length_prefixed(&body, &child) ||
       !CBB_add_bytes(&child, kZero, padding_len) ||
-      !ssl_add_message_cbb(ssl, &cbb)) {
+      !ssl_add_message_cbb(ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    CBB_cleanup(&cbb);
     return -1;
   }
 
@@ -1807,12 +1787,12 @@
     return -1;
   }
 
-  CBB cbb, body;
-  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_CHANNEL_ID) ||
+  ScopedCBB cbb;
+  CBB body;
+  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CHANNEL_ID) ||
       !tls1_write_channel_id(hs, &body) ||
-      !ssl_add_message_cbb(ssl, &cbb)) {
+      !ssl_add_message_cbb(ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    CBB_cleanup(&cbb);
     return -1;
   }
 
@@ -1850,18 +1830,20 @@
     return 1;
   }
 
-  int session_renewed = ssl->session != NULL;
-  SSL_SESSION *session = hs->new_session;
-  if (session_renewed) {
+  SSL_SESSION *session = hs->new_session.get();
+  UniquePtr<SSL_SESSION> renewed_session;
+  if (ssl->session != NULL) {
     /* The server is sending a new ticket for an existing session. Sessions are
      * immutable once established, so duplicate all but the ticket of the
      * existing session. */
-    session = SSL_SESSION_dup(ssl->session, SSL_SESSION_INCLUDE_NONAUTH);
-    if (session == NULL) {
+    renewed_session =
+        SSL_SESSION_dup(ssl->session, SSL_SESSION_INCLUDE_NONAUTH);
+    if (!renewed_session) {
       /* This should never happen. */
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      goto err;
+      return -1;
     }
+    session = renewed_session.get();
   }
 
   /* |tlsext_tick_lifetime_hint| is measured from when the ticket was issued. */
@@ -1869,7 +1851,7 @@
 
   if (!CBS_stow(&ticket, &session->tlsext_tick, &session->tlsext_ticklen)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    goto err;
+    return -1;
   }
   session->tlsext_tick_lifetime_hint = tlsext_tick_lifetime_hint;
 
@@ -1879,20 +1861,16 @@
   if (!EVP_Digest(CBS_data(&ticket), CBS_len(&ticket),
                   session->session_id, &session->session_id_length,
                   EVP_sha256(), NULL)) {
-    goto err;
+    return -1;
   }
 
-  if (session_renewed) {
+  if (renewed_session) {
     session->not_resumable = 0;
     SSL_SESSION_free(ssl->session);
-    ssl->session = session;
+    ssl->session = renewed_session.release();
   }
 
   return 1;
-
-err:
-  if (session_renewed) {
-    SSL_SESSION_free(session);
-  }
-  return -1;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/handshake_server.cc b/src/ssl/handshake_server.cc
index ee5358c..38fbef4 100644
--- a/src/ssl/handshake_server.cc
+++ b/src/ssl/handshake_server.cc
@@ -170,6 +170,8 @@
 #include "../crypto/internal.h"
 
 
+namespace bssl {
+
 static int ssl3_process_client_hello(SSL_HANDSHAKE *hs);
 static int ssl3_select_certificate(SSL_HANDSHAKE *hs);
 static int ssl3_select_parameters(SSL_HANDSHAKE *hs);
@@ -282,6 +284,23 @@
             goto end;
           }
         }
+        hs->state = SSL3_ST_VERIFY_CLIENT_CERT;
+        break;
+
+      case SSL3_ST_VERIFY_CLIENT_CERT:
+        if (sk_CRYPTO_BUFFER_num(hs->new_session->certs) > 0) {
+          switch (ssl_verify_peer_cert(hs)) {
+            case ssl_verify_ok:
+              break;
+            case ssl_verify_invalid:
+              ret = -1;
+              goto end;
+            case ssl_verify_retry:
+              ssl->rwstate = SSL_CERTIFICATE_VERIFY;
+              ret = -1;
+              goto end;
+          }
+        }
         hs->state = SSL3_ST_SR_KEY_EXCH_A;
         break;
 
@@ -411,7 +430,7 @@
             ssl->retain_only_sha256_of_client_certs) {
           sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
           hs->new_session->certs = NULL;
-          ssl->ctx->x509_method->session_clear(hs->new_session);
+          ssl->ctx->x509_method->session_clear(hs->new_session.get());
         }
 
         SSL_SESSION_free(ssl->s3->established_session);
@@ -419,9 +438,8 @@
           SSL_SESSION_up_ref(ssl->session);
           ssl->s3->established_session = ssl->session;
         } else {
-          ssl->s3->established_session = hs->new_session;
+          ssl->s3->established_session = hs->new_session.release();
           ssl->s3->established_session->not_resumable = 0;
-          hs->new_session = NULL;
         }
 
         ssl->s3->initial_handshake_complete = 1;
@@ -586,8 +604,8 @@
   uint32_t mask_a = 0;
 
   if (ssl_has_certificate(ssl)) {
-    mask_a |= ssl_cipher_auth_mask_for_key(hs->local_pubkey);
-    if (EVP_PKEY_id(hs->local_pubkey) == EVP_PKEY_RSA) {
+    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;
     }
   }
@@ -808,12 +826,12 @@
 
   /* Determine whether we are doing session resumption. */
   int tickets_supported = 0, renew_ticket = 0;
-  /* TODO(davidben): Switch |ssl_get_prev_session| to take a |bssl::UniquePtr|
+  /* TODO(davidben): Switch |ssl_get_prev_session| to take a |UniquePtr|
    * output and simplify this. */
   SSL_SESSION *session_raw = nullptr;
   auto session_ret = ssl_get_prev_session(ssl, &session_raw, &tickets_supported,
                                           &renew_ticket, &client_hello);
-  bssl::UniquePtr<SSL_SESSION> session(session_raw);
+  UniquePtr<SSL_SESSION> session(session_raw);
   switch (session_ret) {
     case ssl_session_success:
       break;
@@ -876,7 +894,7 @@
     /* On new sessions, stash the SNI value in the session. */
     if (hs->hostname != NULL) {
       OPENSSL_free(hs->new_session->tlsext_hostname);
-      hs->new_session->tlsext_hostname = BUF_strdup(hs->hostname);
+      hs->new_session->tlsext_hostname = BUF_strdup(hs->hostname.get());
       if (hs->new_session->tlsext_hostname == NULL) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
         return -1;
@@ -912,8 +930,8 @@
 
   /* Now that all parameters are known, initialize the handshake hash and hash
    * the ClientHello. */
-  if (!SSL_TRANSCRIPT_init_hash(&hs->transcript, ssl3_protocol_version(ssl),
-                                hs->new_cipher->algorithm_prf) ||
+  if (!hs->transcript.InitHash(ssl3_protocol_version(ssl),
+                               hs->new_cipher->algorithm_prf) ||
       !ssl_hash_current_message(hs)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
     return -1;
@@ -921,7 +939,7 @@
 
   /* Release the handshake buffer if client authentication isn't required. */
   if (!hs->cert_request) {
-    SSL_TRANSCRIPT_free_buffer(&hs->transcript);
+    hs->transcript.FreeBuffer();
   }
 
   return 1;
@@ -958,13 +976,14 @@
   /* TODO(davidben): Implement the TLS 1.1 and 1.2 downgrade sentinels once TLS
    * 1.3 is finalized and we are not implementing a draft version. */
 
-  const SSL_SESSION *session = hs->new_session;
+  const SSL_SESSION *session = hs->new_session.get();
   if (ssl->session != NULL) {
     session = ssl->session;
   }
 
-  CBB cbb, body, session_id;
-  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_SERVER_HELLO) ||
+  ScopedCBB cbb;
+  CBB body, session_id;
+  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_SERVER_HELLO) ||
       !CBB_add_u16(&body, ssl->version) ||
       !CBB_add_bytes(&body, ssl->s3->server_random, SSL3_RANDOM_SIZE) ||
       !CBB_add_u8_length_prefixed(&body, &session_id) ||
@@ -973,9 +992,8 @@
       !CBB_add_u16(&body, ssl_cipher_get_value(hs->new_cipher)) ||
       !CBB_add_u8(&body, 0 /* no compression */) ||
       !ssl_add_serverhello_tlsext(hs, &body) ||
-      !ssl_add_message_cbb(ssl, &cbb)) {
+      !ssl_add_message_cbb(ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    CBB_cleanup(&cbb);
     return -1;
   }
 
@@ -984,7 +1002,7 @@
 
 static int ssl3_send_server_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  bssl::ScopedCBB cbb;
+  ScopedCBB cbb;
 
   if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
     if (!ssl_has_certificate(ssl)) {
@@ -1049,11 +1067,12 @@
       hs->new_session->group_id = group_id;
 
       /* Set up ECDH, generate a key, and emit the public half. */
-      if (!SSL_ECDH_CTX_init(&hs->ecdh_ctx, group_id) ||
+      hs->key_share = SSLKeyShare::Create(group_id);
+      if (!hs->key_share ||
           !CBB_add_u8(cbb.get(), NAMED_CURVE_TYPE) ||
           !CBB_add_u16(cbb.get(), group_id) ||
           !CBB_add_u8_length_prefixed(cbb.get(), &child) ||
-          !SSL_ECDH_CTX_offer(&hs->ecdh_ctx, &child)) {
+          !hs->key_share->Offer(&child)) {
         return -1;
       }
     } else {
@@ -1070,42 +1089,43 @@
 
 static int ssl3_send_server_key_exchange(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  CBB cbb, body, child;
-  if (!ssl->method->init_message(ssl, &cbb, &body,
+  ScopedCBB cbb;
+  CBB body, child;
+  if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                  SSL3_MT_SERVER_KEY_EXCHANGE) ||
       /* |hs->server_params| contains a prefix for signing. */
       hs->server_params_len < 2 * SSL3_RANDOM_SIZE ||
       !CBB_add_bytes(&body, hs->server_params + 2 * SSL3_RANDOM_SIZE,
                      hs->server_params_len - 2 * SSL3_RANDOM_SIZE)) {
-    goto err;
+    return -1;
   }
 
   /* Add a signature. */
   if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
     if (!ssl_has_private_key(ssl)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      goto err;
+      return -1;
     }
 
     /* Determine the signature algorithm. */
     uint16_t signature_algorithm;
     if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
-      goto err;
+      return -1;
     }
     if (ssl3_protocol_version(ssl) >= TLS1_2_VERSION) {
       if (!CBB_add_u16(&body, signature_algorithm)) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-        goto err;
+        return -1;
       }
     }
 
     /* Add space for the signature. */
-    const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey);
+    const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey.get());
     uint8_t *ptr;
     if (!CBB_add_u16_length_prefixed(&body, &child) ||
         !CBB_reserve(&child, &ptr, max_sig_len)) {
-      goto err;
+      return -1;
     }
 
     size_t sig_len;
@@ -1114,19 +1134,19 @@
                                  hs->server_params_len)) {
       case ssl_private_key_success:
         if (!CBB_did_write(&child, sig_len)) {
-          goto err;
+          return -1;
         }
         break;
       case ssl_private_key_failure:
-        goto err;
+        return -1;
       case ssl_private_key_retry:
         ssl->rwstate = SSL_PRIVATE_KEY_OPERATION;
-        goto err;
+        return -1;
     }
   }
 
-  if (!ssl_add_message_cbb(ssl, &cbb)) {
-    goto err;
+  if (!ssl_add_message_cbb(ssl, cbb.get())) {
+    return -1;
   }
 
   OPENSSL_free(hs->server_params);
@@ -1134,19 +1154,16 @@
   hs->server_params_len = 0;
 
   return 1;
-
-err:
-  CBB_cleanup(&cbb);
-  return -1;
 }
 
 static int ssl3_send_server_hello_done(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  CBB cbb, body;
+  ScopedCBB cbb;
+  CBB body;
 
   if (hs->cert_request) {
     CBB cert_types, sigalgs_cbb;
-    if (!ssl->method->init_message(ssl, &cbb, &body,
+    if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                    SSL3_MT_CERTIFICATE_REQUEST) ||
         !CBB_add_u8_length_prefixed(&body, &cert_types) ||
         !CBB_add_u8(&cert_types, SSL3_CT_RSA_SIGN) ||
@@ -1156,22 +1173,20 @@
          (!CBB_add_u16_length_prefixed(&body, &sigalgs_cbb) ||
           !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb))) ||
         !ssl_add_client_CA_list(ssl, &body) ||
-        !ssl_add_message_cbb(ssl, &cbb)) {
-      goto err;
+        !ssl_add_message_cbb(ssl, cbb.get())) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return -1;
     }
   }
 
-  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_SERVER_HELLO_DONE) ||
-      !ssl_add_message_cbb(ssl, &cbb)) {
-    goto err;
+  if (!ssl->method->init_message(ssl, cbb.get(), &body,
+                                 SSL3_MT_SERVER_HELLO_DONE) ||
+      !ssl_add_message_cbb(ssl, cbb.get())) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return -1;
   }
 
   return 1;
-
-err:
-  OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-  CBB_cleanup(&cbb);
-  return -1;
 }
 
 static int ssl3_get_client_certificate(SSL_HANDSHAKE *hs) {
@@ -1214,21 +1229,22 @@
   CBS_init(&certificate_msg, ssl->init_msg, ssl->init_num);
 
   sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
-  EVP_PKEY_free(hs->peer_pubkey);
-  hs->peer_pubkey = NULL;
+  hs->peer_pubkey.reset();
   uint8_t alert = SSL_AD_DECODE_ERROR;
-  hs->new_session->certs = ssl_parse_cert_chain(
-      &alert, &hs->peer_pubkey,
-      ssl->retain_only_sha256_of_client_certs ? hs->new_session->peer_sha256
-                                              : NULL,
-      &certificate_msg, ssl->ctx->pool);
+  hs->new_session->certs =
+      ssl_parse_cert_chain(&alert, &hs->peer_pubkey,
+                           ssl->retain_only_sha256_of_client_certs
+                               ? hs->new_session->peer_sha256
+                               : NULL,
+                           &certificate_msg, ssl->ctx->pool)
+          .release();
   if (hs->new_session->certs == NULL) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
     return -1;
   }
 
   if (CBS_len(&certificate_msg) != 0 ||
-      !ssl->ctx->x509_method->session_cache_objects(hs->new_session)) {
+      !ssl->ctx->x509_method->session_cache_objects(hs->new_session.get())) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     return -1;
@@ -1236,7 +1252,7 @@
 
   if (sk_CRYPTO_BUFFER_num(hs->new_session->certs) == 0) {
     /* No client certificate so the handshake buffer may be discarded. */
-    SSL_TRANSCRIPT_free_buffer(&hs->transcript);
+    hs->transcript.FreeBuffer();
 
     /* In SSL 3.0, sending no certificate is signaled by omitting the
      * Certificate message. */
@@ -1264,10 +1280,6 @@
     hs->new_session->peer_sha256_valid = 1;
   }
 
-  if (!ssl->ctx->x509_method->session_verify_cert_chain(hs->new_session, ssl)) {
-    return -1;
-  }
-
   return 1;
 }
 
@@ -1337,7 +1349,7 @@
     }
 
     /* Allocate a buffer large enough for an RSA decryption. */
-    const size_t rsa_size = EVP_PKEY_size(hs->local_pubkey);
+    const size_t rsa_size = EVP_PKEY_size(hs->local_pubkey.get());
     decrypt_buf = (uint8_t *)OPENSSL_malloc(rsa_size);
     if (decrypt_buf == NULL) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
@@ -1423,15 +1435,14 @@
 
     /* Compute the premaster. */
     uint8_t alert = SSL_AD_DECODE_ERROR;
-    if (!SSL_ECDH_CTX_finish(&hs->ecdh_ctx, &premaster_secret,
-                             &premaster_secret_len, &alert, CBS_data(&peer_key),
-                             CBS_len(&peer_key))) {
+    if (!hs->key_share->Finish(&premaster_secret, &premaster_secret_len, &alert,
+                               CBS_data(&peer_key), CBS_len(&peer_key))) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
       goto err;
     }
 
     /* The key exchange state may now be discarded. */
-    SSL_ECDH_CTX_cleanup(&hs->ecdh_ctx);
+    hs->key_share.reset();
   } else if (!(alg_k & SSL_kPSK)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
@@ -1474,18 +1485,18 @@
       OPENSSL_memset(premaster_secret, 0, premaster_secret_len);
     }
 
-    CBB new_premaster, child;
+    ScopedCBB new_premaster;
+    CBB child;
     uint8_t *new_data;
     size_t new_len;
-    CBB_zero(&new_premaster);
-    if (!CBB_init(&new_premaster, 2 + psk_len + 2 + premaster_secret_len) ||
-        !CBB_add_u16_length_prefixed(&new_premaster, &child) ||
+    if (!CBB_init(new_premaster.get(),
+                  2 + psk_len + 2 + premaster_secret_len) ||
+        !CBB_add_u16_length_prefixed(new_premaster.get(), &child) ||
         !CBB_add_bytes(&child, premaster_secret, premaster_secret_len) ||
-        !CBB_add_u16_length_prefixed(&new_premaster, &child) ||
+        !CBB_add_u16_length_prefixed(new_premaster.get(), &child) ||
         !CBB_add_bytes(&child, psk, psk_len) ||
-        !CBB_finish(&new_premaster, &new_data, &new_len)) {
+        !CBB_finish(new_premaster.get(), &new_data, &new_len)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      CBB_cleanup(&new_premaster);
       goto err;
     }
 
@@ -1528,8 +1539,8 @@
   /* Only RSA and ECDSA client certificates are supported, so a
    * CertificateVerify is required if and only if there's a client certificate.
    * */
-  if (hs->peer_pubkey == NULL) {
-    SSL_TRANSCRIPT_free_buffer(&hs->transcript);
+  if (!hs->peer_pubkey) {
+    hs->transcript.FreeBuffer();
     return 1;
   }
 
@@ -1559,7 +1570,7 @@
     }
     hs->new_session->peer_signature_algorithm = signature_algorithm;
   } else if (!tls1_get_legacy_signature_algorithm(&signature_algorithm,
-                                                  hs->peer_pubkey)) {
+                                                  hs->peer_pubkey.get())) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_CERTIFICATE);
     return -1;
@@ -1579,23 +1590,22 @@
   if (ssl3_protocol_version(ssl) == SSL3_VERSION) {
     uint8_t digest[EVP_MAX_MD_SIZE];
     size_t digest_len;
-    if (!SSL_TRANSCRIPT_ssl3_cert_verify_hash(&hs->transcript, digest,
-                                              &digest_len, hs->new_session,
-                                              signature_algorithm)) {
+    if (!hs->transcript.GetSSL3CertVerifyHash(
+            digest, &digest_len, hs->new_session.get(), signature_algorithm)) {
       return -1;
     }
 
-    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(hs->peer_pubkey, NULL);
-    sig_ok = pctx != NULL &&
-             EVP_PKEY_verify_init(pctx) &&
-             EVP_PKEY_verify(pctx, CBS_data(&signature), CBS_len(&signature),
-                             digest, digest_len);
-    EVP_PKEY_CTX_free(pctx);
+    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, CBS_data(&signature), CBS_len(&signature), signature_algorithm,
-        hs->peer_pubkey, (const uint8_t *)hs->transcript.buffer->data,
-        hs->transcript.buffer->length);
+        hs->peer_pubkey.get(), hs->transcript.buffer_data(),
+        hs->transcript.buffer_len());
   }
 
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
@@ -1610,7 +1620,7 @@
 
   /* The handshake buffer is no longer necessary, and we may hash the current
    * message.*/
-  SSL_TRANSCRIPT_free_buffer(&hs->transcript);
+  hs->transcript.FreeBuffer();
   if (!ssl_hash_current_message(hs)) {
     return -1;
   }
@@ -1671,33 +1681,31 @@
 
   if (hs->ticket_expected) {
     const SSL_SESSION *session;
-    SSL_SESSION *session_copy = NULL;
+    UniquePtr<SSL_SESSION> session_copy;
     if (ssl->session == NULL) {
       /* Fix the timeout to measure from the ticket issuance time. */
-      ssl_session_rebase_time(ssl, hs->new_session);
-      session = hs->new_session;
+      ssl_session_rebase_time(ssl, hs->new_session.get());
+      session = hs->new_session.get();
     } 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);
-      if (session_copy == NULL) {
+      if (!session_copy) {
         return -1;
       }
 
-      ssl_session_rebase_time(ssl, session_copy);
-      session = session_copy;
+      ssl_session_rebase_time(ssl, session_copy.get());
+      session = session_copy.get();
     }
 
-    CBB cbb, body, ticket;
-    int ok = ssl->method->init_message(ssl, &cbb, &body,
-                                       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_add_message_cbb(ssl, &cbb);
-    SSL_SESSION_free(session_copy);
-    CBB_cleanup(&cbb);
-    if (!ok) {
+    ScopedCBB cbb;
+    CBB body, ticket;
+    if (!ssl->method->init_message(ssl, cbb.get(), &body,
+                                   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_add_message_cbb(ssl, cbb.get())) {
       return -1;
     }
   }
@@ -1709,3 +1717,5 @@
 
   return ssl3_send_finished(hs);
 }
+
+}  // namespace bssl
diff --git a/src/ssl/internal.h b/src/ssl/internal.h
index 6b88070..fb02d35 100644
--- a/src/ssl/internal.h
+++ b/src/ssl/internal.h
@@ -144,7 +144,12 @@
 
 #include <openssl/base.h>
 
+#include <type_traits>
+#include <utility>
+
 #include <openssl/aead.h>
+#include <openssl/err.h>
+#include <openssl/mem.h>
 #include <openssl/ssl.h>
 #include <openssl/stack.h>
 
@@ -158,12 +163,60 @@
 #include <sys/time.h>
 #endif
 
-#if defined(__cplusplus)
-extern "C" {
-#endif
 
+typedef struct cert_st CERT;
 
-typedef struct ssl_handshake_st SSL_HANDSHAKE;
+namespace bssl {
+
+struct SSL_HANDSHAKE;
+
+/* C++ utilities. */
+
+/* New behaves like |new| but uses |OPENSSL_malloc| for memory allocation. It
+ * returns nullptr on allocation error. It only implements single-object
+ * allocation and not new T[n].
+ *
+ * Note: unlike |new|, this does not support non-public constructors. */
+template <typename T, typename... Args>
+T *New(Args &&... args) {
+  T *t = reinterpret_cast<T *>(OPENSSL_malloc(sizeof(T)));
+  if (t == nullptr) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return nullptr;
+  }
+  new (t) T(std::forward<Args>(args)...);
+  return t;
+}
+
+/* Delete behaves like |delete| but uses |OPENSSL_free| to release memory.
+ *
+ * Note: unlike |delete| this does not support non-public destructors. */
+template <typename T>
+void Delete(T *t) {
+  if (t != nullptr) {
+    t->~T();
+    OPENSSL_free(t);
+  }
+}
+
+/* Register all types with non-trivial destructors with |UniquePtr|. Types with
+ * trivial destructors may be C structs which require a |BORINGSSL_MAKE_DELETER|
+ * registration. */
+namespace internal {
+template <typename T>
+struct DeleterImpl<T, typename std::enable_if<
+                          !std::is_trivially_destructible<T>::value>::type> {
+  static void Free(T *t) { Delete(t); }
+};
+}
+
+/* MakeUnique behaves like |std::make_unique| but returns nullptr on allocation
+ * error. */
+template <typename T, typename... Args>
+UniquePtr<T> MakeUnique(Args &&... args) {
+  return UniquePtr<T>(New<T>(std::forward<Args>(args)...));
+}
+
 
 /* Protocol versions.
  *
@@ -312,74 +365,74 @@
 
 /* Transcript layer. */
 
-/* SSL_TRANSCRIPT maintains the handshake transcript as a combination of a
+/* SSLTranscript maintains the handshake transcript as a combination of a
  * buffer and running hash. */
-typedef struct ssl_transcript_st {
-  /* buffer, if non-NULL, contains the handshake transcript. */
-  BUF_MEM *buffer;
+class SSLTranscript {
+ public:
+  SSLTranscript();
+  ~SSLTranscript();
+
+  /* Init initializes the handshake transcript. If called on an existing
+   * transcript, it resets the transcript and hash. It returns true on success
+   * and false on failure. */
+  bool Init();
+
+  /* InitHash initializes the handshake hash based on the PRF and contents of
+   * the handshake transcript. Subsequent calls to |Update| will update the
+   * rolling hash. It returns one on success and zero on failure. It is an error
+   * to call this function after the handshake buffer is released. */
+  bool InitHash(uint16_t version, int algorithm_prf);
+
+  const uint8_t *buffer_data() const {
+    return reinterpret_cast<const uint8_t *>(buffer_->data);
+  }
+  size_t buffer_len() const { return buffer_->length; }
+
+  /* FreeBuffer releases the handshake buffer. Subsequent calls to
+   * |Update| will not update the handshake buffer. */
+  void FreeBuffer();
+
+  /* DigestLen returns the length of the PRF hash. */
+  size_t DigestLen() const;
+
+  /* Digest returns the PRF hash. For TLS 1.1 and below, this is
+   * |EVP_md5_sha1|. */
+  const EVP_MD *Digest() const;
+
+  /* Update adds |in| to the handshake buffer and handshake hash, whichever is
+   * enabled. It returns true on success and false on failure. */
+  bool Update(const uint8_t *in, size_t in_len);
+
+  /* GetHash writes the handshake hash to |out| which must have room for at
+   * least |DigestLen| bytes. On success, it returns true and sets |*out_len| to
+   * 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
+   * on failure. */
+  bool GetFinishedMAC(uint8_t *out, size_t *out_len, const SSL_SESSION *session,
+                      bool from_server, uint16_t version);
+
+ 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. */
-  EVP_MD_CTX 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. */
-  EVP_MD_CTX md5;
-} SSL_TRANSCRIPT;
-
-/* SSL_TRANSCRIPT_init initializes the handshake transcript. If called on an
- * existing transcript, it resets the transcript and hash. It returns one on
- * success and zero on failure. */
-int SSL_TRANSCRIPT_init(SSL_TRANSCRIPT *transcript);
-
-/* SSL_TRANSCRIPT_init_hash initializes the handshake hash based on the PRF and
- * contents of the handshake transcript. Subsequent calls to
- * |SSL_TRANSCRIPT_update| will update the rolling hash. It returns one on
- * success and zero on failure. It is an error to call this function after the
- * handshake buffer is released. */
-int SSL_TRANSCRIPT_init_hash(SSL_TRANSCRIPT *transcript, uint16_t version,
-                             int algorithm_prf);
-
-/* SSL_TRANSCRIPT_cleanup cleans up the hash and transcript. */
-void SSL_TRANSCRIPT_cleanup(SSL_TRANSCRIPT *transcript);
-
-/* SSL_TRANSCRIPT_free_buffer releases the handshake buffer. Subsequent calls to
- * |SSL_TRANSCRIPT_update| will not update the handshake buffer. */
-void SSL_TRANSCRIPT_free_buffer(SSL_TRANSCRIPT *transcript);
-
-/* SSL_TRANSCRIPT_digest_len returns the length of the PRF hash. */
-size_t SSL_TRANSCRIPT_digest_len(const SSL_TRANSCRIPT *transcript);
-
-/* SSL_TRANSCRIPT_md returns the PRF hash. For TLS 1.1 and below, this is
- * |EVP_md5_sha1|. */
-const EVP_MD *SSL_TRANSCRIPT_md(const SSL_TRANSCRIPT *transcript);
-
-/* SSL_TRANSCRIPT_update adds |in| to the handshake buffer and handshake hash,
- * whichever is enabled. It returns one on success and zero on failure. */
-int SSL_TRANSCRIPT_update(SSL_TRANSCRIPT *transcript, const uint8_t *in,
-                          size_t in_len);
-
-/* SSL_TRANSCRIPT_get_hash writes the handshake hash to |out| which must have
- * room for at least |SSL_TRANSCRIPT_digest_len| bytes. On success, it returns
- * one and sets |*out_len| to the number of bytes written. Otherwise, it returns
- * zero. */
-int SSL_TRANSCRIPT_get_hash(const SSL_TRANSCRIPT *transcript, uint8_t *out,
-                            size_t *out_len);
-
-/* SSL_TRANSCRIPT_ssl3_cert_verify_hash 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. */
-int SSL_TRANSCRIPT_ssl3_cert_verify_hash(SSL_TRANSCRIPT *transcript,
-                                         uint8_t *out, size_t *out_len,
-                                         const SSL_SESSION *session,
-                                         int signature_algorithm);
-
-/* SSL_TRANSCRIPT_finish_mac 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 one on success and
- * zero on failure. */
-int SSL_TRANSCRIPT_finish_mac(SSL_TRANSCRIPT *transcript, uint8_t *out,
-                              size_t *out_len, const SSL_SESSION *session,
-                              int from_server, uint16_t version);
+  ScopedEVP_MD_CTX md5_;
+};
 
 /* tls1_prf computes the PRF function for |ssl|. It writes |out_len| bytes to
  * |out|, using |secret| as the secret and |label| as the label. |seed1| and
@@ -393,123 +446,131 @@
 
 /* Encryption layer. */
 
-/* SSL_AEAD_CTX contains information about an AEAD that is being used to encrypt
- * an SSL connection. */
-typedef struct ssl_aead_ctx_st {
-  const SSL_CIPHER *cipher;
-  EVP_AEAD_CTX ctx;
-  /* fixed_nonce contains any bytes of the nonce that are fixed for all
+/* SSLAEADContext contains information about an AEAD that is being used to
+ * encrypt an SSL connection. */
+class SSLAEADContext {
+ public:
+  SSLAEADContext(uint16_t version, const SSL_CIPHER *cipher);
+  ~SSLAEADContext();
+  SSLAEADContext(const SSLAEADContext &&) = delete;
+  SSLAEADContext &operator=(const SSLAEADContext &&) = delete;
+
+  /* CreateNullCipher creates an |SSLAEADContext| for the null cipher. */
+  static UniquePtr<SSLAEADContext> CreateNullCipher();
+
+  /* Create creates an |SSLAEADContext| using the supplied key material. It
+   * returns nullptr on error. Only one of |Open| or |Seal| may be used with the
+   * resulting object, depending on |direction|. |version| is the normalized
+   * protocol version, so DTLS 1.0 is represented as 0x0301, not 0xffef. */
+  static UniquePtr<SSLAEADContext> Create(
+      enum evp_aead_direction_t direction, uint16_t version, int is_dtls,
+      const SSL_CIPHER *cipher, const uint8_t *enc_key, size_t enc_key_len,
+      const uint8_t *mac_key, size_t mac_key_len, const uint8_t *fixed_iv,
+      size_t fixed_iv_len);
+
+  uint16_t version() const { return version_; }
+  const SSL_CIPHER *cipher() const { return cipher_; }
+
+  /* is_null_cipher returns true if this is the null cipher. */
+  bool is_null_cipher() const { return !cipher_; }
+
+  /* ExplicitNonceLen returns the length of the explicit nonce. */
+  size_t ExplicitNonceLen() const;
+
+  /* MaxOverhead returns the maximum overhead of calling |Seal|. */
+  size_t MaxOverhead() const;
+
+  /* MaxSuffixLen returns the maximum suffix length written by |SealScatter|.
+   * |extra_in_len| should equal the argument of the same name passed to
+   * |SealScatter|. */
+  size_t MaxSuffixLen(size_t extra_in_len) const;
+
+  /* Open authenticates and decrypts |in_len| bytes from |in| in-place. On
+   * success, it sets |*out| to the plaintext in |in| and returns true.
+   * Otherwise, it returns false. The output will always be |ExplicitNonceLen|
+   * bytes ahead of |in|. */
+  bool Open(CBS *out, uint8_t type, uint16_t wire_version,
+            const uint8_t seqnum[8], uint8_t *in, size_t in_len);
+
+  /* Seal encrypts and authenticates |in_len| bytes from |in| and writes the
+   * result to |out|. It returns true on success and false on error.
+   *
+   * If |in| and |out| alias then |out| + |ExplicitNonceLen| must be == |in|. */
+  bool Seal(uint8_t *out, size_t *out_len, size_t max_out, uint8_t type,
+            uint16_t wire_version, const uint8_t seqnum[8], const uint8_t *in,
+            size_t in_len);
+
+  /* SealScatter encrypts and authenticates |in_len| bytes from |in| and splits
+   * the result between |out_prefix|, |out| and |out_suffix|. It returns one on
+   * success and zero on error.
+   *
+   * On successful return, exactly |ExplicitNonceLen| bytes are written to
+   * |out_prefix|, |in_len| bytes to |out|, and up to |MaxSuffixLen| bytes to
+   * |out_suffix|. |*out_suffix_len| is set to the actual number of bytes
+   * written to |out_suffix|.
+   *
+   * |extra_in| may point to an additional plaintext buffer. If present,
+   * |extra_in_len| additional bytes are encrypted and authenticated, and the
+   * ciphertext is written to the beginning of |out_suffix|.  |MaxSuffixLen|
+   * may be used to size |out_suffix| accordingly.
+   *
+   * If |in| and |out| alias then |out| must be == |in|. Other arguments may not
+   * alias anything. */
+  bool SealScatter(uint8_t *out_prefix, uint8_t *out, uint8_t *out_suffix,
+                   size_t *out_suffix_len, size_t max_out_suffix_len,
+                   uint8_t type, uint16_t wire_version, const uint8_t seqnum[8],
+                   const uint8_t *in, size_t in_len, const uint8_t *extra_in,
+                   size_t extra_in_len);
+
+  bool GetIV(const uint8_t **out_iv, size_t *out_iv_len) const;
+
+ private:
+  /* GetAdditionalData writes the additional data into |out| and returns the
+   * number of bytes written. */
+  size_t GetAdditionalData(uint8_t out[13], uint8_t type, uint16_t wire_version,
+                           const uint8_t seqnum[8], size_t plaintext_len);
+
+  const SSL_CIPHER *cipher_;
+  ScopedEVP_AEAD_CTX ctx_;
+  /* fixed_nonce_ contains any bytes of the nonce that are fixed for all
    * records. */
-  uint8_t fixed_nonce[12];
-  uint8_t fixed_nonce_len, variable_nonce_len;
-  /* version is the protocol version that should be used with this AEAD. */
-  uint16_t version;
-  /* variable_nonce_included_in_record is non-zero if the variable nonce
+  uint8_t fixed_nonce_[12];
+  uint8_t fixed_nonce_len_ = 0, variable_nonce_len_ = 0;
+  /* version_ is the protocol version that should be used with this AEAD. */
+  uint16_t version_;
+  /* variable_nonce_included_in_record_ is true if the variable nonce
    * for a record is included as a prefix before the ciphertext. */
-  unsigned variable_nonce_included_in_record : 1;
-  /* random_variable_nonce is non-zero if the variable nonce is
+  bool variable_nonce_included_in_record_ : 1;
+  /* random_variable_nonce_ is true if the variable nonce is
    * randomly generated, rather than derived from the sequence
    * number. */
-  unsigned random_variable_nonce : 1;
-  /* omit_length_in_ad is non-zero if the length should be omitted in the
+  bool random_variable_nonce_ : 1;
+  /* omit_length_in_ad_ is true if the length should be omitted in the
    * AEAD's ad parameter. */
-  unsigned omit_length_in_ad : 1;
-  /* omit_version_in_ad is non-zero if the version should be omitted
+  bool omit_length_in_ad_ : 1;
+  /* omit_version_in_ad_ is true if the version should be omitted
    * in the AEAD's ad parameter. */
-  unsigned omit_version_in_ad : 1;
-  /* omit_ad is non-zero if the AEAD's ad parameter should be omitted. */
-  unsigned omit_ad : 1;
-  /* xor_fixed_nonce is non-zero if the fixed nonce should be XOR'd into the
+  bool omit_version_in_ad_ : 1;
+  /* omit_ad_ is true if the AEAD's ad parameter should be omitted. */
+  bool omit_ad_ : 1;
+  /* xor_fixed_nonce_ is true if the fixed nonce should be XOR'd into the
    * variable nonce rather than prepended. */
-  unsigned xor_fixed_nonce : 1;
-} SSL_AEAD_CTX;
-
-/* SSL_AEAD_CTX_new creates a newly-allocated |SSL_AEAD_CTX| using the supplied
- * key material. It returns NULL on error. Only one of |SSL_AEAD_CTX_open| or
- * |SSL_AEAD_CTX_seal| may be used with the resulting object, depending on
- * |direction|. |version| is the normalized protocol version, so DTLS 1.0 is
- * represented as 0x0301, not 0xffef. */
-SSL_AEAD_CTX *SSL_AEAD_CTX_new(enum evp_aead_direction_t direction,
-                               uint16_t version, int is_dtls,
-                               const SSL_CIPHER *cipher, const uint8_t *enc_key,
-                               size_t enc_key_len, const uint8_t *mac_key,
-                               size_t mac_key_len, const uint8_t *fixed_iv,
-                               size_t fixed_iv_len);
-
-/* SSL_AEAD_CTX_free frees |ctx|. */
-void SSL_AEAD_CTX_free(SSL_AEAD_CTX *ctx);
-
-/* SSL_AEAD_CTX_explicit_nonce_len returns the length of the explicit nonce for
- * |ctx|, if any. |ctx| may be NULL to denote the null cipher. */
-size_t SSL_AEAD_CTX_explicit_nonce_len(const SSL_AEAD_CTX *ctx);
-
-/* SSL_AEAD_CTX_max_overhead returns the maximum overhead of calling
- * |SSL_AEAD_CTX_seal|. |ctx| may be NULL to denote the null cipher. */
-size_t SSL_AEAD_CTX_max_overhead(const SSL_AEAD_CTX *ctx);
-
-/* SSL_AEAD_CTX_max_suffix_len returns the maximum suffix length written by
- * |SSL_AEAD_CTX_seal_scatter|. |ctx| may be NULL to denote the null cipher.
- * |extra_in_len| should equal the argument of the same name passed to
- * |SSL_AEAD_CTX_seal_scatter|. */
-size_t SSL_AEAD_CTX_max_suffix_len(const SSL_AEAD_CTX *ctx,
-                                   size_t extra_in_len);
-
-/* SSL_AEAD_CTX_open authenticates and decrypts |in_len| bytes from |in|
- * in-place. On success, it sets |*out| to the plaintext in |in| and returns
- * one. Otherwise, it returns zero. |ctx| may be NULL to denote the null cipher.
- * The output will always be |explicit_nonce_len| bytes ahead of |in|. */
-int SSL_AEAD_CTX_open(SSL_AEAD_CTX *ctx, CBS *out, uint8_t type,
-                      uint16_t wire_version, const uint8_t seqnum[8],
-                      uint8_t *in, size_t in_len);
-
-/* SSL_AEAD_CTX_seal encrypts and authenticates |in_len| bytes from |in| and
- * writes the result to |out|. It returns one on success and zero on
- * error. |ctx| may be NULL to denote the null cipher.
- *
- * If |in| and |out| alias then |out| + |explicit_nonce_len| must be == |in|. */
-int SSL_AEAD_CTX_seal(SSL_AEAD_CTX *ctx, uint8_t *out, size_t *out_len,
-                      size_t max_out, uint8_t type, uint16_t wire_version,
-                      const uint8_t seqnum[8], const uint8_t *in,
-                      size_t in_len);
-
-/* SSL_AEAD_CTX_seal_scatter encrypts and authenticates |in_len| bytes from |in|
- * and splits the result between |out_prefix|, |out| and |out_suffix|. It
- * returns one on success and zero on error. |ctx| may be NULL to denote the
- * null cipher.
- *
- * On successful return, exactly |SSL_AEAD_CTX_explicit_nonce_len| bytes are
- * written to |out_prefix|, |in_len| bytes to |out|, and up to
- * |SSL_AEAD_CTX_max_suffix_len| bytes to |out_suffix|. |*out_suffix_len| is set
- * to the actual number of bytes written to |out_suffix|.
- *
- * |extra_in| may point to an additional plaintext buffer. If present,
- * |extra_in_len| additional bytes are encrypted and authenticated, and the
- * ciphertext is written to the beginning of |out_suffix|.
- * |SSL_AEAD_CTX_max_suffix_len| may be used to size |out_suffix| accordingly.
- *
- * If |in| and |out| alias then |out| must be == |in|. Other arguments may not
- * alias anything. */
-int SSL_AEAD_CTX_seal_scatter(SSL_AEAD_CTX *aead, uint8_t *out_prefix,
-                              uint8_t *out, uint8_t *out_suffix,
-                              size_t *out_suffix_len, size_t max_out_suffix_len,
-                              uint8_t type, uint16_t wire_version,
-                              const uint8_t seqnum[8], const uint8_t *in,
-                              size_t in_len, const uint8_t *extra_in,
-                              size_t extra_in_len);
+  bool xor_fixed_nonce_ : 1;
+};
 
 
 /* DTLS replay bitmap. */
 
 /* DTLS1_BITMAP maintains a sliding window of 64 sequence numbers to detect
  * replayed packets. It should be initialized by zeroing every field. */
-typedef struct dtls1_bitmap_st {
+struct DTLS1_BITMAP {
   /* map is a bit mask of the last 64 sequence numbers. Bit
    * |1<<i| corresponds to |max_seq_num - i|. */
   uint64_t map;
   /* max_seq_num is the largest sequence number seen so far as a 64-bit
    * integer. */
   uint64_t max_seq_num;
-} DTLS1_BITMAP;
+};
 
 
 /* Record layer. */
@@ -658,21 +719,26 @@
 
 /* Custom extensions */
 
-/* ssl_custom_extension (a.k.a. SSL_CUSTOM_EXTENSION) is a structure that
- * contains information about custom-extension callbacks. */
-struct ssl_custom_extension {
+}  // 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;
-};
-
-void SSL_CUSTOM_EXTENSION_free(SSL_CUSTOM_EXTENSION *custom_extension);
+} 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);
@@ -681,48 +747,50 @@
 int custom_ext_add_serverhello(SSL_HANDSHAKE *hs, CBB *extensions);
 
 
-/* ECDH groups. */
+/* Key shares. */
 
-typedef struct ssl_ecdh_ctx_st SSL_ECDH_CTX;
+/* SSLKeyShare abstracts over Diffie-Hellman-like key exchanges. */
+class SSLKeyShare {
+ public:
+  virtual ~SSLKeyShare() {}
 
-/* An SSL_ECDH_METHOD is an implementation of ECDH-like key exchanges for
- * TLS. */
-typedef struct ssl_ecdh_method_st {
-  int nid;
-  uint16_t group_id;
-  const char name[8];
+  /* Create returns a SSLKeyShare instance for use with group |group_id| or
+   * nullptr on error. */
+  static UniquePtr<SSLKeyShare> Create(uint16_t group_id);
 
-  /* cleanup releases state in |ctx|. */
-  void (*cleanup)(SSL_ECDH_CTX *ctx);
+  /* GroupID returns the group ID. */
+  virtual uint16_t GroupID() const = 0;
 
-  /* offer generates a keypair and writes the public value to
-   * |out_public_key|. It returns one on success and zero on error. */
-  int (*offer)(SSL_ECDH_CTX *ctx, CBB *out_public_key);
+  /* Offer generates a keypair and writes the public value to
+   * |out_public_key|. It returns true on success and false on error. */
+  virtual bool Offer(CBB *out_public_key) = 0;
 
-  /* accept performs a key exchange against the |peer_key| generated by |offer|.
-   * On success, it returns one, writes the public value to |out_public_key|,
+  /* Accept performs a key exchange against the |peer_key| generated by |offer|.
+   * On success, it returns true, writes the public value to |out_public_key|,
    * and sets |*out_secret| and |*out_secret_len| to a newly-allocated buffer
    * containing the shared secret. The caller must release this buffer with
-   * |OPENSSL_free|. On failure, it returns zero and sets |*out_alert| to an
-   * alert to send to the peer. */
-  int (*accept)(SSL_ECDH_CTX *ctx, CBB *out_public_key, uint8_t **out_secret,
-                size_t *out_secret_len, uint8_t *out_alert,
-                const uint8_t *peer_key, size_t peer_key_len);
+   * |OPENSSL_free|. On failure, it returns false and sets |*out_alert| to an
+   * alert to send to the peer.
+   *
+   * The default implementation calls |Offer| and then |Finish|, assuming a key
+   * exchange protocol where the peers are symmetric.
+   *
+   * TODO(davidben): out_secret should be a smart pointer. */
+  virtual bool Accept(CBB *out_public_key, uint8_t **out_secret,
+                      size_t *out_secret_len, uint8_t *out_alert,
+                      const uint8_t *peer_key, size_t peer_key_len);
 
-  /* finish performs a key exchange against the |peer_key| generated by
-   * |accept|. On success, it returns one and sets |*out_secret| and
+  /* Finish performs a key exchange against the |peer_key| generated by
+   * |Accept|. On success, it returns true and sets |*out_secret| and
    * |*out_secret_len| to a newly-allocated buffer containing the shared
    * secret. The caller must release this buffer with |OPENSSL_free|. On
    * failure, it returns zero and sets |*out_alert| to an alert to send to the
-   * peer. */
-  int (*finish)(SSL_ECDH_CTX *ctx, uint8_t **out_secret, size_t *out_secret_len,
-                uint8_t *out_alert, const uint8_t *peer_key,
-                size_t peer_key_len);
-} SSL_ECDH_METHOD;
-
-struct ssl_ecdh_ctx_st {
-  const SSL_ECDH_METHOD *method;
-  void *data;
+   * peer.
+   *
+   * TODO(davidben): out_secret should be a smart pointer. */
+  virtual bool Finish(uint8_t **out_secret, size_t *out_secret_len,
+                      uint8_t *out_alert, const uint8_t *peer_key,
+                      size_t peer_key_len) = 0;
 };
 
 /* ssl_nid_to_group_id looks up the group corresponding to |nid|. On success, it
@@ -735,36 +803,6 @@
  * returns one. Otherwise, it returns zero. */
 int ssl_name_to_group_id(uint16_t *out_group_id, const char *name, size_t len);
 
-/* SSL_ECDH_CTX_init sets up |ctx| for use with curve |group_id|. It returns one
- * on success and zero on error. */
-int SSL_ECDH_CTX_init(SSL_ECDH_CTX *ctx, uint16_t group_id);
-
-/* SSL_ECDH_CTX_cleanup releases memory associated with |ctx|. It is legal to
- * call it in the zero state. */
-void SSL_ECDH_CTX_cleanup(SSL_ECDH_CTX *ctx);
-
-/* SSL_ECDH_CTX_get_id returns the group ID for |ctx|. */
-uint16_t SSL_ECDH_CTX_get_id(const SSL_ECDH_CTX *ctx);
-
-/* SSL_ECDH_CTX_get_key calls the |get_key| method of |SSL_ECDH_METHOD|. */
-int SSL_ECDH_CTX_get_key(SSL_ECDH_CTX *ctx, CBS *cbs, CBS *out);
-
-/* SSL_ECDH_CTX_add_key calls the |add_key| method of |SSL_ECDH_METHOD|. */
-int SSL_ECDH_CTX_add_key(SSL_ECDH_CTX *ctx, CBB *cbb, CBB *out_contents);
-
-/* SSL_ECDH_CTX_offer calls the |offer| method of |SSL_ECDH_METHOD|. */
-int SSL_ECDH_CTX_offer(SSL_ECDH_CTX *ctx, CBB *out_public_key);
-
-/* SSL_ECDH_CTX_accept calls the |accept| method of |SSL_ECDH_METHOD|. */
-int SSL_ECDH_CTX_accept(SSL_ECDH_CTX *ctx, CBB *out_public_key,
-                        uint8_t **out_secret, size_t *out_secret_len,
-                        uint8_t *out_alert, const uint8_t *peer_key,
-                        size_t peer_key_len);
-
-/* SSL_ECDH_CTX_finish the |finish| method of |SSL_ECDH_METHOD|. */
-int SSL_ECDH_CTX_finish(SSL_ECDH_CTX *ctx, uint8_t **out_secret,
-                        size_t *out_secret_len, uint8_t *out_alert,
-                        const uint8_t *peer_key, size_t peer_key_len);
 
 /* Handshake messages. */
 
@@ -785,12 +823,12 @@
  * messages ahead of the current message and zero otherwise. */
 int dtls_has_incoming_messages(const SSL *ssl);
 
-typedef struct dtls_outgoing_message_st {
+struct DTLS_OUTGOING_MESSAGE {
   uint8_t *data;
   uint32_t len;
   uint16_t epoch;
   char is_ccs;
-} DTLS_OUTGOING_MESSAGE;
+};
 
 /* dtls_clear_outgoing_messages releases all buffered outgoing messages. */
 void dtls_clear_outgoing_messages(SSL *ssl);
@@ -869,19 +907,17 @@
 
 /* ssl_parse_cert_chain parses a certificate list from |cbs| in the format used
  * by a TLS Certificate message. On success, it returns a newly-allocated
- * |CRYPTO_BUFFER| list and advances |cbs|. Otherwise, it returns NULL and sets
- * |*out_alert| to an alert to send to the peer.
+ * |CRYPTO_BUFFER| list and advances |cbs|. Otherwise, it returns nullptr and
+ * sets |*out_alert| to an alert to send to the peer.
  *
  * If the list is non-empty then |*out_pubkey| will be set to a freshly
  * allocated public-key from the leaf certificate.
  *
  * If the list is non-empty and |out_leaf_sha256| is non-NULL, it writes the
  * SHA-256 hash of the leaf to |out_leaf_sha256|. */
-STACK_OF(CRYPTO_BUFFER) *ssl_parse_cert_chain(uint8_t *out_alert,
-                                              EVP_PKEY **out_pubkey,
-                                              uint8_t *out_leaf_sha256,
-                                              CBS *cbs,
-                                              CRYPTO_BUFFER_POOL *pool);
+UniquePtr<STACK_OF(CRYPTO_BUFFER)> ssl_parse_cert_chain(
+    uint8_t *out_alert, UniquePtr<EVP_PKEY> *out_pubkey,
+    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
@@ -895,16 +931,17 @@
 int ssl_cert_check_digital_signature_key_usage(const CBS *in);
 
 /* ssl_cert_parse_pubkey extracts the public key from the DER-encoded, X.509
- * certificate in |in|. It returns an allocated |EVP_PKEY| or else returns NULL
- * and pushes to the error queue. */
-EVP_PKEY *ssl_cert_parse_pubkey(const CBS *in);
+ * certificate in |in|. It returns an allocated |EVP_PKEY| or else returns
+ * nullptr and pushes to the error queue. */
+UniquePtr<EVP_PKEY> ssl_cert_parse_pubkey(const CBS *in);
 
 /* ssl_parse_client_CA_list parses a CA list from |cbs| in the format used by a
  * TLS CertificateRequest message. On success, it returns a newly-allocated
- * |CRYPTO_BUFFER| list and advances |cbs|. Otherwise, it returns NULL and sets
- * |*out_alert| to an alert to send to the peer. */
-STACK_OF(CRYPTO_BUFFER) *
-    ssl_parse_client_CA_list(SSL *ssl, uint8_t *out_alert, CBS *cbs);
+ * |CRYPTO_BUFFER| list and advances |cbs|. Otherwise, it returns nullptr and
+ * sets |*out_alert| to an alert to send to the peer. */
+UniquePtr<STACK_OF(CRYPTO_BUFFER)> ssl_parse_client_CA_list(SSL *ssl,
+                                                            uint8_t *out_alert,
+                                                            CBS *cbs);
 
 /* 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
@@ -1007,9 +1044,13 @@
   ssl_hs_early_data_rejected,
   ssl_hs_read_end_of_early_data,
   ssl_hs_read_change_cipher_spec,
+  ssl_hs_certificate_verify,
 };
 
-struct ssl_handshake_st {
+struct SSL_HANDSHAKE {
+  explicit SSL_HANDSHAKE(SSL *ssl);
+  ~SSL_HANDSHAKE();
+
   /* ssl is a non-owning pointer to the parent |SSL| object. */
   SSL *ssl;
 
@@ -1020,45 +1061,45 @@
 
   /* wait contains the operation |do_tls13_handshake| is currently blocking on
    * or |ssl_hs_ok| if none. */
-  enum ssl_hs_wait_t wait;
+  enum ssl_hs_wait_t wait = ssl_hs_ok;
 
   /* state contains one of the SSL3_ST_* values. */
-  int state;
+  int state = SSL_ST_INIT;
 
   /* next_state is used when SSL_ST_FLUSH_DATA is entered */
-  int next_state;
+  int next_state = 0;
 
   /* tls13_state is the internal state for the TLS 1.3 handshake. Its values
    * depend on |do_tls13_handshake| but the starting state is always zero. */
-  int tls13_state;
+  int tls13_state = 0;
 
   /* min_version is the minimum accepted protocol version, taking account both
    * |SSL_OP_NO_*| and |SSL_CTX_set_min_proto_version| APIs. */
-  uint16_t min_version;
+  uint16_t min_version = 0;
 
   /* max_version is the maximum accepted protocol version, taking account both
    * |SSL_OP_NO_*| and |SSL_CTX_set_max_proto_version| APIs. */
-  uint16_t max_version;
+  uint16_t max_version = 0;
 
   /* session_id is the session ID in the ClientHello, used for the experimental
    * TLS 1.3 variant. */
-  uint8_t session_id[SSL_MAX_SSL_SESSION_ID_LENGTH];
-  uint8_t session_id_len;
+  uint8_t session_id[SSL_MAX_SSL_SESSION_ID_LENGTH] = {0};
+  uint8_t session_id_len = 0;
 
-  size_t hash_len;
-  uint8_t secret[EVP_MAX_MD_SIZE];
-  uint8_t early_traffic_secret[EVP_MAX_MD_SIZE];
-  uint8_t client_handshake_secret[EVP_MAX_MD_SIZE];
-  uint8_t server_handshake_secret[EVP_MAX_MD_SIZE];
-  uint8_t client_traffic_secret_0[EVP_MAX_MD_SIZE];
-  uint8_t server_traffic_secret_0[EVP_MAX_MD_SIZE];
-  uint8_t expected_client_finished[EVP_MAX_MD_SIZE];
+  size_t hash_len = 0;
+  uint8_t secret[EVP_MAX_MD_SIZE] = {0};
+  uint8_t early_traffic_secret[EVP_MAX_MD_SIZE] = {0};
+  uint8_t client_handshake_secret[EVP_MAX_MD_SIZE] = {0};
+  uint8_t server_handshake_secret[EVP_MAX_MD_SIZE] = {0};
+  uint8_t client_traffic_secret_0[EVP_MAX_MD_SIZE] = {0};
+  uint8_t server_traffic_secret_0[EVP_MAX_MD_SIZE] = {0};
+  uint8_t expected_client_finished[EVP_MAX_MD_SIZE] = {0};
 
   union {
     /* sent is a bitset where the bits correspond to elements of kExtensions
      * in t1_lib.c. Each bit is set if that extension was sent in a
      * ClientHello. It's not used by servers. */
-    uint32_t sent;
+    uint32_t sent = 0;
     /* received is a bitset, like |sent|, but is used by servers to record
      * which extensions were received from a client. */
     uint32_t received;
@@ -1068,7 +1109,7 @@
     /* 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;
+    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|. */
@@ -1077,91 +1118,91 @@
 
   /* retry_group is the group ID selected by the server in HelloRetryRequest in
    * TLS 1.3. */
-  uint16_t retry_group;
+  uint16_t retry_group = 0;
 
-  /* ecdh_ctx is the current ECDH instance. */
-  SSL_ECDH_CTX ecdh_ctx;
+  /* key_share is the current key exchange instance. */
+  UniquePtr<SSLKeyShare> key_share;
 
   /* transcript is the current handshake transcript. */
-  SSL_TRANSCRIPT transcript;
+  SSLTranscript transcript;
 
   /* cookie is the value of the cookie received from the server, if any. */
-  uint8_t *cookie;
-  size_t cookie_len;
+  uint8_t *cookie = nullptr;
+  size_t cookie_len = 0;
 
   /* key_share_bytes is the value of the previously sent KeyShare extension by
    * the client in TLS 1.3. */
-  uint8_t *key_share_bytes;
-  size_t key_share_bytes_len;
+  uint8_t *key_share_bytes = nullptr;
+  size_t key_share_bytes_len = 0;
 
   /* ecdh_public_key, for servers, is the key share to be sent to the client in
    * TLS 1.3. */
-  uint8_t *ecdh_public_key;
-  size_t ecdh_public_key_len;
+  uint8_t *ecdh_public_key = nullptr;
+  size_t ecdh_public_key_len = 0;
 
   /* peer_sigalgs are the signature algorithms that the peer supports. These are
    * taken from the contents of the signature algorithms extension for a server
    * or from the CertificateRequest for a client. */
-  uint16_t *peer_sigalgs;
+  uint16_t *peer_sigalgs = nullptr;
   /* num_peer_sigalgs is the number of entries in |peer_sigalgs|. */
-  size_t num_peer_sigalgs;
+  size_t num_peer_sigalgs = 0;
 
   /* peer_supported_group_list contains the supported group IDs advertised by
    * the peer. This is only set on the server's end. The server does not
    * advertise this extension to the client. */
-  uint16_t *peer_supported_group_list;
-  size_t peer_supported_group_list_len;
+  uint16_t *peer_supported_group_list = nullptr;
+  size_t peer_supported_group_list_len = 0;
 
   /* peer_key is the peer's ECDH key for a TLS 1.2 client. */
-  uint8_t *peer_key;
-  size_t peer_key_len;
+  uint8_t *peer_key = nullptr;
+  size_t peer_key_len = 0;
 
   /* server_params, in a TLS 1.2 server, stores the ServerKeyExchange
    * parameters. It has client and server randoms prepended for signing
    * convenience. */
-  uint8_t *server_params;
-  size_t server_params_len;
+  uint8_t *server_params = nullptr;
+  size_t server_params_len = 0;
 
   /* peer_psk_identity_hint, on the client, is the psk_identity_hint sent by the
    * server when using a TLS 1.2 PSK key exchange. */
-  char *peer_psk_identity_hint;
+  UniquePtr<char> peer_psk_identity_hint;
 
   /* ca_names, on the client, contains the list of CAs received in a
    * CertificateRequest message. */
-  STACK_OF(CRYPTO_BUFFER) *ca_names;
+  UniquePtr<STACK_OF(CRYPTO_BUFFER)> ca_names;
 
   /* cached_x509_ca_names contains a cache of parsed versions of the elements
    * of |ca_names|. */
-  STACK_OF(X509_NAME) *cached_x509_ca_names;
+  STACK_OF(X509_NAME) *cached_x509_ca_names = nullptr;
 
   /* certificate_types, on the client, contains the set of certificate types
    * received in a CertificateRequest message. */
-  uint8_t *certificate_types;
-  size_t num_certificate_types;
+  uint8_t *certificate_types = nullptr;
+  size_t num_certificate_types = 0;
 
   /* hostname, on the server, is the value of the SNI extension. */
-  char *hostname;
+  UniquePtr<char> hostname;
 
   /* local_pubkey is the public key we are authenticating as. */
-  EVP_PKEY *local_pubkey;
+  UniquePtr<EVP_PKEY> local_pubkey;
 
   /* peer_pubkey is the public key parsed from the peer's leaf certificate. */
-  EVP_PKEY *peer_pubkey;
+  UniquePtr<EVP_PKEY> peer_pubkey;
 
   /* new_session is the new mutable session being established by the current
    * handshake. It should not be cached. */
-  SSL_SESSION *new_session;
+  UniquePtr<SSL_SESSION> new_session;
 
   /* early_session is the session corresponding to the current 0-RTT state on
    * the client if |in_early_data| is true. */
-  SSL_SESSION *early_session;
+  UniquePtr<SSL_SESSION> early_session;
 
   /* new_cipher is the cipher being negotiated in this handshake. */
-  const SSL_CIPHER *new_cipher;
+  const SSL_CIPHER *new_cipher = nullptr;
 
   /* key_block is the record-layer key block for TLS 1.2 and earlier. */
-  uint8_t *key_block;
-  uint8_t key_block_len;
+  uint8_t *key_block = nullptr;
+  uint8_t key_block_len = 0;
 
   /* scts_requested is one if the SCT extension is in the ClientHello. */
   unsigned scts_requested:1;
@@ -1227,16 +1268,16 @@
   unsigned pending_private_key_op:1;
 
   /* client_version is the value sent or received in the ClientHello version. */
-  uint16_t client_version;
+  uint16_t client_version = 0;
 
   /* early_data_read is the amount of early data that has been read by the
    * record layer. */
-  uint16_t early_data_read;
+  uint16_t early_data_read = 0;
 
   /* early_data_written is the amount of early data that has been written by the
    * record layer. */
-  uint16_t early_data_written;
-} /* SSL_HANDSHAKE */;
+  uint16_t early_data_written = 0;
+};
 
 SSL_HANDSHAKE *ssl_handshake_new(SSL *ssl);
 
@@ -1326,11 +1367,11 @@
 int ssl_negotiate_alpn(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                        const SSL_CLIENT_HELLO *client_hello);
 
-typedef struct {
+struct SSL_EXTENSION_TYPE {
   uint16_t type;
   int *out_present;
   CBS *out_data;
-} SSL_EXTENSION_TYPE;
+};
 
 /* ssl_parse_extensions parses a TLS extensions block out of |cbs| and advances
  * it. It writes the parsed extensions to pointers denoted by |ext_types|. On
@@ -1341,6 +1382,9 @@
                          const SSL_EXTENSION_TYPE *ext_types,
                          size_t num_ext_types, int ignore_unknown);
 
+/* ssl_verify_peer_cert verifies the peer certificate for |hs|. */
+enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs);
+
 
 /* SSLKEYLOGFILE functions. */
 
@@ -1416,7 +1460,7 @@
 /* From RFC4492, used in encoding the curve type in ECParameters */
 #define NAMED_CURVE_TYPE 3
 
-typedef struct cert_st {
+struct SSLCertConfig {
   EVP_PKEY *privatekey;
 
   /* chain contains the certificate chain, with the leaf at the beginning. The
@@ -1481,160 +1525,22 @@
 
   /* If enable_early_data is non-zero, early data can be sent and accepted. */
   unsigned enable_early_data:1;
-} CERT;
-
-/* SSL_METHOD is a compatibility structure to support the legacy version-locked
- * methods. */
-struct ssl_method_st {
-  /* version, if non-zero, is the only protocol version acceptable to an
-   * SSL_CTX initialized from this method. */
-  uint16_t version;
-  /* method is the underlying SSL_PROTOCOL_METHOD that initializes the
-   * SSL_CTX. */
-  const 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;
 };
 
-/* Used to hold functions for SSLv2 or SSLv3/TLSv1 functions */
-struct ssl_protocol_method_st {
-  /* is_dtls is one if the protocol is DTLS and zero otherwise. */
-  char is_dtls;
-  int (*ssl_new)(SSL *ssl);
-  void (*ssl_free)(SSL *ssl);
-  /* ssl_get_message reads the next handshake message. On success, it returns
-   * one and sets |ssl->s3->tmp.message_type|, |ssl->init_msg|, and
-   * |ssl->init_num|. Otherwise, it returns <= 0. */
-  int (*ssl_get_message)(SSL *ssl);
-  /* get_current_message sets |*out| to the current handshake message. This
-   * includes the protocol-specific message header. */
-  void (*get_current_message)(const SSL *ssl, CBS *out);
-  /* release_current_message is called to release the current handshake message.
-   * If |free_buffer| is one, buffers will also be released. */
-  void (*release_current_message)(SSL *ssl, int free_buffer);
-  /* read_app_data reads up to |len| bytes of application data into |buf|. On
-   * success, it returns the number of bytes read. Otherwise, it returns <= 0
-   * and sets |*out_got_handshake| to whether the failure was due to a
-   * post-handshake handshake message. If so, it fills in the current message as
-   * in |ssl_get_message|. */
-  int (*read_app_data)(SSL *ssl, int *out_got_handshake, uint8_t *buf, int len,
-                       int peek);
-  int (*read_change_cipher_spec)(SSL *ssl);
-  void (*read_close_notify)(SSL *ssl);
-  int (*write_app_data)(SSL *ssl, int *out_needs_handshake, const uint8_t *buf,
-                        int len);
-  int (*dispatch_alert)(SSL *ssl);
-  /* supports_cipher returns one if |cipher| is supported by this protocol and
-   * zero otherwise. */
-  int (*supports_cipher)(const SSL_CIPHER *cipher);
-  /* init_message begins a new handshake message of type |type|. |cbb| is the
-   * root CBB to be passed into |finish_message|. |*body| is set to a child CBB
-   * the caller should write to. It returns one on success and zero on error. */
-  int (*init_message)(SSL *ssl, CBB *cbb, CBB *body, uint8_t type);
-  /* finish_message finishes a handshake message. It sets |*out_msg| to a
-   * newly-allocated buffer with the serialized message. The caller must
-   * release it with |OPENSSL_free| when done. It returns one on success and
-   * zero on error. */
-  int (*finish_message)(SSL *ssl, CBB *cbb, uint8_t **out_msg, size_t *out_len);
-  /* add_message adds a handshake message to the pending flight. It returns one
-   * on success and zero on error. In either case, it takes ownership of |msg|
-   * and releases it with |OPENSSL_free| when done. */
-  int (*add_message)(SSL *ssl, uint8_t *msg, size_t len);
-  /* add_change_cipher_spec adds a ChangeCipherSpec record to the pending
-   * flight. It returns one on success and zero on error. */
-  int (*add_change_cipher_spec)(SSL *ssl);
-  /* add_alert adds an alert to the pending flight. It returns one on success
-   * and zero on error. */
-  int (*add_alert)(SSL *ssl, uint8_t level, uint8_t desc);
-  /* flush_flight flushes the pending flight to the transport. It returns one on
-   * success and <= 0 on error. */
-  int (*flush_flight)(SSL *ssl);
-  /* expect_flight is called when the handshake expects a flight of messages from
-   * the peer. */
-  void (*expect_flight)(SSL *ssl);
-  /* received_flight is called when the handshake has received a flight of
-   * messages from the peer. */
-  void (*received_flight)(SSL *ssl);
-  /* set_read_state sets |ssl|'s read cipher state to |aead_ctx|. It takes
-   * ownership of |aead_ctx|. It returns one on success and zero if changing the
-   * read state is forbidden at this point. */
-  int (*set_read_state)(SSL *ssl, SSL_AEAD_CTX *aead_ctx);
-  /* set_write_state sets |ssl|'s write cipher state to |aead_ctx|. It takes
-   * ownership of |aead_ctx|. It returns one on success and zero if changing the
-   * write state is forbidden at this point. */
-  int (*set_write_state)(SSL *ssl, SSL_AEAD_CTX *aead_ctx);
-};
-
-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);
-
-  /* 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 *ssl);
-
-  /* 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 |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);
-};
-
-/* ssl_crypto_x509_method provides the |ssl_x509_method_st| functions using
+/* ssl_crypto_x509_method provides the |SSL_X509_METHOD| functions using
  * crypto/x509. */
-extern const struct ssl_x509_method_st ssl_crypto_x509_method;
+extern const SSL_X509_METHOD ssl_crypto_x509_method;
 
-typedef struct ssl3_record_st {
+struct SSL3_RECORD {
   /* type is the record type. */
   uint8_t type;
   /* length is the number of unconsumed bytes in the record. */
   uint16_t length;
   /* data is a non-owning pointer to the first unconsumed byte of the record. */
   uint8_t *data;
-} SSL3_RECORD;
+};
 
-typedef struct ssl3_buffer_st {
+struct SSL3_BUFFER {
   /* buf is the memory allocated for this buffer. */
   uint8_t *buf;
   /* offset is the offset into |buf| which the buffer contents start at. */
@@ -1643,7 +1549,7 @@
   uint16_t len;
   /* cap is how much memory beyond |buf| + |offset| is available. */
   uint16_t cap;
-} SSL3_BUFFER;
+};
 
 /* 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. */
@@ -1653,7 +1559,7 @@
   ssl_shutdown_fatal_alert = 2,
 };
 
-typedef struct ssl3_state_st {
+struct SSL3_STATE {
   uint8_t read_sequence[8];
   uint8_t write_sequence[8];
 
@@ -1749,10 +1655,10 @@
   uint32_t pending_flight_offset;
 
   /* aead_read_ctx is the current read cipher state. */
-  SSL_AEAD_CTX *aead_read_ctx;
+  SSLAEADContext *aead_read_ctx;
 
   /* aead_write_ctx is the current write cipher state. */
-  SSL_AEAD_CTX *aead_write_ctx;
+  SSLAEADContext *aead_write_ctx;
 
   /* hs is the handshake state for the current handshake or NULL if there isn't
    * one. */
@@ -1820,7 +1726,7 @@
    * ticket age and the server-computed value in TLS 1.3 server connections
    * which resumed a session. */
   int32_t ticket_age_skew;
-} SSL3_STATE;
+};
 
 /* lengths of messages */
 #define DTLS1_COOKIE_LENGTH 256
@@ -1842,7 +1748,7 @@
 };
 
 /* An hm_fragment is an incoming DTLS message, possibly not yet assembled. */
-typedef struct hm_fragment_st {
+struct hm_fragment {
   /* type is the type of the message. */
   uint8_t type;
   /* seq is the sequence number of this message. */
@@ -1855,14 +1761,14 @@
   /* reassembly is a bitmask of |msg_len| bits corresponding to which parts of
    * the message have been received. It is NULL if the message is complete. */
   uint8_t *reassembly;
-} hm_fragment;
+};
 
 struct OPENSSL_timeval {
   uint64_t tv_sec;
   uint32_t tv_usec;
 };
 
-typedef struct dtls1_state_st {
+struct DTLS1_STATE {
   /* send_cookie is true if we are resending the ClientHello
    * with a cookie from a HelloVerifyRequest. */
   unsigned int send_cookie;
@@ -1883,6 +1789,7 @@
 
   /* save last sequence number for retransmissions */
   uint8_t last_write_sequence[8];
+  SSLAEADContext *last_aead_write_ctx;
 
   /* incoming_messages is a ring buffer of incoming handshake messages that have
    * yet to be processed. The front of the ring buffer is message number
@@ -1914,9 +1821,11 @@
 
   /* timeout_duration_ms is the timeout duration in milliseconds. */
   unsigned timeout_duration_ms;
-} DTLS1_STATE;
+};
 
-struct ssl_st {
+/* 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;
@@ -1955,8 +1864,8 @@
   /* init_num is the length of the current handshake message body. */
   uint32_t init_num;
 
-  struct ssl3_state_st *s3;  /* SSLv3 variables */
-  struct dtls1_state_st *d1; /* DTLSv1 variables */
+  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,
@@ -1972,7 +1881,7 @@
 
   /* client cert? */
   /* This is used to hold the server certificate used */
-  struct cert_st /* CERT */ *cert;
+  CERT *cert;
 
   /* This holds a variable that indicates what we were doing when a 0 or -1 is
    * returned.  This is needed for non-blocking IO so we know what request
@@ -1990,6 +1899,9 @@
   int (*verify_callback)(int ok,
                          X509_STORE_CTX *ctx); /* 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);
 
   /* Server-only: psk_identity_hint is the identity hint to send in
@@ -2107,14 +2019,15 @@
 int ssl_get_new_session(SSL_HANDSHAKE *hs, int is_server);
 int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session);
 
-/* ssl_session_new returns a newly-allocated blank |SSL_SESSION| or NULL on
+/* ssl_session_new returns a newly-allocated blank |SSL_SESSION| or nullptr on
  * error. */
-SSL_SESSION *ssl_session_new(const SSL_X509_METHOD *x509_method);
+UniquePtr<SSL_SESSION> ssl_session_new(const SSL_X509_METHOD *x509_method);
 
 /* SSL_SESSION_parse parses an |SSL_SESSION| from |cbs| and advances |cbs| over
  * the parsed data. */
-SSL_SESSION *SSL_SESSION_parse(CBS *cbs, const SSL_X509_METHOD *x509_method,
-                               CRYPTO_BUFFER_POOL *pool);
+UniquePtr<SSL_SESSION> SSL_SESSION_parse(CBS *cbs,
+                                         const SSL_X509_METHOD *x509_method,
+                                         CRYPTO_BUFFER_POOL *pool);
 
 /* ssl_session_is_context_valid returns one if |session|'s session ID context
  * matches the one set on |ssl| and zero otherwise. */
@@ -2163,10 +2076,10 @@
   (SSL_SESSION_INCLUDE_TICKET | SSL_SESSION_INCLUDE_NONAUTH)
 
 /* SSL_SESSION_dup returns a newly-allocated |SSL_SESSION| with a copy of the
- * fields in |session| or NULL on error. The new session is non-resumable and
+ * fields in |session| or nullptr on error. The new session is non-resumable and
  * must be explicitly marked resumable once it has been filled in. */
-OPENSSL_EXPORT SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session,
-                                            int dup_flags);
+OPENSSL_EXPORT UniquePtr<SSL_SESSION> SSL_SESSION_dup(SSL_SESSION *session,
+                                                      int dup_flags);
 
 /* ssl_session_rebase_time updates |session|'s start time to the current time,
  * adjusting the timeout so the expiration time is unchanged. */
@@ -2373,8 +2286,171 @@
 void ssl_reset_error_state(SSL *ssl);
 
 
-#if defined(__cplusplus)
-} /* extern C */
+/* Utility macros */
+
+#if defined(__clang__)
+/* SSL_FALLTHROUGH annotates a fallthough case in a switch statement. */
+#define SSL_FALLTHROUGH [[clang::fallthrough]]
+#else
+#define SSL_FALLTHROUGH
 #endif
 
+}  // namespace bssl
+
+
+/* Opaque C types.
+ *
+ * The following types are exported to C code as public typedefs, so they must
+ * be defined outside of the namespace. */
+
+/* ssl_method_st backs the public |SSL_METHOD| type. It is a compatibility
+ * structure to support the legacy version-locked methods. */
+struct ssl_method_st {
+  /* version, if non-zero, is the only protocol version acceptable to an
+   * SSL_CTX initialized from this method. */
+  uint16_t version;
+  /* method is the underlying SSL_PROTOCOL_METHOD that initializes the
+   * SSL_CTX. */
+  const 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;
+};
+
+/* ssl_protocol_method_st, aka |SSL_PROTOCOL_METHOD| abstracts between TLS and
+ * DTLS. */
+struct ssl_protocol_method_st {
+  /* is_dtls is one if the protocol is DTLS and zero otherwise. */
+  char is_dtls;
+  int (*ssl_new)(SSL *ssl);
+  void (*ssl_free)(SSL *ssl);
+  /* ssl_get_message reads the next handshake message. On success, it returns
+   * one and sets |ssl->s3->tmp.message_type|, |ssl->init_msg|, and
+   * |ssl->init_num|. Otherwise, it returns <= 0. */
+  int (*ssl_get_message)(SSL *ssl);
+  /* get_current_message sets |*out| to the current handshake message. This
+   * includes the protocol-specific message header. */
+  void (*get_current_message)(const SSL *ssl, CBS *out);
+  /* release_current_message is called to release the current handshake message.
+   * If |free_buffer| is one, buffers will also be released. */
+  void (*release_current_message)(SSL *ssl, int free_buffer);
+  /* read_app_data reads up to |len| bytes of application data into |buf|. On
+   * success, it returns the number of bytes read. Otherwise, it returns <= 0
+   * and sets |*out_got_handshake| to whether the failure was due to a
+   * post-handshake handshake message. If so, it fills in the current message as
+   * in |ssl_get_message|. */
+  int (*read_app_data)(SSL *ssl, int *out_got_handshake, uint8_t *buf, int len,
+                       int peek);
+  int (*read_change_cipher_spec)(SSL *ssl);
+  void (*read_close_notify)(SSL *ssl);
+  int (*write_app_data)(SSL *ssl, int *out_needs_handshake, const uint8_t *buf,
+                        int len);
+  int (*dispatch_alert)(SSL *ssl);
+  /* supports_cipher returns one if |cipher| is supported by this protocol and
+   * zero otherwise. */
+  int (*supports_cipher)(const SSL_CIPHER *cipher);
+  /* init_message begins a new handshake message of type |type|. |cbb| is the
+   * root CBB to be passed into |finish_message|. |*body| is set to a child CBB
+   * the caller should write to. It returns one on success and zero on error. */
+  int (*init_message)(SSL *ssl, CBB *cbb, CBB *body, uint8_t type);
+  /* finish_message finishes a handshake message. It sets |*out_msg| to a
+   * newly-allocated buffer with the serialized message. The caller must
+   * release it with |OPENSSL_free| when done. It returns one on success and
+   * zero on error. */
+  int (*finish_message)(SSL *ssl, CBB *cbb, uint8_t **out_msg, size_t *out_len);
+  /* add_message adds a handshake message to the pending flight. It returns one
+   * on success and zero on error. In either case, it takes ownership of |msg|
+   * and releases it with |OPENSSL_free| when done. */
+  int (*add_message)(SSL *ssl, uint8_t *msg, size_t len);
+  /* add_change_cipher_spec adds a ChangeCipherSpec record to the pending
+   * flight. It returns one on success and zero on error. */
+  int (*add_change_cipher_spec)(SSL *ssl);
+  /* add_alert adds an alert to the pending flight. It returns one on success
+   * and zero on error. */
+  int (*add_alert)(SSL *ssl, uint8_t level, uint8_t desc);
+  /* flush_flight flushes the pending flight to the transport. It returns one on
+   * success and <= 0 on error. */
+  int (*flush_flight)(SSL *ssl);
+  /* expect_flight is called when the handshake expects a flight of messages from
+   * the peer. */
+  void (*expect_flight)(SSL *ssl);
+  /* received_flight is called when the handshake has received a flight of
+   * messages from the peer. */
+  void (*received_flight)(SSL *ssl);
+  /* set_read_state sets |ssl|'s read cipher state to |aead_ctx|. It returns
+   * one on success and zero if changing the read state is forbidden at this
+   * point. */
+  int (*set_read_state)(SSL *ssl,
+                        bssl::UniquePtr<bssl::SSLAEADContext> aead_ctx);
+  /* set_write_state sets |ssl|'s write cipher state to |aead_ctx|. It returns
+   * one on success and zero if changing the write state is forbidden at this
+   * point. */
+  int (*set_write_state)(SSL *ssl,
+                         bssl::UniquePtr<bssl::SSLAEADContext> aead_ctx);
+};
+
+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);
+
+  /* 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 *ssl,
+                                   uint8_t *out_alert);
+
+  /* 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);
+};
+
+/* ssl_st backs the public |SSL| type. It subclasses the true type so that
+ * SSLConnection may be a C++ type with methods and destructor without
+ * polluting the global namespace. */
+struct ssl_st : public bssl::SSLConnection {};
+
+struct cert_st : public bssl::SSLCertConfig {};
+
+
 #endif /* OPENSSL_HEADER_SSL_INTERNAL_H */
diff --git a/src/ssl/s3_both.cc b/src/ssl/s3_both.cc
index 79f71fa..b9aa3bc 100644
--- a/src/ssl/s3_both.cc
+++ b/src/ssl/s3_both.cc
@@ -130,64 +130,62 @@
 #include "internal.h"
 
 
+namespace bssl {
+
+SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
+    : ssl(ssl_arg),
+      scts_requested(0),
+      needs_psk_binder(0),
+      received_hello_retry_request(0),
+      accept_psk_mode(0),
+      cert_request(0),
+      certificate_status_expected(0),
+      ocsp_stapling_requested(0),
+      should_ack_sni(0),
+      in_false_start(0),
+      in_early_data(0),
+      early_data_offered(0),
+      can_early_read(0),
+      can_early_write(0),
+      next_proto_neg_seen(0),
+      ticket_expected(0),
+      extended_master_secret(0),
+      pending_private_key_op(0) {
+}
+
+SSL_HANDSHAKE::~SSL_HANDSHAKE() {
+  OPENSSL_cleanse(secret, sizeof(secret));
+  OPENSSL_cleanse(early_traffic_secret, sizeof(early_traffic_secret));
+  OPENSSL_cleanse(client_handshake_secret, sizeof(client_handshake_secret));
+  OPENSSL_cleanse(server_handshake_secret, sizeof(server_handshake_secret));
+  OPENSSL_cleanse(client_traffic_secret_0, sizeof(client_traffic_secret_0));
+  OPENSSL_cleanse(server_traffic_secret_0, sizeof(server_traffic_secret_0));
+  OPENSSL_free(cookie);
+  OPENSSL_free(key_share_bytes);
+  OPENSSL_free(ecdh_public_key);
+  OPENSSL_free(peer_sigalgs);
+  OPENSSL_free(peer_supported_group_list);
+  OPENSSL_free(peer_key);
+  OPENSSL_free(server_params);
+  ssl->ctx->x509_method->hs_flush_cached_ca_names(this);
+  OPENSSL_free(certificate_types);
+
+  if (key_block != NULL) {
+    OPENSSL_cleanse(key_block, key_block_len);
+    OPENSSL_free(key_block);
+  }
+}
+
 SSL_HANDSHAKE *ssl_handshake_new(SSL *ssl) {
-  SSL_HANDSHAKE *hs = (SSL_HANDSHAKE *)OPENSSL_malloc(sizeof(SSL_HANDSHAKE));
-  if (hs == NULL) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return NULL;
+  UniquePtr<SSL_HANDSHAKE> hs = MakeUnique<SSL_HANDSHAKE>(ssl);
+  if (!hs ||
+      !hs->transcript.Init()) {
+    return nullptr;
   }
-  OPENSSL_memset(hs, 0, sizeof(SSL_HANDSHAKE));
-  hs->ssl = ssl;
-  hs->wait = ssl_hs_ok;
-  hs->state = SSL_ST_INIT;
-  if (!SSL_TRANSCRIPT_init(&hs->transcript)) {
-    ssl_handshake_free(hs);
-    return NULL;
-  }
-  return hs;
+  return hs.release();
 }
 
-void ssl_handshake_free(SSL_HANDSHAKE *hs) {
-  if (hs == NULL) {
-    return;
-  }
-
-  OPENSSL_cleanse(hs->secret, sizeof(hs->secret));
-  OPENSSL_cleanse(hs->early_traffic_secret, sizeof(hs->early_traffic_secret));
-  OPENSSL_cleanse(hs->client_handshake_secret,
-                  sizeof(hs->client_handshake_secret));
-  OPENSSL_cleanse(hs->server_handshake_secret,
-                  sizeof(hs->server_handshake_secret));
-  OPENSSL_cleanse(hs->client_traffic_secret_0,
-                  sizeof(hs->client_traffic_secret_0));
-  OPENSSL_cleanse(hs->server_traffic_secret_0,
-                  sizeof(hs->server_traffic_secret_0));
-  SSL_ECDH_CTX_cleanup(&hs->ecdh_ctx);
-  SSL_TRANSCRIPT_cleanup(&hs->transcript);
-  OPENSSL_free(hs->cookie);
-  OPENSSL_free(hs->key_share_bytes);
-  OPENSSL_free(hs->ecdh_public_key);
-  SSL_SESSION_free(hs->new_session);
-  SSL_SESSION_free(hs->early_session);
-  OPENSSL_free(hs->peer_sigalgs);
-  OPENSSL_free(hs->peer_supported_group_list);
-  OPENSSL_free(hs->peer_key);
-  OPENSSL_free(hs->server_params);
-  OPENSSL_free(hs->peer_psk_identity_hint);
-  sk_CRYPTO_BUFFER_pop_free(hs->ca_names, CRYPTO_BUFFER_free);
-  hs->ssl->ctx->x509_method->hs_flush_cached_ca_names(hs);
-  OPENSSL_free(hs->certificate_types);
-
-  if (hs->key_block != NULL) {
-    OPENSSL_cleanse(hs->key_block, hs->key_block_len);
-    OPENSSL_free(hs->key_block);
-  }
-
-  OPENSSL_free(hs->hostname);
-  EVP_PKEY_free(hs->peer_pubkey);
-  EVP_PKEY_free(hs->local_pubkey);
-  OPENSSL_free(hs);
-}
+void ssl_handshake_free(SSL_HANDSHAKE *hs) { Delete(hs); }
 
 int ssl_check_message_type(SSL *ssl, int type) {
   if (ssl->s3->tmp.message_type != type) {
@@ -270,7 +268,7 @@
     if (ssl->server &&
         ssl->s3->have_version &&
         ssl->version == TLS1_3_RECORD_TYPE_EXPERIMENT_VERSION &&
-        ssl->s3->aead_write_ctx == NULL) {
+        ssl->s3->aead_write_ctx->is_null_cipher()) {
       type = SSL3_RT_PLAINTEXT_HANDSHAKE;
     }
 
@@ -281,10 +279,10 @@
   } while (added < len);
 
   ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_HANDSHAKE, msg, len);
-  /* TODO(svaldez): Move this up a layer to fix abstraction for SSL_TRANSCRIPT
-   * on hs. */
+  /* TODO(svaldez): Move this up a layer to fix abstraction for SSLTranscript on
+   * hs. */
   if (ssl->s3->hs != NULL &&
-      !SSL_TRANSCRIPT_update(&ssl->s3->hs->transcript, msg, len)) {
+      !ssl->s3->hs->transcript.Update(msg, len)) {
     goto err;
   }
   ret = 1;
@@ -381,9 +379,8 @@
 
   uint8_t finished[EVP_MAX_MD_SIZE];
   size_t finished_len;
-  if (!SSL_TRANSCRIPT_finish_mac(&hs->transcript, finished, &finished_len,
-                                 session, ssl->server,
-                                 ssl3_protocol_version(ssl))) {
+  if (!hs->transcript.GetFinishedMAC(finished, &finished_len, session,
+                                     ssl->server, ssl3_protocol_version(ssl))) {
     return 0;
   }
 
@@ -411,12 +408,12 @@
     }
   }
 
-  CBB cbb, body;
-  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_FINISHED) ||
+  ScopedCBB cbb;
+  CBB body;
+  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_FINISHED) ||
       !CBB_add_bytes(&body, finished, finished_len) ||
-      !ssl_add_message_cbb(ssl, &cbb)) {
+      !ssl_add_message_cbb(ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    CBB_cleanup(&cbb);
     return -1;
   }
 
@@ -437,9 +434,9 @@
   /* Snapshot the finished hash before incorporating the new message. */
   uint8_t finished[EVP_MAX_MD_SIZE];
   size_t finished_len;
-  if (!SSL_TRANSCRIPT_finish_mac(&hs->transcript, finished, &finished_len,
-                                 SSL_get_session(ssl), !ssl->server,
-                                 ssl3_protocol_version(ssl)) ||
+  if (!hs->transcript.GetFinishedMAC(finished, &finished_len,
+                                     SSL_get_session(ssl), !ssl->server,
+                                     ssl3_protocol_version(ssl)) ||
       !ssl_hash_current_message(hs)) {
     return -1;
   }
@@ -476,12 +473,12 @@
 }
 
 int ssl3_output_cert_chain(SSL *ssl) {
-  CBB cbb, body;
-  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_CERTIFICATE) ||
+  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)) {
+      !ssl_add_message_cbb(ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    CBB_cleanup(&cbb);
     return 0;
   }
 
@@ -591,9 +588,8 @@
   /* The V2ClientHello without the length is incorporated into the handshake
    * hash. This is only ever called at the start of the handshake, so hs is
    * guaranteed to be non-NULL. */
-  if (!SSL_TRANSCRIPT_update(&ssl->s3->hs->transcript,
-                             CBS_data(&v2_client_hello),
-                             CBS_len(&v2_client_hello))) {
+  if (!ssl->s3->hs->transcript.Update(CBS_data(&v2_client_hello),
+                                      CBS_len(&v2_client_hello))) {
     return -1;
   }
 
@@ -636,19 +632,18 @@
                                2 /* cipher list length */ +
                                CBS_len(&cipher_specs) / 3 * 2 +
                                1 /* compression length */ + 1 /* compression */;
-  CBB client_hello, hello_body, cipher_suites;
-  CBB_zero(&client_hello);
+  ScopedCBB client_hello;
+  CBB hello_body, cipher_suites;
   if (!BUF_MEM_reserve(ssl->init_buf, max_v3_client_hello) ||
-      !CBB_init_fixed(&client_hello, (uint8_t *)ssl->init_buf->data,
+      !CBB_init_fixed(client_hello.get(), (uint8_t *)ssl->init_buf->data,
                       ssl->init_buf->max) ||
-      !CBB_add_u8(&client_hello, SSL3_MT_CLIENT_HELLO) ||
-      !CBB_add_u24_length_prefixed(&client_hello, &hello_body) ||
+      !CBB_add_u8(client_hello.get(), SSL3_MT_CLIENT_HELLO) ||
+      !CBB_add_u24_length_prefixed(client_hello.get(), &hello_body) ||
       !CBB_add_u16(&hello_body, version) ||
       !CBB_add_bytes(&hello_body, random, SSL3_RANDOM_SIZE) ||
       /* No session id. */
       !CBB_add_u8(&hello_body, 0) ||
       !CBB_add_u16_length_prefixed(&hello_body, &cipher_suites)) {
-    CBB_cleanup(&client_hello);
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     return -1;
   }
@@ -657,7 +652,6 @@
   while (CBS_len(&cipher_specs) > 0) {
     uint32_t cipher_spec;
     if (!CBS_get_u24(&cipher_specs, &cipher_spec)) {
-      CBB_cleanup(&client_hello);
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       return -1;
     }
@@ -667,16 +661,15 @@
       continue;
     }
     if (!CBB_add_u16(&cipher_suites, cipher_spec)) {
-      CBB_cleanup(&client_hello);
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       return -1;
     }
   }
 
   /* Add the null compression scheme and finish. */
-  if (!CBB_add_u8(&hello_body, 1) || !CBB_add_u8(&hello_body, 0) ||
-      !CBB_finish(&client_hello, NULL, &ssl->init_buf->length)) {
-    CBB_cleanup(&client_hello);
+  if (!CBB_add_u8(&hello_body, 1) ||
+      !CBB_add_u8(&hello_body, 0) ||
+      !CBB_finish(client_hello.get(), NULL, &ssl->init_buf->length)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return -1;
   }
@@ -759,7 +752,7 @@
 
   CBS cbs;
   hs->ssl->method->get_current_message(hs->ssl, &cbs);
-  return SSL_TRANSCRIPT_update(&hs->transcript, CBS_data(&cbs), CBS_len(&cbs));
+  return hs->transcript.Update(CBS_data(&cbs), CBS_len(&cbs));
 }
 
 void ssl3_release_current_message(SSL *ssl, int free_buffer) {
@@ -830,3 +823,53 @@
 
   return 1;
 }
+
+enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  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);
+    switch (ret) {
+      case ssl_verify_ok:
+        hs->new_session->verify_result = X509_V_OK;
+        break;
+      case ssl_verify_invalid:
+        hs->new_session->verify_result = X509_V_ERR_APPLICATION_VERIFICATION;
+        break;
+      case ssl_verify_retry:
+        break;
+    }
+  } else {
+    ret = ssl->ctx->x509_method->session_verify_cert_chain(
+              hs->new_session.get(), ssl, &alert)
+              ? ssl_verify_ok
+              : ssl_verify_invalid;
+  }
+
+  if (ret == ssl_verify_invalid) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_VERIFY_FAILED);
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+  }
+
+  return ret;
+}
+
+uint16_t ssl_get_grease_value(const SSL *ssl, enum ssl_grease_index_t index) {
+  /* Use the client_random or server_random for entropy. This both avoids
+   * calling |RAND_bytes| on a single byte repeatedly and ensures the values are
+   * deterministic. This allows the same ClientHello be sent twice for a
+   * HelloRetryRequest or the same group be advertised in both supported_groups
+   * and key_shares. */
+  uint16_t ret = ssl->server ? ssl->s3->server_random[index]
+                             : ssl->s3->client_random[index];
+  /* The first four bytes of server_random are a timestamp prior to TLS 1.3, but
+   * servers have no fields to GREASE until TLS 1.3. */
+  assert(!ssl->server || ssl3_protocol_version(ssl) >= TLS1_3_VERSION);
+  /* This generates a random value of the form 0xωaωa, for all 0 ≤ ω < 16. */
+  ret = (ret & 0xf0) | 0x0a;
+  ret |= ret << 8;
+  return ret;
+}
+
+}  // namespace bssl
diff --git a/src/ssl/s3_lib.cc b/src/ssl/s3_lib.cc
index 9548bbd..cc9be2d 100644
--- a/src/ssl/s3_lib.cc
+++ b/src/ssl/s3_lib.cc
@@ -162,7 +162,15 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 int ssl3_new(SSL *ssl) {
+  UniquePtr<SSLAEADContext> aead_read_ctx = SSLAEADContext::CreateNullCipher();
+  UniquePtr<SSLAEADContext> aead_write_ctx = SSLAEADContext::CreateNullCipher();
+  if (!aead_read_ctx || !aead_write_ctx) {
+    return 0;
+  }
+
   SSL3_STATE *s3 = (SSL3_STATE *)OPENSSL_malloc(sizeof *s3);
   if (s3 == NULL) {
     return 0;
@@ -175,6 +183,8 @@
     return 0;
   }
 
+  s3->aead_read_ctx = aead_read_ctx.release();
+  s3->aead_write_ctx = aead_write_ctx.release();
   ssl->s3 = s3;
 
   /* Set the version to the highest supported version.
@@ -198,8 +208,8 @@
   ssl_handshake_free(ssl->s3->hs);
   OPENSSL_free(ssl->s3->next_proto_negotiated);
   OPENSSL_free(ssl->s3->alpn_selected);
-  SSL_AEAD_CTX_free(ssl->s3->aead_read_ctx);
-  SSL_AEAD_CTX_free(ssl->s3->aead_write_ctx);
+  Delete(ssl->s3->aead_read_ctx);
+  Delete(ssl->s3->aead_write_ctx);
   BUF_MEM_free(ssl->s3->pending_flight);
 
   OPENSSL_cleanse(ssl->s3, sizeof *ssl->s3);
@@ -215,3 +225,5 @@
 
   return ssl->ctx->cipher_list;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/s3_pkt.cc b/src/ssl/s3_pkt.cc
index 4ae2e34..262df6d 100644
--- a/src/ssl/s3_pkt.cc
+++ b/src/ssl/s3_pkt.cc
@@ -122,6 +122,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 static int do_ssl3_write(SSL *ssl, int type, const uint8_t *buf, unsigned len);
 
 /* ssl3_get_record reads a new input record. On success, it places it in
@@ -192,7 +194,7 @@
 int ssl3_write_app_data(SSL *ssl, int *out_needs_handshake, const uint8_t *buf,
                         int len) {
   assert(ssl_can_write(ssl));
-  assert(ssl->s3->aead_write_ctx != NULL);
+  assert(!ssl->s3->aead_write_ctx->is_null_cipher());
 
   *out_needs_handshake = 0;
 
@@ -372,7 +374,7 @@
 int ssl3_read_app_data(SSL *ssl, int *out_got_handshake, uint8_t *buf, int len,
                        int peek) {
   assert(ssl_can_read(ssl));
-  assert(ssl->s3->aead_read_ctx != NULL);
+  assert(!ssl->s3->aead_read_ctx->is_null_cipher());
   *out_got_handshake = 0;
 
   ssl->method->release_current_message(ssl, 0 /* don't free buffer */);
@@ -517,7 +519,7 @@
      * as-is. This manifests as an application data record when we expect
      * handshake. Report a dedicated error code for this case. */
     if (!ssl->server && rr->type == SSL3_RT_APPLICATION_DATA &&
-        ssl->s3->aead_read_ctx == NULL) {
+        ssl->s3->aead_read_ctx->is_null_cipher()) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_APPLICATION_DATA_INSTEAD_OF_HANDSHAKE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
       return -1;
@@ -528,7 +530,7 @@
     if (rr->type != SSL3_RT_HANDSHAKE &&
         !(!ssl->server &&
           ssl->tls13_variant == tls13_record_type_experiment &&
-          ssl->s3->aead_read_ctx == NULL &&
+          ssl->s3->aead_read_ctx->is_null_cipher() &&
           rr->type == SSL3_RT_PLAINTEXT_HANDSHAKE)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
@@ -590,3 +592,5 @@
 
   return 1;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/ssl_aead_ctx.cc b/src/ssl/ssl_aead_ctx.cc
index 5264a65..3b8d1b2 100644
--- a/src/ssl/ssl_aead_ctx.cc
+++ b/src/ssl/ssl_aead_ctx.cc
@@ -25,12 +25,38 @@
 #include "internal.h"
 
 
-SSL_AEAD_CTX *SSL_AEAD_CTX_new(enum evp_aead_direction_t direction,
-                               uint16_t version, int is_dtls,
-                               const SSL_CIPHER *cipher, const uint8_t *enc_key,
-                               size_t enc_key_len, const uint8_t *mac_key,
-                               size_t mac_key_len, const uint8_t *fixed_iv,
-                               size_t fixed_iv_len) {
+#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
+#define FUZZER_MODE true
+#else
+#define FUZZER_MODE false
+#endif
+
+namespace bssl {
+
+SSLAEADContext::SSLAEADContext(uint16_t version_arg,
+                               const SSL_CIPHER *cipher_arg)
+    : cipher_(cipher_arg),
+      version_(version_arg),
+      variable_nonce_included_in_record_(false),
+      random_variable_nonce_(false),
+      omit_length_in_ad_(false),
+      omit_version_in_ad_(false),
+      omit_ad_(false),
+      xor_fixed_nonce_(false) {
+  OPENSSL_memset(fixed_nonce_, 0, sizeof(fixed_nonce_));
+}
+
+SSLAEADContext::~SSLAEADContext() {}
+
+UniquePtr<SSLAEADContext> SSLAEADContext::CreateNullCipher() {
+  return MakeUnique<SSLAEADContext>(0 /* version */, nullptr /* cipher */);
+}
+
+UniquePtr<SSLAEADContext> SSLAEADContext::Create(
+    enum evp_aead_direction_t direction, uint16_t version, int is_dtls,
+    const SSL_CIPHER *cipher, const uint8_t *enc_key, size_t enc_key_len,
+    const uint8_t *mac_key, size_t mac_key_len, const uint8_t *fixed_iv,
+    size_t fixed_iv_len) {
   const EVP_AEAD *aead;
   size_t expected_mac_key_len, expected_fixed_iv_len;
   if (!ssl_cipher_get_evp_aead(&aead, &expected_mac_key_len,
@@ -40,7 +66,7 @@
       expected_fixed_iv_len != fixed_iv_len ||
       expected_mac_key_len != mac_key_len) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return 0;
+    return nullptr;
   }
 
   uint8_t merged_key[EVP_AEAD_MAX_KEY_LENGTH];
@@ -49,7 +75,7 @@
      * suites). */
     if (mac_key_len + enc_key_len + fixed_iv_len > sizeof(merged_key)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return 0;
+      return nullptr;
     }
     OPENSSL_memcpy(merged_key, mac_key, mac_key_len);
     OPENSSL_memcpy(merged_key + mac_key_len, enc_key, enc_key_len);
@@ -60,306 +86,280 @@
     enc_key_len += fixed_iv_len;
   }
 
-  SSL_AEAD_CTX *aead_ctx = (SSL_AEAD_CTX *)OPENSSL_malloc(sizeof(SSL_AEAD_CTX));
-  if (aead_ctx == NULL) {
+  UniquePtr<SSLAEADContext> aead_ctx =
+      MakeUnique<SSLAEADContext>(version, cipher);
+  if (!aead_ctx) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return NULL;
+    return nullptr;
   }
-  OPENSSL_memset(aead_ctx, 0, sizeof(SSL_AEAD_CTX));
-  aead_ctx->cipher = cipher;
-  aead_ctx->version = version;
 
   if (!EVP_AEAD_CTX_init_with_direction(
-          &aead_ctx->ctx, aead, enc_key, enc_key_len,
+          aead_ctx->ctx_.get(), aead, enc_key, enc_key_len,
           EVP_AEAD_DEFAULT_TAG_LENGTH, direction)) {
-    OPENSSL_free(aead_ctx);
-    return NULL;
+    return nullptr;
   }
 
   assert(EVP_AEAD_nonce_length(aead) <= EVP_AEAD_MAX_NONCE_LENGTH);
   static_assert(EVP_AEAD_MAX_NONCE_LENGTH < 256,
                 "variable_nonce_len doesn't fit in uint8_t");
-  aead_ctx->variable_nonce_len = (uint8_t)EVP_AEAD_nonce_length(aead);
+  aead_ctx->variable_nonce_len_ = (uint8_t)EVP_AEAD_nonce_length(aead);
   if (mac_key_len == 0) {
-    assert(fixed_iv_len <= sizeof(aead_ctx->fixed_nonce));
-    OPENSSL_memcpy(aead_ctx->fixed_nonce, fixed_iv, fixed_iv_len);
-    aead_ctx->fixed_nonce_len = fixed_iv_len;
+    assert(fixed_iv_len <= sizeof(aead_ctx->fixed_nonce_));
+    OPENSSL_memcpy(aead_ctx->fixed_nonce_, fixed_iv, fixed_iv_len);
+    aead_ctx->fixed_nonce_len_ = fixed_iv_len;
 
     if (cipher->algorithm_enc & SSL_CHACHA20POLY1305) {
       /* The fixed nonce into the actual nonce (the sequence number). */
-      aead_ctx->xor_fixed_nonce = 1;
-      aead_ctx->variable_nonce_len = 8;
+      aead_ctx->xor_fixed_nonce_ = true;
+      aead_ctx->variable_nonce_len_ = 8;
     } else {
       /* The fixed IV is prepended to the nonce. */
-      assert(fixed_iv_len <= aead_ctx->variable_nonce_len);
-      aead_ctx->variable_nonce_len -= fixed_iv_len;
+      assert(fixed_iv_len <= aead_ctx->variable_nonce_len_);
+      aead_ctx->variable_nonce_len_ -= fixed_iv_len;
     }
 
     /* AES-GCM uses an explicit nonce. */
     if (cipher->algorithm_enc & (SSL_AES128GCM | SSL_AES256GCM)) {
-      aead_ctx->variable_nonce_included_in_record = 1;
+      aead_ctx->variable_nonce_included_in_record_ = true;
     }
 
     /* The TLS 1.3 construction XORs the fixed nonce into the sequence number
      * and omits the additional data. */
     if (version >= TLS1_3_VERSION) {
-      aead_ctx->xor_fixed_nonce = 1;
-      aead_ctx->variable_nonce_len = 8;
-      aead_ctx->variable_nonce_included_in_record = 0;
-      aead_ctx->omit_ad = 1;
-      assert(fixed_iv_len >= aead_ctx->variable_nonce_len);
+      aead_ctx->xor_fixed_nonce_ = true;
+      aead_ctx->variable_nonce_len_ = 8;
+      aead_ctx->variable_nonce_included_in_record_ = false;
+      aead_ctx->omit_ad_ = true;
+      assert(fixed_iv_len >= aead_ctx->variable_nonce_len_);
     }
   } else {
     assert(version < TLS1_3_VERSION);
-    aead_ctx->variable_nonce_included_in_record = 1;
-    aead_ctx->random_variable_nonce = 1;
-    aead_ctx->omit_length_in_ad = 1;
-    aead_ctx->omit_version_in_ad = (version == SSL3_VERSION);
+    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_ = (version == SSL3_VERSION);
   }
 
   return aead_ctx;
 }
 
-void SSL_AEAD_CTX_free(SSL_AEAD_CTX *aead) {
-  if (aead == NULL) {
-    return;
-  }
-  EVP_AEAD_CTX_cleanup(&aead->ctx);
-  OPENSSL_free(aead);
-}
-
-size_t SSL_AEAD_CTX_explicit_nonce_len(const SSL_AEAD_CTX *aead) {
-#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  aead = NULL;
-#endif
-
-  if (aead != NULL && aead->variable_nonce_included_in_record) {
-    return aead->variable_nonce_len;
+size_t SSLAEADContext::ExplicitNonceLen() const {
+  if (!FUZZER_MODE && variable_nonce_included_in_record_) {
+    return variable_nonce_len_;
   }
   return 0;
 }
 
-size_t SSL_AEAD_CTX_max_suffix_len(const SSL_AEAD_CTX *aead,
-                                   size_t extra_in_len) {
-#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  aead = NULL;
-#endif
-
+size_t SSLAEADContext::MaxSuffixLen(size_t extra_in_len) const {
   return extra_in_len +
-         (aead == NULL ? 0 : EVP_AEAD_max_overhead(aead->ctx.aead));
+         (is_null_cipher() || FUZZER_MODE
+              ? 0
+              : EVP_AEAD_max_overhead(EVP_AEAD_CTX_aead(ctx_.get())));
 }
 
-size_t SSL_AEAD_CTX_max_overhead(const SSL_AEAD_CTX *aead) {
-  return SSL_AEAD_CTX_explicit_nonce_len(aead) +
-         SSL_AEAD_CTX_max_suffix_len(aead, 0);
+size_t SSLAEADContext::MaxOverhead() const {
+  return ExplicitNonceLen() + MaxSuffixLen(0);
 }
 
-/* ssl_aead_ctx_get_ad writes the additional data for |aead| into |out| and
- * returns the number of bytes written. */
-static size_t ssl_aead_ctx_get_ad(SSL_AEAD_CTX *aead, uint8_t out[13],
-                                  uint8_t type, uint16_t wire_version,
-                                  const uint8_t seqnum[8],
-                                  size_t plaintext_len) {
-  if (aead->omit_ad) {
+size_t SSLAEADContext::GetAdditionalData(uint8_t out[13], uint8_t type,
+                                         uint16_t wire_version,
+                                         const uint8_t seqnum[8],
+                                         size_t plaintext_len) {
+  if (omit_ad_) {
     return 0;
   }
 
   OPENSSL_memcpy(out, seqnum, 8);
   size_t len = 8;
   out[len++] = type;
-  if (!aead->omit_version_in_ad) {
-    out[len++] = (uint8_t)(wire_version >> 8);
-    out[len++] = (uint8_t)wire_version;
+  if (!omit_version_in_ad_) {
+    out[len++] = static_cast<uint8_t>((wire_version >> 8));
+    out[len++] = static_cast<uint8_t>(wire_version);
   }
-  if (!aead->omit_length_in_ad) {
-    out[len++] = (uint8_t)(plaintext_len >> 8);
-    out[len++] = (uint8_t)plaintext_len;
+  if (!omit_length_in_ad_) {
+    out[len++] = static_cast<uint8_t>((plaintext_len >> 8));
+    out[len++] = static_cast<uint8_t>(plaintext_len);
   }
   return len;
 }
 
-int SSL_AEAD_CTX_open(SSL_AEAD_CTX *aead, CBS *out, uint8_t type,
-                      uint16_t wire_version, const uint8_t seqnum[8],
-                      uint8_t *in, size_t in_len) {
-#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  aead = NULL;
-#endif
-
-  if (aead == NULL) {
+bool SSLAEADContext::Open(CBS *out, uint8_t type, uint16_t wire_version,
+                          const uint8_t seqnum[8], uint8_t *in, size_t in_len) {
+  if (is_null_cipher() || FUZZER_MODE) {
     /* Handle the initial NULL cipher. */
     CBS_init(out, in, in_len);
-    return 1;
+    return true;
   }
 
   /* TLS 1.2 AEADs include the length in the AD and are assumed to have fixed
    * overhead. Otherwise the parameter is unused. */
   size_t plaintext_len = 0;
-  if (!aead->omit_length_in_ad) {
-    size_t overhead = SSL_AEAD_CTX_max_overhead(aead);
+  if (!omit_length_in_ad_) {
+    size_t overhead = MaxOverhead();
     if (in_len < overhead) {
       /* Publicly invalid. */
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_PACKET_LENGTH);
-      return 0;
+      return false;
     }
     plaintext_len = in_len - overhead;
   }
   uint8_t ad[13];
-  size_t ad_len = ssl_aead_ctx_get_ad(aead, ad, type, wire_version, seqnum,
-                                      plaintext_len);
+  size_t ad_len =
+      GetAdditionalData(ad, type, wire_version, seqnum, plaintext_len);
 
   /* Assemble the nonce. */
   uint8_t nonce[EVP_AEAD_MAX_NONCE_LENGTH];
   size_t nonce_len = 0;
 
   /* Prepend the fixed nonce, or left-pad with zeros if XORing. */
-  if (aead->xor_fixed_nonce) {
-    nonce_len = aead->fixed_nonce_len - aead->variable_nonce_len;
+  if (xor_fixed_nonce_) {
+    nonce_len = fixed_nonce_len_ - variable_nonce_len_;
     OPENSSL_memset(nonce, 0, nonce_len);
   } else {
-    OPENSSL_memcpy(nonce, aead->fixed_nonce, aead->fixed_nonce_len);
-    nonce_len += aead->fixed_nonce_len;
+    OPENSSL_memcpy(nonce, fixed_nonce_, fixed_nonce_len_);
+    nonce_len += fixed_nonce_len_;
   }
 
   /* Add the variable nonce. */
-  if (aead->variable_nonce_included_in_record) {
-    if (in_len < aead->variable_nonce_len) {
+  if (variable_nonce_included_in_record_) {
+    if (in_len < variable_nonce_len_) {
       /* Publicly invalid. */
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_PACKET_LENGTH);
-      return 0;
+      return false;
     }
-    OPENSSL_memcpy(nonce + nonce_len, in, aead->variable_nonce_len);
-    in += aead->variable_nonce_len;
-    in_len -= aead->variable_nonce_len;
+    OPENSSL_memcpy(nonce + nonce_len, in, variable_nonce_len_);
+    in += variable_nonce_len_;
+    in_len -= variable_nonce_len_;
   } else {
-    assert(aead->variable_nonce_len == 8);
-    OPENSSL_memcpy(nonce + nonce_len, seqnum, aead->variable_nonce_len);
+    assert(variable_nonce_len_ == 8);
+    OPENSSL_memcpy(nonce + nonce_len, seqnum, variable_nonce_len_);
   }
-  nonce_len += aead->variable_nonce_len;
+  nonce_len += variable_nonce_len_;
 
   /* XOR the fixed nonce, if necessary. */
-  if (aead->xor_fixed_nonce) {
-    assert(nonce_len == aead->fixed_nonce_len);
-    for (size_t i = 0; i < aead->fixed_nonce_len; i++) {
-      nonce[i] ^= aead->fixed_nonce[i];
+  if (xor_fixed_nonce_) {
+    assert(nonce_len == fixed_nonce_len_);
+    for (size_t i = 0; i < fixed_nonce_len_; i++) {
+      nonce[i] ^= fixed_nonce_[i];
     }
   }
 
   /* Decrypt in-place. */
   size_t len;
-  if (!EVP_AEAD_CTX_open(&aead->ctx, in, &len, in_len, nonce, nonce_len,
-                         in, in_len, ad, ad_len)) {
-    return 0;
+  if (!EVP_AEAD_CTX_open(ctx_.get(), in, &len, in_len, nonce, nonce_len, in,
+                         in_len, ad, ad_len)) {
+    return false;
   }
   CBS_init(out, in, len);
-  return 1;
+  return true;
 }
 
-int SSL_AEAD_CTX_seal_scatter(SSL_AEAD_CTX *aead, uint8_t *out_prefix,
-                              uint8_t *out, uint8_t *out_suffix,
-                              size_t *out_suffix_len, size_t max_out_suffix_len,
-                              uint8_t type, uint16_t wire_version,
-                              const uint8_t seqnum[8], const uint8_t *in,
-                              size_t in_len, const uint8_t *extra_in,
-                              size_t extra_in_len) {
-#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  aead = NULL;
-#endif
-
+bool SSLAEADContext::SealScatter(uint8_t *out_prefix, uint8_t *out,
+                                 uint8_t *out_suffix, size_t *out_suffix_len,
+                                 size_t max_out_suffix_len, uint8_t type,
+                                 uint16_t wire_version, const uint8_t seqnum[8],
+                                 const uint8_t *in, size_t in_len,
+                                 const uint8_t *extra_in, size_t extra_in_len) {
   if ((in != out && buffers_alias(in, in_len, out, in_len)) ||
       buffers_alias(in, in_len, out_suffix, max_out_suffix_len)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_OUTPUT_ALIASES_INPUT);
-    return 0;
+    return false;
   }
   if (extra_in_len > max_out_suffix_len) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
-    return 0;
+    return false;
   }
 
-  if (aead == NULL) {
+  if (is_null_cipher() || FUZZER_MODE) {
     /* Handle the initial NULL cipher. */
     OPENSSL_memmove(out, in, in_len);
     OPENSSL_memmove(out_suffix, extra_in, extra_in_len);
     *out_suffix_len = extra_in_len;
-    return 1;
+    return true;
   }
 
   uint8_t ad[13];
-  size_t ad_len = ssl_aead_ctx_get_ad(aead, ad, type, wire_version, seqnum,
-                                      in_len);
+  size_t ad_len = GetAdditionalData(ad, type, wire_version, seqnum, in_len);
 
   /* Assemble the nonce. */
   uint8_t nonce[EVP_AEAD_MAX_NONCE_LENGTH];
   size_t nonce_len = 0;
 
   /* Prepend the fixed nonce, or left-pad with zeros if XORing. */
-  if (aead->xor_fixed_nonce) {
-    nonce_len = aead->fixed_nonce_len - aead->variable_nonce_len;
+  if (xor_fixed_nonce_) {
+    nonce_len = fixed_nonce_len_ - variable_nonce_len_;
     OPENSSL_memset(nonce, 0, nonce_len);
   } else {
-    OPENSSL_memcpy(nonce, aead->fixed_nonce, aead->fixed_nonce_len);
-    nonce_len += aead->fixed_nonce_len;
+    OPENSSL_memcpy(nonce, fixed_nonce_, fixed_nonce_len_);
+    nonce_len += fixed_nonce_len_;
   }
 
   /* Select the variable nonce. */
-  if (aead->random_variable_nonce) {
-    assert(aead->variable_nonce_included_in_record);
-    if (!RAND_bytes(nonce + nonce_len, aead->variable_nonce_len)) {
-      return 0;
+  if (random_variable_nonce_) {
+    assert(variable_nonce_included_in_record_);
+    if (!RAND_bytes(nonce + nonce_len, variable_nonce_len_)) {
+      return false;
     }
   } else {
     /* When sending we use the sequence number as the variable part of the
      * nonce. */
-    assert(aead->variable_nonce_len == 8);
-    OPENSSL_memcpy(nonce + nonce_len, seqnum, aead->variable_nonce_len);
+    assert(variable_nonce_len_ == 8);
+    OPENSSL_memcpy(nonce + nonce_len, seqnum, variable_nonce_len_);
   }
-  nonce_len += aead->variable_nonce_len;
+  nonce_len += variable_nonce_len_;
 
   /* Emit the variable nonce if included in the record. */
-  if (aead->variable_nonce_included_in_record) {
-    assert(!aead->xor_fixed_nonce);
-    if (buffers_alias(in, in_len, out_prefix, aead->variable_nonce_len)) {
+  if (variable_nonce_included_in_record_) {
+    assert(!xor_fixed_nonce_);
+    if (buffers_alias(in, in_len, out_prefix, variable_nonce_len_)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_OUTPUT_ALIASES_INPUT);
-      return 0;
+      return false;
     }
-    OPENSSL_memcpy(out_prefix, nonce + aead->fixed_nonce_len,
-                   aead->variable_nonce_len);
+    OPENSSL_memcpy(out_prefix, nonce + fixed_nonce_len_,
+                   variable_nonce_len_);
   }
 
   /* XOR the fixed nonce, if necessary. */
-  if (aead->xor_fixed_nonce) {
-    assert(nonce_len == aead->fixed_nonce_len);
-    for (size_t i = 0; i < aead->fixed_nonce_len; i++) {
-      nonce[i] ^= aead->fixed_nonce[i];
+  if (xor_fixed_nonce_) {
+    assert(nonce_len == fixed_nonce_len_);
+    for (size_t i = 0; i < fixed_nonce_len_; i++) {
+      nonce[i] ^= fixed_nonce_[i];
     }
   }
 
-  return EVP_AEAD_CTX_seal_scatter(&aead->ctx, out, out_suffix, out_suffix_len,
-                                   max_out_suffix_len, nonce, nonce_len, in,
-                                   in_len, extra_in, extra_in_len, ad, ad_len);
+  return !!EVP_AEAD_CTX_seal_scatter(
+      ctx_.get(), out, out_suffix, out_suffix_len, max_out_suffix_len, nonce,
+      nonce_len, in, in_len, extra_in, extra_in_len, ad, ad_len);
 }
 
-int SSL_AEAD_CTX_seal(SSL_AEAD_CTX *aead, uint8_t *out, size_t *out_len,
-                      size_t max_out_len, uint8_t type, uint16_t wire_version,
-                      const uint8_t seqnum[8], const uint8_t *in,
-                      size_t in_len) {
-  size_t prefix_len = SSL_AEAD_CTX_explicit_nonce_len(aead);
+bool SSLAEADContext::Seal(uint8_t *out, size_t *out_len, size_t max_out_len,
+                          uint8_t type, uint16_t wire_version,
+                          const uint8_t seqnum[8], const uint8_t *in,
+                          size_t in_len) {
+  size_t prefix_len = ExplicitNonceLen();
   if (in_len + prefix_len < in_len) {
     OPENSSL_PUT_ERROR(CIPHER, SSL_R_RECORD_TOO_LARGE);
-    return 0;
+    return false;
   }
   if (in_len + prefix_len > max_out_len) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
-    return 0;
+    return false;
   }
 
   size_t suffix_len;
-  if (!SSL_AEAD_CTX_seal_scatter(aead, out, out + prefix_len,
-                                 out + prefix_len + in_len, &suffix_len,
-                                 max_out_len - prefix_len - in_len, type,
-                                 wire_version, seqnum, in, in_len, 0, 0)) {
-    return 0;
+  if (!SealScatter(out, out + prefix_len, out + prefix_len + in_len,
+                   &suffix_len, max_out_len - prefix_len - in_len, type,
+                   wire_version, seqnum, in, in_len, 0, 0)) {
+    return false;
   }
-  assert(suffix_len <= SSL_AEAD_CTX_max_suffix_len(aead, 0));
+  assert(suffix_len <= MaxSuffixLen(0));
   *out_len = prefix_len + in_len + suffix_len;
-  return 1;
+  return true;
 }
+
+bool SSLAEADContext::GetIV(const uint8_t **out_iv, size_t *out_iv_len) const {
+  return !is_null_cipher() &&
+         EVP_AEAD_CTX_get_iv(ctx_.get(), out_iv, out_iv_len);
+}
+
+}  // namespace bssl
diff --git a/src/ssl/ssl_asn1.cc b/src/ssl/ssl_asn1.cc
index 1d6140e..d360a7a 100644
--- a/src/ssl/ssl_asn1.cc
+++ b/src/ssl/ssl_asn1.cc
@@ -102,6 +102,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 /* An SSL_SESSION is serialized as the following ASN.1 structure:
  *
  * SSLSession ::= SEQUENCE {
@@ -196,15 +198,14 @@
 
 static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data,
                                      size_t *out_len, int for_ticket) {
-  CBB cbb, session, child, child2;
-
   if (in == NULL || in->cipher == NULL) {
     return 0;
   }
 
-  CBB_zero(&cbb);
-  if (!CBB_init(&cbb, 0) ||
-      !CBB_add_asn1(&cbb, &session, CBS_ASN1_SEQUENCE) ||
+  ScopedCBB cbb;
+  CBB session, child, child2;
+  if (!CBB_init(cbb.get(), 0) ||
+      !CBB_add_asn1(cbb.get(), &session, CBS_ASN1_SEQUENCE) ||
       !CBB_add_asn1_uint64(&session, kVersion) ||
       !CBB_add_asn1_uint64(&session, in->ssl_version) ||
       !CBB_add_asn1(&session, &child, CBS_ASN1_OCTETSTRING) ||
@@ -220,7 +221,7 @@
       !CBB_add_asn1(&session, &child, kTimeoutTag) ||
       !CBB_add_asn1_uint64(&child, in->timeout)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    goto err;
+    return 0;
   }
 
   /* The peer certificate is only serialized if the SHA-256 isn't
@@ -231,7 +232,7 @@
         !CBB_add_bytes(&child, CRYPTO_BUFFER_data(buffer),
                        CRYPTO_BUFFER_len(buffer))) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -241,14 +242,14 @@
       !CBB_add_asn1(&child, &child2, CBS_ASN1_OCTETSTRING) ||
       !CBB_add_bytes(&child2, in->sid_ctx, in->sid_ctx_length)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    goto err;
+    return 0;
   }
 
   if (in->verify_result != X509_V_OK) {
     if (!CBB_add_asn1(&session, &child, kVerifyResultTag) ||
         !CBB_add_asn1_uint64(&child, in->verify_result)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -258,7 +259,7 @@
         !CBB_add_bytes(&child2, (const uint8_t *)in->tlsext_hostname,
                        strlen(in->tlsext_hostname))) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -268,7 +269,7 @@
         !CBB_add_bytes(&child2, (const uint8_t *)in->psk_identity,
                        strlen(in->psk_identity))) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -276,7 +277,7 @@
     if (!CBB_add_asn1(&session, &child, kTicketLifetimeHintTag) ||
         !CBB_add_asn1_uint64(&child, in->tlsext_tick_lifetime_hint)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -285,7 +286,7 @@
         !CBB_add_asn1(&child, &child2, CBS_ASN1_OCTETSTRING) ||
         !CBB_add_bytes(&child2, in->tlsext_tick, in->tlsext_ticklen)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -294,7 +295,7 @@
         !CBB_add_asn1(&child, &child2, CBS_ASN1_OCTETSTRING) ||
         !CBB_add_bytes(&child2, in->peer_sha256, sizeof(in->peer_sha256))) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -304,7 +305,7 @@
         !CBB_add_bytes(&child2, in->original_handshake_hash,
                        in->original_handshake_hash_len)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -314,7 +315,7 @@
         !CBB_add_bytes(&child2, in->tlsext_signed_cert_timestamp_list,
                        in->tlsext_signed_cert_timestamp_list_length)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -323,7 +324,7 @@
         !CBB_add_asn1(&child, &child2, CBS_ASN1_OCTETSTRING) ||
         !CBB_add_bytes(&child2, in->ocsp_response, in->ocsp_response_length)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -332,7 +333,7 @@
         !CBB_add_asn1(&child, &child2, CBS_ASN1_BOOLEAN) ||
         !CBB_add_u8(&child2, 0xff)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -340,7 +341,7 @@
       (!CBB_add_asn1(&session, &child, kGroupIDTag) ||
        !CBB_add_asn1_uint64(&child, in->group_id))) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    goto err;
+    return 0;
   }
 
   /* The certificate chain is only serialized if the leaf's SHA-256 isn't
@@ -350,14 +351,14 @@
       sk_CRYPTO_BUFFER_num(in->certs) >= 2) {
     if (!CBB_add_asn1(&session, &child, kCertChainTag)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      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);
       if (!CBB_add_bytes(&child, CRYPTO_BUFFER_data(buffer),
                          CRYPTO_BUFFER_len(buffer))) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-        goto err;
+        return 0;
       }
     }
   }
@@ -367,7 +368,7 @@
         !CBB_add_asn1(&child, &child2, CBS_ASN1_OCTETSTRING) ||
         !CBB_add_u32(&child2, in->ticket_age_add)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -376,7 +377,7 @@
         !CBB_add_asn1(&child, &child2, CBS_ASN1_BOOLEAN) ||
         !CBB_add_u8(&child2, 0x00)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
   }
 
@@ -384,21 +385,21 @@
       (!CBB_add_asn1(&session, &child, kPeerSignatureAlgorithmTag) ||
        !CBB_add_asn1_uint64(&child, in->peer_signature_algorithm))) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    goto err;
+    return 0;
   }
 
   if (in->ticket_max_early_data != 0 &&
       (!CBB_add_asn1(&session, &child, kTicketMaxEarlyDataTag) ||
        !CBB_add_asn1_uint64(&child, in->ticket_max_early_data))) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    goto err;
+    return 0;
   }
 
   if (in->timeout != in->auth_timeout &&
       (!CBB_add_asn1(&session, &child, kAuthTimeoutTag) ||
        !CBB_add_asn1_uint64(&child, in->auth_timeout))) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    goto err;
+    return 0;
   }
 
   if (in->early_alpn) {
@@ -407,68 +408,15 @@
         !CBB_add_bytes(&child2, (const uint8_t *)in->early_alpn,
                        in->early_alpn_len)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
-    }
-  }
-
-  if (!CBB_finish(&cbb, out_data, out_len)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    goto err;
-  }
-  return 1;
-
- err:
-  CBB_cleanup(&cbb);
-  return 0;
-}
-
-int SSL_SESSION_to_bytes(const SSL_SESSION *in, uint8_t **out_data,
-                         size_t *out_len) {
-  if (in->not_resumable) {
-    /* If the caller has an unresumable session, e.g. if |SSL_get_session| were
-     * called on a TLS 1.3 or False Started connection, serialize with a
-     * placeholder value so it is not accidentally deserialized into a resumable
-     * one. */
-    static const char kNotResumableSession[] = "NOT RESUMABLE";
-
-    *out_len = strlen(kNotResumableSession);
-    *out_data = (uint8_t *)BUF_memdup(kNotResumableSession, *out_len);
-    if (*out_data == NULL) {
       return 0;
     }
-
-    return 1;
   }
 
-  return SSL_SESSION_to_bytes_full(in, out_data, out_len, 0);
-}
-
-int SSL_SESSION_to_bytes_for_ticket(const SSL_SESSION *in, uint8_t **out_data,
-                                    size_t *out_len) {
-  return SSL_SESSION_to_bytes_full(in, out_data, out_len, 1);
-}
-
-int i2d_SSL_SESSION(SSL_SESSION *in, uint8_t **pp) {
-  uint8_t *out;
-  size_t len;
-
-  if (!SSL_SESSION_to_bytes(in, &out, &len)) {
-    return -1;
+  if (!CBB_finish(cbb.get(), out_data, out_len)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
   }
-
-  if (len > INT_MAX) {
-    OPENSSL_free(out);
-    OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
-    return -1;
-  }
-
-  if (pp) {
-    OPENSSL_memcpy(*pp, out, len);
-    *pp += len;
-  }
-  OPENSSL_free(out);
-
-  return len;
+  return 1;
 }
 
 /* SSL_SESSION_parse_string gets an optional ASN.1 OCTET STRING
@@ -574,11 +522,12 @@
   return 1;
 }
 
-SSL_SESSION *SSL_SESSION_parse(CBS *cbs, const SSL_X509_METHOD *x509_method,
-                               CRYPTO_BUFFER_POOL *pool) {
-  SSL_SESSION *ret = ssl_session_new(x509_method);
-  if (ret == NULL) {
-    goto err;
+UniquePtr<SSL_SESSION> SSL_SESSION_parse(CBS *cbs,
+                                         const SSL_X509_METHOD *x509_method,
+                                         CRYPTO_BUFFER_POOL *pool) {
+  UniquePtr<SSL_SESSION> ret = ssl_session_new(x509_method);
+  if (!ret) {
+    return nullptr;
   }
 
   CBS session;
@@ -595,7 +544,7 @@
       ssl_version > UINT16_MAX ||
       !ssl_protocol_version_from_wire(&unused, ssl_version)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
   ret->ssl_version = ssl_version;
 
@@ -605,12 +554,12 @@
       !CBS_get_u16(&cipher, &cipher_value) ||
       CBS_len(&cipher) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
   ret->cipher = SSL_get_cipher_by_value(cipher_value);
   if (ret->cipher == NULL) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_CIPHER);
-    goto err;
+    return nullptr;
   }
 
   CBS session_id, master_key;
@@ -619,7 +568,7 @@
       !CBS_get_asn1(&session, &master_key, CBS_ASN1_OCTETSTRING) ||
       CBS_len(&master_key) > SSL_MAX_MASTER_KEY_LENGTH) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
   OPENSSL_memcpy(ret->session_id, CBS_data(&session_id), CBS_len(&session_id));
   ret->session_id_length = CBS_len(&session_id);
@@ -634,7 +583,7 @@
       !CBS_get_asn1_uint64(&child, &timeout) ||
       timeout > UINT32_MAX) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
 
   ret->timeout = (uint32_t)timeout;
@@ -644,7 +593,7 @@
   if (!CBS_get_optional_asn1(&session, &peer, &has_peer, kPeerTag) ||
       (has_peer && CBS_len(&peer) == 0)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
   /* |peer| is processed with the certificate chain. */
 
@@ -661,7 +610,7 @@
                              kTicketLifetimeHintTag, 0) ||
       !SSL_SESSION_parse_octet_string(&session, &ret->tlsext_tick,
                                       &ret->tlsext_ticklen, kTicketTag)) {
-    goto err;
+    return nullptr;
   }
 
   if (CBS_peek_asn1_tag(&session, kPeerSHA256Tag)) {
@@ -671,7 +620,7 @@
         CBS_len(&peer_sha256) != sizeof(ret->peer_sha256) ||
         CBS_len(&child) != 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-      goto err;
+      return nullptr;
     }
     OPENSSL_memcpy(ret->peer_sha256, CBS_data(&peer_sha256),
                    sizeof(ret->peer_sha256));
@@ -691,7 +640,7 @@
       !SSL_SESSION_parse_octet_string(
           &session, &ret->ocsp_response, &ret->ocsp_response_length,
           kOCSPResponseTag)) {
-    goto err;
+    return nullptr;
   }
 
   int extended_master_secret;
@@ -699,13 +648,13 @@
                                   kExtendedMasterSecretTag,
                                   0 /* default to false */)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
   ret->extended_master_secret = !!extended_master_secret;
 
   if (!SSL_SESSION_parse_u16(&session, &ret->group_id, kGroupIDTag, 0)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
 
   CBS cert_chain;
@@ -715,17 +664,17 @@
                              kCertChainTag) ||
       (has_cert_chain && CBS_len(&cert_chain) == 0)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
   if (has_cert_chain && !has_peer) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
   if (has_peer || has_cert_chain) {
     ret->certs = sk_CRYPTO_BUFFER_new_null();
     if (ret->certs == NULL) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return nullptr;
     }
 
     if (has_peer) {
@@ -735,7 +684,7 @@
           !sk_CRYPTO_BUFFER_push(ret->certs, buffer)) {
         CRYPTO_BUFFER_free(buffer);
         OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-        goto err;
+        return nullptr;
       }
     }
 
@@ -744,7 +693,7 @@
       if (!CBS_get_any_asn1_element(&cert_chain, &cert, NULL, NULL) ||
           CBS_len(&cert) == 0) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-        goto err;
+        return nullptr;
       }
 
       /* TODO(agl): this should use the |SSL_CTX|'s pool. */
@@ -753,14 +702,14 @@
           !sk_CRYPTO_BUFFER_push(ret->certs, buffer)) {
         CRYPTO_BUFFER_free(buffer);
         OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-        goto err;
+        return nullptr;
       }
     }
   }
 
-  if (!x509_method->session_cache_objects(ret)) {
+  if (!x509_method->session_cache_objects(ret.get())) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
 
   CBS age_add;
@@ -770,7 +719,7 @@
       (age_add_present &&
        !CBS_get_u32(&age_add, &ret->ticket_age_add)) ||
       CBS_len(&age_add) != 0) {
-    goto err;
+    return nullptr;
   }
   ret->ticket_age_add_valid = age_add_present;
 
@@ -778,7 +727,7 @@
   if (!CBS_get_optional_asn1_bool(&session, &is_server, kIsServerTag,
                                   1 /* default to true */)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
   /* TODO: in time we can include |is_server| for servers too, then we can
      enforce that client and server sessions are never mixed up. */
@@ -795,28 +744,77 @@
                                       &ret->early_alpn_len, kEarlyALPNTag) ||
       CBS_len(&session) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    goto err;
+    return nullptr;
   }
 
   return ret;
+}
 
-err:
-  SSL_SESSION_free(ret);
-  return NULL;
+}  // namespace bssl
+
+using namespace bssl;
+
+int SSL_SESSION_to_bytes(const SSL_SESSION *in, uint8_t **out_data,
+                         size_t *out_len) {
+  if (in->not_resumable) {
+    /* If the caller has an unresumable session, e.g. if |SSL_get_session| were
+     * called on a TLS 1.3 or False Started connection, serialize with a
+     * placeholder value so it is not accidentally deserialized into a resumable
+     * one. */
+    static const char kNotResumableSession[] = "NOT RESUMABLE";
+
+    *out_len = strlen(kNotResumableSession);
+    *out_data = (uint8_t *)BUF_memdup(kNotResumableSession, *out_len);
+    if (*out_data == NULL) {
+      return 0;
+    }
+
+    return 1;
+  }
+
+  return SSL_SESSION_to_bytes_full(in, out_data, out_len, 0);
+}
+
+int SSL_SESSION_to_bytes_for_ticket(const SSL_SESSION *in, uint8_t **out_data,
+                                    size_t *out_len) {
+  return SSL_SESSION_to_bytes_full(in, out_data, out_len, 1);
+}
+
+int i2d_SSL_SESSION(SSL_SESSION *in, uint8_t **pp) {
+  uint8_t *out;
+  size_t len;
+
+  if (!SSL_SESSION_to_bytes(in, &out, &len)) {
+    return -1;
+  }
+
+  if (len > INT_MAX) {
+    OPENSSL_free(out);
+    OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
+    return -1;
+  }
+
+  if (pp) {
+    OPENSSL_memcpy(*pp, out, len);
+    *pp += len;
+  }
+  OPENSSL_free(out);
+
+  return len;
 }
 
 SSL_SESSION *SSL_SESSION_from_bytes(const uint8_t *in, size_t in_len,
                                     const SSL_CTX *ctx) {
   CBS cbs;
   CBS_init(&cbs, in, in_len);
-  SSL_SESSION *ret = SSL_SESSION_parse(&cbs, ctx->x509_method, ctx->pool);
-  if (ret == NULL) {
+  UniquePtr<SSL_SESSION> ret =
+      SSL_SESSION_parse(&cbs, ctx->x509_method, ctx->pool);
+  if (!ret) {
     return NULL;
   }
   if (CBS_len(&cbs) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-    SSL_SESSION_free(ret);
     return NULL;
   }
-  return ret;
+  return ret.release();
 }
diff --git a/src/ssl/ssl_buffer.cc b/src/ssl/ssl_buffer.cc
index 579899b..7f11b6f 100644
--- a/src/ssl/ssl_buffer.cc
+++ b/src/ssl/ssl_buffer.cc
@@ -27,6 +27,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 /* BIO uses int instead of size_t. No lengths will exceed uint16_t, so this will
  * not overflow. */
 static_assert(0xffff <= INT_MAX, "uint16_t does not fit in int");
@@ -289,3 +291,5 @@
 void ssl_write_buffer_clear(SSL *ssl) {
   clear_buffer(&ssl->s3->write_buffer);
 }
+
+}  // namespace bssl
diff --git a/src/ssl/ssl_cert.cc b/src/ssl/ssl_cert.cc
index df4b9c8..4f457f6 100644
--- a/src/ssl/ssl_cert.cc
+++ b/src/ssl/ssl_cert.cc
@@ -118,6 +118,8 @@
 #include <limits.h>
 #include <string.h>
 
+#include <utility>
+
 #include <openssl/bn.h>
 #include <openssl/buf.h>
 #include <openssl/bytestring.h>
@@ -131,6 +133,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 CERT *ssl_cert_new(const SSL_X509_METHOD *x509_method) {
   CERT *ret = (CERT *)OPENSSL_malloc(sizeof(CERT));
   if (ret == NULL) {
@@ -252,19 +256,17 @@
  * |leaf_cert_and_privkey_ok|. */
 static enum leaf_cert_and_privkey_result_t check_leaf_cert_and_privkey(
     CRYPTO_BUFFER *leaf_buffer, EVP_PKEY *privkey) {
-  enum leaf_cert_and_privkey_result_t ret = leaf_cert_and_privkey_error;
-
   CBS cert_cbs;
   CRYPTO_BUFFER_init_CBS(leaf_buffer, &cert_cbs);
-  EVP_PKEY *pubkey = ssl_cert_parse_pubkey(&cert_cbs);
-  if (pubkey == NULL) {
+  UniquePtr<EVP_PKEY> pubkey = ssl_cert_parse_pubkey(&cert_cbs);
+  if (!pubkey) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    goto out;
+    return leaf_cert_and_privkey_error;
   }
 
   if (!ssl_is_key_type_supported(pubkey->type)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
-    goto out;
+    return leaf_cert_and_privkey_error;
   }
 
   /* An ECC certificate may be usable for ECDH or ECDSA. We only support ECDSA
@@ -272,22 +274,17 @@
   if (pubkey->type == EVP_PKEY_EC &&
       !ssl_cert_check_digital_signature_key_usage(&cert_cbs)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
-    goto out;
+    return leaf_cert_and_privkey_error;
   }
 
   if (privkey != NULL &&
       /* Sanity-check that the private key and the certificate match. */
-      !ssl_compare_public_and_private_key(pubkey, privkey)) {
+      !ssl_compare_public_and_private_key(pubkey.get(), privkey)) {
     ERR_clear_error();
-    ret = leaf_cert_and_privkey_mismatch;
-    goto out;
+    return leaf_cert_and_privkey_mismatch;
   }
 
-  ret = leaf_cert_and_privkey_ok;
-
-out:
-  EVP_PKEY_free(pubkey);
-  return ret;
+  return leaf_cert_and_privkey_ok;
 }
 
 static int cert_set_chain_and_key(
@@ -340,20 +337,6 @@
   return 1;
 }
 
-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);
-}
-
-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,
-                                privkey_method);
-}
-
 int ssl_set_cert(CERT *cert, CRYPTO_BUFFER *buffer) {
   switch (check_leaf_cert_and_privkey(buffer, cert->privatekey)) {
     case leaf_cert_and_privkey_error:
@@ -393,54 +376,29 @@
   return 1;
 }
 
-int SSL_CTX_use_certificate_ASN1(SSL_CTX *ctx, size_t der_len,
-                                 const uint8_t *der) {
-  CRYPTO_BUFFER *buffer = CRYPTO_BUFFER_new(der, der_len, NULL);
-  if (buffer == NULL) {
-    return 0;
-  }
-
-  const int ok = ssl_set_cert(ctx->cert, buffer);
-  CRYPTO_BUFFER_free(buffer);
-  return ok;
-}
-
-int SSL_use_certificate_ASN1(SSL *ssl, const uint8_t *der, size_t der_len) {
-  CRYPTO_BUFFER *buffer = CRYPTO_BUFFER_new(der, der_len, NULL);
-  if (buffer == NULL) {
-    return 0;
-  }
-
-  const int ok = ssl_set_cert(ssl->cert, buffer);
-  CRYPTO_BUFFER_free(buffer);
-  return ok;
-}
-
 int ssl_has_certificate(const SSL *ssl) {
   return ssl->cert->chain != NULL &&
          sk_CRYPTO_BUFFER_value(ssl->cert->chain, 0) != NULL &&
          ssl_has_private_key(ssl);
 }
 
-STACK_OF(CRYPTO_BUFFER) *ssl_parse_cert_chain(uint8_t *out_alert,
-                                              EVP_PKEY **out_pubkey,
-                                              uint8_t *out_leaf_sha256,
-                                              CBS *cbs,
-                                              CRYPTO_BUFFER_POOL *pool) {
-  *out_pubkey = NULL;
-
-  STACK_OF(CRYPTO_BUFFER) *ret = sk_CRYPTO_BUFFER_new_null();
-  if (ret == NULL) {
+UniquePtr<STACK_OF(CRYPTO_BUFFER)>
+    ssl_parse_cert_chain(uint8_t *out_alert, UniquePtr<EVP_PKEY> *out_pubkey,
+                         uint8_t *out_leaf_sha256, CBS *cbs,
+                         CRYPTO_BUFFER_POOL *pool) {
+  UniquePtr<EVP_PKEY> pubkey;
+  UniquePtr<STACK_OF(CRYPTO_BUFFER)> ret(sk_CRYPTO_BUFFER_new_null());
+  if (!ret) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return NULL;
+    return nullptr;
   }
 
   CBS certificate_list;
   if (!CBS_get_u24_length_prefixed(cbs, &certificate_list)) {
     *out_alert = SSL_AD_DECODE_ERROR;
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    goto err;
+    return nullptr;
   }
 
   while (CBS_len(&certificate_list) > 0) {
@@ -449,14 +407,14 @@
         CBS_len(&certificate) == 0) {
       *out_alert = SSL_AD_DECODE_ERROR;
       OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_LENGTH_MISMATCH);
-      goto err;
+      return nullptr;
     }
 
-    if (sk_CRYPTO_BUFFER_num(ret) == 0) {
-      *out_pubkey = ssl_cert_parse_pubkey(&certificate);
-      if (*out_pubkey == NULL) {
+    if (sk_CRYPTO_BUFFER_num(ret.get()) == 0) {
+      pubkey = ssl_cert_parse_pubkey(&certificate);
+      if (!pubkey) {
         *out_alert = SSL_AD_DECODE_ERROR;
-        goto err;
+        return nullptr;
       }
 
       /* Retain the hash of the leaf certificate if requested. */
@@ -467,26 +425,17 @@
 
     CRYPTO_BUFFER *buf =
         CRYPTO_BUFFER_new_from_CBS(&certificate, pool);
-    if (buf == NULL) {
-      *out_alert = SSL_AD_DECODE_ERROR;
-      goto err;
-    }
-
-    if (!sk_CRYPTO_BUFFER_push(ret, buf)) {
+    if (buf == NULL ||
+        !sk_CRYPTO_BUFFER_push(ret.get(), buf)) {
       *out_alert = SSL_AD_INTERNAL_ERROR;
       CRYPTO_BUFFER_free(buf);
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return nullptr;
     }
   }
 
+  *out_pubkey = std::move(pubkey);
   return ret;
-
-err:
-  EVP_PKEY_free(*out_pubkey);
-  *out_pubkey = NULL;
-  sk_CRYPTO_BUFFER_pop_free(ret, CRYPTO_BUFFER_free);
-  return NULL;
 }
 
 int ssl_add_cert_chain(SSL *ssl, CBB *cbb) {
@@ -561,14 +510,14 @@
   return 1;
 }
 
-EVP_PKEY *ssl_cert_parse_pubkey(const CBS *in) {
+UniquePtr<EVP_PKEY> ssl_cert_parse_pubkey(const CBS *in) {
   CBS buf = *in, tbs_cert;
   if (!ssl_cert_skip_to_spki(&buf, &tbs_cert)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_CANNOT_PARSE_LEAF_CERT);
-    return NULL;
+    return nullptr;
   }
 
-  return EVP_parse_public_key(&tbs_cert);
+  return UniquePtr<EVP_PKEY>(EVP_parse_public_key(&tbs_cert));
 }
 
 int ssl_compare_public_and_private_key(const EVP_PKEY *pubkey,
@@ -593,6 +542,7 @@
       break;
     case -2:
       OPENSSL_PUT_ERROR(X509, X509_R_UNKNOWN_KEY_TYPE);
+      break;
     default:
       assert(0);
       break;
@@ -615,15 +565,13 @@
 
   CBS cert_cbs;
   CRYPTO_BUFFER_init_CBS(sk_CRYPTO_BUFFER_value(cert->chain, 0), &cert_cbs);
-  EVP_PKEY *pubkey = ssl_cert_parse_pubkey(&cert_cbs);
+  UniquePtr<EVP_PKEY> pubkey = ssl_cert_parse_pubkey(&cert_cbs);
   if (!pubkey) {
     OPENSSL_PUT_ERROR(X509, X509_R_UNKNOWN_KEY_TYPE);
     return 0;
   }
 
-  const int ok = ssl_compare_public_and_private_key(pubkey, privkey);
-  EVP_PKEY_free(pubkey);
-  return ok;
+  return ssl_compare_public_and_private_key(pubkey.get(), privkey);
 }
 
 int ssl_cert_check_digital_signature_key_usage(const CBS *in) {
@@ -703,22 +651,23 @@
   return 0;
 }
 
-STACK_OF(CRYPTO_BUFFER) *
-    ssl_parse_client_CA_list(SSL *ssl, uint8_t *out_alert, CBS *cbs) {
+UniquePtr<STACK_OF(CRYPTO_BUFFER)> ssl_parse_client_CA_list(SSL *ssl,
+                                                            uint8_t *out_alert,
+                                                            CBS *cbs) {
   CRYPTO_BUFFER_POOL *const pool = ssl->ctx->pool;
 
-  STACK_OF(CRYPTO_BUFFER) *ret = sk_CRYPTO_BUFFER_new_null();
-  if (ret == NULL) {
+  UniquePtr<STACK_OF(CRYPTO_BUFFER)> ret(sk_CRYPTO_BUFFER_new_null());
+  if (!ret) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return NULL;
+    return nullptr;
   }
 
   CBS child;
   if (!CBS_get_u16_length_prefixed(cbs, &child)) {
     *out_alert = SSL_AD_DECODE_ERROR;
     OPENSSL_PUT_ERROR(SSL, SSL_R_LENGTH_MISMATCH);
-    goto err;
+    return nullptr;
   }
 
   while (CBS_len(&child) > 0) {
@@ -726,31 +675,27 @@
     if (!CBS_get_u16_length_prefixed(&child, &distinguished_name)) {
       *out_alert = SSL_AD_DECODE_ERROR;
       OPENSSL_PUT_ERROR(SSL, SSL_R_CA_DN_TOO_LONG);
-      goto err;
+      return nullptr;
     }
 
     CRYPTO_BUFFER *buffer =
         CRYPTO_BUFFER_new_from_CBS(&distinguished_name, pool);
     if (buffer == NULL ||
-        !sk_CRYPTO_BUFFER_push(ret, buffer)) {
+        !sk_CRYPTO_BUFFER_push(ret.get(), buffer)) {
       CRYPTO_BUFFER_free(buffer);
       *out_alert = SSL_AD_INTERNAL_ERROR;
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return nullptr;
     }
   }
 
-  if (!ssl->ctx->x509_method->check_client_CA_list(ret)) {
+  if (!ssl->ctx->x509_method->check_client_CA_list(ret.get())) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    goto err;
+    return nullptr;
   }
 
   return ret;
-
-err:
-  sk_CRYPTO_BUFFER_pop_free(ret, CRYPTO_BUFFER_free);
-  return NULL;
 }
 
 int ssl_add_client_CA_list(SSL *ssl, CBB *cbb) {
@@ -780,31 +725,6 @@
   return CBB_flush(cbb);
 }
 
-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);
-}
-
-void SSL_set_cert_cb(SSL *ssl, int (*cb)(SSL *ssl, void *arg), void *arg) {
-  ssl_cert_set_cert_cb(ssl->cert, cb, arg);
-}
-
-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;
-}
-
-STACK_OF(CRYPTO_BUFFER) *SSL_get0_server_requested_CAs(const SSL *ssl) {
-  if (ssl->s3->hs == NULL) {
-    return NULL;
-  }
-  return ssl->s3->hs->ca_names;
-}
-
 int ssl_check_leaf_certificate(SSL_HANDSHAKE *hs, EVP_PKEY *pkey,
                                const CRYPTO_BUFFER *leaf) {
   SSL *const ssl = hs->ssl;
@@ -860,11 +780,76 @@
   CBS leaf;
   CRYPTO_BUFFER_init_CBS(sk_CRYPTO_BUFFER_value(ssl->cert->chain, 0), &leaf);
 
-  EVP_PKEY_free(hs->local_pubkey);
   hs->local_pubkey = ssl_cert_parse_pubkey(&leaf);
   return hs->local_pubkey != NULL;
 }
 
+}  // namespace bssl
+
+using namespace bssl;
+
+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);
+}
+
+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,
+                                privkey_method);
+}
+
+int SSL_CTX_use_certificate_ASN1(SSL_CTX *ctx, size_t der_len,
+                                 const uint8_t *der) {
+  CRYPTO_BUFFER *buffer = CRYPTO_BUFFER_new(der, der_len, NULL);
+  if (buffer == NULL) {
+    return 0;
+  }
+
+  const int ok = ssl_set_cert(ctx->cert, buffer);
+  CRYPTO_BUFFER_free(buffer);
+  return ok;
+}
+
+int SSL_use_certificate_ASN1(SSL *ssl, const uint8_t *der, size_t der_len) {
+  CRYPTO_BUFFER *buffer = CRYPTO_BUFFER_new(der, der_len, NULL);
+  if (buffer == NULL) {
+    return 0;
+  }
+
+  const int ok = ssl_set_cert(ssl->cert, buffer);
+  CRYPTO_BUFFER_free(buffer);
+  return ok;
+}
+
+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);
+}
+
+void SSL_set_cert_cb(SSL *ssl, int (*cb)(SSL *ssl, void *arg), void *arg) {
+  ssl_cert_set_cert_cb(ssl->cert, cb, arg);
+}
+
+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;
+}
+
+STACK_OF(CRYPTO_BUFFER) *SSL_get0_server_requested_CAs(const SSL *ssl) {
+  if (ssl->s3->hs == NULL) {
+    return NULL;
+  }
+  return ssl->s3->hs->ca_names.get();
+}
+
 static int set_signed_cert_timestamp_list(CERT *cert, const uint8_t *list,
                                            size_t list_len) {
   CBS sct_list;
@@ -903,3 +888,15 @@
   ssl->cert->ocsp_response = CRYPTO_BUFFER_new(response, response_len, NULL);
   return ssl->cert->ocsp_response != NULL;
 }
+
+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;
+}
+
+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;
+}
diff --git a/src/ssl/ssl_cipher.cc b/src/ssl/ssl_cipher.cc
index c0f4122..dbb4c75 100644
--- a/src/ssl/ssl_cipher.cc
+++ b/src/ssl/ssl_cipher.cc
@@ -154,6 +154,8 @@
 #include "../crypto/internal.h"
 
 
+namespace bssl {
+
 /* kCiphers is an array of all supported ciphers, sorted by id. */
 static const SSL_CIPHER kCiphers[] = {
     /* The RSA ciphers */
@@ -643,14 +645,6 @@
   }
 }
 
-const SSL_CIPHER *SSL_get_cipher_by_value(uint16_t value) {
-  SSL_CIPHER c;
-
-  c.id = 0x03000000L | value;
-  return reinterpret_cast<const SSL_CIPHER *>(bsearch(
-      &c, kCiphers, kCiphersLen, sizeof(SSL_CIPHER), ssl_cipher_id_cmp));
-}
-
 int ssl_cipher_get_evp_aead(const EVP_AEAD **out_aead,
                             size_t *out_mac_secret_len,
                             size_t *out_fixed_iv_len,
@@ -1371,8 +1365,6 @@
   return 0;
 }
 
-uint32_t SSL_CIPHER_get_id(const SSL_CIPHER *cipher) { return cipher->id; }
-
 uint16_t ssl_cipher_get_value(const SSL_CIPHER *cipher) {
   uint32_t id = cipher->id;
   /* All ciphers are SSLv3. */
@@ -1380,6 +1372,68 @@
   return id & 0xffff;
 }
 
+uint32_t ssl_cipher_auth_mask_for_key(const EVP_PKEY *key) {
+  switch (EVP_PKEY_id(key)) {
+    case EVP_PKEY_RSA:
+      return SSL_aRSA;
+    case EVP_PKEY_EC:
+    case EVP_PKEY_ED25519:
+      /* Ed25519 keys in TLS 1.2 repurpose the ECDSA ciphers. */
+      return SSL_aECDSA;
+    default:
+      return 0;
+  }
+}
+
+int ssl_cipher_uses_certificate_auth(const SSL_CIPHER *cipher) {
+  return (cipher->algorithm_auth & SSL_aCERT) != 0;
+}
+
+int ssl_cipher_requires_server_key_exchange(const SSL_CIPHER *cipher) {
+  /* Ephemeral Diffie-Hellman key exchanges require a ServerKeyExchange. */
+  if (cipher->algorithm_mkey & SSL_kECDHE) {
+    return 1;
+  }
+
+  /* It is optional in all others. */
+  return 0;
+}
+
+size_t ssl_cipher_get_record_split_len(const SSL_CIPHER *cipher) {
+  size_t block_size;
+  switch (cipher->algorithm_enc) {
+    case SSL_3DES:
+      block_size = 8;
+      break;
+    case SSL_AES128:
+    case SSL_AES256:
+      block_size = 16;
+      break;
+    default:
+      return 0;
+  }
+
+  /* All supported TLS 1.0 ciphers use SHA-1. */
+  assert(cipher->algorithm_mac == SSL_SHA1);
+  size_t ret = 1 + SHA_DIGEST_LENGTH;
+  ret += block_size - (ret % block_size);
+  return ret;
+}
+
+}  // namespace bssl
+
+using namespace bssl;
+
+const SSL_CIPHER *SSL_get_cipher_by_value(uint16_t value) {
+  SSL_CIPHER c;
+
+  c.id = 0x03000000L | value;
+  return reinterpret_cast<const SSL_CIPHER *>(bsearch(
+      &c, kCiphers, kCiphersLen, sizeof(SSL_CIPHER), ssl_cipher_id_cmp));
+}
+
+uint32_t SSL_CIPHER_get_id(const SSL_CIPHER *cipher) { return cipher->id; }
+
 int SSL_CIPHER_is_AES(const SSL_CIPHER *cipher) {
   return (cipher->algorithm_enc & SSL_AES) != 0;
 }
@@ -1697,51 +1751,3 @@
 const char *SSL_COMP_get_name(const COMP_METHOD *comp) { return NULL; }
 
 void SSL_COMP_free_compression_methods(void) {}
-
-uint32_t ssl_cipher_auth_mask_for_key(const EVP_PKEY *key) {
-  switch (EVP_PKEY_id(key)) {
-    case EVP_PKEY_RSA:
-      return SSL_aRSA;
-    case EVP_PKEY_EC:
-    case EVP_PKEY_ED25519:
-      /* Ed25519 keys in TLS 1.2 repurpose the ECDSA ciphers. */
-      return SSL_aECDSA;
-    default:
-      return 0;
-  }
-}
-
-int ssl_cipher_uses_certificate_auth(const SSL_CIPHER *cipher) {
-  return (cipher->algorithm_auth & SSL_aCERT) != 0;
-}
-
-int ssl_cipher_requires_server_key_exchange(const SSL_CIPHER *cipher) {
-  /* Ephemeral Diffie-Hellman key exchanges require a ServerKeyExchange. */
-  if (cipher->algorithm_mkey & SSL_kECDHE) {
-    return 1;
-  }
-
-  /* It is optional in all others. */
-  return 0;
-}
-
-size_t ssl_cipher_get_record_split_len(const SSL_CIPHER *cipher) {
-  size_t block_size;
-  switch (cipher->algorithm_enc) {
-    case SSL_3DES:
-      block_size = 8;
-      break;
-    case SSL_AES128:
-    case SSL_AES256:
-      block_size = 16;
-      break;
-    default:
-      return 0;
-  }
-
-  /* All supported TLS 1.0 ciphers use SHA-1. */
-  assert(cipher->algorithm_mac == SSL_SHA1);
-  size_t ret = 1 + SHA_DIGEST_LENGTH;
-  ret += block_size - (ret % block_size);
-  return ret;
-}
diff --git a/src/ssl/ssl_ecdh.cc b/src/ssl/ssl_ecdh.cc
deleted file mode 100644
index fa1cbe9..0000000
--- a/src/ssl/ssl_ecdh.cc
+++ /dev/null
@@ -1,342 +0,0 @@
-/* Copyright (c) 2015, 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/bn.h>
-#include <openssl/bytestring.h>
-#include <openssl/curve25519.h>
-#include <openssl/ec.h>
-#include <openssl/err.h>
-#include <openssl/mem.h>
-#include <openssl/nid.h>
-
-#include "internal.h"
-#include "../crypto/internal.h"
-
-
-/* |EC_POINT| implementation. */
-
-static void ssl_ec_point_cleanup(SSL_ECDH_CTX *ctx) {
-  BIGNUM *private_key = (BIGNUM *)ctx->data;
-  BN_clear_free(private_key);
-}
-
-static int ssl_ec_point_offer(SSL_ECDH_CTX *ctx, CBB *out) {
-  /* Set up a shared |BN_CTX| for all operations. */
-  bssl::UniquePtr<BN_CTX> bn_ctx(BN_CTX_new());
-  if (!bn_ctx) {
-    return 0;
-  }
-  bssl::BN_CTXScope scope(bn_ctx.get());
-
-  /* Generate a private key. */
-  bssl::UniquePtr<EC_GROUP> group(EC_GROUP_new_by_curve_name(ctx->method->nid));
-  bssl::UniquePtr<BIGNUM> private_key(BN_new());
-  if (!group || !private_key ||
-      !BN_rand_range_ex(private_key.get(), 1,
-                        EC_GROUP_get0_order(group.get()))) {
-    return 0;
-  }
-
-  /* Compute the corresponding public key and serialize it. */
-  bssl::UniquePtr<EC_POINT> public_key(EC_POINT_new(group.get()));
-  if (!public_key ||
-      !EC_POINT_mul(group.get(), public_key.get(), private_key.get(), NULL,
-                    NULL, bn_ctx.get()) ||
-      !EC_POINT_point2cbb(out, group.get(), public_key.get(),
-                          POINT_CONVERSION_UNCOMPRESSED, bn_ctx.get())) {
-    return 0;
-  }
-
-  assert(ctx->data == NULL);
-  ctx->data = private_key.release();
-  return 1;
-}
-
-static int ssl_ec_point_finish(SSL_ECDH_CTX *ctx, uint8_t **out_secret,
-                               size_t *out_secret_len, uint8_t *out_alert,
-                               const uint8_t *peer_key, size_t peer_key_len) {
-  BIGNUM *private_key = (BIGNUM *)ctx->data;
-  assert(private_key != NULL);
-  *out_alert = SSL_AD_INTERNAL_ERROR;
-
-  /* Set up a shared |BN_CTX| for all operations. */
-  bssl::UniquePtr<BN_CTX> bn_ctx(BN_CTX_new());
-  if (!bn_ctx) {
-    return 0;
-  }
-  bssl::BN_CTXScope scope(bn_ctx.get());
-
-  bssl::UniquePtr<EC_GROUP> group(EC_GROUP_new_by_curve_name(ctx->method->nid));
-  if (!group) {
-    return 0;
-  }
-
-  bssl::UniquePtr<EC_POINT> peer_point(EC_POINT_new(group.get()));
-  bssl::UniquePtr<EC_POINT> result(EC_POINT_new(group.get()));
-  BIGNUM *x = BN_CTX_get(bn_ctx.get());
-  if (!peer_point || !result || !x) {
-    return 0;
-  }
-
-  if (!EC_POINT_oct2point(group.get(), peer_point.get(), peer_key, peer_key_len,
-                          bn_ctx.get())) {
-    *out_alert = SSL_AD_DECODE_ERROR;
-    return 0;
-  }
-
-  /* Compute the x-coordinate of |peer_key| * |private_key|. */
-  if (!EC_POINT_mul(group.get(), result.get(), NULL, peer_point.get(),
-                    private_key, bn_ctx.get()) ||
-      !EC_POINT_get_affine_coordinates_GFp(group.get(), result.get(), x, NULL,
-                                           bn_ctx.get())) {
-    return 0;
-  }
-
-  /* Encode the x-coordinate left-padded with zeros. */
-  size_t secret_len = (EC_GROUP_get_degree(group.get()) + 7) / 8;
-  bssl::UniquePtr<uint8_t> secret((uint8_t *)OPENSSL_malloc(secret_len));
-  if (!secret || !BN_bn2bin_padded(secret.get(), secret_len, x)) {
-    return 0;
-  }
-
-  *out_secret = secret.release();
-  *out_secret_len = secret_len;
-  return 1;
-}
-
-static int ssl_ec_point_accept(SSL_ECDH_CTX *ctx, CBB *out_public_key,
-                               uint8_t **out_secret, size_t *out_secret_len,
-                               uint8_t *out_alert, const uint8_t *peer_key,
-                               size_t peer_key_len) {
-  *out_alert = SSL_AD_INTERNAL_ERROR;
-  if (!ssl_ec_point_offer(ctx, out_public_key) ||
-      !ssl_ec_point_finish(ctx, out_secret, out_secret_len, out_alert, peer_key,
-                           peer_key_len)) {
-    return 0;
-  }
-  return 1;
-}
-
-/* X25119 implementation. */
-
-static void ssl_x25519_cleanup(SSL_ECDH_CTX *ctx) {
-  if (ctx->data == NULL) {
-    return;
-  }
-  OPENSSL_cleanse(ctx->data, 32);
-  OPENSSL_free(ctx->data);
-}
-
-static int ssl_x25519_offer(SSL_ECDH_CTX *ctx, CBB *out) {
-  assert(ctx->data == NULL);
-
-  ctx->data = OPENSSL_malloc(32);
-  if (ctx->data == NULL) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return 0;
-  }
-  uint8_t public_key[32];
-  X25519_keypair(public_key, (uint8_t *)ctx->data);
-  return CBB_add_bytes(out, public_key, sizeof(public_key));
-}
-
-static int ssl_x25519_finish(SSL_ECDH_CTX *ctx, uint8_t **out_secret,
-                             size_t *out_secret_len, uint8_t *out_alert,
-                             const uint8_t *peer_key, size_t peer_key_len) {
-  assert(ctx->data != NULL);
-  *out_alert = SSL_AD_INTERNAL_ERROR;
-
-  uint8_t *secret = (uint8_t *)OPENSSL_malloc(32);
-  if (secret == NULL) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return 0;
-  }
-
-  if (peer_key_len != 32 ||
-      !X25519(secret, (uint8_t *)ctx->data, peer_key)) {
-    OPENSSL_free(secret);
-    *out_alert = SSL_AD_DECODE_ERROR;
-    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
-    return 0;
-  }
-
-  *out_secret = secret;
-  *out_secret_len = 32;
-  return 1;
-}
-
-static int ssl_x25519_accept(SSL_ECDH_CTX *ctx, CBB *out_public_key,
-                             uint8_t **out_secret, size_t *out_secret_len,
-                             uint8_t *out_alert, const uint8_t *peer_key,
-                             size_t peer_key_len) {
-  *out_alert = SSL_AD_INTERNAL_ERROR;
-  if (!ssl_x25519_offer(ctx, out_public_key) ||
-      !ssl_x25519_finish(ctx, out_secret, out_secret_len, out_alert, peer_key,
-                         peer_key_len)) {
-    return 0;
-  }
-  return 1;
-}
-
-
-static const SSL_ECDH_METHOD kMethods[] = {
-    {
-        NID_secp224r1,
-        SSL_CURVE_SECP224R1,
-        "P-224",
-        ssl_ec_point_cleanup,
-        ssl_ec_point_offer,
-        ssl_ec_point_accept,
-        ssl_ec_point_finish,
-    },
-    {
-        NID_X9_62_prime256v1,
-        SSL_CURVE_SECP256R1,
-        "P-256",
-        ssl_ec_point_cleanup,
-        ssl_ec_point_offer,
-        ssl_ec_point_accept,
-        ssl_ec_point_finish,
-    },
-    {
-        NID_secp384r1,
-        SSL_CURVE_SECP384R1,
-        "P-384",
-        ssl_ec_point_cleanup,
-        ssl_ec_point_offer,
-        ssl_ec_point_accept,
-        ssl_ec_point_finish,
-    },
-    {
-        NID_secp521r1,
-        SSL_CURVE_SECP521R1,
-        "P-521",
-        ssl_ec_point_cleanup,
-        ssl_ec_point_offer,
-        ssl_ec_point_accept,
-        ssl_ec_point_finish,
-    },
-    {
-        NID_X25519,
-        SSL_CURVE_X25519,
-        "X25519",
-        ssl_x25519_cleanup,
-        ssl_x25519_offer,
-        ssl_x25519_accept,
-        ssl_x25519_finish,
-    },
-};
-
-static const SSL_ECDH_METHOD *method_from_group_id(uint16_t group_id) {
-  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kMethods); i++) {
-    if (kMethods[i].group_id == group_id) {
-      return &kMethods[i];
-    }
-  }
-  return NULL;
-}
-
-static const SSL_ECDH_METHOD *method_from_nid(int nid) {
-  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kMethods); i++) {
-    if (kMethods[i].nid == nid) {
-      return &kMethods[i];
-    }
-  }
-  return NULL;
-}
-
-static const SSL_ECDH_METHOD *method_from_name(const char *name, size_t len) {
-  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kMethods); i++) {
-    if (len == strlen(kMethods[i].name) &&
-        !strncmp(kMethods[i].name, name, len)) {
-      return &kMethods[i];
-    }
-  }
-  return NULL;
-}
-
-const char* SSL_get_curve_name(uint16_t group_id) {
-  const SSL_ECDH_METHOD *method = method_from_group_id(group_id);
-  if (method == NULL) {
-    return NULL;
-  }
-  return method->name;
-}
-
-int ssl_nid_to_group_id(uint16_t *out_group_id, int nid) {
-  const SSL_ECDH_METHOD *method = method_from_nid(nid);
-  if (method == NULL) {
-    return 0;
-  }
-  *out_group_id = method->group_id;
-  return 1;
-}
-
-int ssl_name_to_group_id(uint16_t *out_group_id, const char *name, size_t len) {
-  const SSL_ECDH_METHOD *method = method_from_name(name, len);
-  if (method == NULL) {
-    return 0;
-  }
-  *out_group_id = method->group_id;
-  return 1;
-}
-
-int SSL_ECDH_CTX_init(SSL_ECDH_CTX *ctx, uint16_t group_id) {
-  SSL_ECDH_CTX_cleanup(ctx);
-
-  const SSL_ECDH_METHOD *method = method_from_group_id(group_id);
-  if (method == NULL) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_ELLIPTIC_CURVE);
-    return 0;
-  }
-  ctx->method = method;
-  return 1;
-}
-
-void SSL_ECDH_CTX_cleanup(SSL_ECDH_CTX *ctx) {
-  if (ctx->method == NULL) {
-    return;
-  }
-  ctx->method->cleanup(ctx);
-  ctx->method = NULL;
-  ctx->data = NULL;
-}
-
-uint16_t SSL_ECDH_CTX_get_id(const SSL_ECDH_CTX *ctx) {
-  return ctx->method->group_id;
-}
-
-int SSL_ECDH_CTX_offer(SSL_ECDH_CTX *ctx, CBB *out_public_key) {
-  return ctx->method->offer(ctx, out_public_key);
-}
-
-int SSL_ECDH_CTX_accept(SSL_ECDH_CTX *ctx, CBB *out_public_key,
-                        uint8_t **out_secret, size_t *out_secret_len,
-                        uint8_t *out_alert, const uint8_t *peer_key,
-                        size_t peer_key_len) {
-  return ctx->method->accept(ctx, out_public_key, out_secret, out_secret_len,
-                             out_alert, peer_key, peer_key_len);
-}
-
-int SSL_ECDH_CTX_finish(SSL_ECDH_CTX *ctx, uint8_t **out_secret,
-                        size_t *out_secret_len, uint8_t *out_alert,
-                        const uint8_t *peer_key, size_t peer_key_len) {
-  return ctx->method->finish(ctx, out_secret, out_secret_len, out_alert,
-                             peer_key, peer_key_len);
-}
diff --git a/src/ssl/ssl_key_share.cc b/src/ssl/ssl_key_share.cc
new file mode 100644
index 0000000..eb61535
--- /dev/null
+++ b/src/ssl/ssl_key_share.cc
@@ -0,0 +1,245 @@
+/* Copyright (c) 2015, 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/bn.h>
+#include <openssl/bytestring.h>
+#include <openssl/curve25519.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/mem.h>
+#include <openssl/nid.h>
+
+#include "internal.h"
+#include "../crypto/internal.h"
+
+
+namespace bssl {
+
+namespace {
+
+class ECKeyShare : public SSLKeyShare {
+ public:
+  ECKeyShare(int nid, uint16_t group_id) : nid_(nid), group_id_(group_id) {}
+  ~ECKeyShare() override {}
+
+  uint16_t GroupID() const override { return group_id_; }
+
+  bool Offer(CBB *out) override {
+    assert(!private_key_);
+    /* Set up a shared |BN_CTX| for all operations. */
+    UniquePtr<BN_CTX> bn_ctx(BN_CTX_new());
+    if (!bn_ctx) {
+      return false;
+    }
+    BN_CTXScope scope(bn_ctx.get());
+
+    /* Generate a private key. */
+    UniquePtr<EC_GROUP> group(EC_GROUP_new_by_curve_name(nid_));
+    private_key_.reset(BN_new());
+    if (!group || !private_key_ ||
+        !BN_rand_range_ex(private_key_.get(), 1,
+                          EC_GROUP_get0_order(group.get()))) {
+      return false;
+    }
+
+    /* Compute the corresponding public key and serialize it. */
+    UniquePtr<EC_POINT> public_key(EC_POINT_new(group.get()));
+    if (!public_key ||
+        !EC_POINT_mul(group.get(), public_key.get(), private_key_.get(), NULL,
+                      NULL, bn_ctx.get()) ||
+        !EC_POINT_point2cbb(out, group.get(), public_key.get(),
+                            POINT_CONVERSION_UNCOMPRESSED, bn_ctx.get())) {
+      return false;
+    }
+
+    return true;
+  }
+
+  bool Finish(uint8_t **out_secret, size_t *out_secret_len, uint8_t *out_alert,
+              const uint8_t *peer_key, size_t peer_key_len) override {
+    assert(private_key_);
+    *out_alert = SSL_AD_INTERNAL_ERROR;
+
+    /* Set up a shared |BN_CTX| for all operations. */
+    UniquePtr<BN_CTX> bn_ctx(BN_CTX_new());
+    if (!bn_ctx) {
+      return false;
+    }
+    BN_CTXScope scope(bn_ctx.get());
+
+    UniquePtr<EC_GROUP> group(EC_GROUP_new_by_curve_name(nid_));
+    if (!group) {
+      return false;
+    }
+
+    UniquePtr<EC_POINT> peer_point(EC_POINT_new(group.get()));
+    UniquePtr<EC_POINT> result(EC_POINT_new(group.get()));
+    BIGNUM *x = BN_CTX_get(bn_ctx.get());
+    if (!peer_point || !result || !x) {
+      return false;
+    }
+
+    if (!EC_POINT_oct2point(group.get(), peer_point.get(), peer_key,
+                            peer_key_len, bn_ctx.get())) {
+      *out_alert = SSL_AD_DECODE_ERROR;
+      return false;
+    }
+
+    /* Compute the x-coordinate of |peer_key| * |private_key_|. */
+    if (!EC_POINT_mul(group.get(), result.get(), NULL, peer_point.get(),
+                      private_key_.get(), bn_ctx.get()) ||
+        !EC_POINT_get_affine_coordinates_GFp(group.get(), result.get(), x, NULL,
+                                             bn_ctx.get())) {
+      return false;
+    }
+
+    /* Encode the x-coordinate left-padded with zeros. */
+    size_t secret_len = (EC_GROUP_get_degree(group.get()) + 7) / 8;
+    UniquePtr<uint8_t> secret((uint8_t *)OPENSSL_malloc(secret_len));
+    if (!secret || !BN_bn2bin_padded(secret.get(), secret_len, x)) {
+      return false;
+    }
+
+    *out_secret = secret.release();
+    *out_secret_len = secret_len;
+    return true;
+  }
+
+ private:
+  UniquePtr<BIGNUM> private_key_;
+  int nid_;
+  uint16_t group_id_;
+};
+
+class X25519KeyShare : public SSLKeyShare {
+ public:
+  X25519KeyShare() {}
+  ~X25519KeyShare() override {
+    OPENSSL_cleanse(private_key_, sizeof(private_key_));
+  }
+
+  uint16_t GroupID() const override { return SSL_CURVE_X25519; }
+
+  bool Offer(CBB *out) override {
+    uint8_t public_key[32];
+    X25519_keypair(public_key, private_key_);
+    return !!CBB_add_bytes(out, public_key, sizeof(public_key));
+  }
+
+  bool Finish(uint8_t **out_secret, size_t *out_secret_len, uint8_t *out_alert,
+              const uint8_t *peer_key, size_t peer_key_len) override {
+    *out_alert = SSL_AD_INTERNAL_ERROR;
+
+    UniquePtr<uint8_t> secret((uint8_t *)OPENSSL_malloc(32));
+    if (!secret) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+      return false;
+    }
+
+    if (peer_key_len != 32 || !X25519(secret.get(), private_key_, peer_key)) {
+      *out_alert = SSL_AD_DECODE_ERROR;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
+      return false;
+    }
+
+    *out_secret = secret.release();
+    *out_secret_len = 32;
+    return true;
+  }
+
+ private:
+  uint8_t private_key_[32];
+};
+
+const struct {
+  int nid;
+  uint16_t group_id;
+  const char name[8];
+} kNamedGroups[] = {
+    {NID_secp224r1, SSL_CURVE_SECP224R1, "P-224"},
+    {NID_X9_62_prime256v1, SSL_CURVE_SECP256R1, "P-256"},
+    {NID_secp384r1, SSL_CURVE_SECP384R1, "P-384"},
+    {NID_secp521r1, SSL_CURVE_SECP521R1, "P-521"},
+    {NID_X25519, SSL_CURVE_X25519, "X25519"},
+};
+
+}  // namespace
+
+UniquePtr<SSLKeyShare> SSLKeyShare::Create(uint16_t group_id) {
+  switch (group_id) {
+    case SSL_CURVE_SECP224R1:
+      return UniquePtr<SSLKeyShare>(
+          New<ECKeyShare>(NID_secp224r1, SSL_CURVE_SECP224R1));
+    case SSL_CURVE_SECP256R1:
+      return UniquePtr<SSLKeyShare>(
+          New<ECKeyShare>(NID_X9_62_prime256v1, SSL_CURVE_SECP256R1));
+    case SSL_CURVE_SECP384R1:
+      return UniquePtr<SSLKeyShare>(
+          New<ECKeyShare>(NID_secp384r1, SSL_CURVE_SECP384R1));
+    case SSL_CURVE_SECP521R1:
+      return UniquePtr<SSLKeyShare>(
+          New<ECKeyShare>(NID_secp521r1, SSL_CURVE_SECP521R1));
+    case SSL_CURVE_X25519:
+      return UniquePtr<SSLKeyShare>(New<X25519KeyShare>());
+    default:
+      return nullptr;
+  }
+}
+
+bool SSLKeyShare::Accept(CBB *out_public_key, uint8_t **out_secret,
+                         size_t *out_secret_len, uint8_t *out_alert,
+                         const uint8_t *peer_key, size_t peer_key_len) {
+  *out_alert = SSL_AD_INTERNAL_ERROR;
+  return Offer(out_public_key) &&
+         Finish(out_secret, out_secret_len, out_alert, peer_key, peer_key_len);
+}
+
+int ssl_nid_to_group_id(uint16_t *out_group_id, int nid) {
+  for (const auto &group : kNamedGroups) {
+    if (group.nid == nid) {
+      *out_group_id = group.group_id;
+      return 1;
+    }
+  }
+  return 0;
+}
+
+int ssl_name_to_group_id(uint16_t *out_group_id, const char *name, size_t len) {
+  for (const auto &group : kNamedGroups) {
+    if (len == strlen(group.name) &&
+        !strncmp(group.name, name, len)) {
+      *out_group_id = group.group_id;
+      return 1;
+    }
+  }
+  return 0;
+}
+
+}  // namespace bssl
+
+using namespace bssl;
+
+const char* SSL_get_curve_name(uint16_t group_id) {
+  for (const auto &group : kNamedGroups) {
+    if (group.group_id == group_id) {
+      return group.name;
+    }
+  }
+  return nullptr;
+}
diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc
index 7441925..8e7bd88 100644
--- a/src/ssl/ssl_lib.cc
+++ b/src/ssl/ssl_lib.cc
@@ -162,6 +162,8 @@
 #endif
 
 
+namespace bssl {
+
 /* |SSL_R_UNKNOWN_PROTOCOL| is no longer emitted, but continue to define it
  * to avoid downstream churn. */
 OPENSSL_DECLARE_ERROR_REASON(SSL, UNKNOWN_PROTOCOL)
@@ -185,6 +187,224 @@
 static CRYPTO_EX_DATA_CLASS g_ex_data_class_ssl_ctx =
     CRYPTO_EX_DATA_CLASS_INIT_WITH_APP_DATA;
 
+void ssl_reset_error_state(SSL *ssl) {
+  /* Functions which use |SSL_get_error| must reset I/O and error state on
+   * entry. */
+  ssl->rwstate = SSL_NOTHING;
+  ERR_clear_error();
+  ERR_clear_system_error();
+}
+
+int ssl_can_write(const SSL *ssl) {
+  return !SSL_in_init(ssl) || ssl->s3->hs->can_early_write;
+}
+
+int ssl_can_read(const SSL *ssl) {
+  return !SSL_in_init(ssl) || ssl->s3->hs->can_early_read;
+}
+
+void ssl_cipher_preference_list_free(
+    struct ssl_cipher_preference_list_st *cipher_list) {
+  if (cipher_list == NULL) {
+    return;
+  }
+  sk_SSL_CIPHER_free(cipher_list->ciphers);
+  OPENSSL_free(cipher_list->in_group_flags);
+  OPENSSL_free(cipher_list);
+}
+
+void ssl_update_cache(SSL_HANDSHAKE *hs, int mode) {
+  SSL *const ssl = hs->ssl;
+  SSL_CTX *ctx = ssl->session_ctx;
+  /* Never cache sessions with empty session IDs. */
+  if (ssl->s3->established_session->session_id_length == 0 ||
+      (ctx->session_cache_mode & mode) != mode) {
+    return;
+  }
+
+  /* Clients never use the internal session cache. */
+  int use_internal_cache = ssl->server && !(ctx->session_cache_mode &
+                                            SSL_SESS_CACHE_NO_INTERNAL_STORE);
+
+  /* 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 != ssl->session ||
+      (!ssl->server && hs->ticket_expected)) {
+    if (use_internal_cache) {
+      SSL_CTX_add_session(ctx, ssl->s3->established_session);
+    }
+    if (ctx->new_session_cb != NULL) {
+      SSL_SESSION_up_ref(ssl->s3->established_session);
+      if (!ctx->new_session_cb(ssl, ssl->s3->established_session)) {
+        /* |new_session_cb|'s return value signals whether it took ownership. */
+        SSL_SESSION_free(ssl->s3->established_session);
+      }
+    }
+  }
+
+  if (use_internal_cache &&
+      !(ctx->session_cache_mode & SSL_SESS_CACHE_NO_AUTO_CLEAR)) {
+    /* Automatically flush the internal session cache every 255 connections. */
+    int flush_cache = 0;
+    CRYPTO_MUTEX_lock_write(&ctx->lock);
+    ctx->handshakes_since_cache_flush++;
+    if (ctx->handshakes_since_cache_flush >= 255) {
+      flush_cache = 1;
+      ctx->handshakes_since_cache_flush = 0;
+    }
+    CRYPTO_MUTEX_unlock_write(&ctx->lock);
+
+    if (flush_cache) {
+      struct OPENSSL_timeval now;
+      ssl_get_current_time(ssl, &now);
+      SSL_CTX_flush_sessions(ctx, now.tv_sec);
+    }
+  }
+}
+
+static int cbb_add_hex(CBB *cbb, const uint8_t *in, size_t in_len) {
+  static const char hextable[] = "0123456789abcdef";
+  uint8_t *out;
+
+  if (!CBB_add_space(cbb, &out, in_len * 2)) {
+    return 0;
+  }
+
+  for (size_t i = 0; i < in_len; i++) {
+    *(out++) = (uint8_t)hextable[in[i] >> 4];
+    *(out++) = (uint8_t)hextable[in[i] & 0xf];
+  }
+
+  return 1;
+}
+
+int ssl_log_secret(const SSL *ssl, const char *label, const uint8_t *secret,
+                   size_t secret_len) {
+  if (ssl->ctx->keylog_callback == NULL) {
+    return 1;
+  }
+
+  ScopedCBB cbb;
+  uint8_t *out;
+  size_t out_len;
+  if (!CBB_init(cbb.get(), strlen(label) + 1 + SSL3_RANDOM_SIZE * 2 + 1 +
+                          secret_len * 2 + 1) ||
+      !CBB_add_bytes(cbb.get(), (const uint8_t *)label, strlen(label)) ||
+      !CBB_add_bytes(cbb.get(), (const uint8_t *)" ", 1) ||
+      !cbb_add_hex(cbb.get(), ssl->s3->client_random, SSL3_RANDOM_SIZE) ||
+      !CBB_add_bytes(cbb.get(), (const uint8_t *)" ", 1) ||
+      !cbb_add_hex(cbb.get(), secret, secret_len) ||
+      !CBB_add_u8(cbb.get(), 0 /* NUL */) ||
+      !CBB_finish(cbb.get(), &out, &out_len)) {
+    return 0;
+  }
+
+  ssl->ctx->keylog_callback(ssl, (const char *)out);
+  OPENSSL_free(out);
+  return 1;
+}
+
+int ssl3_can_false_start(const SSL *ssl) {
+  const SSL_CIPHER *const cipher = SSL_get_current_cipher(ssl);
+
+  /* False Start only for TLS 1.2 with an ECDHE+AEAD cipher and ALPN or NPN. */
+  return !SSL_is_dtls(ssl) &&
+      SSL_version(ssl) == TLS1_2_VERSION &&
+      (ssl->s3->alpn_selected != NULL ||
+       ssl->s3->next_proto_negotiated != NULL) &&
+      cipher != NULL &&
+      cipher->algorithm_mkey == SSL_kECDHE &&
+      cipher->algorithm_mac == SSL_AEAD;
+}
+
+void ssl_do_info_callback(const SSL *ssl, int type, int value) {
+  void (*cb)(const SSL *ssl, int type, int value) = NULL;
+  if (ssl->info_callback != NULL) {
+    cb = ssl->info_callback;
+  } else if (ssl->ctx->info_callback != NULL) {
+    cb = ssl->ctx->info_callback;
+  }
+
+  if (cb != NULL) {
+    cb(ssl, type, value);
+  }
+}
+
+void ssl_do_msg_callback(SSL *ssl, int is_write, int content_type,
+                         const void *buf, size_t len) {
+  if (ssl->msg_callback == NULL) {
+    return;
+  }
+
+  /* |version| is zero when calling for |SSL3_RT_HEADER| and |SSL2_VERSION| for
+   * a V2ClientHello. */
+  int version;
+  switch (content_type) {
+    case 0:
+      /* V2ClientHello */
+      version = SSL2_VERSION;
+      break;
+    case SSL3_RT_HEADER:
+      version = 0;
+      break;
+    default:
+      version = SSL_version(ssl);
+  }
+
+  ssl->msg_callback(is_write, version, content_type, buf, len, ssl,
+                    ssl->msg_callback_arg);
+}
+
+void ssl_get_current_time(const SSL *ssl, struct OPENSSL_timeval *out_clock) {
+  if (ssl->ctx->current_time_cb != NULL) {
+    /* TODO(davidben): Update current_time_cb to use OPENSSL_timeval. See
+     * https://crbug.com/boringssl/155. */
+    struct timeval clock;
+    ssl->ctx->current_time_cb(ssl, &clock);
+    if (clock.tv_sec < 0) {
+      assert(0);
+      out_clock->tv_sec = 0;
+      out_clock->tv_usec = 0;
+    } else {
+      out_clock->tv_sec = (uint64_t)clock.tv_sec;
+      out_clock->tv_usec = (uint32_t)clock.tv_usec;
+    }
+    return;
+  }
+
+#if defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE)
+  out_clock->tv_sec = 1234;
+  out_clock->tv_usec = 1234;
+#elif defined(OPENSSL_WINDOWS)
+  struct _timeb time;
+  _ftime(&time);
+  if (time.time < 0) {
+    assert(0);
+    out_clock->tv_sec = 0;
+    out_clock->tv_usec = 0;
+  } else {
+    out_clock->tv_sec = time.time;
+    out_clock->tv_usec = time.millitm * 1000;
+  }
+#else
+  struct timeval clock;
+  gettimeofday(&clock, NULL);
+  if (clock.tv_sec < 0) {
+    assert(0);
+    out_clock->tv_sec = 0;
+    out_clock->tv_usec = 0;
+  } else {
+    out_clock->tv_sec = (uint64_t)clock.tv_sec;
+    out_clock->tv_usec = (uint32_t)clock.tv_usec;
+  }
+#endif
+}
+
+}  // namespace bssl
+
+using namespace bssl;
+
 int SSL_library_init(void) {
   CRYPTO_library_init();
   return 1;
@@ -392,6 +612,7 @@
   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 =
       ctx->retain_only_sha256_of_client_certs;
 
@@ -561,14 +782,6 @@
 
 BIO *SSL_get_wbio(const SSL *ssl) { return ssl->wbio; }
 
-void ssl_reset_error_state(SSL *ssl) {
-  /* Functions which use |SSL_get_error| must reset I/O and error state on
-   * entry. */
-  ssl->rwstate = SSL_NOTHING;
-  ERR_clear_error();
-  ERR_clear_system_error();
-}
-
 int SSL_do_handshake(SSL *ssl) {
   ssl_reset_error_state(ssl);
 
@@ -620,14 +833,6 @@
   return SSL_do_handshake(ssl);
 }
 
-int ssl_can_write(const SSL *ssl) {
-  return !SSL_in_init(ssl) || ssl->s3->hs->can_early_write;
-}
-
-int ssl_can_read(const SSL *ssl) {
-  return !SSL_in_init(ssl) || ssl->s3->hs->can_early_read;
-}
-
 static int ssl_do_renegotiate(SSL *ssl) {
   /* 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
@@ -880,8 +1085,7 @@
 
   hs->wait = ssl_hs_ok;
   hs->in_early_data = 0;
-  SSL_SESSION_free(hs->early_session);
-  hs->early_session = NULL;
+  hs->early_session.reset();
 
   /* Discard any unfinished writes from the perspective of |SSL_write|'s
    * retry. The handshake will transparently flush out the pending record
@@ -984,6 +1188,9 @@
 
     case SSL_EARLY_DATA_REJECTED:
       return SSL_ERROR_EARLY_DATA_REJECTED;
+
+    case SSL_CERTIFICATE_VERIFY:
+      return SSL_ERROR_WANT_CERTIFICATE_VERIFY;
   }
 
   return SSL_ERROR_SYSCALL;
@@ -1104,16 +1311,6 @@
   return ssl->cert->sid_ctx;
 }
 
-void ssl_cipher_preference_list_free(
-    struct ssl_cipher_preference_list_st *cipher_list) {
-  if (cipher_list == NULL) {
-    return;
-  }
-  sk_SSL_CIPHER_free(cipher_list->ciphers);
-  OPENSSL_free(cipher_list->in_group_flags);
-  OPENSSL_free(cipher_list);
-}
-
 void SSL_certs_clear(SSL *ssl) { ssl_cert_clear_certs(ssl->cert); }
 
 int SSL_get_fd(const SSL *ssl) { return SSL_get_rfd(ssl); }
@@ -1532,7 +1729,7 @@
 
   /* During the handshake, report the handshake value. */
   if (ssl->s3->hs != NULL) {
-    return ssl->s3->hs->hostname;
+    return ssl->s3->hs->hostname.get();
   }
 
   /* SSL_get_servername may also be called after the handshake to look up the
@@ -1554,12 +1751,22 @@
   return TLSEXT_NAMETYPE_host_name;
 }
 
-void SSL_CTX_enable_signed_cert_timestamps(SSL_CTX *ctx) {
-  ctx->signed_cert_timestamps_enabled = 1;
+void SSL_CTX_set_custom_verify(
+    SSL_CTX *ctx, int mode,
+    enum ssl_verify_result_t (*callback)(SSL *ssl, uint8_t *out_alert)) {
+  ctx->verify_mode = mode;
+  ctx->custom_verify_callback = callback;
 }
 
-void SSL_CTX_i_promise_to_verify_certs_after_the_handshake(SSL_CTX *ctx) {
-  ctx->i_promise_to_verify_certs_after_the_handshake = 1;
+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;
+}
+
+void SSL_CTX_enable_signed_cert_timestamps(SSL_CTX *ctx) {
+  ctx->signed_cert_timestamps_enabled = 1;
 }
 
 void SSL_enable_signed_cert_timestamps(SSL *ssl) {
@@ -1806,56 +2013,6 @@
   return ssl->s3->hs->num_certificate_types;
 }
 
-void ssl_update_cache(SSL_HANDSHAKE *hs, int mode) {
-  SSL *const ssl = hs->ssl;
-  SSL_CTX *ctx = ssl->session_ctx;
-  /* Never cache sessions with empty session IDs. */
-  if (ssl->s3->established_session->session_id_length == 0 ||
-      (ctx->session_cache_mode & mode) != mode) {
-    return;
-  }
-
-  /* Clients never use the internal session cache. */
-  int use_internal_cache = ssl->server && !(ctx->session_cache_mode &
-                                            SSL_SESS_CACHE_NO_INTERNAL_STORE);
-
-  /* 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 != ssl->session ||
-      (!ssl->server && hs->ticket_expected)) {
-    if (use_internal_cache) {
-      SSL_CTX_add_session(ctx, ssl->s3->established_session);
-    }
-    if (ctx->new_session_cb != NULL) {
-      SSL_SESSION_up_ref(ssl->s3->established_session);
-      if (!ctx->new_session_cb(ssl, ssl->s3->established_session)) {
-        /* |new_session_cb|'s return value signals whether it took ownership. */
-        SSL_SESSION_free(ssl->s3->established_session);
-      }
-    }
-  }
-
-  if (use_internal_cache &&
-      !(ctx->session_cache_mode & SSL_SESS_CACHE_NO_AUTO_CLEAR)) {
-    /* Automatically flush the internal session cache every 255 connections. */
-    int flush_cache = 0;
-    CRYPTO_MUTEX_lock_write(&ctx->lock);
-    ctx->handshakes_since_cache_flush++;
-    if (ctx->handshakes_since_cache_flush >= 255) {
-      flush_cache = 1;
-      ctx->handshakes_since_cache_flush = 0;
-    }
-    CRYPTO_MUTEX_unlock_write(&ctx->lock);
-
-    if (flush_cache) {
-      struct OPENSSL_timeval now;
-      ssl_get_current_time(ssl, &now);
-      SSL_CTX_flush_sessions(ctx, now.tv_sec);
-    }
-  }
-}
-
 EVP_PKEY *SSL_get_privatekey(const SSL *ssl) {
   if (ssl->cert != NULL) {
     return ssl->cert->privatekey;
@@ -1873,10 +2030,7 @@
 }
 
 const SSL_CIPHER *SSL_get_current_cipher(const SSL *ssl) {
-  if (ssl->s3->aead_write_ctx == NULL) {
-    return NULL;
-  }
-  return ssl->s3->aead_write_ctx->cipher;
+  return ssl->s3->aead_write_ctx->cipher();
 }
 
 int SSL_session_reused(const SSL *ssl) {
@@ -2155,49 +2309,6 @@
   ctx->current_time_cb = cb;
 }
 
-static int cbb_add_hex(CBB *cbb, const uint8_t *in, size_t in_len) {
-  static const char hextable[] = "0123456789abcdef";
-  uint8_t *out;
-
-  if (!CBB_add_space(cbb, &out, in_len * 2)) {
-    return 0;
-  }
-
-  for (size_t i = 0; i < in_len; i++) {
-    *(out++) = (uint8_t)hextable[in[i] >> 4];
-    *(out++) = (uint8_t)hextable[in[i] & 0xf];
-  }
-
-  return 1;
-}
-
-int ssl_log_secret(const SSL *ssl, const char *label, const uint8_t *secret,
-                   size_t secret_len) {
-  if (ssl->ctx->keylog_callback == NULL) {
-    return 1;
-  }
-
-  CBB cbb;
-  uint8_t *out;
-  size_t out_len;
-  if (!CBB_init(&cbb, strlen(label) + 1 + SSL3_RANDOM_SIZE * 2 + 1 +
-                          secret_len * 2 + 1) ||
-      !CBB_add_bytes(&cbb, (const uint8_t *)label, strlen(label)) ||
-      !CBB_add_bytes(&cbb, (const uint8_t *)" ", 1) ||
-      !cbb_add_hex(&cbb, ssl->s3->client_random, SSL3_RANDOM_SIZE) ||
-      !CBB_add_bytes(&cbb, (const uint8_t *)" ", 1) ||
-      !cbb_add_hex(&cbb, secret, secret_len) ||
-      !CBB_add_u8(&cbb, 0 /* NUL */) ||
-      !CBB_finish(&cbb, &out, &out_len)) {
-    CBB_cleanup(&cbb);
-    return 0;
-  }
-
-  ssl->ctx->keylog_callback(ssl, (const char *)out);
-  OPENSSL_free(out);
-  return 1;
-}
-
 int SSL_is_init_finished(const SSL *ssl) {
   return !SSL_in_init(ssl);
 }
@@ -2225,19 +2336,6 @@
   *ssl_session_size = sizeof(SSL_SESSION);
 }
 
-int ssl3_can_false_start(const SSL *ssl) {
-  const SSL_CIPHER *const cipher = SSL_get_current_cipher(ssl);
-
-  /* False Start only for TLS 1.2 with an ECDHE+AEAD cipher and ALPN or NPN. */
-  return !SSL_is_dtls(ssl) &&
-      SSL_version(ssl) == TLS1_2_VERSION &&
-      (ssl->s3->alpn_selected != NULL ||
-       ssl->s3->next_proto_negotiated != NULL) &&
-      cipher != NULL &&
-      cipher->algorithm_mkey == SSL_kECDHE &&
-      cipher->algorithm_mac == SSL_AEAD;
-}
-
 int SSL_is_server(const SSL *ssl) { return ssl->server; }
 
 int SSL_is_dtls(const SSL *ssl) { return ssl->method->is_dtls; }
@@ -2259,15 +2357,9 @@
 
 int SSL_get_ivs(const SSL *ssl, const uint8_t **out_read_iv,
                 const uint8_t **out_write_iv, size_t *out_iv_len) {
-  if (ssl->s3->aead_read_ctx == NULL || ssl->s3->aead_write_ctx == NULL) {
-    return 0;
-  }
-
   size_t write_iv_len;
-  if (!EVP_AEAD_CTX_get_iv(&ssl->s3->aead_read_ctx->ctx, out_read_iv,
-                           out_iv_len) ||
-      !EVP_AEAD_CTX_get_iv(&ssl->s3->aead_write_ctx->ctx, out_write_iv,
-                           &write_iv_len) ||
+  if (!ssl->s3->aead_read_ctx->GetIV(out_read_iv, out_iv_len) ||
+      !ssl->s3->aead_write_ctx->GetIV(out_write_iv, &write_iv_len) ||
       *out_iv_len != write_iv_len) {
     return 0;
   }
@@ -2408,44 +2500,6 @@
   return 1;
 }
 
-void ssl_do_info_callback(const SSL *ssl, int type, int value) {
-  void (*cb)(const SSL *ssl, int type, int value) = NULL;
-  if (ssl->info_callback != NULL) {
-    cb = ssl->info_callback;
-  } else if (ssl->ctx->info_callback != NULL) {
-    cb = ssl->ctx->info_callback;
-  }
-
-  if (cb != NULL) {
-    cb(ssl, type, value);
-  }
-}
-
-void ssl_do_msg_callback(SSL *ssl, int is_write, int content_type,
-                         const void *buf, size_t len) {
-  if (ssl->msg_callback == NULL) {
-    return;
-  }
-
-  /* |version| is zero when calling for |SSL3_RT_HEADER| and |SSL2_VERSION| for
-   * a V2ClientHello. */
-  int version;
-  switch (content_type) {
-    case 0:
-      /* V2ClientHello */
-      version = SSL2_VERSION;
-      break;
-    case SSL3_RT_HEADER:
-      version = 0;
-      break;
-    default:
-      version = SSL_version(ssl);
-  }
-
-  ssl->msg_callback(is_write, version, content_type, buf, len, ssl,
-                    ssl->msg_callback_arg);
-}
-
 int SSL_CTX_sess_connect(const SSL_CTX *ctx) { return 0; }
 int SSL_CTX_sess_connect_good(const SSL_CTX *ctx) { return 0; }
 int SSL_CTX_sess_connect_renegotiate(const SSL_CTX *ctx) { return 0; }
@@ -2488,51 +2542,6 @@
   return SSL_set1_curves(ssl, &nid, 1);
 }
 
-void ssl_get_current_time(const SSL *ssl, struct OPENSSL_timeval *out_clock) {
-  if (ssl->ctx->current_time_cb != NULL) {
-    /* TODO(davidben): Update current_time_cb to use OPENSSL_timeval. See
-     * https://crbug.com/boringssl/155. */
-    struct timeval clock;
-    ssl->ctx->current_time_cb(ssl, &clock);
-    if (clock.tv_sec < 0) {
-      assert(0);
-      out_clock->tv_sec = 0;
-      out_clock->tv_usec = 0;
-    } else {
-      out_clock->tv_sec = (uint64_t)clock.tv_sec;
-      out_clock->tv_usec = (uint32_t)clock.tv_usec;
-    }
-    return;
-  }
-
-#if defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE)
-  out_clock->tv_sec = 1234;
-  out_clock->tv_usec = 1234;
-#elif defined(OPENSSL_WINDOWS)
-  struct _timeb time;
-  _ftime(&time);
-  if (time.time < 0) {
-    assert(0);
-    out_clock->tv_sec = 0;
-    out_clock->tv_usec = 0;
-  } else {
-    out_clock->tv_sec = time.time;
-    out_clock->tv_usec = time.millitm * 1000;
-  }
-#else
-  struct timeval clock;
-  gettimeofday(&clock, NULL);
-  if (clock.tv_sec < 0) {
-    assert(0);
-    out_clock->tv_sec = 0;
-    out_clock->tv_usec = 0;
-  } else {
-    out_clock->tv_sec = (uint64_t)clock.tv_sec;
-    out_clock->tv_usec = (uint32_t)clock.tv_usec;
-  }
-#endif
-}
-
 void SSL_CTX_set_ticket_aead_method(SSL_CTX *ctx,
                                     const SSL_TICKET_AEAD_METHOD *aead_method) {
   ctx->ticket_aead_method = aead_method;
diff --git a/src/ssl/ssl_privkey.cc b/src/ssl/ssl_privkey.cc
index 5b620f8..3e3fa94 100644
--- a/src/ssl/ssl_privkey.cc
+++ b/src/ssl/ssl_privkey.cc
@@ -69,6 +69,8 @@
 #include "../crypto/internal.h"
 
 
+namespace bssl {
+
 int ssl_is_key_type_supported(int key_type) {
   return key_type == EVP_PKEY_RSA || key_type == EVP_PKEY_EC ||
          key_type == EVP_PKEY_ED25519;
@@ -94,6 +96,229 @@
   return 1;
 }
 
+typedef struct {
+  uint16_t sigalg;
+  int pkey_type;
+  int curve;
+  const EVP_MD *(*digest_func)(void);
+  char is_rsa_pss;
+} SSL_SIGNATURE_ALGORITHM;
+
+static const SSL_SIGNATURE_ALGORITHM kSignatureAlgorithms[] = {
+    {SSL_SIGN_RSA_PKCS1_MD5_SHA1, EVP_PKEY_RSA, NID_undef, &EVP_md5_sha1, 0},
+    {SSL_SIGN_RSA_PKCS1_SHA1, EVP_PKEY_RSA, NID_undef, &EVP_sha1, 0},
+    {SSL_SIGN_RSA_PKCS1_SHA256, EVP_PKEY_RSA, NID_undef, &EVP_sha256, 0},
+    {SSL_SIGN_RSA_PKCS1_SHA384, EVP_PKEY_RSA, NID_undef, &EVP_sha384, 0},
+    {SSL_SIGN_RSA_PKCS1_SHA512, EVP_PKEY_RSA, NID_undef, &EVP_sha512, 0},
+
+    {SSL_SIGN_RSA_PSS_SHA256, EVP_PKEY_RSA, NID_undef, &EVP_sha256, 1},
+    {SSL_SIGN_RSA_PSS_SHA384, EVP_PKEY_RSA, NID_undef, &EVP_sha384, 1},
+    {SSL_SIGN_RSA_PSS_SHA512, EVP_PKEY_RSA, NID_undef, &EVP_sha512, 1},
+
+    {SSL_SIGN_ECDSA_SHA1, EVP_PKEY_EC, NID_undef, &EVP_sha1, 0},
+    {SSL_SIGN_ECDSA_SECP256R1_SHA256, EVP_PKEY_EC, NID_X9_62_prime256v1,
+     &EVP_sha256, 0},
+    {SSL_SIGN_ECDSA_SECP384R1_SHA384, EVP_PKEY_EC, NID_secp384r1, &EVP_sha384,
+     0},
+    {SSL_SIGN_ECDSA_SECP521R1_SHA512, EVP_PKEY_EC, NID_secp521r1, &EVP_sha512,
+     0},
+
+    {SSL_SIGN_ED25519, EVP_PKEY_ED25519, NID_undef, NULL, 0},
+};
+
+static const SSL_SIGNATURE_ALGORITHM *get_signature_algorithm(uint16_t sigalg) {
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kSignatureAlgorithms); i++) {
+    if (kSignatureAlgorithms[i].sigalg == sigalg) {
+      return &kSignatureAlgorithms[i];
+    }
+  }
+  return NULL;
+}
+
+int ssl_has_private_key(const SSL *ssl) {
+  return ssl->cert->privatekey != NULL || ssl->cert->key_method != NULL;
+}
+
+static int pkey_supports_algorithm(const SSL *ssl, EVP_PKEY *pkey,
+                                   uint16_t sigalg) {
+  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
+  if (alg == NULL ||
+      EVP_PKEY_id(pkey) != alg->pkey_type) {
+    return 0;
+  }
+
+  if (ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
+    /* RSA keys may only be used with RSA-PSS. */
+    if (alg->pkey_type == EVP_PKEY_RSA && !alg->is_rsa_pss) {
+      return 0;
+    }
+
+    /* EC keys have a curve requirement. */
+    if (alg->pkey_type == EVP_PKEY_EC &&
+        (alg->curve == NID_undef ||
+         EC_GROUP_get_curve_name(
+             EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey))) != alg->curve)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int setup_ctx(SSL *ssl, EVP_MD_CTX *ctx, EVP_PKEY *pkey, uint16_t sigalg,
+                     int is_verify) {
+  if (!pkey_supports_algorithm(ssl, pkey, sigalg)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SIGNATURE_TYPE);
+    return 0;
+  }
+
+  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
+  const EVP_MD *digest = alg->digest_func != NULL ? alg->digest_func() : NULL;
+  EVP_PKEY_CTX *pctx;
+  if (is_verify) {
+    if (!EVP_DigestVerifyInit(ctx, &pctx, digest, NULL, pkey)) {
+      return 0;
+    }
+  } else if (!EVP_DigestSignInit(ctx, &pctx, digest, NULL, pkey)) {
+    return 0;
+  }
+
+  if (alg->is_rsa_pss) {
+    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 0;
+    }
+  }
+
+  return 1;
+}
+
+static int legacy_sign_digest_supported(const SSL_SIGNATURE_ALGORITHM *alg) {
+  return (alg->pkey_type == EVP_PKEY_EC || alg->pkey_type == EVP_PKEY_RSA) &&
+         !alg->is_rsa_pss;
+}
+
+static enum ssl_private_key_result_t legacy_sign(
+    SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out, uint16_t sigalg,
+    const uint8_t *in, size_t in_len) {
+  /* TODO(davidben): Remove support for |sign_digest|-only
+   * |SSL_PRIVATE_KEY_METHOD|s. */
+  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
+  if (alg == NULL || !legacy_sign_digest_supported(alg)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL_FOR_CUSTOM_KEY);
+    return ssl_private_key_failure;
+  }
+
+  const EVP_MD *md = alg->digest_func();
+  uint8_t hash[EVP_MAX_MD_SIZE];
+  unsigned hash_len;
+  if (!EVP_Digest(in, in_len, hash, &hash_len, md, NULL)) {
+    return ssl_private_key_failure;
+  }
+
+  return ssl->cert->key_method->sign_digest(ssl, out, out_len, max_out, md,
+                                            hash, hash_len);
+}
+
+enum ssl_private_key_result_t ssl_private_key_sign(
+    SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, size_t max_out,
+    uint16_t sigalg, const uint8_t *in, size_t in_len) {
+  SSL *const ssl = hs->ssl;
+  if (ssl->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);
+    } else {
+      ret = (ssl->cert->key_method->sign != NULL
+                 ? ssl->cert->key_method->sign
+                 : legacy_sign)(ssl, out, out_len, max_out, sigalg, in, in_len);
+    }
+    hs->pending_private_key_op = ret == ssl_private_key_retry;
+    return ret;
+  }
+
+  *out_len = max_out;
+  ScopedEVP_MD_CTX ctx;
+  if (!setup_ctx(ssl, ctx.get(), ssl->cert->privatekey, sigalg, 0 /* sign */) ||
+      !EVP_DigestSign(ctx.get(), out, out_len, in, in_len)) {
+    return ssl_private_key_failure;
+  }
+  return ssl_private_key_success;
+}
+
+int ssl_public_key_verify(SSL *ssl, const uint8_t *signature,
+                          size_t signature_len, uint16_t sigalg, EVP_PKEY *pkey,
+                          const uint8_t *in, size_t in_len) {
+  ScopedEVP_MD_CTX ctx;
+  return setup_ctx(ssl, ctx.get(), pkey, sigalg, 1 /* verify */) &&
+         EVP_DigestVerify(ctx.get(), signature, signature_len, in, in_len);
+}
+
+enum ssl_private_key_result_t ssl_private_key_decrypt(
+    SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, size_t max_out,
+    const uint8_t *in, size_t in_len) {
+  SSL *const ssl = hs->ssl;
+  if (ssl->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);
+    } else {
+      ret = ssl->cert->key_method->decrypt(ssl, out, out_len, max_out, in,
+                                           in_len);
+    }
+    hs->pending_private_key_op = ret == ssl_private_key_retry;
+    return ret;
+  }
+
+  RSA *rsa = EVP_PKEY_get0_RSA(ssl->cert->privatekey);
+  if (rsa == NULL) {
+    /* Decrypt operations are only supported for RSA keys. */
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return ssl_private_key_failure;
+  }
+
+  /* Decrypt with no padding. PKCS#1 padding will be removed as part
+   * of the timing-sensitive code by the caller. */
+  if (!RSA_decrypt(rsa, out_len, out, max_out, in, in_len, RSA_NO_PADDING)) {
+    return ssl_private_key_failure;
+  }
+  return ssl_private_key_success;
+}
+
+int ssl_private_key_supports_signature_algorithm(SSL_HANDSHAKE *hs,
+                                                 uint16_t sigalg) {
+  SSL *const ssl = hs->ssl;
+  if (!pkey_supports_algorithm(ssl, hs->local_pubkey.get(), sigalg)) {
+    return 0;
+  }
+
+  /* Ensure the RSA key is large enough for the hash. RSASSA-PSS requires that
+   * emLen be at least hLen + sLen + 2. Both hLen and sLen are the size of the
+   * hash in TLS. Reasonable RSA key sizes are large enough for the largest
+   * defined RSASSA-PSS algorithm, but 1024-bit RSA is slightly too small for
+   * SHA-512. 1024-bit RSA is sometimes used for test credentials, so check the
+   * size so that we can fall back to another algorithm in that case. */
+  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
+  if (alg->is_rsa_pss && (size_t)EVP_PKEY_size(hs->local_pubkey.get()) <
+                             2 * EVP_MD_size(alg->digest_func()) + 2) {
+    return 0;
+  }
+
+  /* Newer algorithms require message-based private keys.
+   * TODO(davidben): Remove this check when sign_digest is gone. */
+  if (ssl->cert->key_method != NULL &&
+      ssl->cert->key_method->sign == NULL &&
+      !legacy_sign_digest_supported(alg)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+}  // namespace bssl
+
+using namespace bssl;
+
 int SSL_use_RSAPrivateKey(SSL *ssl, RSA *rsa) {
   EVP_PKEY *pkey;
   int ret;
@@ -119,7 +344,7 @@
 }
 
 int SSL_use_RSAPrivateKey_ASN1(SSL *ssl, const uint8_t *der, size_t der_len) {
-  bssl::UniquePtr<RSA> rsa(RSA_private_key_from_bytes(der, der_len));
+  UniquePtr<RSA> rsa(RSA_private_key_from_bytes(der, der_len));
   if (!rsa) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_ASN1_LIB);
     return 0;
@@ -314,226 +539,3 @@
 
   return 1;
 }
-
-typedef struct {
-  uint16_t sigalg;
-  int pkey_type;
-  int curve;
-  const EVP_MD *(*digest_func)(void);
-  char is_rsa_pss;
-} SSL_SIGNATURE_ALGORITHM;
-
-static const SSL_SIGNATURE_ALGORITHM kSignatureAlgorithms[] = {
-    {SSL_SIGN_RSA_PKCS1_MD5_SHA1, EVP_PKEY_RSA, NID_undef, &EVP_md5_sha1, 0},
-    {SSL_SIGN_RSA_PKCS1_SHA1, EVP_PKEY_RSA, NID_undef, &EVP_sha1, 0},
-    {SSL_SIGN_RSA_PKCS1_SHA256, EVP_PKEY_RSA, NID_undef, &EVP_sha256, 0},
-    {SSL_SIGN_RSA_PKCS1_SHA384, EVP_PKEY_RSA, NID_undef, &EVP_sha384, 0},
-    {SSL_SIGN_RSA_PKCS1_SHA512, EVP_PKEY_RSA, NID_undef, &EVP_sha512, 0},
-
-    {SSL_SIGN_RSA_PSS_SHA256, EVP_PKEY_RSA, NID_undef, &EVP_sha256, 1},
-    {SSL_SIGN_RSA_PSS_SHA384, EVP_PKEY_RSA, NID_undef, &EVP_sha384, 1},
-    {SSL_SIGN_RSA_PSS_SHA512, EVP_PKEY_RSA, NID_undef, &EVP_sha512, 1},
-
-    {SSL_SIGN_ECDSA_SHA1, EVP_PKEY_EC, NID_undef, &EVP_sha1, 0},
-    {SSL_SIGN_ECDSA_SECP256R1_SHA256, EVP_PKEY_EC, NID_X9_62_prime256v1,
-     &EVP_sha256, 0},
-    {SSL_SIGN_ECDSA_SECP384R1_SHA384, EVP_PKEY_EC, NID_secp384r1, &EVP_sha384,
-     0},
-    {SSL_SIGN_ECDSA_SECP521R1_SHA512, EVP_PKEY_EC, NID_secp521r1, &EVP_sha512,
-     0},
-
-    {SSL_SIGN_ED25519, EVP_PKEY_ED25519, NID_undef, NULL, 0},
-};
-
-static const SSL_SIGNATURE_ALGORITHM *get_signature_algorithm(uint16_t sigalg) {
-  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kSignatureAlgorithms); i++) {
-    if (kSignatureAlgorithms[i].sigalg == sigalg) {
-      return &kSignatureAlgorithms[i];
-    }
-  }
-  return NULL;
-}
-
-int ssl_has_private_key(const SSL *ssl) {
-  return ssl->cert->privatekey != NULL || ssl->cert->key_method != NULL;
-}
-
-static int pkey_supports_algorithm(const SSL *ssl, EVP_PKEY *pkey,
-                                   uint16_t sigalg) {
-  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
-  if (alg == NULL ||
-      EVP_PKEY_id(pkey) != alg->pkey_type) {
-    return 0;
-  }
-
-  if (ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
-    /* RSA keys may only be used with RSA-PSS. */
-    if (alg->pkey_type == EVP_PKEY_RSA && !alg->is_rsa_pss) {
-      return 0;
-    }
-
-    /* EC keys have a curve requirement. */
-    if (alg->pkey_type == EVP_PKEY_EC &&
-        (alg->curve == NID_undef ||
-         EC_GROUP_get_curve_name(
-             EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey))) != alg->curve)) {
-      return 0;
-    }
-  }
-
-  return 1;
-}
-
-static int setup_ctx(SSL *ssl, EVP_MD_CTX *ctx, EVP_PKEY *pkey, uint16_t sigalg,
-                     int is_verify) {
-  if (!pkey_supports_algorithm(ssl, pkey, sigalg)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SIGNATURE_TYPE);
-    return 0;
-  }
-
-  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
-  const EVP_MD *digest = alg->digest_func != NULL ? alg->digest_func() : NULL;
-  EVP_PKEY_CTX *pctx;
-  if (is_verify) {
-    if (!EVP_DigestVerifyInit(ctx, &pctx, digest, NULL, pkey)) {
-      return 0;
-    }
-  } else if (!EVP_DigestSignInit(ctx, &pctx, digest, NULL, pkey)) {
-    return 0;
-  }
-
-  if (alg->is_rsa_pss) {
-    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 0;
-    }
-  }
-
-  return 1;
-}
-
-static int legacy_sign_digest_supported(const SSL_SIGNATURE_ALGORITHM *alg) {
-  return (alg->pkey_type == EVP_PKEY_EC || alg->pkey_type == EVP_PKEY_RSA) &&
-         !alg->is_rsa_pss;
-}
-
-static enum ssl_private_key_result_t legacy_sign(
-    SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out, uint16_t sigalg,
-    const uint8_t *in, size_t in_len) {
-  /* TODO(davidben): Remove support for |sign_digest|-only
-   * |SSL_PRIVATE_KEY_METHOD|s. */
-  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
-  if (alg == NULL || !legacy_sign_digest_supported(alg)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL_FOR_CUSTOM_KEY);
-    return ssl_private_key_failure;
-  }
-
-  const EVP_MD *md = alg->digest_func();
-  uint8_t hash[EVP_MAX_MD_SIZE];
-  unsigned hash_len;
-  if (!EVP_Digest(in, in_len, hash, &hash_len, md, NULL)) {
-    return ssl_private_key_failure;
-  }
-
-  return ssl->cert->key_method->sign_digest(ssl, out, out_len, max_out, md,
-                                            hash, hash_len);
-}
-
-enum ssl_private_key_result_t ssl_private_key_sign(
-    SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, size_t max_out,
-    uint16_t sigalg, const uint8_t *in, size_t in_len) {
-  SSL *const ssl = hs->ssl;
-  if (ssl->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);
-    } else {
-      ret = (ssl->cert->key_method->sign != NULL
-                 ? ssl->cert->key_method->sign
-                 : legacy_sign)(ssl, out, out_len, max_out, sigalg, in, in_len);
-    }
-    hs->pending_private_key_op = ret == ssl_private_key_retry;
-    return ret;
-  }
-
-  *out_len = max_out;
-  EVP_MD_CTX ctx;
-  EVP_MD_CTX_init(&ctx);
-  int ret = setup_ctx(ssl, &ctx, ssl->cert->privatekey, sigalg, 0 /* sign */) &&
-            EVP_DigestSign(&ctx, out, out_len, in, in_len);
-  EVP_MD_CTX_cleanup(&ctx);
-  return ret ? ssl_private_key_success : ssl_private_key_failure;
-}
-
-int ssl_public_key_verify(SSL *ssl, const uint8_t *signature,
-                          size_t signature_len, uint16_t sigalg, EVP_PKEY *pkey,
-                          const uint8_t *in, size_t in_len) {
-  EVP_MD_CTX ctx;
-  EVP_MD_CTX_init(&ctx);
-  int ret = setup_ctx(ssl, &ctx, pkey, sigalg, 1 /* verify */) &&
-            EVP_DigestVerify(&ctx, signature, signature_len, in, in_len);
-  EVP_MD_CTX_cleanup(&ctx);
-  return ret;
-}
-
-enum ssl_private_key_result_t ssl_private_key_decrypt(
-    SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, size_t max_out,
-    const uint8_t *in, size_t in_len) {
-  SSL *const ssl = hs->ssl;
-  if (ssl->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);
-    } else {
-      ret = ssl->cert->key_method->decrypt(ssl, out, out_len, max_out, in,
-                                           in_len);
-    }
-    hs->pending_private_key_op = ret == ssl_private_key_retry;
-    return ret;
-  }
-
-  RSA *rsa = EVP_PKEY_get0_RSA(ssl->cert->privatekey);
-  if (rsa == NULL) {
-    /* Decrypt operations are only supported for RSA keys. */
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return ssl_private_key_failure;
-  }
-
-  /* Decrypt with no padding. PKCS#1 padding will be removed as part
-   * of the timing-sensitive code by the caller. */
-  if (!RSA_decrypt(rsa, out_len, out, max_out, in, in_len, RSA_NO_PADDING)) {
-    return ssl_private_key_failure;
-  }
-  return ssl_private_key_success;
-}
-
-int ssl_private_key_supports_signature_algorithm(SSL_HANDSHAKE *hs,
-                                                 uint16_t sigalg) {
-  SSL *const ssl = hs->ssl;
-  if (!pkey_supports_algorithm(ssl, hs->local_pubkey, sigalg)) {
-    return 0;
-  }
-
-  /* Ensure the RSA key is large enough for the hash. RSASSA-PSS requires that
-   * emLen be at least hLen + sLen + 2. Both hLen and sLen are the size of the
-   * hash in TLS. Reasonable RSA key sizes are large enough for the largest
-   * defined RSASSA-PSS algorithm, but 1024-bit RSA is slightly too small for
-   * SHA-512. 1024-bit RSA is sometimes used for test credentials, so check the
-   * size so that we can fall back to another algorithm in that case. */
-  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
-  if (alg->is_rsa_pss &&
-      (size_t)EVP_PKEY_size(hs->local_pubkey) <
-          2 * EVP_MD_size(alg->digest_func()) + 2) {
-    return 0;
-  }
-
-  /* Newer algorithms require message-based private keys.
-   * TODO(davidben): Remove this check when sign_digest is gone. */
-  if (ssl->cert->key_method != NULL &&
-      ssl->cert->key_method->sign == NULL &&
-      !legacy_sign_digest_supported(alg)) {
-    return 0;
-  }
-
-  return 1;
-}
diff --git a/src/ssl/ssl_session.cc b/src/ssl/ssl_session.cc
index 9cb78cc..02d6422 100644
--- a/src/ssl/ssl_session.cc
+++ b/src/ssl/ssl_session.cc
@@ -139,6 +139,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <utility>
+
 #include <openssl/err.h>
 #include <openssl/lhash.h>
 #include <openssl/mem.h>
@@ -148,6 +150,8 @@
 #include "../crypto/internal.h"
 
 
+namespace bssl {
+
 /* The address of this is a magic value, a pointer to which is returned by
  * SSL_magic_pending_session_ptr(). It allows a session callback to indicate
  * that it needs to asynchronously fetch session information. */
@@ -160,13 +164,14 @@
 static void SSL_SESSION_list_add(SSL_CTX *ctx, SSL_SESSION *session);
 static int remove_session_lock(SSL_CTX *ctx, SSL_SESSION *session, int lock);
 
-SSL_SESSION *ssl_session_new(const SSL_X509_METHOD *x509_method) {
-  SSL_SESSION *session = (SSL_SESSION *)OPENSSL_malloc(sizeof(SSL_SESSION));
-  if (session == NULL) {
+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, 0, sizeof(SSL_SESSION));
+  OPENSSL_memset(session.get(), 0, sizeof(SSL_SESSION));
 
   session->x509_method = x509_method;
   session->verify_result = X509_V_ERR_INVALID_CALL;
@@ -178,14 +183,10 @@
   return session;
 }
 
-SSL_SESSION *SSL_SESSION_new(const SSL_CTX *ctx) {
-  return ssl_session_new(ctx->x509_method);
-}
-
-SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int dup_flags) {
-  SSL_SESSION *new_session = ssl_session_new(session->x509_method);
-  if (new_session == NULL) {
-    goto err;
+UniquePtr<SSL_SESSION> SSL_SESSION_dup(SSL_SESSION *session, int dup_flags) {
+  UniquePtr<SSL_SESSION> new_session = ssl_session_new(session->x509_method);
+  if (!new_session) {
+    return nullptr;
   }
 
   new_session->is_server = session->is_server;
@@ -203,25 +204,25 @@
   if (session->psk_identity != NULL) {
     new_session->psk_identity = BUF_strdup(session->psk_identity);
     if (new_session->psk_identity == NULL) {
-      goto err;
+      return nullptr;
     }
   }
   if (session->certs != NULL) {
     new_session->certs = sk_CRYPTO_BUFFER_new_null();
     if (new_session->certs == NULL) {
-      goto err;
+      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)) {
-        goto err;
+        return nullptr;
       }
       CRYPTO_BUFFER_up_ref(buffer);
     }
   }
 
-  if (!session->x509_method->session_dup(new_session, session)) {
-    goto err;
+  if (!session->x509_method->session_dup(new_session.get(), session)) {
+    return nullptr;
   }
 
   new_session->verify_result = session->verify_result;
@@ -231,7 +232,7 @@
     new_session->ocsp_response = (uint8_t *)BUF_memdup(
         session->ocsp_response, session->ocsp_response_length);
     if (new_session->ocsp_response == NULL) {
-      goto err;
+      return nullptr;
     }
   }
 
@@ -242,7 +243,7 @@
         session->tlsext_signed_cert_timestamp_list,
         session->tlsext_signed_cert_timestamp_list_length);
     if (new_session->tlsext_signed_cert_timestamp_list == NULL) {
-      goto err;
+      return nullptr;
     }
   }
 
@@ -253,7 +254,7 @@
   if (session->tlsext_hostname != NULL) {
     new_session->tlsext_hostname = BUF_strdup(session->tlsext_hostname);
     if (new_session->tlsext_hostname == NULL) {
-      goto err;
+      return nullptr;
     }
   }
 
@@ -285,7 +286,7 @@
       new_session->early_alpn =
           (uint8_t *)BUF_memdup(session->early_alpn, session->early_alpn_len);
       if (new_session->early_alpn == NULL) {
-        goto err;
+        return nullptr;
       }
     }
     new_session->early_alpn_len = session->early_alpn_len;
@@ -297,7 +298,7 @@
       new_session->tlsext_tick =
           (uint8_t *)BUF_memdup(session->tlsext_tick, session->tlsext_ticklen);
       if (new_session->tlsext_tick == NULL) {
-        goto err;
+        return nullptr;
       }
     }
     new_session->tlsext_ticklen = session->tlsext_ticklen;
@@ -307,11 +308,6 @@
 
   new_session->not_resumable = 1;
   return new_session;
-
-err:
-  SSL_SESSION_free(new_session);
-  OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-  return 0;
 }
 
 void ssl_session_rebase_time(SSL *ssl, SSL_SESSION *session) {
@@ -359,152 +355,6 @@
   }
 }
 
-int SSL_SESSION_up_ref(SSL_SESSION *session) {
-  CRYPTO_refcount_inc(&session->references);
-  return 1;
-}
-
-void SSL_SESSION_free(SSL_SESSION *session) {
-  if (session == NULL ||
-      !CRYPTO_refcount_dec_and_test_zero(&session->references)) {
-    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_hostname);
-  OPENSSL_free(session->tlsext_tick);
-  OPENSSL_free(session->tlsext_signed_cert_timestamp_list);
-  OPENSSL_free(session->ocsp_response);
-  OPENSSL_free(session->psk_identity);
-  OPENSSL_free(session->early_alpn);
-  OPENSSL_cleanse(session, sizeof(*session));
-  OPENSSL_free(session);
-}
-
-const uint8_t *SSL_SESSION_get_id(const SSL_SESSION *session,
-                                  unsigned *out_len) {
-  if (out_len != NULL) {
-    *out_len = session->session_id_length;
-  }
-  return session->session_id;
-}
-
-uint32_t SSL_SESSION_get_timeout(const SSL_SESSION *session) {
-  return session->timeout;
-}
-
-uint64_t SSL_SESSION_get_time(const SSL_SESSION *session) {
-  if (session == NULL) {
-    /* NULL should crash, but silently accept it here for compatibility. */
-    return 0;
-  }
-  return session->time;
-}
-
-X509 *SSL_SESSION_get0_peer(const SSL_SESSION *session) {
-  return session->x509_peer;
-}
-
-size_t SSL_SESSION_get_master_key(const SSL_SESSION *session, uint8_t *out,
-                                  size_t max_out) {
-  /* TODO(davidben): Fix master_key_length's type and remove these casts. */
-  if (max_out == 0) {
-    return (size_t)session->master_key_length;
-  }
-  if (max_out > (size_t)session->master_key_length) {
-    max_out = (size_t)session->master_key_length;
-  }
-  OPENSSL_memcpy(out, session->master_key, max_out);
-  return max_out;
-}
-
-uint64_t SSL_SESSION_set_time(SSL_SESSION *session, uint64_t time) {
-  if (session == NULL) {
-    return 0;
-  }
-
-  session->time = time;
-  return time;
-}
-
-uint32_t SSL_SESSION_set_timeout(SSL_SESSION *session, uint32_t timeout) {
-  if (session == NULL) {
-    return 0;
-  }
-
-  session->timeout = timeout;
-  session->auth_timeout = timeout;
-  return 1;
-}
-
-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)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_SESSION_ID_CONTEXT_TOO_LONG);
-    return 0;
-  }
-
-  assert(sizeof(session->sid_ctx) < 256);
-  session->sid_ctx_length = (uint8_t)sid_ctx_len;
-  OPENSSL_memcpy(session->sid_ctx, sid_ctx, sid_ctx_len);
-
-  return 1;
-}
-
-SSL_SESSION *SSL_magic_pending_session_ptr(void) {
-  return (SSL_SESSION *)&g_pending_session_magic;
-}
-
-SSL_SESSION *SSL_get_session(const SSL *ssl) {
-  /* Once the handshake completes we return the established session. Otherwise
-   * we return the intermediate session, either |session| (for resumption) or
-   * |new_session| if doing a full handshake. */
-  if (!SSL_in_init(ssl)) {
-    return ssl->s3->established_session;
-  }
-  SSL_HANDSHAKE *hs = ssl->s3->hs;
-  if (hs->early_session != NULL) {
-    return hs->early_session;
-  }
-  if (hs->new_session != NULL) {
-    return hs->new_session;
-  }
-  return ssl->session;
-}
-
-SSL_SESSION *SSL_get1_session(SSL *ssl) {
-  SSL_SESSION *ret = SSL_get_session(ssl);
-  if (ret != NULL) {
-    SSL_SESSION_up_ref(ret);
-  }
-  return ret;
-}
-
-int SSL_SESSION_get_ex_new_index(long argl, void *argp,
-                                 CRYPTO_EX_unused *unused,
-                                 CRYPTO_EX_dup *dup_unused,
-                                 CRYPTO_EX_free *free_func) {
-  int index;
-  if (!CRYPTO_get_ex_new_index(&g_ex_data_class, &index, argl, argp,
-                               free_func)) {
-    return -1;
-  }
-  return index;
-}
-
-int SSL_SESSION_set_ex_data(SSL_SESSION *session, int idx, void *arg) {
-  return CRYPTO_set_ex_data(&session->ex_data, idx, arg);
-}
-
-void *SSL_SESSION_get_ex_data(const SSL_SESSION *session, int idx) {
-  return CRYPTO_get_ex_data(&session->ex_data, idx);
-}
-
 uint16_t SSL_SESSION_protocol_version(const SSL_SESSION *session) {
   uint16_t ret;
   if (!ssl_protocol_version_from_wire(&ret, session->ssl_version)) {
@@ -529,7 +379,7 @@
     return 0;
   }
 
-  SSL_SESSION *session = ssl_session_new(ssl->ctx->x509_method);
+  UniquePtr<SSL_SESSION> session = ssl_session_new(ssl->ctx->x509_method);
   if (session == NULL) {
     return 0;
   }
@@ -563,7 +413,7 @@
     } else {
       session->session_id_length = SSL3_SSL_SESSION_ID_LENGTH;
       if (!RAND_bytes(session->session_id, session->session_id_length)) {
-        goto err;
+        return 0;
       }
     }
   } else {
@@ -572,7 +422,7 @@
 
   if (ssl->cert->sid_ctx_length > sizeof(session->sid_ctx)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    goto err;
+    return 0;
   }
   OPENSSL_memcpy(session->sid_ctx, ssl->cert->sid_ctx,
                  ssl->cert->sid_ctx_length);
@@ -582,21 +432,16 @@
   session->not_resumable = 1;
   session->verify_result = X509_V_ERR_INVALID_CALL;
 
-  SSL_SESSION_free(hs->new_session);
-  hs->new_session = session;
+  hs->new_session = std::move(session);
   ssl_set_session(ssl, NULL);
   return 1;
-
-err:
-  SSL_SESSION_free(session);
-  return 0;
 }
 
 static int ssl_encrypt_ticket_with_cipher_ctx(SSL *ssl, CBB *out,
                                               const uint8_t *session_buf,
                                               size_t session_len) {
-  bssl::ScopedEVP_CIPHER_CTX ctx;
-  bssl::ScopedHMAC_CTX hctx;
+  ScopedEVP_CIPHER_CTX ctx;
+  ScopedHMAC_CTX hctx;
 
   /* If the session is too long, emit a dummy value rather than abort the
    * connection. */
@@ -881,6 +726,249 @@
   return ssl_session_success;
 }
 
+static int remove_session_lock(SSL_CTX *ctx, SSL_SESSION *session, int lock) {
+  int ret = 0;
+
+  if (session != NULL && session->session_id_length != 0) {
+    if (lock) {
+      CRYPTO_MUTEX_lock_write(&ctx->lock);
+    }
+    SSL_SESSION *found_session = lh_SSL_SESSION_retrieve(ctx->sessions,
+                                                         session);
+    if (found_session == session) {
+      ret = 1;
+      found_session = lh_SSL_SESSION_delete(ctx->sessions, session);
+      SSL_SESSION_list_remove(ctx, session);
+    }
+
+    if (lock) {
+      CRYPTO_MUTEX_unlock_write(&ctx->lock);
+    }
+
+    if (ret) {
+      if (ctx->remove_session_cb != NULL) {
+        ctx->remove_session_cb(ctx, found_session);
+      }
+      SSL_SESSION_free(found_session);
+    }
+  }
+
+  return ret;
+}
+
+void ssl_set_session(SSL *ssl, SSL_SESSION *session) {
+  if (ssl->session == session) {
+    return;
+  }
+
+  SSL_SESSION_free(ssl->session);
+  ssl->session = session;
+  if (session != NULL) {
+    SSL_SESSION_up_ref(session);
+  }
+}
+
+/* locked by SSL_CTX in the calling function */
+static void SSL_SESSION_list_remove(SSL_CTX *ctx, SSL_SESSION *session) {
+  if (session->next == NULL || session->prev == NULL) {
+    return;
+  }
+
+  if (session->next == (SSL_SESSION *)&ctx->session_cache_tail) {
+    /* last element in list */
+    if (session->prev == (SSL_SESSION *)&ctx->session_cache_head) {
+      /* only one element in list */
+      ctx->session_cache_head = NULL;
+      ctx->session_cache_tail = NULL;
+    } else {
+      ctx->session_cache_tail = session->prev;
+      session->prev->next = (SSL_SESSION *)&(ctx->session_cache_tail);
+    }
+  } else {
+    if (session->prev == (SSL_SESSION *)&ctx->session_cache_head) {
+      /* first element in list */
+      ctx->session_cache_head = session->next;
+      session->next->prev = (SSL_SESSION *)&(ctx->session_cache_head);
+    } else { /* middle of list */
+      session->next->prev = session->prev;
+      session->prev->next = session->next;
+    }
+  }
+  session->prev = session->next = NULL;
+}
+
+static void SSL_SESSION_list_add(SSL_CTX *ctx, SSL_SESSION *session) {
+  if (session->next != NULL && session->prev != NULL) {
+    SSL_SESSION_list_remove(ctx, session);
+  }
+
+  if (ctx->session_cache_head == NULL) {
+    ctx->session_cache_head = session;
+    ctx->session_cache_tail = session;
+    session->prev = (SSL_SESSION *)&(ctx->session_cache_head);
+    session->next = (SSL_SESSION *)&(ctx->session_cache_tail);
+  } else {
+    session->next = ctx->session_cache_head;
+    session->next->prev = session;
+    session->prev = (SSL_SESSION *)&(ctx->session_cache_head);
+    ctx->session_cache_head = session;
+  }
+}
+
+}  // namespace bssl
+
+using namespace bssl;
+
+SSL_SESSION *SSL_SESSION_new(const SSL_CTX *ctx) {
+  return ssl_session_new(ctx->x509_method).release();
+}
+
+int SSL_SESSION_up_ref(SSL_SESSION *session) {
+  CRYPTO_refcount_inc(&session->references);
+  return 1;
+}
+
+void SSL_SESSION_free(SSL_SESSION *session) {
+  if (session == NULL ||
+      !CRYPTO_refcount_dec_and_test_zero(&session->references)) {
+    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_hostname);
+  OPENSSL_free(session->tlsext_tick);
+  OPENSSL_free(session->tlsext_signed_cert_timestamp_list);
+  OPENSSL_free(session->ocsp_response);
+  OPENSSL_free(session->psk_identity);
+  OPENSSL_free(session->early_alpn);
+  OPENSSL_cleanse(session, sizeof(*session));
+  OPENSSL_free(session);
+}
+
+const uint8_t *SSL_SESSION_get_id(const SSL_SESSION *session,
+                                  unsigned *out_len) {
+  if (out_len != NULL) {
+    *out_len = session->session_id_length;
+  }
+  return session->session_id;
+}
+
+uint32_t SSL_SESSION_get_timeout(const SSL_SESSION *session) {
+  return session->timeout;
+}
+
+uint64_t SSL_SESSION_get_time(const SSL_SESSION *session) {
+  if (session == NULL) {
+    /* NULL should crash, but silently accept it here for compatibility. */
+    return 0;
+  }
+  return session->time;
+}
+
+X509 *SSL_SESSION_get0_peer(const SSL_SESSION *session) {
+  return session->x509_peer;
+}
+
+size_t SSL_SESSION_get_master_key(const SSL_SESSION *session, uint8_t *out,
+                                  size_t max_out) {
+  /* TODO(davidben): Fix master_key_length's type and remove these casts. */
+  if (max_out == 0) {
+    return (size_t)session->master_key_length;
+  }
+  if (max_out > (size_t)session->master_key_length) {
+    max_out = (size_t)session->master_key_length;
+  }
+  OPENSSL_memcpy(out, session->master_key, max_out);
+  return max_out;
+}
+
+uint64_t SSL_SESSION_set_time(SSL_SESSION *session, uint64_t time) {
+  if (session == NULL) {
+    return 0;
+  }
+
+  session->time = time;
+  return time;
+}
+
+uint32_t SSL_SESSION_set_timeout(SSL_SESSION *session, uint32_t timeout) {
+  if (session == NULL) {
+    return 0;
+  }
+
+  session->timeout = timeout;
+  session->auth_timeout = timeout;
+  return 1;
+}
+
+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)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_SESSION_ID_CONTEXT_TOO_LONG);
+    return 0;
+  }
+
+  assert(sizeof(session->sid_ctx) < 256);
+  session->sid_ctx_length = (uint8_t)sid_ctx_len;
+  OPENSSL_memcpy(session->sid_ctx, sid_ctx, sid_ctx_len);
+
+  return 1;
+}
+
+SSL_SESSION *SSL_magic_pending_session_ptr(void) {
+  return (SSL_SESSION *)&g_pending_session_magic;
+}
+
+SSL_SESSION *SSL_get_session(const SSL *ssl) {
+  /* Once the handshake completes we return the established session. Otherwise
+   * we return the intermediate session, either |session| (for resumption) or
+   * |new_session| if doing a full handshake. */
+  if (!SSL_in_init(ssl)) {
+    return ssl->s3->established_session;
+  }
+  SSL_HANDSHAKE *hs = ssl->s3->hs;
+  if (hs->early_session) {
+    return hs->early_session.get();
+  }
+  if (hs->new_session) {
+    return hs->new_session.get();
+  }
+  return ssl->session;
+}
+
+SSL_SESSION *SSL_get1_session(SSL *ssl) {
+  SSL_SESSION *ret = SSL_get_session(ssl);
+  if (ret != NULL) {
+    SSL_SESSION_up_ref(ret);
+  }
+  return ret;
+}
+
+int SSL_SESSION_get_ex_new_index(long argl, void *argp,
+                                 CRYPTO_EX_unused *unused,
+                                 CRYPTO_EX_dup *dup_unused,
+                                 CRYPTO_EX_free *free_func) {
+  int index;
+  if (!CRYPTO_get_ex_new_index(&g_ex_data_class, &index, argl, argp,
+                               free_func)) {
+    return -1;
+  }
+  return index;
+}
+
+int SSL_SESSION_set_ex_data(SSL_SESSION *session, int idx, void *arg) {
+  return CRYPTO_set_ex_data(&session->ex_data, idx, arg);
+}
+
+void *SSL_SESSION_get_ex_data(const SSL_SESSION *session, int idx) {
+  return CRYPTO_get_ex_data(&session->ex_data, idx);
+}
+
 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. */
@@ -927,36 +1015,6 @@
   return remove_session_lock(ctx, session, 1);
 }
 
-static int remove_session_lock(SSL_CTX *ctx, SSL_SESSION *session, int lock) {
-  int ret = 0;
-
-  if (session != NULL && session->session_id_length != 0) {
-    if (lock) {
-      CRYPTO_MUTEX_lock_write(&ctx->lock);
-    }
-    SSL_SESSION *found_session = lh_SSL_SESSION_retrieve(ctx->sessions,
-                                                         session);
-    if (found_session == session) {
-      ret = 1;
-      found_session = lh_SSL_SESSION_delete(ctx->sessions, session);
-      SSL_SESSION_list_remove(ctx, session);
-    }
-
-    if (lock) {
-      CRYPTO_MUTEX_unlock_write(&ctx->lock);
-    }
-
-    if (ret) {
-      if (ctx->remove_session_cb != NULL) {
-        ctx->remove_session_cb(ctx, found_session);
-      }
-      SSL_SESSION_free(found_session);
-    }
-  }
-
-  return ret;
-}
-
 int SSL_set_session(SSL *ssl, SSL_SESSION *session) {
   /* SSL_set_session may only be called before the handshake has started. */
   if (ssl->s3->initial_handshake_complete ||
@@ -969,18 +1027,6 @@
   return 1;
 }
 
-void ssl_set_session(SSL *ssl, SSL_SESSION *session) {
-  if (ssl->session == session) {
-    return;
-  }
-
-  SSL_SESSION_free(ssl->session);
-  ssl->session = session;
-  if (session != NULL) {
-    SSL_SESSION_up_ref(session);
-  }
-}
-
 uint32_t SSL_CTX_set_timeout(SSL_CTX *ctx, uint32_t timeout) {
   if (ctx == NULL) {
     return 0;
@@ -1046,53 +1092,6 @@
   CRYPTO_MUTEX_unlock_write(&ctx->lock);
 }
 
-/* locked by SSL_CTX in the calling function */
-static void SSL_SESSION_list_remove(SSL_CTX *ctx, SSL_SESSION *session) {
-  if (session->next == NULL || session->prev == NULL) {
-    return;
-  }
-
-  if (session->next == (SSL_SESSION *)&ctx->session_cache_tail) {
-    /* last element in list */
-    if (session->prev == (SSL_SESSION *)&ctx->session_cache_head) {
-      /* only one element in list */
-      ctx->session_cache_head = NULL;
-      ctx->session_cache_tail = NULL;
-    } else {
-      ctx->session_cache_tail = session->prev;
-      session->prev->next = (SSL_SESSION *)&(ctx->session_cache_tail);
-    }
-  } else {
-    if (session->prev == (SSL_SESSION *)&ctx->session_cache_head) {
-      /* first element in list */
-      ctx->session_cache_head = session->next;
-      session->next->prev = (SSL_SESSION *)&(ctx->session_cache_head);
-    } else { /* middle of list */
-      session->next->prev = session->prev;
-      session->prev->next = session->next;
-    }
-  }
-  session->prev = session->next = NULL;
-}
-
-static void SSL_SESSION_list_add(SSL_CTX *ctx, SSL_SESSION *session) {
-  if (session->next != NULL && session->prev != NULL) {
-    SSL_SESSION_list_remove(ctx, session);
-  }
-
-  if (ctx->session_cache_head == NULL) {
-    ctx->session_cache_head = session;
-    ctx->session_cache_tail = session;
-    session->prev = (SSL_SESSION *)&(ctx->session_cache_head);
-    session->next = (SSL_SESSION *)&(ctx->session_cache_tail);
-  } else {
-    session->next = ctx->session_cache_head;
-    session->next->prev = session;
-    session->prev = (SSL_SESSION *)&(ctx->session_cache_head);
-    ctx->session_cache_head = session;
-  }
-}
-
 void SSL_CTX_sess_set_new_cb(SSL_CTX *ctx,
                              int (*cb)(SSL *ssl, SSL_SESSION *session)) {
   ctx->new_session_cb = cb;
diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc
index 2c648ac..a57298f 100644
--- a/src/ssl/ssl_test.cc
+++ b/src/ssl/ssl_test.cc
@@ -1293,6 +1293,15 @@
       PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr));
 }
 
+static const uint8_t kTestName[] = {
+    0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+    0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+    0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
+    0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49,
+    0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67,
+    0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64,
+};
+
 static bool CompleteHandshakes(SSL *client, SSL *server) {
   // Drive both their handshakes to completion.
   for (;;) {
@@ -1517,8 +1526,8 @@
                                      nullptr /* no session */));
 
   SSL_SESSION *session0 = SSL_get_session(client.get());
-  bssl::UniquePtr<SSL_SESSION> session1(
-      SSL_SESSION_dup(session0, SSL_SESSION_DUP_ALL));
+  bssl::UniquePtr<SSL_SESSION> session1 =
+      bssl::SSL_SESSION_dup(session0, SSL_SESSION_DUP_ALL);
   ASSERT_TRUE(session1);
 
   session1->not_resumable = 0;
@@ -3276,7 +3285,11 @@
   ASSERT_TRUE(SSL_CTX_set_chain_and_key(server_ctx.get(), &chain[0],
                                         chain.size(), key.get(), nullptr));
 
-  SSL_CTX_i_promise_to_verify_certs_after_the_handshake(client_ctx.get());
+  SSL_CTX_set_custom_verify(
+      client_ctx.get(), SSL_VERIFY_PEER,
+      [](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t {
+        return ssl_verify_ok;
+      });
 
   bssl::UniquePtr<SSL> client, server;
   ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
@@ -3284,6 +3297,70 @@
                                      nullptr /* no session */));
 }
 
+TEST(SSLTest, ClientCABuffers) {
+  bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_with_buffers_method()));
+  ASSERT_TRUE(client_ctx);
+  bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_with_buffers_method()));
+  ASSERT_TRUE(server_ctx);
+
+  bssl::UniquePtr<EVP_PKEY> key = GetChainTestKey();
+  ASSERT_TRUE(key);
+  bssl::UniquePtr<CRYPTO_BUFFER> leaf = GetChainTestCertificateBuffer();
+  ASSERT_TRUE(leaf);
+  bssl::UniquePtr<CRYPTO_BUFFER> intermediate =
+      GetChainTestIntermediateBuffer();
+  ASSERT_TRUE(intermediate);
+  std::vector<CRYPTO_BUFFER *> chain = {
+      leaf.get(),
+      intermediate.get(),
+  };
+  ASSERT_TRUE(SSL_CTX_set_chain_and_key(server_ctx.get(), &chain[0],
+                                        chain.size(), key.get(), nullptr));
+
+  bssl::UniquePtr<CRYPTO_BUFFER> ca_name(
+      CRYPTO_BUFFER_new(kTestName, sizeof(kTestName), nullptr));
+  ASSERT_TRUE(ca_name);
+  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();
+  SSL_CTX_set0_client_CAs(server_ctx.get(), ca_names.release());
+
+  // Configure client and server to accept all certificates.
+  SSL_CTX_set_custom_verify(
+      client_ctx.get(), SSL_VERIFY_PEER,
+      [](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t {
+        return ssl_verify_ok;
+      });
+  SSL_CTX_set_custom_verify(
+      server_ctx.get(), SSL_VERIFY_PEER,
+      [](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t {
+        return ssl_verify_ok;
+      });
+
+  bool cert_cb_called = false;
+  SSL_CTX_set_cert_cb(
+      client_ctx.get(),
+      [](SSL *ssl, void *arg) -> int {
+        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);
+        EXPECT_EQ(Bytes(kTestName), Bytes(CRYPTO_BUFFER_data(peer_name),
+                                          CRYPTO_BUFFER_len(peer_name)));
+        *reinterpret_cast<bool *>(arg) = true;
+        return 1;
+      },
+      &cert_cb_called);
+
+  bssl::UniquePtr<SSL> client, server;
+  ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
+                                     server_ctx.get(),
+                                     nullptr /* no session */));
+  EXPECT_TRUE(cert_cb_called);
+}
+
 // Configuring the empty cipher list, though an error, should still modify the
 // configuration.
 TEST(SSLTest, EmptyCipherList) {
diff --git a/src/ssl/ssl_transcript.cc b/src/ssl/ssl_transcript.cc
index 9cc3777..4a00d0f 100644
--- a/src/ssl/ssl_transcript.cc
+++ b/src/ssl/ssl_transcript.cc
@@ -150,136 +150,117 @@
 #include "internal.h"
 
 
-int SSL_TRANSCRIPT_init(SSL_TRANSCRIPT *transcript) {
-  SSL_TRANSCRIPT_cleanup(transcript);
-  transcript->buffer = BUF_MEM_new();
-  return transcript->buffer != NULL;
+namespace bssl {
+
+SSLTranscript::SSLTranscript() {}
+
+SSLTranscript::~SSLTranscript() {}
+
+bool SSLTranscript::Init() {
+  buffer_.reset(BUF_MEM_new());
+  if (!buffer_) {
+    return false;
+  }
+
+  hash_.Reset();
+  md5_.Reset();
+  return true;
 }
 
-/* init_digest_with_data calls |EVP_DigestInit_ex| on |ctx| with |md| and then
+/* InitDigestWithData calls |EVP_DigestInit_ex| on |ctx| with |md| and then
  * writes the data in |buf| to it. */
-static int init_digest_with_data(EVP_MD_CTX *ctx, const EVP_MD *md,
-                                 const BUF_MEM *buf) {
+static bool InitDigestWithData(EVP_MD_CTX *ctx, const EVP_MD *md,
+                               const BUF_MEM *buf) {
   if (!EVP_DigestInit_ex(ctx, md, NULL)) {
-    return 0;
+    return false;
   }
   EVP_DigestUpdate(ctx, buf->data, buf->length);
-  return 1;
+  return true;
 }
 
-int SSL_TRANSCRIPT_init_hash(SSL_TRANSCRIPT *transcript, uint16_t version,
-                             int algorithm_prf) {
+bool SSLTranscript::InitHash(uint16_t version, int algorithm_prf) {
   const EVP_MD *md = ssl_get_handshake_digest(algorithm_prf, version);
 
   /* 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 (!init_digest_with_data(&transcript->md5, EVP_md5(),
-                               transcript->buffer)) {
-      return 0;
+    if (!InitDigestWithData(md5_.get(), EVP_md5(), buffer_.get())) {
+      return false;
     }
     md = EVP_sha1();
   }
 
-  if (!init_digest_with_data(&transcript->hash, md, transcript->buffer)) {
-    return 0;
-  }
-
-  return 1;
+  return InitDigestWithData(hash_.get(), md, buffer_.get());
 }
 
-void SSL_TRANSCRIPT_cleanup(SSL_TRANSCRIPT *transcript) {
-  SSL_TRANSCRIPT_free_buffer(transcript);
-  EVP_MD_CTX_cleanup(&transcript->hash);
-  EVP_MD_CTX_cleanup(&transcript->md5);
+void SSLTranscript::FreeBuffer() {
+  buffer_.reset();
 }
 
-void SSL_TRANSCRIPT_free_buffer(SSL_TRANSCRIPT *transcript) {
-  BUF_MEM_free(transcript->buffer);
-  transcript->buffer = NULL;
+size_t SSLTranscript::DigestLen() const {
+  return EVP_MD_size(Digest());
 }
 
-size_t SSL_TRANSCRIPT_digest_len(const SSL_TRANSCRIPT *transcript) {
-  return EVP_MD_size(SSL_TRANSCRIPT_md(transcript));
-}
-
-const EVP_MD *SSL_TRANSCRIPT_md(const SSL_TRANSCRIPT *transcript) {
-  if (EVP_MD_CTX_md(&transcript->md5) != NULL) {
+const EVP_MD *SSLTranscript::Digest() const {
+  if (EVP_MD_CTX_md(md5_.get()) != nullptr) {
     return EVP_md5_sha1();
   }
-  return EVP_MD_CTX_md(&transcript->hash);
+  return EVP_MD_CTX_md(hash_.get());
 }
 
-int SSL_TRANSCRIPT_update(SSL_TRANSCRIPT *transcript, const uint8_t *in,
-                          size_t in_len) {
+bool SSLTranscript::Update(const uint8_t *in, size_t in_len) {
   /* Depending on the state of the handshake, either the handshake buffer may be
    * active, the rolling hash, or both. */
-  if (transcript->buffer != NULL) {
-    size_t new_len = transcript->buffer->length + in_len;
+  if (buffer_) {
+    size_t new_len = buffer_->length + in_len;
     if (new_len < in_len) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
-      return 0;
+      return false;
     }
-    if (!BUF_MEM_grow(transcript->buffer, new_len)) {
-      return 0;
+    if (!BUF_MEM_grow(buffer_.get(), new_len)) {
+      return false;
     }
-    OPENSSL_memcpy(transcript->buffer->data + new_len - in_len, in, in_len);
+    OPENSSL_memcpy(buffer_->data + new_len - in_len, in, in_len);
   }
 
-  if (EVP_MD_CTX_md(&transcript->hash) != NULL) {
-    EVP_DigestUpdate(&transcript->hash, in, in_len);
+  if (EVP_MD_CTX_md(hash_.get()) != NULL) {
+    EVP_DigestUpdate(hash_.get(), in, in_len);
   }
-  if (EVP_MD_CTX_md(&transcript->md5) != NULL) {
-    EVP_DigestUpdate(&transcript->md5, in, in_len);
+  if (EVP_MD_CTX_md(md5_.get()) != NULL) {
+    EVP_DigestUpdate(md5_.get(), in, in_len);
   }
 
-  return 1;
+  return true;
 }
 
-int SSL_TRANSCRIPT_get_hash(const SSL_TRANSCRIPT *transcript, uint8_t *out,
-                            size_t *out_len) {
-  int ret = 0;
-  EVP_MD_CTX ctx;
-  EVP_MD_CTX_init(&ctx);
+bool SSLTranscript::GetHash(uint8_t *out, size_t *out_len) {
+  ScopedEVP_MD_CTX ctx;
   unsigned md5_len = 0;
-  if (EVP_MD_CTX_md(&transcript->md5) != NULL) {
-    if (!EVP_MD_CTX_copy_ex(&ctx, &transcript->md5) ||
-        !EVP_DigestFinal_ex(&ctx, out, &md5_len)) {
-      goto err;
+  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, &transcript->hash) ||
-      !EVP_DigestFinal_ex(&ctx, out + md5_len, &len)) {
-    goto err;
+  if (!EVP_MD_CTX_copy_ex(ctx.get(), hash_.get()) ||
+      !EVP_DigestFinal_ex(ctx.get(), out + md5_len, &len)) {
+    return false;
   }
 
   *out_len = md5_len + len;
-  ret = 1;
-
-err:
-  EVP_MD_CTX_cleanup(&ctx);
-  return ret;
+  return true;
 }
 
-static int ssl3_handshake_mac(SSL_TRANSCRIPT *transcript,
-                              const SSL_SESSION *session,
-                              const EVP_MD_CTX *ctx_template,
-                              const char *sender, size_t sender_len,
-                              uint8_t *p, size_t *out_len) {
-  unsigned int len;
-  size_t npad, n;
-  unsigned int i;
-  uint8_t md_buf[EVP_MAX_MD_SIZE];
-  EVP_MD_CTX ctx;
-
-  EVP_MD_CTX_init(&ctx);
-  if (!EVP_MD_CTX_copy_ex(&ctx, ctx_template)) {
-    EVP_MD_CTX_cleanup(&ctx);
+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 0;
+    return false;
   }
 
   static const uint8_t kPad1[48] = {
@@ -296,89 +277,83 @@
       0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
   };
 
-  n = EVP_MD_CTX_size(&ctx);
+  size_t n = EVP_MD_CTX_size(ctx.get());
 
-  npad = (48 / n) * n;
-  if (sender != NULL) {
-    EVP_DigestUpdate(&ctx, sender, sender_len);
-  }
-  EVP_DigestUpdate(&ctx, session->master_key, session->master_key_length);
-  EVP_DigestUpdate(&ctx, kPad1, npad);
-  EVP_DigestFinal_ex(&ctx, md_buf, &i);
+  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, EVP_MD_CTX_md(&ctx), NULL)) {
-    EVP_MD_CTX_cleanup(&ctx);
+  if (!EVP_DigestInit_ex(ctx.get(), EVP_MD_CTX_md(ctx.get()), NULL)) {
     OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
-    return 0;
+    return false;
   }
-  EVP_DigestUpdate(&ctx, session->master_key, session->master_key_length);
-  EVP_DigestUpdate(&ctx, kPad2, npad);
-  EVP_DigestUpdate(&ctx, md_buf, i);
-  EVP_DigestFinal_ex(&ctx, p, &len);
-
-  EVP_MD_CTX_cleanup(&ctx);
+  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 1;
+  return true;
 }
 
-int SSL_TRANSCRIPT_ssl3_cert_verify_hash(SSL_TRANSCRIPT *transcript,
-                                         uint8_t *out, size_t *out_len,
-                                         const SSL_SESSION *session,
-                                         int signature_algorithm) {
-  if (SSL_TRANSCRIPT_md(transcript) != EVP_md5_sha1()) {
+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 0;
+    return false;
   }
 
   if (signature_algorithm == SSL_SIGN_RSA_PKCS1_MD5_SHA1) {
     size_t md5_len, len;
-    if (!ssl3_handshake_mac(transcript, session, &transcript->md5, NULL, 0, out,
-                            &md5_len) ||
-        !ssl3_handshake_mac(transcript, session, &transcript->hash, NULL, 0,
-                            out + md5_len, &len)) {
-      return 0;
+    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 1;
+    return true;
   }
 
   if (signature_algorithm == SSL_SIGN_ECDSA_SHA1) {
-    return ssl3_handshake_mac(transcript, session, &transcript->hash, NULL, 0,
-                              out, out_len);
+    return SSL3HandshakeMAC(session, hash_.get(), NULL, 0, out, out_len);
   }
 
   OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-  return 0;
+  return false;
 }
 
-int SSL_TRANSCRIPT_finish_mac(SSL_TRANSCRIPT *transcript, uint8_t *out,
-                              size_t *out_len, const SSL_SESSION *session,
-                              int from_server, uint16_t version) {
+bool SSLTranscript::GetFinishedMAC(uint8_t *out, size_t *out_len,
+                                   const SSL_SESSION *session, bool from_server,
+                                   uint16_t version) {
   if (version == SSL3_VERSION) {
-    if (SSL_TRANSCRIPT_md(transcript) != EVP_md5_sha1()) {
+    if (Digest() != EVP_md5_sha1()) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return 0;
+      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 (!ssl3_handshake_mac(transcript, session, &transcript->md5, sender,
-                            sender_len, out, &md5_len) ||
-        !ssl3_handshake_mac(transcript, session, &transcript->hash, sender,
-                            sender_len, out + md5_len, &len)) {
-      return 0;
+    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 1;
+    return true;
   }
 
   /* At this point, the handshake should have released the handshake buffer on
    * its own. */
-  assert(transcript->buffer == NULL);
+  assert(!buffer_);
 
   const char *label = TLS_MD_CLIENT_FINISH_CONST;
   size_t label_len = TLS_MD_SERVER_FINISH_CONST_SIZE;
@@ -389,17 +364,19 @@
 
   uint8_t digests[EVP_MAX_MD_SIZE];
   size_t digests_len;
-  if (!SSL_TRANSCRIPT_get_hash(transcript, digests, &digests_len)) {
-    return 0;
+  if (!GetHash(digests, &digests_len)) {
+    return false;
   }
 
   static const size_t kFinishedLen = 12;
-  if (!tls1_prf(SSL_TRANSCRIPT_md(transcript), out, kFinishedLen,
-                session->master_key, session->master_key_length, label,
-                label_len, digests, digests_len, NULL, 0)) {
-    return 0;
+  if (!tls1_prf(Digest(), out, kFinishedLen, session->master_key,
+                session->master_key_length, label, label_len, digests,
+                digests_len, NULL, 0)) {
+    return false;
   }
 
   *out_len = kFinishedLen;
-  return 1;
+  return true;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/ssl_versions.cc b/src/ssl/ssl_versions.cc
index 8b54bd2..0b13781 100644
--- a/src/ssl/ssl_versions.cc
+++ b/src/ssl/ssl_versions.cc
@@ -23,6 +23,8 @@
 #include "../crypto/internal.h"
 
 
+namespace bssl {
+
 int ssl_protocol_version_from_wire(uint16_t *out, uint16_t version) {
   switch (version) {
     case SSL3_VERSION:
@@ -141,22 +143,6 @@
   return set_version_bound(method, out, version);
 }
 
-int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, uint16_t version) {
-  return set_min_version(ctx->method, &ctx->conf_min_version, version);
-}
-
-int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, uint16_t version) {
-  return set_max_version(ctx->method, &ctx->conf_max_version, version);
-}
-
-int SSL_set_min_proto_version(SSL *ssl, uint16_t version) {
-  return set_min_version(ssl->method, &ssl->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);
-}
-
 const struct {
   uint16_t version;
   uint32_t flag;
@@ -238,17 +224,7 @@
   return ssl->version;
 }
 
-int SSL_version(const SSL *ssl) {
-  uint16_t ret = ssl_version(ssl);
-  /* Report TLS 1.3 draft version as TLS 1.3 in the public API. */
-  if (ret == TLS1_3_DRAFT_VERSION || ret == TLS1_3_EXPERIMENT_VERSION ||
-      ret == TLS1_3_RECORD_TYPE_EXPERIMENT_VERSION) {
-    return TLS1_3_VERSION;
-  }
-  return ret;
-}
-
-static const char *ssl_get_version(int version) {
+static const char *ssl_version_to_string(uint16_t version) {
   switch (version) {
     /* Report TLS 1.3 draft version as TLS 1.3 in the public API. */
     case TLS1_3_DRAFT_VERSION:
@@ -279,14 +255,6 @@
   }
 }
 
-const char *SSL_get_version(const SSL *ssl) {
-  return ssl_get_version(ssl_version(ssl));
-}
-
-const char *SSL_SESSION_get_version(const SSL_SESSION *session) {
-  return ssl_get_version(session->ssl_version);
-}
-
 uint16_t ssl3_protocol_version(const SSL *ssl) {
   assert(ssl->s3->have_version);
   uint16_t version;
@@ -312,6 +280,7 @@
     }
   } else {
     if ((ssl->tls13_variant != tls13_experiment &&
+         ssl->tls13_variant != tls13_no_session_id_experiment &&
          version == TLS1_3_EXPERIMENT_VERSION) ||
         (ssl->tls13_variant != tls13_record_type_experiment &&
          version == TLS1_3_RECORD_TYPE_EXPERIMENT_VERSION) ||
@@ -371,3 +340,41 @@
   *out_alert = SSL_AD_PROTOCOL_VERSION;
   return 0;
 }
+
+}  // namespace bssl
+
+using namespace bssl;
+
+int SSL_CTX_set_min_proto_version(SSL_CTX *ctx, uint16_t version) {
+  return set_min_version(ctx->method, &ctx->conf_min_version, version);
+}
+
+int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, uint16_t version) {
+  return set_max_version(ctx->method, &ctx->conf_max_version, version);
+}
+
+int SSL_set_min_proto_version(SSL *ssl, uint16_t version) {
+  return set_min_version(ssl->method, &ssl->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);
+}
+
+int SSL_version(const SSL *ssl) {
+  uint16_t ret = ssl_version(ssl);
+  /* Report TLS 1.3 draft version as TLS 1.3 in the public API. */
+  if (ret == TLS1_3_DRAFT_VERSION || ret == TLS1_3_EXPERIMENT_VERSION ||
+      ret == TLS1_3_RECORD_TYPE_EXPERIMENT_VERSION) {
+    return TLS1_3_VERSION;
+  }
+  return ret;
+}
+
+const char *SSL_get_version(const SSL *ssl) {
+  return ssl_version_to_string(ssl_version(ssl));
+}
+
+const char *SSL_SESSION_get_version(const SSL_SESSION *session) {
+  return ssl_version_to_string(session->ssl_version);
+}
diff --git a/src/ssl/ssl_x509.cc b/src/ssl/ssl_x509.cc
index 77fc0e2..fd64308 100644
--- a/src/ssl/ssl_x509.cc
+++ b/src/ssl/ssl_x509.cc
@@ -155,6 +155,8 @@
 #include "../crypto/internal.h"
 
 
+namespace bssl {
+
 /* check_ssl_x509_method asserts that |ssl| has the X509-based method
  * installed. Calling an X509-based method on an |ssl| with a different method
  * will likely misbehave and possibly crash or leak memory. */
@@ -168,205 +170,6 @@
   assert(ctx == NULL || ctx->x509_method == &ssl_crypto_x509_method);
 }
 
-X509 *SSL_get_peer_certificate(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  if (ssl == NULL) {
-    return NULL;
-  }
-  SSL_SESSION *session = SSL_get_session(ssl);
-  if (session == NULL || session->x509_peer == NULL) {
-    return NULL;
-  }
-  X509_up_ref(session->x509_peer);
-  return session->x509_peer;
-}
-
-STACK_OF(X509) *SSL_get_peer_cert_chain(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  if (ssl == NULL) {
-    return NULL;
-  }
-  SSL_SESSION *session = SSL_get_session(ssl);
-  if (session == NULL ||
-      session->x509_chain == NULL) {
-    return NULL;
-  }
-
-  if (!ssl->server) {
-    return session->x509_chain;
-  }
-
-  /* OpenSSL historically didn't include the leaf certificate in the returned
-   * certificate chain, but only for servers. */
-  if (session->x509_chain_without_leaf == NULL) {
-    session->x509_chain_without_leaf = sk_X509_new_null();
-    if (session->x509_chain_without_leaf == NULL) {
-      return NULL;
-    }
-
-    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)) {
-        sk_X509_pop_free(session->x509_chain_without_leaf, X509_free);
-        session->x509_chain_without_leaf = NULL;
-        return NULL;
-      }
-      X509_up_ref(cert);
-    }
-  }
-
-  return session->x509_chain_without_leaf;
-}
-
-STACK_OF(X509) *SSL_get_peer_full_cert_chain(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  SSL_SESSION *session = SSL_get_session(ssl);
-  if (session == NULL) {
-    return NULL;
-  }
-
-  return session->x509_chain;
-}
-
-int SSL_CTX_set_purpose(SSL_CTX *ctx, int purpose) {
-  check_ssl_ctx_x509_method(ctx);
-  return X509_VERIFY_PARAM_set_purpose(ctx->param, purpose);
-}
-
-int SSL_set_purpose(SSL *ssl, int purpose) {
-  check_ssl_x509_method(ssl);
-  return X509_VERIFY_PARAM_set_purpose(ssl->param, purpose);
-}
-
-int SSL_CTX_set_trust(SSL_CTX *ctx, int trust) {
-  check_ssl_ctx_x509_method(ctx);
-  return X509_VERIFY_PARAM_set_trust(ctx->param, trust);
-}
-
-int SSL_set_trust(SSL *ssl, int trust) {
-  check_ssl_x509_method(ssl);
-  return X509_VERIFY_PARAM_set_trust(ssl->param, trust);
-}
-
-int SSL_CTX_set1_param(SSL_CTX *ctx, const X509_VERIFY_PARAM *param) {
-  check_ssl_ctx_x509_method(ctx);
-  return X509_VERIFY_PARAM_set1(ctx->param, param);
-}
-
-int SSL_set1_param(SSL *ssl, const X509_VERIFY_PARAM *param) {
-  check_ssl_x509_method(ssl);
-  return X509_VERIFY_PARAM_set1(ssl->param, param);
-}
-
-X509_VERIFY_PARAM *SSL_CTX_get0_param(SSL_CTX *ctx) {
-  check_ssl_ctx_x509_method(ctx);
-  return ctx->param;
-}
-
-X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  return ssl->param;
-}
-
-int SSL_get_verify_depth(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  return X509_VERIFY_PARAM_get_depth(ssl->param);
-}
-
-int (*SSL_get_verify_callback(const SSL *ssl))(int, X509_STORE_CTX *) {
-  check_ssl_x509_method(ssl);
-  return ssl->verify_callback;
-}
-
-int SSL_CTX_get_verify_mode(const SSL_CTX *ctx) {
-  check_ssl_ctx_x509_method(ctx);
-  return ctx->verify_mode;
-}
-
-int SSL_CTX_get_verify_depth(const SSL_CTX *ctx) {
-  check_ssl_ctx_x509_method(ctx);
-  return X509_VERIFY_PARAM_get_depth(ctx->param);
-}
-
-int (*SSL_CTX_get_verify_callback(const SSL_CTX *ctx))(
-    int ok, X509_STORE_CTX *store_ctx) {
-  check_ssl_ctx_x509_method(ctx);
-  return ctx->default_verify_callback;
-}
-
-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 (callback != NULL) {
-    ssl->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);
-}
-
-void SSL_CTX_set_cert_verify_callback(SSL_CTX *ctx,
-                                      int (*cb)(X509_STORE_CTX *store_ctx,
-                                                void *arg),
-                                      void *arg) {
-  check_ssl_ctx_x509_method(ctx);
-  ctx->app_verify_callback = cb;
-  ctx->app_verify_arg = arg;
-}
-
-void SSL_CTX_set_verify(SSL_CTX *ctx, int mode,
-                        int (*cb)(int, X509_STORE_CTX *)) {
-  check_ssl_ctx_x509_method(ctx);
-  ctx->verify_mode = mode;
-  ctx->default_verify_callback = cb;
-}
-
-void SSL_CTX_set_verify_depth(SSL_CTX *ctx, int depth) {
-  check_ssl_ctx_x509_method(ctx);
-  X509_VERIFY_PARAM_set_depth(ctx->param, depth);
-}
-
-int SSL_CTX_set_default_verify_paths(SSL_CTX *ctx) {
-  check_ssl_ctx_x509_method(ctx);
-  return X509_STORE_set_default_paths(ctx->cert_store);
-}
-
-int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *ca_file,
-                                  const char *ca_dir) {
-  check_ssl_ctx_x509_method(ctx);
-  return X509_STORE_load_locations(ctx->cert_store, ca_file, ca_dir);
-}
-
-void SSL_set_verify_result(SSL *ssl, long result) {
-  check_ssl_x509_method(ssl);
-  if (result != X509_V_OK) {
-    abort();
-  }
-}
-
-long SSL_get_verify_result(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  SSL_SESSION *session = SSL_get_session(ssl);
-  if (session == NULL) {
-    return X509_V_ERR_INVALID_CALL;
-  }
-  return session->verify_result;
-}
-
-X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *ctx) {
-  check_ssl_ctx_x509_method(ctx);
-  return ctx->cert_store;
-}
-
-void SSL_CTX_set_cert_store(SSL_CTX *ctx, X509_STORE *store) {
-  check_ssl_ctx_x509_method(ctx);
-  X509_STORE_free(ctx->cert_store);
-  ctx->cert_store = store;
-}
-
 /* x509_to_buffer returns a |CRYPTO_BUFFER| that contains the serialised
  * contents of |x509|. */
 static CRYPTO_BUFFER *x509_to_buffer(X509 *x509) {
@@ -620,7 +423,9 @@
 }
 
 static int ssl_crypto_x509_session_verify_cert_chain(SSL_SESSION *session,
-                                                     SSL *ssl) {
+                                                     SSL *ssl,
+                                                     uint8_t *out_alert) {
+  *out_alert = SSL_AD_INTERNAL_ERROR;
   STACK_OF(X509) *const cert_chain = session->x509_chain;
   if (cert_chain == NULL || sk_X509_num(cert_chain) == 0) {
     return 0;
@@ -666,8 +471,7 @@
 
   /* If |SSL_VERIFY_NONE|, the error is non-fatal, but we keep the result. */
   if (verify_ret <= 0 && ssl->verify_mode != SSL_VERIFY_NONE) {
-    ssl3_send_alert(ssl, SSL3_AL_FATAL, ssl_verify_alarm_type(ctx.error));
-    OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_VERIFY_FAILED);
+    *out_alert = ssl_verify_alarm_type(ctx.error);
     goto err;
   }
 
@@ -784,6 +588,209 @@
   ssl_crypto_x509_ssl_ctx_flush_cached_client_CA,
 };
 
+}  // namespace bssl
+
+using namespace bssl;
+
+X509 *SSL_get_peer_certificate(const SSL *ssl) {
+  check_ssl_x509_method(ssl);
+  if (ssl == NULL) {
+    return NULL;
+  }
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL || session->x509_peer == NULL) {
+    return NULL;
+  }
+  X509_up_ref(session->x509_peer);
+  return session->x509_peer;
+}
+
+STACK_OF(X509) *SSL_get_peer_cert_chain(const SSL *ssl) {
+  check_ssl_x509_method(ssl);
+  if (ssl == NULL) {
+    return NULL;
+  }
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL ||
+      session->x509_chain == NULL) {
+    return NULL;
+  }
+
+  if (!ssl->server) {
+    return session->x509_chain;
+  }
+
+  /* OpenSSL historically didn't include the leaf certificate in the returned
+   * certificate chain, but only for servers. */
+  if (session->x509_chain_without_leaf == NULL) {
+    session->x509_chain_without_leaf = sk_X509_new_null();
+    if (session->x509_chain_without_leaf == NULL) {
+      return NULL;
+    }
+
+    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)) {
+        sk_X509_pop_free(session->x509_chain_without_leaf, X509_free);
+        session->x509_chain_without_leaf = NULL;
+        return NULL;
+      }
+      X509_up_ref(cert);
+    }
+  }
+
+  return session->x509_chain_without_leaf;
+}
+
+STACK_OF(X509) *SSL_get_peer_full_cert_chain(const SSL *ssl) {
+  check_ssl_x509_method(ssl);
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL) {
+    return NULL;
+  }
+
+  return session->x509_chain;
+}
+
+int SSL_CTX_set_purpose(SSL_CTX *ctx, int purpose) {
+  check_ssl_ctx_x509_method(ctx);
+  return X509_VERIFY_PARAM_set_purpose(ctx->param, purpose);
+}
+
+int SSL_set_purpose(SSL *ssl, int purpose) {
+  check_ssl_x509_method(ssl);
+  return X509_VERIFY_PARAM_set_purpose(ssl->param, purpose);
+}
+
+int SSL_CTX_set_trust(SSL_CTX *ctx, int trust) {
+  check_ssl_ctx_x509_method(ctx);
+  return X509_VERIFY_PARAM_set_trust(ctx->param, trust);
+}
+
+int SSL_set_trust(SSL *ssl, int trust) {
+  check_ssl_x509_method(ssl);
+  return X509_VERIFY_PARAM_set_trust(ssl->param, trust);
+}
+
+int SSL_CTX_set1_param(SSL_CTX *ctx, const X509_VERIFY_PARAM *param) {
+  check_ssl_ctx_x509_method(ctx);
+  return X509_VERIFY_PARAM_set1(ctx->param, param);
+}
+
+int SSL_set1_param(SSL *ssl, const X509_VERIFY_PARAM *param) {
+  check_ssl_x509_method(ssl);
+  return X509_VERIFY_PARAM_set1(ssl->param, param);
+}
+
+X509_VERIFY_PARAM *SSL_CTX_get0_param(SSL_CTX *ctx) {
+  check_ssl_ctx_x509_method(ctx);
+  return ctx->param;
+}
+
+X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl) {
+  check_ssl_x509_method(ssl);
+  return ssl->param;
+}
+
+int SSL_get_verify_depth(const SSL *ssl) {
+  check_ssl_x509_method(ssl);
+  return X509_VERIFY_PARAM_get_depth(ssl->param);
+}
+
+int (*SSL_get_verify_callback(const SSL *ssl))(int, X509_STORE_CTX *) {
+  check_ssl_x509_method(ssl);
+  return ssl->verify_callback;
+}
+
+int SSL_CTX_get_verify_mode(const SSL_CTX *ctx) {
+  check_ssl_ctx_x509_method(ctx);
+  return ctx->verify_mode;
+}
+
+int SSL_CTX_get_verify_depth(const SSL_CTX *ctx) {
+  check_ssl_ctx_x509_method(ctx);
+  return X509_VERIFY_PARAM_get_depth(ctx->param);
+}
+
+int (*SSL_CTX_get_verify_callback(const SSL_CTX *ctx))(
+    int ok, X509_STORE_CTX *store_ctx) {
+  check_ssl_ctx_x509_method(ctx);
+  return ctx->default_verify_callback;
+}
+
+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 (callback != NULL) {
+    ssl->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);
+}
+
+void SSL_CTX_set_cert_verify_callback(SSL_CTX *ctx,
+                                      int (*cb)(X509_STORE_CTX *store_ctx,
+                                                void *arg),
+                                      void *arg) {
+  check_ssl_ctx_x509_method(ctx);
+  ctx->app_verify_callback = cb;
+  ctx->app_verify_arg = arg;
+}
+
+void SSL_CTX_set_verify(SSL_CTX *ctx, int mode,
+                        int (*cb)(int, X509_STORE_CTX *)) {
+  check_ssl_ctx_x509_method(ctx);
+  ctx->verify_mode = mode;
+  ctx->default_verify_callback = cb;
+}
+
+void SSL_CTX_set_verify_depth(SSL_CTX *ctx, int depth) {
+  check_ssl_ctx_x509_method(ctx);
+  X509_VERIFY_PARAM_set_depth(ctx->param, depth);
+}
+
+int SSL_CTX_set_default_verify_paths(SSL_CTX *ctx) {
+  check_ssl_ctx_x509_method(ctx);
+  return X509_STORE_set_default_paths(ctx->cert_store);
+}
+
+int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *ca_file,
+                                  const char *ca_dir) {
+  check_ssl_ctx_x509_method(ctx);
+  return X509_STORE_load_locations(ctx->cert_store, ca_file, ca_dir);
+}
+
+void SSL_set_verify_result(SSL *ssl, long result) {
+  check_ssl_x509_method(ssl);
+  if (result != X509_V_OK) {
+    abort();
+  }
+}
+
+long SSL_get_verify_result(const SSL *ssl) {
+  check_ssl_x509_method(ssl);
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL) {
+    return X509_V_ERR_INVALID_CALL;
+  }
+  return session->verify_result;
+}
+
+X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *ctx) {
+  check_ssl_ctx_x509_method(ctx);
+  return ctx->cert_store;
+}
+
+void SSL_CTX_set_cert_store(SSL_CTX *ctx, X509_STORE *store) {
+  check_ssl_ctx_x509_method(ctx);
+  X509_STORE_free(ctx->cert_store);
+  ctx->cert_store = store;
+}
+
 static int ssl_use_certificate(CERT *cert, X509 *x) {
   if (x == NULL) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER);
@@ -1045,7 +1052,7 @@
 }
 
 static SSL_SESSION *ssl_session_new_with_crypto_x509(void) {
-  return ssl_session_new(&ssl_crypto_x509_method);
+  return ssl_session_new(&ssl_crypto_x509_method).release();
 }
 
 SSL_SESSION *d2i_SSL_SESSION_bio(BIO *bio, SSL_SESSION **out) {
@@ -1068,18 +1075,18 @@
   CBS cbs;
   CBS_init(&cbs, *pp, length);
 
-  SSL_SESSION *ret = SSL_SESSION_parse(&cbs, &ssl_crypto_x509_method,
-                                       NULL /* no buffer pool */);
-  if (ret == NULL) {
+  UniquePtr<SSL_SESSION> ret = SSL_SESSION_parse(&cbs, &ssl_crypto_x509_method,
+                                                 NULL /* no buffer pool */);
+  if (!ret) {
     return NULL;
   }
 
   if (a) {
     SSL_SESSION_free(*a);
-    *a = ret;
+    *a = ret.get();
   }
   *pp = CBS_data(&cbs);
-  return ret;
+  return ret.release();
 }
 
 STACK_OF(X509_NAME) *SSL_dup_CA_list(STACK_OF(X509_NAME) *list) {
@@ -1179,7 +1186,7 @@
    * indeterminate mode and |ssl->server| is unset. */
   if (ssl->handshake_func != NULL && !ssl->server) {
     if (ssl->s3->hs != NULL) {
-      return buffer_names_to_x509(ssl->s3->hs->ca_names,
+      return buffer_names_to_x509(ssl->s3->hs->ca_names.get(),
                                   &ssl->s3->hs->cached_x509_ca_names);
     }
 
diff --git a/src/ssl/t1_enc.cc b/src/ssl/t1_enc.cc
index c224240..2349df0 100644
--- a/src/ssl/t1_enc.cc
+++ b/src/ssl/t1_enc.cc
@@ -138,6 +138,8 @@
 #include <assert.h>
 #include <string.h>
 
+#include <utility>
+
 #include <openssl/err.h>
 #include <openssl/evp.h>
 #include <openssl/hmac.h>
@@ -150,6 +152,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 /* tls1_P_hash computes the TLS P_<hash> function as described in RFC 5246,
  * section 5. It XORs |out_len| bytes to |out|, using |md| as the hash and
  * |secret| as the secret. |seed1| through |seed3| are concatenated to form the
@@ -159,36 +163,33 @@
                        const uint8_t *seed1, size_t seed1_len,
                        const uint8_t *seed2, size_t seed2_len,
                        const uint8_t *seed3, size_t seed3_len) {
-  HMAC_CTX ctx, ctx_tmp, ctx_init;
+  ScopedHMAC_CTX ctx, ctx_tmp, ctx_init;
   uint8_t A1[EVP_MAX_MD_SIZE];
   unsigned A1_len;
   int ret = 0;
 
   size_t chunk = EVP_MD_size(md);
 
-  HMAC_CTX_init(&ctx);
-  HMAC_CTX_init(&ctx_tmp);
-  HMAC_CTX_init(&ctx_init);
-  if (!HMAC_Init_ex(&ctx_init, secret, secret_len, md, NULL) ||
-      !HMAC_CTX_copy_ex(&ctx, &ctx_init) ||
-      !HMAC_Update(&ctx, seed1, seed1_len) ||
-      !HMAC_Update(&ctx, seed2, seed2_len) ||
-      !HMAC_Update(&ctx, seed3, seed3_len) ||
-      !HMAC_Final(&ctx, A1, &A1_len)) {
+  if (!HMAC_Init_ex(ctx_init.get(), secret, secret_len, md, NULL) ||
+      !HMAC_CTX_copy_ex(ctx.get(), ctx_init.get()) ||
+      !HMAC_Update(ctx.get(), seed1, seed1_len) ||
+      !HMAC_Update(ctx.get(), seed2, seed2_len) ||
+      !HMAC_Update(ctx.get(), seed3, seed3_len) ||
+      !HMAC_Final(ctx.get(), A1, &A1_len)) {
     goto err;
   }
 
   for (;;) {
     unsigned len;
     uint8_t hmac[EVP_MAX_MD_SIZE];
-    if (!HMAC_CTX_copy_ex(&ctx, &ctx_init) ||
-        !HMAC_Update(&ctx, A1, A1_len) ||
+    if (!HMAC_CTX_copy_ex(ctx.get(), ctx_init.get()) ||
+        !HMAC_Update(ctx.get(), A1, A1_len) ||
         /* Save a copy of |ctx| to compute the next A1 value below. */
-        (out_len > chunk && !HMAC_CTX_copy_ex(&ctx_tmp, &ctx)) ||
-        !HMAC_Update(&ctx, seed1, seed1_len) ||
-        !HMAC_Update(&ctx, seed2, seed2_len) ||
-        !HMAC_Update(&ctx, seed3, seed3_len) ||
-        !HMAC_Final(&ctx, hmac, &len)) {
+        (out_len > chunk && !HMAC_CTX_copy_ex(ctx_tmp.get(), ctx.get())) ||
+        !HMAC_Update(ctx.get(), seed1, seed1_len) ||
+        !HMAC_Update(ctx.get(), seed2, seed2_len) ||
+        !HMAC_Update(ctx.get(), seed3, seed3_len) ||
+        !HMAC_Final(ctx.get(), hmac, &len)) {
       goto err;
     }
     assert(len == chunk);
@@ -209,7 +210,7 @@
     }
 
     /* Calculate the next A1 value. */
-    if (!HMAC_Final(&ctx_tmp, A1, &A1_len)) {
+    if (!HMAC_Final(ctx_tmp.get(), A1, &A1_len)) {
       goto err;
     }
   }
@@ -217,9 +218,6 @@
   ret = 1;
 
 err:
-  HMAC_CTX_cleanup(&ctx);
-  HMAC_CTX_cleanup(&ctx_tmp);
-  HMAC_CTX_cleanup(&ctx_init);
   OPENSSL_cleanse(A1, sizeof(A1));
   return ret;
 }
@@ -264,15 +262,13 @@
                     size_t secret_len, const char *label, size_t label_len,
                     const uint8_t *seed1, size_t seed1_len,
                     const uint8_t *seed2, size_t seed2_len) {
-  EVP_MD_CTX md5;
-  EVP_MD_CTX sha1;
+  ScopedEVP_MD_CTX md5;
+  ScopedEVP_MD_CTX sha1;
   uint8_t buf[16], smd[SHA_DIGEST_LENGTH];
   uint8_t c = 'A';
   size_t i, j, k;
 
   k = 0;
-  EVP_MD_CTX_init(&md5);
-  EVP_MD_CTX_init(&sha1);
   for (i = 0; i < out_len; i += MD5_DIGEST_LENGTH) {
     k++;
     if (k > sizeof(buf)) {
@@ -285,41 +281,38 @@
       buf[j] = c;
     }
     c++;
-    if (!EVP_DigestInit_ex(&sha1, EVP_sha1(), NULL)) {
+    if (!EVP_DigestInit_ex(sha1.get(), EVP_sha1(), NULL)) {
       OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
       return 0;
     }
-    EVP_DigestUpdate(&sha1, buf, k);
-    EVP_DigestUpdate(&sha1, secret, secret_len);
+    EVP_DigestUpdate(sha1.get(), buf, k);
+    EVP_DigestUpdate(sha1.get(), secret, secret_len);
     /* |label| is ignored for SSLv3. */
     if (seed1_len) {
-      EVP_DigestUpdate(&sha1, seed1, seed1_len);
+      EVP_DigestUpdate(sha1.get(), seed1, seed1_len);
     }
     if (seed2_len) {
-      EVP_DigestUpdate(&sha1, seed2, seed2_len);
+      EVP_DigestUpdate(sha1.get(), seed2, seed2_len);
     }
-    EVP_DigestFinal_ex(&sha1, smd, NULL);
+    EVP_DigestFinal_ex(sha1.get(), smd, NULL);
 
-    if (!EVP_DigestInit_ex(&md5, EVP_md5(), NULL)) {
+    if (!EVP_DigestInit_ex(md5.get(), EVP_md5(), NULL)) {
       OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
       return 0;
     }
-    EVP_DigestUpdate(&md5, secret, secret_len);
-    EVP_DigestUpdate(&md5, smd, SHA_DIGEST_LENGTH);
+    EVP_DigestUpdate(md5.get(), secret, secret_len);
+    EVP_DigestUpdate(md5.get(), smd, SHA_DIGEST_LENGTH);
     if (i + MD5_DIGEST_LENGTH > out_len) {
-      EVP_DigestFinal_ex(&md5, smd, NULL);
+      EVP_DigestFinal_ex(md5.get(), smd, NULL);
       OPENSSL_memcpy(out, smd, out_len - i);
     } else {
-      EVP_DigestFinal_ex(&md5, out, NULL);
+      EVP_DigestFinal_ex(md5.get(), out, NULL);
     }
 
     out += MD5_DIGEST_LENGTH;
   }
 
   OPENSSL_cleanse(smd, SHA_DIGEST_LENGTH);
-  EVP_MD_CTX_cleanup(&md5);
-  EVP_MD_CTX_cleanup(&sha1);
-
   return 1;
 }
 
@@ -330,8 +323,8 @@
   }
 
   SSL_SESSION *session = ssl->session;
-  if (hs->new_session != NULL) {
-    session = hs->new_session;
+  if (hs->new_session) {
+    session = hs->new_session.get();
   }
 
   const EVP_AEAD *aead = NULL;
@@ -428,20 +421,61 @@
     iv = server_write_iv;
   }
 
-  SSL_AEAD_CTX *aead_ctx = SSL_AEAD_CTX_new(
-      is_read ? evp_aead_open : evp_aead_seal, ssl3_protocol_version(ssl), SSL_is_dtls(ssl),
-      hs->new_cipher, key, key_len, mac_secret, mac_secret_len, iv, iv_len);
-  if (aead_ctx == NULL) {
+  UniquePtr<SSLAEADContext> aead_ctx = SSLAEADContext::Create(
+      is_read ? evp_aead_open : evp_aead_seal, ssl3_protocol_version(ssl),
+      SSL_is_dtls(ssl), hs->new_cipher, key, key_len, mac_secret,
+      mac_secret_len, iv, iv_len);
+  if (!aead_ctx) {
     return 0;
   }
 
   if (is_read) {
-    return ssl->method->set_read_state(ssl, aead_ctx);
+    return ssl->method->set_read_state(ssl, std::move(aead_ctx));
   }
 
-  return ssl->method->set_write_state(ssl, aead_ctx);
+  return ssl->method->set_write_state(ssl, std::move(aead_ctx));
 }
 
+int tls1_generate_master_secret(SSL_HANDSHAKE *hs, uint8_t *out,
+                                const uint8_t *premaster,
+                                size_t premaster_len) {
+  const SSL *ssl = hs->ssl;
+  if (hs->extended_master_secret) {
+    uint8_t digests[EVP_MAX_MD_SIZE];
+    size_t digests_len;
+    if (!hs->transcript.GetHash(digests, &digests_len) ||
+        !tls1_prf(hs->transcript.Digest(), out, SSL3_MASTER_SECRET_SIZE,
+                  premaster, premaster_len, TLS_MD_EXTENDED_MASTER_SECRET_CONST,
+                  TLS_MD_EXTENDED_MASTER_SECRET_CONST_SIZE, digests,
+                  digests_len, NULL, 0)) {
+      return 0;
+    }
+  } else {
+    if (ssl3_protocol_version(ssl) == SSL3_VERSION) {
+      if (!ssl3_prf(out, SSL3_MASTER_SECRET_SIZE, premaster, premaster_len,
+                    TLS_MD_MASTER_SECRET_CONST, TLS_MD_MASTER_SECRET_CONST_SIZE,
+                    ssl->s3->client_random, SSL3_RANDOM_SIZE,
+                    ssl->s3->server_random, SSL3_RANDOM_SIZE)) {
+        return 0;
+      }
+    } else {
+      if (!tls1_prf(hs->transcript.Digest(), out, SSL3_MASTER_SECRET_SIZE,
+                    premaster, premaster_len, TLS_MD_MASTER_SECRET_CONST,
+                    TLS_MD_MASTER_SECRET_CONST_SIZE, ssl->s3->client_random,
+                    SSL3_RANDOM_SIZE, ssl->s3->server_random,
+                    SSL3_RANDOM_SIZE)) {
+        return 0;
+      }
+    }
+  }
+
+  return SSL3_MASTER_SECRET_SIZE;
+}
+
+}  // namespace bssl
+
+using namespace bssl;
+
 size_t SSL_get_key_block_len(const SSL *ssl) {
   return 2 * ((size_t)ssl->s3->tmp.new_mac_secret_len +
               (size_t)ssl->s3->tmp.new_key_len +
@@ -470,43 +504,6 @@
                   ssl->s3->client_random, SSL3_RANDOM_SIZE);
 }
 
-int tls1_generate_master_secret(SSL_HANDSHAKE *hs, uint8_t *out,
-                                const uint8_t *premaster,
-                                size_t premaster_len) {
-  const SSL *ssl = hs->ssl;
-  if (hs->extended_master_secret) {
-    uint8_t digests[EVP_MAX_MD_SIZE];
-    size_t digests_len;
-    if (!SSL_TRANSCRIPT_get_hash(&hs->transcript, digests, &digests_len) ||
-        !tls1_prf(SSL_TRANSCRIPT_md(&hs->transcript), out,
-                  SSL3_MASTER_SECRET_SIZE, premaster, premaster_len,
-                  TLS_MD_EXTENDED_MASTER_SECRET_CONST,
-                  TLS_MD_EXTENDED_MASTER_SECRET_CONST_SIZE, digests,
-                  digests_len, NULL, 0)) {
-      return 0;
-    }
-  } else {
-    if (ssl3_protocol_version(ssl) == SSL3_VERSION) {
-      if (!ssl3_prf(out, SSL3_MASTER_SECRET_SIZE, premaster, premaster_len,
-                    TLS_MD_MASTER_SECRET_CONST, TLS_MD_MASTER_SECRET_CONST_SIZE,
-                    ssl->s3->client_random, SSL3_RANDOM_SIZE,
-                    ssl->s3->server_random, SSL3_RANDOM_SIZE)) {
-        return 0;
-      }
-    } else {
-      if (!tls1_prf(SSL_TRANSCRIPT_md(&hs->transcript), out,
-                    SSL3_MASTER_SECRET_SIZE, premaster, premaster_len,
-                    TLS_MD_MASTER_SECRET_CONST, TLS_MD_MASTER_SECRET_CONST_SIZE,
-                    ssl->s3->client_random, SSL3_RANDOM_SIZE,
-                    ssl->s3->server_random, SSL3_RANDOM_SIZE)) {
-        return 0;
-      }
-    }
-  }
-
-  return SSL3_MASTER_SECRET_SIZE;
-}
-
 int SSL_export_keying_material(SSL *ssl, uint8_t *out, size_t out_len,
                                const char *label, size_t label_len,
                                const uint8_t *context, size_t context_len,
diff --git a/src/ssl/t1_lib.cc b/src/ssl/t1_lib.cc
index 76469eb..19f256d 100644
--- a/src/ssl/t1_lib.cc
+++ b/src/ssl/t1_lib.cc
@@ -126,6 +126,8 @@
 #include "../crypto/internal.h"
 
 
+namespace bssl {
+
 static int ssl_check_clienthello_tlsext(SSL_HANDSHAKE *hs);
 
 static int compare_uint16_t(const void *p1, const void *p2) {
@@ -289,20 +291,6 @@
   return 0;
 }
 
-int SSL_early_callback_ctx_extension_get(const SSL_CLIENT_HELLO *client_hello,
-                                         uint16_t extension_type,
-                                         const uint8_t **out_data,
-                                         size_t *out_len) {
-  CBS cbs;
-  if (!ssl_client_hello_get_extension(client_hello, &cbs, extension_type)) {
-    return 0;
-  }
-
-  *out_data = CBS_data(&cbs);
-  *out_len = CBS_len(&cbs);
-  return 1;
-}
-
 static const uint16_t kDefaultGroups[] = {
     SSL_CURVE_X25519,
     SSL_CURVE_SECP256R1,
@@ -508,10 +496,6 @@
     SSL_SIGN_RSA_PKCS1_SHA1,
 };
 
-void SSL_CTX_set_ed25519_enabled(SSL_CTX *ctx, int enabled) {
-  ctx->ed25519_enabled = !!enabled;
-}
-
 int tls12_add_verify_sigalgs(const SSL *ssl, CBB *out) {
   const uint16_t *sigalgs = kVerifySignatureAlgorithms;
   size_t num_sigalgs = OPENSSL_ARRAY_SIZE(kVerifySignatureAlgorithms);
@@ -690,10 +674,12 @@
   }
 
   /* Copy the hostname as a string. */
-  if (!CBS_strdup(&host_name, &hs->hostname)) {
+  char *hostname_raw = nullptr;
+  if (!CBS_strdup(&host_name, &hostname_raw)) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     return 0;
   }
+  hs->hostname.reset(hostname_raw);
 
   hs->should_ack_sni = 1;
   return 1;
@@ -2149,7 +2135,7 @@
   uint16_t group_id = hs->retry_group;
   if (hs->received_hello_retry_request) {
     /* We received a HelloRetryRequest without a new curve, so there is no new
-     * share to append. Leave |ecdh_ctx| as-is. */
+     * share to append. Leave |hs->key_share| as-is. */
     if (group_id == 0 &&
         !CBB_add_bytes(&kse_bytes, hs->key_share_bytes,
                        hs->key_share_bytes_len)) {
@@ -2183,11 +2169,12 @@
     group_id = groups[0];
   }
 
+  hs->key_share = SSLKeyShare::Create(group_id);
   CBB key_exchange;
-  if (!CBB_add_u16(&kse_bytes, group_id) ||
+  if (!hs->key_share ||
+      !CBB_add_u16(&kse_bytes, group_id) ||
       !CBB_add_u16_length_prefixed(&kse_bytes, &key_exchange) ||
-      !SSL_ECDH_CTX_init(&hs->ecdh_ctx, group_id) ||
-      !SSL_ECDH_CTX_offer(&hs->ecdh_ctx, &key_exchange) ||
+      !hs->key_share->Offer(&key_exchange) ||
       !CBB_flush(&kse_bytes)) {
     return 0;
   }
@@ -2218,20 +2205,20 @@
     return 0;
   }
 
-  if (SSL_ECDH_CTX_get_id(&hs->ecdh_ctx) != group_id) {
+  if (hs->key_share->GroupID() != group_id) {
     *out_alert = SSL_AD_ILLEGAL_PARAMETER;
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
     return 0;
   }
 
-  if (!SSL_ECDH_CTX_finish(&hs->ecdh_ctx, out_secret, out_secret_len, out_alert,
-                           CBS_data(&peer_key), CBS_len(&peer_key))) {
+  if (!hs->key_share->Finish(out_secret, out_secret_len, out_alert,
+                             CBS_data(&peer_key), CBS_len(&peer_key))) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     return 0;
   }
 
   hs->new_session->group_id = group_id;
-  SSL_ECDH_CTX_cleanup(&hs->ecdh_ctx);
+  hs->key_share.reset();
   return 1;
 }
 
@@ -2288,24 +2275,19 @@
   /* Compute the DH secret. */
   uint8_t *secret = NULL;
   size_t secret_len;
-  SSL_ECDH_CTX group;
-  OPENSSL_memset(&group, 0, sizeof(SSL_ECDH_CTX));
-  CBB public_key;
-  if (!CBB_init(&public_key, 32) ||
-      !SSL_ECDH_CTX_init(&group, group_id) ||
-      !SSL_ECDH_CTX_accept(&group, &public_key, &secret, &secret_len, out_alert,
-                           CBS_data(&peer_key), CBS_len(&peer_key)) ||
-      !CBB_finish(&public_key, &hs->ecdh_public_key,
+  ScopedCBB public_key;
+  UniquePtr<SSLKeyShare> key_share = SSLKeyShare::Create(group_id);
+  if (!key_share ||
+      !CBB_init(public_key.get(), 32) ||
+      !key_share->Accept(public_key.get(), &secret, &secret_len, out_alert,
+                         CBS_data(&peer_key), CBS_len(&peer_key)) ||
+      !CBB_finish(public_key.get(), &hs->ecdh_public_key,
                   &hs->ecdh_public_key_len)) {
     OPENSSL_free(secret);
-    SSL_ECDH_CTX_cleanup(&group);
-    CBB_cleanup(&public_key);
     *out_alert = SSL_AD_ILLEGAL_PARAMETER;
     return 0;
   }
 
-  SSL_ECDH_CTX_cleanup(&group);
-
   *out_secret = secret;
   *out_secret_len = secret_len;
   *out_found = 1;
@@ -2658,12 +2640,6 @@
   return NULL;
 }
 
-int SSL_extension_supported(unsigned extension_value) {
-  uint32_t index;
-  return extension_value == TLSEXT_TYPE_padding ||
-         tls_extension_find(&index, extension_value) != NULL;
-}
-
 int 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. */
@@ -3044,8 +3020,8 @@
                                    size_t ticket_len) {
   const SSL_CTX *const ssl_ctx = ssl->session_ctx;
 
-  bssl::ScopedHMAC_CTX hmac_ctx;
-  bssl::ScopedEVP_CIPHER_CTX cipher_ctx;
+  ScopedHMAC_CTX hmac_ctx;
+  ScopedEVP_CIPHER_CTX cipher_ctx;
 
   /* 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
@@ -3105,7 +3081,7 @@
   const uint8_t *ciphertext = ticket + SSL_TICKET_KEY_NAME_LEN + iv_len;
   size_t ciphertext_len = ticket_len - SSL_TICKET_KEY_NAME_LEN - iv_len -
                           mac_len;
-  bssl::UniquePtr<uint8_t> plaintext((uint8_t *)OPENSSL_malloc(ciphertext_len));
+  UniquePtr<uint8_t> plaintext((uint8_t *)OPENSSL_malloc(ciphertext_len));
   if (!plaintext) {
     return ssl_ticket_aead_error;
   }
@@ -3263,7 +3239,7 @@
   /* Before TLS 1.2, the signature algorithm isn't negotiated as part of the
    * handshake. */
   if (ssl3_protocol_version(ssl) < TLS1_2_VERSION) {
-    if (!tls1_get_legacy_signature_algorithm(out, hs->local_pubkey)) {
+    if (!tls1_get_legacy_signature_algorithm(out, hs->local_pubkey.get())) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS);
       return 0;
     }
@@ -3328,15 +3304,14 @@
     return 0;
   }
 
-  bssl::UniquePtr<EC_GROUP> p256(
-      EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+  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;
   }
 
-  bssl::UniquePtr<ECDSA_SIG> sig(ECDSA_SIG_new());
-  bssl::UniquePtr<BIGNUM> x(BN_new()), y(BN_new());
+  UniquePtr<ECDSA_SIG> sig(ECDSA_SIG_new());
+  UniquePtr<BIGNUM> x(BN_new()), y(BN_new());
   if (!sig || !x || !y) {
     return 0;
   }
@@ -3349,8 +3324,8 @@
     return 0;
   }
 
-  bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
-  bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get()));
+  UniquePtr<EC_KEY> key(EC_KEY_new());
+  UniquePtr<EC_POINT> point(EC_POINT_new(p256.get()));
   if (!key || !point ||
       !EC_POINT_set_affine_coordinates_GFp(p256.get(), point.get(), x.get(),
                                            y.get(), nullptr) ||
@@ -3464,7 +3439,7 @@
 
   uint8_t hs_hash[EVP_MAX_MD_SIZE];
   size_t hs_hash_len;
-  if (!SSL_TRANSCRIPT_get_hash(&hs->transcript, hs_hash, &hs_hash_len)) {
+  if (!hs->transcript.GetHash(hs_hash, &hs_hash_len)) {
     return 0;
   }
   SHA256_Update(&ctx, hs_hash, (size_t)hs_hash_len);
@@ -3490,9 +3465,8 @@
       "original_handshake_hash is too small");
 
   size_t digest_len;
-  if (!SSL_TRANSCRIPT_get_hash(&hs->transcript,
-                               hs->new_session->original_handshake_hash,
-                               &digest_len)) {
+  if (!hs->transcript.GetHash(hs->new_session->original_handshake_hash,
+                              &digest_len)) {
     return -1;
   }
 
@@ -3543,3 +3517,31 @@
 
   return 1;
 }
+
+}  // namespace bssl
+
+using namespace bssl;
+
+int SSL_early_callback_ctx_extension_get(const SSL_CLIENT_HELLO *client_hello,
+                                         uint16_t extension_type,
+                                         const uint8_t **out_data,
+                                         size_t *out_len) {
+  CBS cbs;
+  if (!ssl_client_hello_get_extension(client_hello, &cbs, extension_type)) {
+    return 0;
+  }
+
+  *out_data = CBS_data(&cbs);
+  *out_len = CBS_len(&cbs);
+  return 1;
+}
+
+void SSL_CTX_set_ed25519_enabled(SSL_CTX *ctx, int enabled) {
+  ctx->ed25519_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/bssl_shim.cc b/src/ssl/test/bssl_shim.cc
index cd846be..a056be0 100644
--- a/src/ssl/test/bssl_shim.cc
+++ b/src/ssl/test/bssl_shim.cc
@@ -112,6 +112,7 @@
   bool alpn_select_done = false;
   bool is_resume = false;
   bool early_callback_ready = false;
+  bool custom_verify_ready = false;
 };
 
 static void TestStateExFree(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
@@ -684,27 +685,52 @@
   return 1;
 }
 
-static int VerifySucceed(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());
+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 0;
+      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;
+  }
+
+  if (config->verify_fail) {
+    store_ctx->error = X509_V_ERR_APPLICATION_VERIFICATION;
+    return 0;
+  }
+
   return 1;
 }
 
-static int VerifyFail(X509_STORE_CTX *store_ctx, void *arg) {
-  store_ctx->error = X509_V_ERR_APPLICATION_VERIFICATION;
-  return 0;
+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;
+  }
+
+  if (config->verify_fail) {
+    return ssl_verify_invalid;
+  }
+
+  return ssl_verify_ok;
 }
 
 static int NextProtosAdvertisedCallback(SSL *ssl, const uint8_t **out,
@@ -1051,12 +1077,12 @@
 };
 
 static void ssl_ctx_add_session(SSL_SESSION *session, void *void_param) {
-  SSL_SESSION *new_session = SSL_SESSION_dup(
+  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((SSL_CTX *)void_param, new_session);
+    SSL_CTX_add_session(ctx, new_session.get());
   }
-  SSL_SESSION_free(new_session);
 }
 
 static bssl::UniquePtr<SSL_CTX> SetupCtx(SSL_CTX *old_ctx,
@@ -1139,10 +1165,8 @@
     return nullptr;
   }
 
-  if (config->verify_fail) {
-    SSL_CTX_set_cert_verify_callback(ssl_ctx.get(), VerifyFail, NULL);
-  } else {
-    SSL_CTX_set_cert_verify_callback(ssl_ctx.get(), VerifySucceed, NULL);
+  if (!config->use_custom_verify_callback) {
+    SSL_CTX_set_cert_verify_callback(ssl_ctx.get(), CertVerifyCallback, NULL);
   }
 
   if (!config->signed_cert_timestamps.empty() &&
@@ -1270,6 +1294,9 @@
     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;
   }
@@ -1763,20 +1790,23 @@
   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) {
-    SSL_set_verify(ssl.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
-                   NULL);
+    mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
   }
   if (config->verify_peer) {
-    SSL_set_verify(ssl.get(), SSL_VERIFY_PEER, NULL);
+    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.
-    SSL_set_verify(ssl.get(),
-                   SSL_VERIFY_PEER | SSL_VERIFY_PEER_IF_NO_OBC |
-                       SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
-                   NULL);
+    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);
diff --git a/src/ssl/test/runner/common.go b/src/ssl/test/runner/common.go
index fd9fb3d..a6a521b 100644
--- a/src/ssl/test/runner/common.go
+++ b/src/ssl/test/runner/common.go
@@ -39,9 +39,10 @@
 )
 
 const (
-	TLS13Default              = 0
-	TLS13Experiment           = 1
-	TLS13RecordTypeExperiment = 2
+	TLS13Default               = 0
+	TLS13Experiment            = 1
+	TLS13RecordTypeExperiment  = 2
+	TLS13NoSessionIDExperiment = 3
 )
 
 var allTLSWireVersions = []uint16{
@@ -717,6 +718,18 @@
 	// normally expected to look ahead for ChangeCipherSpec.)
 	EmptyTicketSessionID bool
 
+	// SendClientHelloSessionID, if not nil, is the session ID sent in the
+	// ClientHello.
+	SendClientHelloSessionID []byte
+
+	// ExpectClientHelloSessionID, if true, causes the server to fail the
+	// connection if there is not a SessionID in the ClientHello.
+	ExpectClientHelloSessionID bool
+
+	// ExpectEmptyClientHelloSessionID, if true, causes the server to fail the
+	// connection if there is a SessionID in the ClientHello.
+	ExpectEmptyClientHelloSessionID bool
+
 	// ExpectNoTLS12Session, if true, causes the server to fail the
 	// connection if either a session ID or TLS 1.2 ticket is offered.
 	ExpectNoTLS12Session bool
@@ -1389,9 +1402,13 @@
 	// and ServerHello messages to be omitted.
 	OmitExtensions bool
 
-	// EmptyExtensions, if true, causese the extensions field in ClientHello
+	// EmptyExtensions, if true, causes the extensions field in ClientHello
 	// and ServerHello messages to be present, but empty.
 	EmptyExtensions bool
+
+	// ExpectRecordSplitting, if true, causes application records to only be
+	// accepted if they follow a 1/n-1 record split.
+	ExpectRecordSplitting bool
 }
 
 func (c *Config) serverInit() {
@@ -1496,7 +1513,7 @@
 // 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 != TLS13Experiment && wireVers == tls13ExperimentVersion) ||
+	if (c.TLS13Variant != TLS13Experiment && c.TLS13Variant != TLS13NoSessionIDExperiment && wireVers == tls13ExperimentVersion) ||
 		(c.TLS13Variant != TLS13RecordTypeExperiment && wireVers == tls13RecordTypeExperimentVersion) ||
 		(c.TLS13Variant != TLS13Default && wireVers == tls13DraftVersion) {
 		return 0, false
diff --git a/src/ssl/test/runner/conn.go b/src/ssl/test/runner/conn.go
index c974bd4..047c3c5 100644
--- a/src/ssl/test/runner/conn.go
+++ b/src/ssl/test/runner/conn.go
@@ -98,6 +98,7 @@
 	pendingFragments [][]byte // pending outgoing handshake fragments.
 
 	keyUpdateRequested bool
+	seenOneByteRecord  bool
 
 	tmp [16]byte
 }
@@ -844,6 +845,13 @@
 		}
 		typ = encTyp
 	}
+
+	length := len(b.data[b.off:])
+	if c.config.Bugs.ExpectRecordSplitting && typ == recordTypeApplicationData && length != 1 && !c.seenOneByteRecord {
+		return 0, nil, c.in.setErrorLocked(fmt.Errorf("tls: application data records were not split"))
+	}
+
+	c.seenOneByteRecord = typ == recordTypeApplicationData && length == 1
 	return typ, b, nil
 }
 
diff --git a/src/ssl/test/runner/fuzzer_mode.json b/src/ssl/test/runner/fuzzer_mode.json
index 3957bea..834be40 100644
--- a/src/ssl/test/runner/fuzzer_mode.json
+++ b/src/ssl/test/runner/fuzzer_mode.json
@@ -48,6 +48,8 @@
     "*-EarlyData-Reject-Client": "Trial decryption does not work with the NULL cipher.",
     "*-EarlyData-RejectTicket-Client": "Trial decryption does not work with the NULL cipher.",
 
-    "Renegotiate-Client-BadExt*": "Fuzzer mode does not check renegotiation_info."
+    "Renegotiate-Client-BadExt*": "Fuzzer mode does not check renegotiation_info.",
+
+    "CBCRecordSplitting*": "Fuzzer mode does not implement record-splitting."
   }
 }
diff --git a/src/ssl/test/runner/handshake_client.go b/src/ssl/test/runner/handshake_client.go
index 05e7311..10e841a 100644
--- a/src/ssl/test/runner/handshake_client.go
+++ b/src/ssl/test/runner/handshake_client.go
@@ -358,6 +358,9 @@
 	if c.config.Bugs.OmitEarlyDataExtension {
 		hello.hasEarlyData = false
 	}
+	if c.config.Bugs.SendClientHelloSessionID != nil {
+		hello.sessionId = c.config.Bugs.SendClientHelloSessionID
+	}
 
 	var helloBytes []byte
 	if c.config.Bugs.SendV2ClientHello {
@@ -684,6 +687,10 @@
 func (hs *clientHandshakeState) doTLS13Handshake() error {
 	c := hs.c
 
+	if c.wireVersion == tls13ExperimentVersion && !bytes.Equal(hs.hello.sessionId, hs.serverHello.sessionId) {
+		return errors.New("tls: session IDs did not match.")
+	}
+
 	// Once the PRF hash is known, TLS 1.3 does not require a handshake
 	// buffer.
 	hs.finishedHash.discardHandshakeBuffer()
diff --git a/src/ssl/test/runner/handshake_server.go b/src/ssl/test/runner/handshake_server.go
index b31a562..3e70185 100644
--- a/src/ssl/test/runner/handshake_server.go
+++ b/src/ssl/test/runner/handshake_server.go
@@ -1087,6 +1087,13 @@
 		copy(hs.hello.random[len(hs.hello.random)-8:], downgradeTLS12)
 	}
 
+	if len(hs.clientHello.sessionId) > 0 && c.config.Bugs.ExpectEmptyClientHelloSessionID {
+		return false, errors.New("tls: expected empty session ID from client")
+	}
+	if len(hs.clientHello.sessionId) == 0 && c.config.Bugs.ExpectClientHelloSessionID {
+		return false, errors.New("tls: expected non-empty session ID from client")
+	}
+
 	foundCompression := false
 	// We only support null compression, so check that the client offered it.
 	for _, compression := range hs.clientHello.compressionMethods {
diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go
index 29747db..c5f9971 100644
--- a/src/ssl/test/runner/runner.go
+++ b/src/ssl/test/runner/runner.go
@@ -527,6 +527,9 @@
 	if *deterministic {
 		config.Time = func() time.Time { return time.Unix(1234, 1234) }
 	}
+	if test.tls13Variant != 0 {
+		config.TLS13Variant = test.tls13Variant
+	}
 
 	conn = &timeoutConn{conn, *idleTimeout}
 
@@ -1038,7 +1041,6 @@
 	}
 
 	if test.tls13Variant != 0 {
-		test.config.TLS13Variant = test.tls13Variant
 		flags = append(flags, "-tls13-variant", strconv.Itoa(test.tls13Variant))
 	}
 
@@ -3324,6 +3326,9 @@
 				MaxVersion:   VersionTLS10,
 				MinVersion:   VersionTLS10,
 				CipherSuites: []uint16{t.cipher},
+				Bugs: ProtocolBugs{
+					ExpectRecordSplitting: true,
+				},
 			},
 			messageLen:    -1, // read until EOF
 			resumeSession: true,
@@ -3339,6 +3344,9 @@
 				MaxVersion:   VersionTLS10,
 				MinVersion:   VersionTLS10,
 				CipherSuites: []uint16{t.cipher},
+				Bugs: ProtocolBugs{
+					ExpectRecordSplitting: true,
+				},
 			},
 			messageLen: -1, // read until EOF
 			flags: []string{
@@ -4009,25 +4017,23 @@
 			config: Config{
 				MaxVersion:       VersionTLS13,
 				MinVersion:       VersionTLS13,
-				TLS13Variant:     TLS13Experiment,
 				MaxEarlyDataSize: 16384,
 			},
 			resumeConfig: &Config{
 				MaxVersion:       VersionTLS13,
 				MinVersion:       VersionTLS13,
-				TLS13Variant:     TLS13Experiment,
 				MaxEarlyDataSize: 16384,
 				Bugs: ProtocolBugs{
 					ExpectEarlyData: [][]byte{{'h', 'e', 'l', 'l', 'o'}},
 				},
 			},
+			tls13Variant:  TLS13Experiment,
 			resumeSession: true,
 			flags: []string{
 				"-enable-early-data",
 				"-expect-early-data-info",
 				"-expect-accept-early-data",
 				"-on-resume-shim-writes-first",
-				"-tls13-variant", "1",
 			},
 		})
 
@@ -4049,13 +4055,13 @@
 					ExpectEarlyData: [][]byte{{'h', 'e', 'l', 'l', 'o'}},
 				},
 			},
+			tls13Variant:  TLS13RecordTypeExperiment,
 			resumeSession: true,
 			flags: []string{
 				"-enable-early-data",
 				"-expect-early-data-info",
 				"-expect-accept-early-data",
 				"-on-resume-shim-writes-first",
-				"-tls13-variant", "2",
 			},
 		})
 
@@ -4547,47 +4553,49 @@
 		if config.protocol == dtls && !vers.hasDTLS {
 			continue
 		}
-		for _, testType := range []testType{clientTest, serverTest} {
-			suffix := "-Client"
-			if testType == serverTest {
-				suffix = "-Server"
-			}
-			suffix += "-" + vers.name
+		for _, useCustomCallback := range []bool{false, true} {
+			for _, testType := range []testType{clientTest, serverTest} {
+				suffix := "-Client"
+				if testType == serverTest {
+					suffix = "-Server"
+				}
+				suffix += "-" + vers.name
+				if useCustomCallback {
+					suffix += "-CustomCallback"
+				}
 
-			flag := "-verify-peer"
-			if testType == serverTest {
-				flag = "-require-any-client-certificate"
-			}
+				flags := []string{"-verify-peer"}
+				if testType == serverTest {
+					flags = append(flags, "-require-any-client-certificate")
+				}
+				if useCustomCallback {
+					flags = append(flags, "-use-custom-verify-callback")
+				}
 
-			tests = append(tests, testCase{
-				testType: testType,
-				name:     "CertificateVerificationSucceed" + suffix,
-				config: Config{
-					MaxVersion:   vers.version,
-					Certificates: []Certificate{rsaCertificate},
-				},
-				tls13Variant: vers.tls13Variant,
-				flags: []string{
-					flag,
-					"-expect-verify-result",
-				},
-				resumeSession: true,
-			})
-			tests = append(tests, testCase{
-				testType: testType,
-				name:     "CertificateVerificationFail" + suffix,
-				config: Config{
-					MaxVersion:   vers.version,
-					Certificates: []Certificate{rsaCertificate},
-				},
-				tls13Variant: vers.tls13Variant,
-				flags: []string{
-					flag,
-					"-verify-fail",
-				},
-				shouldFail:    true,
-				expectedError: ":CERTIFICATE_VERIFY_FAILED:",
-			})
+				tests = append(tests, testCase{
+					testType: testType,
+					name:     "CertificateVerificationSucceed" + suffix,
+					config: Config{
+						MaxVersion:   vers.version,
+						Certificates: []Certificate{rsaCertificate},
+					},
+					tls13Variant:  vers.tls13Variant,
+					flags:         append([]string{"-expect-verify-result"}, flags...),
+					resumeSession: true,
+				})
+				tests = append(tests, testCase{
+					testType: testType,
+					name:     "CertificateVerificationFail" + suffix,
+					config: Config{
+						MaxVersion:   vers.version,
+						Certificates: []Certificate{rsaCertificate},
+					},
+					tls13Variant:  vers.tls13Variant,
+					flags:         append([]string{"-verify-fail"}, flags...),
+					shouldFail:    true,
+					expectedError: ":CERTIFICATE_VERIFY_FAILED:",
+				})
+			}
 		}
 
 		// By default, the client is in a soft fail mode where the peer
@@ -11030,6 +11038,93 @@
 		},
 	})
 
+	for _, noSessionID := range []bool{false, true} {
+		prefix := "TLS13Experiment"
+		variant := TLS13Experiment
+		if noSessionID {
+			prefix = "TLS13NoSessionIDExperiment"
+			variant = TLS13NoSessionIDExperiment
+		}
+
+		// Test that enabling a TLS 1.3 variant does not interfere with
+		// TLS 1.2 session ID resumption.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     prefix + "-ResumeTLS12SessionID",
+			config: Config{
+				MaxVersion:             VersionTLS12,
+				SessionTicketsDisabled: true,
+			},
+			resumeSession: true,
+			flags:         []string{"-tls13-variant", strconv.Itoa(variant)},
+		})
+
+		// Test that the server correctly echoes back session IDs of
+		// various lengths.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     prefix + "-EmptySessionID",
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendClientHelloSessionID: []byte{},
+				},
+			},
+			tls13Variant: variant,
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     prefix + "-ShortSessionID",
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendClientHelloSessionID: make([]byte, 16),
+				},
+			},
+			tls13Variant: variant,
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     prefix + "-FullSessionID",
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendClientHelloSessionID: make([]byte, 32),
+				},
+			},
+			tls13Variant: variant,
+		})
+	}
+
+	// Test that the client sends a fake session ID in TLS13Experiment.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13Experiment-RequireSessionID",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectClientHelloSessionID: true,
+			},
+		},
+		tls13Variant: TLS13Experiment,
+	})
+
+	// Test that the client does not send a fake session ID in
+	// TLS13NoSessionIDExperiment.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13NoSessionIDExperiment-RequireEmptySessionID",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectEmptyClientHelloSessionID: true,
+			},
+		},
+		tls13Variant: TLS13NoSessionIDExperiment,
+	})
+
 	testCases = append(testCases, testCase{
 		testType: clientTest,
 		name:     "TLS13-EarlyData-Reject-Client",
@@ -11059,23 +11154,21 @@
 		config: Config{
 			MaxVersion:       VersionTLS13,
 			MaxEarlyDataSize: 16384,
-			TLS13Variant:     TLS13Experiment,
 		},
 		resumeConfig: &Config{
 			MaxVersion:       VersionTLS13,
-			TLS13Variant:     TLS13Experiment,
 			MaxEarlyDataSize: 16384,
 			Bugs: ProtocolBugs{
 				AlwaysRejectEarlyData: true,
 			},
 		},
+		tls13Variant:  TLS13Experiment,
 		resumeSession: true,
 		flags: []string{
 			"-enable-early-data",
 			"-expect-early-data-info",
 			"-expect-reject-early-data",
 			"-on-resume-shim-writes-first",
-			"-tls13-variant", "1",
 		},
 	})
 
@@ -11085,23 +11178,21 @@
 		config: Config{
 			MaxVersion:       VersionTLS13,
 			MaxEarlyDataSize: 16384,
-			TLS13Variant:     TLS13RecordTypeExperiment,
 		},
 		resumeConfig: &Config{
 			MaxVersion:       VersionTLS13,
-			TLS13Variant:     TLS13RecordTypeExperiment,
 			MaxEarlyDataSize: 16384,
 			Bugs: ProtocolBugs{
 				AlwaysRejectEarlyData: true,
 			},
 		},
+		tls13Variant:  TLS13RecordTypeExperiment,
 		resumeSession: true,
 		flags: []string{
 			"-enable-early-data",
 			"-expect-early-data-info",
 			"-expect-reject-early-data",
 			"-on-resume-shim-writes-first",
-			"-tls13-variant", "2",
 		},
 	})
 
diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc
index f925504..fa7dfe1 100644
--- a/src/ssl/test/test_config.cc
+++ b/src/ssl/test/test_config.cc
@@ -129,6 +129,7 @@
   { "-handshake-twice", &TestConfig::handshake_twice },
   { "-allow-unknown-alpn-protos", &TestConfig::allow_unknown_alpn_protos },
   { "-enable-ed25519", &TestConfig::enable_ed25519 },
+  { "-use-custom-verify-callback", &TestConfig::use_custom_verify_callback },
 };
 
 const Flag<std::string> kStringFlags[] = {
diff --git a/src/ssl/test/test_config.h b/src/ssl/test/test_config.h
index e157936..1e5912e 100644
--- a/src/ssl/test/test_config.h
+++ b/src/ssl/test/test_config.h
@@ -144,6 +144,7 @@
   bool handshake_twice = false;
   bool allow_unknown_alpn_protos = false;
   bool enable_ed25519 = false;
+  bool use_custom_verify_callback = false;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_initial,
diff --git a/src/ssl/tls13_both.cc b/src/ssl/tls13_both.cc
index 763dc0e..338975b 100644
--- a/src/ssl/tls13_both.cc
+++ b/src/ssl/tls13_both.cc
@@ -17,6 +17,8 @@
 #include <assert.h>
 #include <string.h>
 
+#include <utility>
+
 #include <openssl/bytestring.h>
 #include <openssl/err.h>
 #include <openssl/hkdf.h>
@@ -28,6 +30,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 /* kMaxKeyUpdates is the number of consecutive KeyUpdates that will be
  * processed. Without this limit an attacker could force unbounded processing
  * without being able to return application data. */
@@ -53,7 +57,7 @@
         }
         ssl->method->expect_flight(ssl);
         hs->wait = ssl_hs_read_message;
-        /* Fall-through. */
+        SSL_FALLTHROUGH;
       }
 
       case ssl_hs_read_message: {
@@ -102,6 +106,11 @@
         hs->wait = ssl_hs_ok;
         return -1;
 
+      case ssl_hs_certificate_verify:
+        ssl->rwstate = SSL_CERTIFICATE_VERIFY;
+        hs->wait = ssl_hs_ok;
+        return -1;
+
       case ssl_hs_early_data_rejected:
         ssl->rwstate = SSL_EARLY_DATA_REJECTED;
         /* Cause |SSL_write| to start failing immediately. */
@@ -132,14 +141,16 @@
 int tls13_get_cert_verify_signature_input(
     SSL_HANDSHAKE *hs, uint8_t **out, size_t *out_len,
     enum ssl_cert_verify_context_t cert_verify_context) {
-  CBB cbb;
-  if (!CBB_init(&cbb, 64 + 33 + 1 + 2 * EVP_MAX_MD_SIZE)) {
-    goto err;
+  ScopedCBB cbb;
+  if (!CBB_init(cbb.get(), 64 + 33 + 1 + 2 * EVP_MAX_MD_SIZE)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
   }
 
   for (size_t i = 0; i < 64; i++) {
-    if (!CBB_add_u8(&cbb, 0x20)) {
-      goto err;
+    if (!CBB_add_u8(cbb.get(), 0x20)) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+      return 0;
     }
   }
 
@@ -159,28 +170,25 @@
     context = (const uint8_t *)kContext;
     context_len = sizeof(kContext);
   } else {
-    goto err;
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
   }
 
-  if (!CBB_add_bytes(&cbb, context, context_len)) {
-    goto err;
+  if (!CBB_add_bytes(cbb.get(), context, context_len)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
   }
 
   uint8_t context_hash[EVP_MAX_MD_SIZE];
   size_t context_hash_len;
-  if (!SSL_TRANSCRIPT_get_hash(&hs->transcript, context_hash,
-                               &context_hash_len) ||
-      !CBB_add_bytes(&cbb, context_hash, context_hash_len) ||
-      !CBB_finish(&cbb, out, out_len)) {
-    goto err;
+  if (!hs->transcript.GetHash(context_hash, &context_hash_len) ||
+      !CBB_add_bytes(cbb.get(), context_hash, context_hash_len) ||
+      !CBB_finish(cbb.get(), out, out_len)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
   }
 
   return 1;
-
-err:
-  OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-  CBB_cleanup(&cbb);
-  return 0;
 }
 
 int tls13_process_certificate(SSL_HANDSHAKE *hs, int allow_anonymous) {
@@ -194,24 +202,22 @@
     return 0;
   }
 
-  const int retain_sha256 =
-      ssl->server && ssl->retain_only_sha256_of_client_certs;
-  int ret = 0;
-
-  EVP_PKEY *pkey = NULL;
-  STACK_OF(CRYPTO_BUFFER) *certs = sk_CRYPTO_BUFFER_new_null();
-  if (certs == NULL) {
+  UniquePtr<STACK_OF(CRYPTO_BUFFER)> certs(sk_CRYPTO_BUFFER_new_null());
+  if (!certs) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    goto err;
+    return 0;
   }
 
   if (!CBS_get_u24_length_prefixed(&cbs, &certificate_list)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    goto err;
+    return 0;
   }
 
+  const bool retain_sha256 =
+      ssl->server && ssl->retain_only_sha256_of_client_certs;
+  UniquePtr<EVP_PKEY> pkey;
   while (CBS_len(&certificate_list) > 0) {
     CBS certificate, extensions;
     if (!CBS_get_u24_length_prefixed(&certificate_list, &certificate) ||
@@ -219,21 +225,21 @@
         CBS_len(&certificate) == 0) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
       OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_LENGTH_MISMATCH);
-      goto err;
+      return 0;
     }
 
-    if (sk_CRYPTO_BUFFER_num(certs) == 0) {
+    if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) {
       pkey = ssl_cert_parse_pubkey(&certificate);
-      if (pkey == NULL) {
+      if (!pkey) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
         OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-        goto err;
+        return 0;
       }
       /* TLS 1.3 always uses certificate keys for signing thus the correct
        * keyUsage is enforced. */
       if (!ssl_cert_check_digital_signature_key_usage(&certificate)) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-        goto err;
+        return 0;
       }
 
       if (retain_sha256) {
@@ -246,11 +252,11 @@
     CRYPTO_BUFFER *buf =
         CRYPTO_BUFFER_new_from_CBS(&certificate, ssl->ctx->pool);
     if (buf == NULL ||
-        !sk_CRYPTO_BUFFER_push(certs, buf)) {
+        !sk_CRYPTO_BUFFER_push(certs.get(), buf)) {
       CRYPTO_BUFFER_free(buf);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
+      return 0;
     }
 
     /* Parse out the extensions. */
@@ -266,7 +272,7 @@
                               OPENSSL_ARRAY_SIZE(ext_types),
                               0 /* reject unknown */)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-      goto err;
+      return 0;
     }
 
     /* All Certificate extensions are parsed, but only the leaf extensions are
@@ -275,7 +281,7 @@
       if (ssl->server || !ssl->ocsp_stapling_enabled) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
-        goto err;
+        return 0;
       }
 
       uint8_t status_type;
@@ -286,14 +292,14 @@
           CBS_len(&ocsp_response) == 0 ||
           CBS_len(&status_request) != 0) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-        goto err;
+        return 0;
       }
 
-      if (sk_CRYPTO_BUFFER_num(certs) == 1 &&
+      if (sk_CRYPTO_BUFFER_num(certs.get()) == 1 &&
           !CBS_stow(&ocsp_response, &hs->new_session->ocsp_response,
                     &hs->new_session->ocsp_response_length)) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-        goto err;
+        return 0;
       }
     }
 
@@ -301,21 +307,21 @@
       if (ssl->server || !ssl->signed_cert_timestamps_enabled) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
-        goto err;
+        return 0;
       }
 
       if (!ssl_is_sct_list_valid(&sct)) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_PARSING_EXTENSION);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-        goto err;
+        return 0;
       }
 
-      if (sk_CRYPTO_BUFFER_num(certs) == 1 &&
+      if (sk_CRYPTO_BUFFER_num(certs.get()) == 1 &&
           !CBS_stow(
               &sct, &hs->new_session->tlsext_signed_cert_timestamp_list,
               &hs->new_session->tlsext_signed_cert_timestamp_list_length)) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-        goto err;
+        return 0;
       }
     }
   }
@@ -323,28 +329,25 @@
   if (CBS_len(&cbs) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    goto err;
+    return 0;
   }
 
-  EVP_PKEY_free(hs->peer_pubkey);
-  hs->peer_pubkey = pkey;
-  pkey = NULL;
+  hs->peer_pubkey = std::move(pkey);
 
   sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
-  hs->new_session->certs = certs;
-  certs = NULL;
+  hs->new_session->certs = certs.release();
 
-  if (!ssl->ctx->x509_method->session_cache_objects(hs->new_session)) {
+  if (!ssl->ctx->x509_method->session_cache_objects(hs->new_session.get())) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    goto err;
+    return 0;
   }
 
   if (sk_CRYPTO_BUFFER_num(hs->new_session->certs) == 0) {
     if (!allow_anonymous) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED);
-      goto err;
+      return 0;
     }
 
     /* OpenSSL returns X509_V_OK when no certificates are requested. This is
@@ -352,23 +355,11 @@
     hs->new_session->verify_result = X509_V_OK;
 
     /* No certificate, so nothing more to do. */
-    ret = 1;
-    goto err;
+    return 1;
   }
 
   hs->new_session->peer_sha256_valid = retain_sha256;
-
-  if (!ssl->ctx->x509_method->session_verify_cert_chain(hs->new_session,
-                                                        ssl)) {
-    goto err;
-  }
-
-  ret = 1;
-
-err:
-  sk_CRYPTO_BUFFER_pop_free(certs, CRYPTO_BUFFER_free);
-  EVP_PKEY_free(pkey);
-  return ret;
+  return 1;
 }
 
 int tls13_process_certificate_verify(SSL_HANDSHAKE *hs) {
@@ -404,11 +395,11 @@
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
     return 0;
   }
-  bssl::UniquePtr<uint8_t> free_msg(msg);
+  UniquePtr<uint8_t> free_msg(msg);
 
-  int sig_ok =
-      ssl_public_key_verify(ssl, CBS_data(&signature), CBS_len(&signature),
-                            signature_algorithm, hs->peer_pubkey, msg, msg_len);
+  int sig_ok = ssl_public_key_verify(ssl, CBS_data(&signature),
+                                     CBS_len(&signature), signature_algorithm,
+                                     hs->peer_pubkey.get(), msg, msg_len);
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
   sig_ok = 1;
   ERR_clear_error();
@@ -456,7 +447,7 @@
 
 int tls13_add_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  bssl::ScopedCBB cbb;
+  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. */
@@ -533,7 +524,7 @@
     return ssl_private_key_failure;
   }
 
-  bssl::ScopedCBB cbb;
+  ScopedCBB cbb;
   CBB body;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                  SSL3_MT_CERTIFICATE_VERIFY) ||
@@ -544,7 +535,7 @@
 
   /* Sign the digest. */
   CBB child;
-  const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey);
+  const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey.get());
   uint8_t *sig;
   size_t sig_len;
   if (!CBB_add_u16_length_prefixed(&body, &child) ||
@@ -561,7 +552,7 @@
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
     return ssl_private_key_failure;
   }
-  bssl::UniquePtr<uint8_t> free_msg(msg);
+  UniquePtr<uint8_t> free_msg(msg);
 
   enum ssl_private_key_result_t sign_result = ssl_private_key_sign(
       hs, sig, &sig_len, max_sig_len, signature_algorithm, msg, msg_len);
@@ -588,11 +579,11 @@
     return 0;
   }
 
-  CBB cbb, body;
-  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_FINISHED) ||
+  ScopedCBB cbb;
+  CBB body;
+  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_FINISHED) ||
       !CBB_add_bytes(&body, verify_data, verify_data_len) ||
-      !ssl_add_message_cbb(ssl, &cbb)) {
-    CBB_cleanup(&cbb);
+      !ssl_add_message_cbb(ssl, cbb.get())) {
     return 0;
   }
 
@@ -619,12 +610,12 @@
   /* Acknowledge the KeyUpdate */
   if (key_update_request == SSL_KEY_UPDATE_REQUESTED &&
       !ssl->s3->key_update_pending) {
-    CBB cbb, body;
-    if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_KEY_UPDATE) ||
+    ScopedCBB cbb;
+    CBB body;
+    if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_KEY_UPDATE) ||
         !CBB_add_u8(&body, SSL_KEY_UPDATE_NOT_REQUESTED) ||
-        !ssl_add_message_cbb(ssl, &cbb) ||
+        !ssl_add_message_cbb(ssl, cbb.get()) ||
         !tls13_rotate_traffic_key(ssl, evp_aead_seal)) {
-      CBB_cleanup(&cbb);
       return 0;
     }
 
@@ -661,3 +652,5 @@
   OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
   return 0;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/tls13_client.cc b/src/ssl/tls13_client.cc
index 7f961bf..4cc7e60 100644
--- a/src/ssl/tls13_client.cc
+++ b/src/ssl/tls13_client.cc
@@ -18,6 +18,8 @@
 #include <limits.h>
 #include <string.h>
 
+#include <utility>
+
 #include <openssl/bytestring.h>
 #include <openssl/digest.h>
 #include <openssl/err.h>
@@ -28,6 +30,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 enum client_hs_state_t {
   state_process_hello_retry_request = 0,
   state_send_second_client_hello,
@@ -126,13 +130,13 @@
 
     /* Check that the HelloRetryRequest does not request the key share that
      * was provided in the initial ClientHello. */
-    if (SSL_ECDH_CTX_get_id(&hs->ecdh_ctx) == group_id) {
+    if (hs->key_share->GroupID() == group_id) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
       OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
       return ssl_hs_error;
     }
 
-    SSL_ECDH_CTX_cleanup(&hs->ecdh_ctx);
+    hs->key_share.reset();
     hs->retry_group = group_id;
   }
 
@@ -151,7 +155,10 @@
 
 static enum ssl_hs_wait_t do_send_second_client_hello(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  if (!ssl->method->set_write_state(ssl, NULL) ||
+  /* Restore the null cipher. We may have switched due to 0-RTT. */
+  bssl::UniquePtr<SSLAEADContext> null_ctx = SSLAEADContext::CreateNullCipher();
+  if (!null_ctx ||
+      !ssl->method->set_write_state(ssl, std::move(null_ctx)) ||
       !ssl_write_client_hello(hs)) {
     return ssl_hs_error;
   }
@@ -275,14 +282,14 @@
     ssl->s3->session_reused = 1;
     /* Only authentication information carries over in TLS 1.3. */
     hs->new_session = SSL_SESSION_dup(ssl->session, SSL_SESSION_DUP_AUTH_ONLY);
-    if (hs->new_session == NULL) {
+    if (!hs->new_session) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       return ssl_hs_error;
     }
     ssl_set_session(ssl, NULL);
 
     /* Resumption incorporates fresh key material, so refresh the timeout. */
-    ssl_session_renew_timeout(ssl, hs->new_session,
+    ssl_session_renew_timeout(ssl, hs->new_session.get(),
                               ssl->session_ctx->session_psk_dhe_timeout);
   } else if (!ssl_get_new_session(hs, 0)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
@@ -449,9 +456,9 @@
   }
 
   uint8_t alert = SSL_AD_DECODE_ERROR;
-  STACK_OF(CRYPTO_BUFFER) *ca_names =
+  UniquePtr<STACK_OF(CRYPTO_BUFFER)> ca_names =
       ssl_parse_client_CA_list(ssl, &alert, &cbs);
-  if (ca_names == NULL) {
+  if (!ca_names) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
@@ -461,14 +468,12 @@
   if (!CBS_get_u16_length_prefixed(&cbs, &extensions) ||
       CBS_len(&cbs) != 0) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    sk_CRYPTO_BUFFER_pop_free(ca_names, CRYPTO_BUFFER_free);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     return ssl_hs_error;
   }
 
   hs->cert_request = 1;
-  sk_CRYPTO_BUFFER_pop_free(hs->ca_names, CRYPTO_BUFFER_free);
-  hs->ca_names = ca_names;
+  hs->ca_names = std::move(ca_names);
   ssl->ctx->x509_method->hs_flush_cached_ca_names(hs);
 
   if (!ssl_hash_current_message(hs)) {
@@ -494,6 +499,16 @@
 static enum ssl_hs_wait_t do_process_server_certificate_verify(
     SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+  switch (ssl_verify_peer_cert(hs)) {
+    case ssl_verify_ok:
+      break;
+    case ssl_verify_invalid:
+      return ssl_hs_error;
+    case ssl_verify_retry:
+      hs->tls13_state = state_process_server_certificate_verify;
+      return ssl_hs_certificate_verify;
+  }
+
   if (!ssl_check_message_type(ssl, SSL3_MT_CERTIFICATE_VERIFY) ||
       !tls13_process_certificate_verify(hs) ||
       !ssl_hash_current_message(hs)) {
@@ -615,11 +630,11 @@
       return ssl_hs_channel_id_lookup;
     }
 
-    CBB cbb, body;
-    if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_CHANNEL_ID) ||
+    ScopedCBB cbb;
+    CBB body;
+    if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CHANNEL_ID) ||
         !tls1_write_channel_id(hs, &body) ||
-        !ssl_add_message_cbb(ssl, &cbb)) {
-      CBB_cleanup(&cbb);
+        !ssl_add_message_cbb(ssl, cbb.get())) {
       return ssl_hs_error;
     }
   }
@@ -704,8 +719,8 @@
 }
 
 int tls13_process_new_session_ticket(SSL *ssl) {
-  bssl::UniquePtr<SSL_SESSION> session(SSL_SESSION_dup(
-      ssl->s3->established_session, SSL_SESSION_INCLUDE_NONAUTH));
+  UniquePtr<SSL_SESSION> session(SSL_SESSION_dup(ssl->s3->established_session,
+                                                 SSL_SESSION_INCLUDE_NONAUTH));
   if (!session) {
     return 0;
   }
@@ -770,9 +785,11 @@
 }
 
 void ssl_clear_tls13_state(SSL_HANDSHAKE *hs) {
-  SSL_ECDH_CTX_cleanup(&hs->ecdh_ctx);
+  hs->key_share.reset();
 
   OPENSSL_free(hs->key_share_bytes);
   hs->key_share_bytes = NULL;
   hs->key_share_bytes_len = 0;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/tls13_enc.cc b/src/ssl/tls13_enc.cc
index 97f0ed9..1ae4849 100644
--- a/src/ssl/tls13_enc.cc
+++ b/src/ssl/tls13_enc.cc
@@ -17,6 +17,8 @@
 #include <assert.h>
 #include <string.h>
 
+#include <utility>
+
 #include <openssl/aead.h>
 #include <openssl/bytestring.h>
 #include <openssl/digest.h>
@@ -28,13 +30,15 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 static int init_key_schedule(SSL_HANDSHAKE *hs, uint16_t version,
                               int algorithm_prf) {
-  if (!SSL_TRANSCRIPT_init_hash(&hs->transcript, version, algorithm_prf)) {
+  if (!hs->transcript.InitHash(version, algorithm_prf)) {
     return 0;
   }
 
-  hs->hash_len = SSL_TRANSCRIPT_digest_len(&hs->transcript);
+  hs->hash_len = hs->transcript.DigestLen();
 
   /* Initialize the secret to the zero key. */
   OPENSSL_memset(hs->secret, 0, hs->hash_len);
@@ -48,7 +52,7 @@
     return 0;
   }
 
-  SSL_TRANSCRIPT_free_buffer(&hs->transcript);
+  hs->transcript.FreeBuffer();
   return 1;
 }
 
@@ -60,9 +64,8 @@
 
 int tls13_advance_key_schedule(SSL_HANDSHAKE *hs, const uint8_t *in,
                                size_t len) {
-  return HKDF_extract(hs->secret, &hs->hash_len,
-                      SSL_TRANSCRIPT_md(&hs->transcript), in, len, hs->secret,
-                      hs->hash_len);
+  return HKDF_extract(hs->secret, &hs->hash_len, hs->transcript.Digest(), in,
+                      len, hs->secret, hs->hash_len);
 }
 
 static int hkdf_expand_label(uint8_t *out, const EVP_MD *digest,
@@ -71,20 +74,20 @@
                              const uint8_t *hash, size_t hash_len, size_t len) {
   static const char kTLS13LabelVersion[] = "TLS 1.3, ";
 
-  CBB cbb, child;
+  ScopedCBB cbb;
+  CBB child;
   uint8_t *hkdf_label;
   size_t hkdf_label_len;
-  if (!CBB_init(&cbb, 2 + 1 + strlen(kTLS13LabelVersion) + label_len + 1 +
-                          hash_len) ||
-      !CBB_add_u16(&cbb, len) ||
-      !CBB_add_u8_length_prefixed(&cbb, &child) ||
+  if (!CBB_init(cbb.get(), 2 + 1 + strlen(kTLS13LabelVersion) + label_len + 1 +
+                               hash_len) ||
+      !CBB_add_u16(cbb.get(), len) ||
+      !CBB_add_u8_length_prefixed(cbb.get(), &child) ||
       !CBB_add_bytes(&child, (const uint8_t *)kTLS13LabelVersion,
                      strlen(kTLS13LabelVersion)) ||
       !CBB_add_bytes(&child, label, label_len) ||
-      !CBB_add_u8_length_prefixed(&cbb, &child) ||
+      !CBB_add_u8_length_prefixed(cbb.get(), &child) ||
       !CBB_add_bytes(&child, hash, hash_len) ||
-      !CBB_finish(&cbb, &hkdf_label, &hkdf_label_len)) {
-    CBB_cleanup(&cbb);
+      !CBB_finish(cbb.get(), &hkdf_label, &hkdf_label_len)) {
     return 0;
   }
 
@@ -101,12 +104,11 @@
                          const uint8_t *label, size_t label_len) {
   uint8_t context_hash[EVP_MAX_MD_SIZE];
   size_t context_hash_len;
-  if (!SSL_TRANSCRIPT_get_hash(&hs->transcript, context_hash,
-                               &context_hash_len)) {
+  if (!hs->transcript.GetHash(context_hash, &context_hash_len)) {
     return 0;
   }
 
-  return hkdf_expand_label(out, SSL_TRANSCRIPT_md(&hs->transcript), hs->secret,
+  return hkdf_expand_label(out, hs->transcript.Digest(), hs->secret,
                            hs->hash_len, label, label_len, context_hash,
                            context_hash_len, len);
 }
@@ -148,19 +150,19 @@
     return 0;
   }
 
-  SSL_AEAD_CTX *traffic_aead =
-      SSL_AEAD_CTX_new(direction, version, SSL_is_dtls(ssl), session->cipher,
-                       key, key_len, NULL, 0, iv, iv_len);
-  if (traffic_aead == NULL) {
+  UniquePtr<SSLAEADContext> traffic_aead = SSLAEADContext::Create(
+      direction, version, SSL_is_dtls(ssl), session->cipher, key, key_len, NULL,
+      0, iv, iv_len);
+  if (!traffic_aead) {
     return 0;
   }
 
   if (direction == evp_aead_open) {
-    if (!ssl->method->set_read_state(ssl, traffic_aead)) {
+    if (!ssl->method->set_read_state(ssl, std::move(traffic_aead))) {
       return 0;
     }
   } else {
-    if (!ssl->method->set_write_state(ssl, traffic_aead)) {
+    if (!ssl->method->set_write_state(ssl, std::move(traffic_aead))) {
       return 0;
     }
   }
@@ -309,11 +311,9 @@
 
   uint8_t context_hash[EVP_MAX_MD_SIZE];
   size_t context_hash_len;
-  if (!SSL_TRANSCRIPT_get_hash(&hs->transcript, context_hash,
-                               &context_hash_len) ||
-      !tls13_verify_data(SSL_TRANSCRIPT_md(&hs->transcript), out, out_len,
-                         traffic_secret, hs->hash_len, context_hash,
-                         context_hash_len)) {
+  if (!hs->transcript.GetHash(context_hash, &context_hash_len) ||
+      !tls13_verify_data(hs->transcript.Digest(), out, out_len, traffic_secret,
+                         hs->hash_len, context_hash, context_hash_len)) {
     return 0;
   }
   return 1;
@@ -379,21 +379,17 @@
     return 0;
   }
 
-  EVP_MD_CTX ctx;
-  EVP_MD_CTX_init(&ctx);
+  ScopedEVP_MD_CTX ctx;
   uint8_t context[EVP_MAX_MD_SIZE];
   unsigned context_len;
-  if (!EVP_DigestInit_ex(&ctx, digest, NULL) ||
-      !EVP_DigestUpdate(&ctx, hs->transcript.buffer->data,
-                        hs->transcript.buffer->length) ||
-      !EVP_DigestUpdate(&ctx, msg, len - hash_len - 3) ||
-      !EVP_DigestFinal_ex(&ctx, context, &context_len)) {
-    EVP_MD_CTX_cleanup(&ctx);
+  if (!EVP_DigestInit_ex(ctx.get(), digest, NULL) ||
+      !EVP_DigestUpdate(ctx.get(), hs->transcript.buffer_data(),
+                        hs->transcript.buffer_len()) ||
+      !EVP_DigestUpdate(ctx.get(), msg, len - hash_len - 3) ||
+      !EVP_DigestFinal_ex(ctx.get(), context, &context_len)) {
     return 0;
   }
 
-  EVP_MD_CTX_cleanup(&ctx);
-
   uint8_t verify_data[EVP_MAX_MD_SIZE] = {0};
   if (!tls13_psk_binder(verify_data, digest, ssl->session->master_key,
                         ssl->session->master_key_length, context, context_len,
@@ -407,7 +403,7 @@
 
 int tls13_verify_psk_binder(SSL_HANDSHAKE *hs, SSL_SESSION *session,
                             CBS *binders) {
-  size_t hash_len = SSL_TRANSCRIPT_digest_len(&hs->transcript);
+  size_t hash_len = hs->transcript.DigestLen();
 
   /* Get the full ClientHello, including message header. It must be large enough
    * to exclude the binders. */
@@ -423,14 +419,13 @@
   uint8_t context[EVP_MAX_MD_SIZE];
   unsigned context_len;
   if (!EVP_Digest(CBS_data(&message), CBS_len(&message) - CBS_len(binders) - 2,
-                  context, &context_len, SSL_TRANSCRIPT_md(&hs->transcript),
-                  NULL)) {
+                  context, &context_len, hs->transcript.Digest(), NULL)) {
     return 0;
   }
 
   uint8_t verify_data[EVP_MAX_MD_SIZE] = {0};
   CBS binder;
-  if (!tls13_psk_binder(verify_data, SSL_TRANSCRIPT_md(&hs->transcript),
+  if (!tls13_psk_binder(verify_data, hs->transcript.Digest(),
                         session->master_key, session->master_key_length,
                         context, context_len, hash_len) ||
       /* We only consider the first PSK, so compare against the first binder. */
@@ -452,3 +447,5 @@
 
   return 1;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/tls13_server.cc b/src/ssl/tls13_server.cc
index 4e66016..067c427 100644
--- a/src/ssl/tls13_server.cc
+++ b/src/ssl/tls13_server.cc
@@ -36,6 +36,8 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 enum server_hs_state_t {
   state_select_parameters = 0,
   state_select_session,
@@ -157,40 +159,38 @@
    * the client makes several connections before getting a renewal. */
   static const int kNumTickets = 2;
 
-  SSL_SESSION *session = hs->new_session;
-  CBB cbb;
-  CBB_zero(&cbb);
-
   /* Rebase the session timestamp so that it is measured from ticket
    * issuance. */
-  ssl_session_rebase_time(ssl, session);
+  ssl_session_rebase_time(ssl, hs->new_session.get());
 
   for (int i = 0; i < kNumTickets; i++) {
-    if (!RAND_bytes((uint8_t *)&session->ticket_age_add, 4)) {
-      goto err;
+    if (!RAND_bytes((uint8_t *)&hs->new_session->ticket_age_add, 4)) {
+      return 0;
     }
-    session->ticket_age_add_valid = 1;
+    hs->new_session->ticket_age_add_valid = 1;
 
+    ScopedCBB cbb;
     CBB body, ticket, extensions;
-    if (!ssl->method->init_message(ssl, &cbb, &body,
+    if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                    SSL3_MT_NEW_SESSION_TICKET) ||
-        !CBB_add_u32(&body, session->timeout) ||
-        !CBB_add_u32(&body, session->ticket_age_add) ||
+        !CBB_add_u32(&body, hs->new_session->timeout) ||
+        !CBB_add_u32(&body, hs->new_session->ticket_age_add) ||
         !CBB_add_u16_length_prefixed(&body, &ticket) ||
-        !ssl_encrypt_ticket(ssl, &ticket, session) ||
+        !ssl_encrypt_ticket(ssl, &ticket, hs->new_session.get()) ||
         !CBB_add_u16_length_prefixed(&body, &extensions)) {
-      goto err;
+      return 0;
     }
 
     if (ssl->cert->enable_early_data) {
-      session->ticket_max_early_data = kMaxEarlyDataAccepted;
+      hs->new_session->ticket_max_early_data = kMaxEarlyDataAccepted;
 
       CBB early_data_info;
       if (!CBB_add_u16(&extensions, TLSEXT_TYPE_ticket_early_data_info) ||
           !CBB_add_u16_length_prefixed(&extensions, &early_data_info) ||
-          !CBB_add_u32(&early_data_info, session->ticket_max_early_data) ||
+          !CBB_add_u32(&early_data_info,
+                       hs->new_session->ticket_max_early_data) ||
           !CBB_flush(&extensions)) {
-        goto err;
+        return 0;
       }
     }
 
@@ -198,19 +198,15 @@
     if (!CBB_add_u16(&extensions,
                      ssl_get_grease_value(ssl, ssl_grease_ticket_extension)) ||
         !CBB_add_u16(&extensions, 0 /* empty */)) {
-      goto err;
+      return 0;
     }
 
-    if (!ssl_add_message_cbb(ssl, &cbb)) {
-      goto err;
+    if (!ssl_add_message_cbb(ssl, cbb.get())) {
+      return 0;
     }
   }
 
   return 1;
-
-err:
-  CBB_cleanup(&cbb);
-  return 0;
 }
 
 static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
@@ -397,7 +393,7 @@
       ssl->s3->session_reused = 1;
 
       /* Resumption incorporates fresh key material, so refresh the timeout. */
-      ssl_session_renew_timeout(ssl, hs->new_session,
+      ssl_session_renew_timeout(ssl, hs->new_session.get(),
                                 ssl->session_ctx->session_psk_dhe_timeout);
       break;
 
@@ -415,7 +411,7 @@
 
   if (hs->hostname != NULL) {
     OPENSSL_free(hs->new_session->tlsext_hostname);
-    hs->new_session->tlsext_hostname = BUF_strdup(hs->hostname);
+    hs->new_session->tlsext_hostname = BUF_strdup(hs->hostname.get());
     if (hs->new_session->tlsext_hostname == NULL) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       return ssl_hs_error;
@@ -479,9 +475,10 @@
 
 static enum ssl_hs_wait_t do_send_hello_retry_request(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  CBB cbb, body, extensions;
+  ScopedCBB cbb;
+  CBB body, extensions;
   uint16_t group_id;
-  if (!ssl->method->init_message(ssl, &cbb, &body,
+  if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                  SSL3_MT_HELLO_RETRY_REQUEST) ||
       !CBB_add_u16(&body, ssl->version) ||
       !tls1_get_shared_group(hs, &group_id) ||
@@ -489,8 +486,7 @@
       !CBB_add_u16(&extensions, TLSEXT_TYPE_key_share) ||
       !CBB_add_u16(&extensions, 2 /* length */) ||
       !CBB_add_u16(&extensions, group_id) ||
-      !ssl_add_message_cbb(ssl, &cbb)) {
-    CBB_cleanup(&cbb);
+      !ssl_add_message_cbb(ssl, cbb.get())) {
     return ssl_hs_error;
   }
 
@@ -540,8 +536,9 @@
   }
 
   /* Send a ServerHello. */
-  CBB cbb, body, extensions, session_id;
-  if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_SERVER_HELLO) ||
+  ScopedCBB cbb;
+  CBB body, extensions, session_id;
+  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_SERVER_HELLO) ||
       !CBB_add_u16(&body, version) ||
       !RAND_bytes(ssl->s3->server_random, sizeof(ssl->s3->server_random)) ||
       !CBB_add_bytes(&body, ssl->s3->server_random, SSL3_RANDOM_SIZE) ||
@@ -555,28 +552,28 @@
       !ssl_ext_key_share_add_serverhello(hs, &extensions) ||
       (ssl->version == TLS1_3_EXPERIMENT_VERSION &&
        !ssl_ext_supported_versions_add_serverhello(hs, &extensions)) ||
-      !ssl_add_message_cbb(ssl, &cbb)) {
-    goto err;
+      !ssl_add_message_cbb(ssl, cbb.get())) {
+    return ssl_hs_error;
   }
 
   if (ssl->version == TLS1_3_EXPERIMENT_VERSION &&
       !ssl3_add_change_cipher_spec(ssl)) {
-    goto err;
+    return ssl_hs_error;
   }
 
   /* Derive and enable the handshake traffic secrets. */
   if (!tls13_derive_handshake_secrets(hs) ||
       !tls13_set_traffic_key(ssl, evp_aead_seal, hs->server_handshake_secret,
                              hs->hash_len)) {
-    goto err;
+    return ssl_hs_error;
   }
 
   /* Send EncryptedExtensions. */
-  if (!ssl->method->init_message(ssl, &cbb, &body,
+  if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                  SSL3_MT_ENCRYPTED_EXTENSIONS) ||
       !ssl_add_serverhello_tlsext(hs, &body) ||
-      !ssl_add_message_cbb(ssl, &cbb)) {
-    goto err;
+      !ssl_add_message_cbb(ssl, cbb.get())) {
+    return ssl_hs_error;
   }
 
   if (!ssl->s3->session_reused) {
@@ -592,15 +589,15 @@
   /* Send a CertificateRequest, if necessary. */
   if (hs->cert_request) {
     CBB sigalgs_cbb;
-    if (!ssl->method->init_message(ssl, &cbb, &body,
+    if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                    SSL3_MT_CERTIFICATE_REQUEST) ||
         !CBB_add_u8(&body, 0 /* no certificate_request_context. */) ||
         !CBB_add_u16_length_prefixed(&body, &sigalgs_cbb) ||
         !tls12_add_verify_sigalgs(ssl, &sigalgs_cbb) ||
         !ssl_add_client_CA_list(ssl, &body) ||
         !CBB_add_u16(&body, 0 /* empty certificate_extensions. */) ||
-        !ssl_add_message_cbb(ssl, &cbb)) {
-      goto err;
+        !ssl_add_message_cbb(ssl, cbb.get())) {
+      return ssl_hs_error;
     }
   }
 
@@ -608,11 +605,11 @@
   if (!ssl->s3->session_reused) {
     if (!ssl_has_certificate(ssl)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET);
-      goto err;
+      return ssl_hs_error;
     }
 
     if (!tls13_add_certificate(hs)) {
-      goto err;
+      return ssl_hs_error;
     }
 
     hs->tls13_state = state_send_server_certificate_verify;
@@ -621,10 +618,6 @@
 
   hs->tls13_state = state_send_server_finished;
   return ssl_hs_ok;
-
-err:
-  CBB_cleanup(&cbb);
-  return ssl_hs_error;
 }
 
 static enum ssl_hs_wait_t do_send_server_certificate_verify(SSL_HANDSHAKE *hs) {
@@ -678,12 +671,11 @@
      * TODO(davidben): This will need to be updated for DTLS 1.3. */
     assert(!SSL_is_dtls(hs->ssl));
     assert(hs->hash_len <= 0xff);
-    uint8_t header[4] = {SSL3_MT_FINISHED, 0, 0, static_cast<uint8_t>(hs->hash_len)};
-    if (!SSL_TRANSCRIPT_update(&hs->transcript, header, sizeof(header)) ||
-        !SSL_TRANSCRIPT_update(&hs->transcript, hs->expected_client_finished,
-                               hs->hash_len) ||
-        !tls13_derive_resumption_secret(hs) ||
-        !add_new_session_tickets(hs)) {
+    uint8_t header[4] = {SSL3_MT_FINISHED, 0, 0,
+                         static_cast<uint8_t>(hs->hash_len)};
+    if (!hs->transcript.Update(header, sizeof(header)) ||
+        !hs->transcript.Update(hs->expected_client_finished, hs->hash_len) ||
+        !tls13_derive_resumption_secret(hs) || !add_new_session_tickets(hs)) {
       return ssl_hs_error;
     }
   }
@@ -766,6 +758,16 @@
     return ssl_hs_ok;
   }
 
+  switch (ssl_verify_peer_cert(hs)) {
+    case ssl_verify_ok:
+      break;
+    case ssl_verify_invalid:
+      return ssl_hs_error;
+    case ssl_verify_retry:
+      hs->tls13_state = state_process_client_certificate_verify;
+      return ssl_hs_certificate_verify;
+  }
+
   if (!ssl_check_message_type(ssl, SSL3_MT_CERTIFICATE_VERIFY) ||
       !tls13_process_certificate_verify(hs) ||
       !ssl_hash_current_message(hs)) {
@@ -900,3 +902,5 @@
 
   return ssl_hs_ok;
 }
+
+}  // namespace bssl
diff --git a/src/ssl/tls_method.cc b/src/ssl/tls_method.cc
index d039b7d..4751e2e 100644
--- a/src/ssl/tls_method.cc
+++ b/src/ssl/tls_method.cc
@@ -65,33 +65,34 @@
 #include "internal.h"
 
 
+namespace bssl {
+
 static int ssl3_supports_cipher(const SSL_CIPHER *cipher) { return 1; }
 
 static void ssl3_expect_flight(SSL *ssl) {}
 
 static void ssl3_received_flight(SSL *ssl) {}
 
-static int ssl3_set_read_state(SSL *ssl, SSL_AEAD_CTX *aead_ctx) {
+static int ssl3_set_read_state(SSL *ssl, UniquePtr<SSLAEADContext> aead_ctx) {
   if (ssl->s3->rrec.length != 0) {
     /* There may not be unprocessed record data at a cipher change. */
     OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFERED_MESSAGES_ON_CIPHER_CHANGE);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-    SSL_AEAD_CTX_free(aead_ctx);
     return 0;
   }
 
   OPENSSL_memset(ssl->s3->read_sequence, 0, sizeof(ssl->s3->read_sequence));
 
-  SSL_AEAD_CTX_free(ssl->s3->aead_read_ctx);
-  ssl->s3->aead_read_ctx = aead_ctx;
+  Delete(ssl->s3->aead_read_ctx);
+  ssl->s3->aead_read_ctx = aead_ctx.release();
   return 1;
 }
 
-static int ssl3_set_write_state(SSL *ssl, SSL_AEAD_CTX *aead_ctx) {
+static int ssl3_set_write_state(SSL *ssl, UniquePtr<SSLAEADContext> aead_ctx) {
   OPENSSL_memset(ssl->s3->write_sequence, 0, sizeof(ssl->s3->write_sequence));
 
-  SSL_AEAD_CTX_free(ssl->s3->aead_write_ctx);
-  ssl->s3->aead_write_ctx = aead_ctx;
+  Delete(ssl->s3->aead_write_ctx);
+  ssl->s3->aead_write_ctx = aead_ctx.release();
   return 1;
 }
 
@@ -120,6 +121,64 @@
     ssl3_set_write_state,
 };
 
+static int ssl_noop_x509_check_client_CA_names(
+    STACK_OF(CRYPTO_BUFFER) *names) {
+  return 1;
+}
+
+static void ssl_noop_x509_clear(CERT *cert) {}
+static void ssl_noop_x509_free(CERT *cert) {}
+static void ssl_noop_x509_dup(CERT *new_cert, const CERT *cert) {}
+static void ssl_noop_x509_flush_cached_leaf(CERT *cert) {}
+static void ssl_noop_x509_flush_cached_chain(CERT *cert) {}
+static int ssl_noop_x509_session_cache_objects(SSL_SESSION *sess) {
+  return 1;
+}
+static int ssl_noop_x509_session_dup(SSL_SESSION *new_session,
+                                       const SSL_SESSION *session) {
+  return 1;
+}
+static void ssl_noop_x509_session_clear(SSL_SESSION *session) {}
+static int ssl_noop_x509_session_verify_cert_chain(SSL_SESSION *session,
+                                                   SSL *ssl,
+                                                   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_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) {}
+
+static const SSL_X509_METHOD ssl_noop_x509_method = {
+  ssl_noop_x509_check_client_CA_names,
+  ssl_noop_x509_clear,
+  ssl_noop_x509_free,
+  ssl_noop_x509_dup,
+  ssl_noop_x509_flush_cached_chain,
+  ssl_noop_x509_flush_cached_leaf,
+  ssl_noop_x509_session_cache_objects,
+  ssl_noop_x509_session_dup,
+  ssl_noop_x509_session_clear,
+  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_flush_cached_client_CA,
+  ssl_noop_x509_ssl_auto_chain_if_needed,
+  ssl_noop_x509_ssl_ctx_new,
+  ssl_noop_x509_ssl_ctx_free,
+  ssl_noop_x509_ssl_ctx_flush_cached_client_CA,
+};
+
+}  // namespace bssl
+
+using namespace bssl;
+
 const SSL_METHOD *TLS_method(void) {
   static const SSL_METHOD kMethod = {
       0,
@@ -133,6 +192,15 @@
   return TLS_method();
 }
 
+const SSL_METHOD *TLS_with_buffers_method(void) {
+  static const SSL_METHOD kMethod = {
+      0,
+      &kTLSProtocolMethod,
+      &ssl_noop_x509_method,
+  };
+  return &kMethod;
+}
+
 /* Legacy version-locked methods. */
 
 const SSL_METHOD *TLSv1_2_method(void) {
@@ -220,72 +288,3 @@
 const SSL_METHOD *TLS_client_method(void) {
   return TLS_method();
 }
-
-static int ssl_noop_x509_check_client_CA_names(
-    STACK_OF(CRYPTO_BUFFER) *names) {
-  return 1;
-}
-
-static void ssl_noop_x509_clear(CERT *cert) {}
-static void ssl_noop_x509_free(CERT *cert) {}
-static void ssl_noop_x509_dup(CERT *new_cert, const CERT *cert) {}
-static void ssl_noop_x509_flush_cached_leaf(CERT *cert) {}
-static void ssl_noop_x509_flush_cached_chain(CERT *cert) {}
-static int ssl_noop_x509_session_cache_objects(SSL_SESSION *sess) {
-  return 1;
-}
-static int ssl_noop_x509_session_dup(SSL_SESSION *new_session,
-                                       const SSL_SESSION *session) {
-  return 1;
-}
-static void ssl_noop_x509_session_clear(SSL_SESSION *session) {}
-static int ssl_noop_x509_session_verify_cert_chain(SSL_SESSION *session,
-                                                   SSL *ssl) {
-  if (!ssl->ctx->i_promise_to_verify_certs_after_the_handshake) {
-    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNKNOWN_CA);
-    OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_VERIFY_FAILED);
-    return 0;
-  }
-
-  session->verify_result = X509_V_OK;
-  return 1;
-}
-
-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_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) {}
-
-static const SSL_X509_METHOD ssl_noop_x509_method = {
-  ssl_noop_x509_check_client_CA_names,
-  ssl_noop_x509_clear,
-  ssl_noop_x509_free,
-  ssl_noop_x509_dup,
-  ssl_noop_x509_flush_cached_chain,
-  ssl_noop_x509_flush_cached_leaf,
-  ssl_noop_x509_session_cache_objects,
-  ssl_noop_x509_session_dup,
-  ssl_noop_x509_session_clear,
-  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_flush_cached_client_CA,
-  ssl_noop_x509_ssl_auto_chain_if_needed,
-  ssl_noop_x509_ssl_ctx_new,
-  ssl_noop_x509_ssl_ctx_free,
-  ssl_noop_x509_ssl_ctx_flush_cached_client_CA,
-};
-
-const SSL_METHOD *TLS_with_buffers_method(void) {
-  static const SSL_METHOD kMethod = {
-      0,
-      &kTLSProtocolMethod,
-      &ssl_noop_x509_method,
-  };
-  return &kMethod;
-}
diff --git a/src/ssl/tls_record.cc b/src/ssl/tls_record.cc
index 4708296..437d02f 100644
--- a/src/ssl/tls_record.cc
+++ b/src/ssl/tls_record.cc
@@ -119,6 +119,8 @@
 #include "../crypto/internal.h"
 
 
+namespace bssl {
+
 /* kMaxEmptyRecords is the number of consecutive, empty records that will be
  * processed. Without this limit an attacker could send empty records at a
  * faster rate than we can process and cause record processing to loop
@@ -140,10 +142,10 @@
  * state needs record-splitting and zero otherwise. */
 static int ssl_needs_record_splitting(const SSL *ssl) {
 #if !defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  return ssl->s3->aead_write_ctx != NULL &&
-         ssl->s3->aead_write_ctx->version < TLS1_1_VERSION &&
+  return !ssl->s3->aead_write_ctx->is_null_cipher() &&
+         ssl->s3->aead_write_ctx->version() < TLS1_1_VERSION &&
          (ssl->mode & SSL_MODE_CBC_RECORD_SPLITTING) != 0 &&
-         SSL_CIPHER_is_block_cipher(ssl->s3->aead_write_ctx->cipher);
+         SSL_CIPHER_is_block_cipher(ssl->s3->aead_write_ctx->cipher());
 #else
   return 0;
 #endif
@@ -168,38 +170,19 @@
     header_len = SSL3_RT_HEADER_LENGTH;
   }
 
-  return header_len + SSL_AEAD_CTX_explicit_nonce_len(ssl->s3->aead_read_ctx);
+  return header_len + ssl->s3->aead_read_ctx->ExplicitNonceLen();
 }
 
 size_t ssl_seal_align_prefix_len(const SSL *ssl) {
   if (SSL_is_dtls(ssl)) {
-    return DTLS1_RT_HEADER_LENGTH +
-           SSL_AEAD_CTX_explicit_nonce_len(ssl->s3->aead_write_ctx);
+    return DTLS1_RT_HEADER_LENGTH + ssl->s3->aead_write_ctx->ExplicitNonceLen();
   }
 
-  size_t ret = SSL3_RT_HEADER_LENGTH +
-               SSL_AEAD_CTX_explicit_nonce_len(ssl->s3->aead_write_ctx);
+  size_t ret =
+      SSL3_RT_HEADER_LENGTH + ssl->s3->aead_write_ctx->ExplicitNonceLen();
   if (ssl_needs_record_splitting(ssl)) {
     ret += SSL3_RT_HEADER_LENGTH;
-    ret += ssl_cipher_get_record_split_len(ssl->s3->aead_write_ctx->cipher);
-  }
-  return ret;
-}
-
-size_t SSL_max_seal_overhead(const SSL *ssl) {
-  if (SSL_is_dtls(ssl)) {
-    return dtls_max_seal_overhead(ssl, dtls1_use_current_epoch);
-  }
-
-  size_t ret = SSL3_RT_HEADER_LENGTH;
-  ret += SSL_AEAD_CTX_max_overhead(ssl->s3->aead_write_ctx);
-  /* TLS 1.3 needs an extra byte for the encrypted record type. */
-  if (ssl->s3->aead_write_ctx != NULL &&
-      ssl->s3->aead_write_ctx->version >= TLS1_3_VERSION) {
-    ret += 1;
-  }
-  if (ssl_needs_record_splitting(ssl)) {
-    ret *= 2;
+    ret += ssl_cipher_get_record_split_len(ssl->s3->aead_write_ctx->cipher());
   }
   return ret;
 }
@@ -223,7 +206,7 @@
   }
 
   int version_ok;
-  if (ssl->s3->aead_read_ctx == NULL) {
+  if (ssl->s3->aead_read_ctx->is_null_cipher()) {
     /* Only check the first byte. Enforcing beyond that can prevent decoding
      * version negotiation failure alerts. */
     version_ok = (version >> 8) == SSL3_VERSION_MAJOR;
@@ -263,17 +246,16 @@
   /* Skip early data received when expecting a second ClientHello if we rejected
    * 0RTT. */
   if (ssl->s3->skip_early_data &&
-      ssl->s3->aead_read_ctx == NULL &&
+      ssl->s3->aead_read_ctx->is_null_cipher() &&
       type == SSL3_RT_APPLICATION_DATA) {
     goto skipped_data;
   }
 
   /* Decrypt the body in-place. */
-  if (!SSL_AEAD_CTX_open(ssl->s3->aead_read_ctx, out, type, version,
-                         ssl->s3->read_sequence, (uint8_t *)CBS_data(&body),
-                         CBS_len(&body))) {
-    if (ssl->s3->skip_early_data &&
-        ssl->s3->aead_read_ctx != NULL) {
+  if (!ssl->s3->aead_read_ctx->Open(out, type, version, ssl->s3->read_sequence,
+                                    (uint8_t *)CBS_data(&body),
+                                    CBS_len(&body))) {
+    if (ssl->s3->skip_early_data && !ssl->s3->aead_read_ctx->is_null_cipher()) {
       ERR_clear_error();
       goto skipped_data;
     }
@@ -291,8 +273,8 @@
   }
 
   /* TLS 1.3 hides the record type inside the encrypted data. */
-  if (ssl->s3->aead_read_ctx != NULL &&
-      ssl->s3->aead_read_ctx->version >= TLS1_3_VERSION) {
+  if (!ssl->s3->aead_read_ctx->is_null_cipher() &&
+      ssl->s3->aead_read_ctx->version() >= TLS1_3_VERSION) {
     /* The outer record type is always application_data. */
     if (type != SSL3_RT_APPLICATION_DATA) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_OUTER_RECORD_TYPE);
@@ -373,8 +355,8 @@
   /* TLS 1.3 hides the actual record type inside the encrypted data. */
   uint8_t *extra_in = NULL;
   size_t extra_in_len = 0;
-  if (ssl->s3->aead_write_ctx != NULL &&
-      ssl->s3->aead_write_ctx->version >= TLS1_3_VERSION) {
+  if (!ssl->s3->aead_write_ctx->is_null_cipher() &&
+      ssl->s3->aead_write_ctx->version() >= TLS1_3_VERSION) {
     extra_in = &type;
     extra_in_len = 1;
     out_prefix[0] = SSL3_RT_APPLICATION_DATA;
@@ -397,18 +379,17 @@
   out_prefix[2] = wire_version & 0xff;
 
   /* Write the ciphertext, leaving two bytes for the length. */
-  if (!SSL_AEAD_CTX_seal_scatter(
-          ssl->s3->aead_write_ctx, out_prefix + SSL3_RT_HEADER_LENGTH, out,
-          out_suffix, out_suffix_len, max_out_suffix_len, type, wire_version,
-          ssl->s3->write_sequence, in, in_len, extra_in, extra_in_len) ||
+  if (!ssl->s3->aead_write_ctx->SealScatter(
+          out_prefix + SSL3_RT_HEADER_LENGTH, out, out_suffix, out_suffix_len,
+          max_out_suffix_len, type, wire_version, ssl->s3->write_sequence, in,
+          in_len, extra_in, extra_in_len) ||
       !ssl_record_sequence_update(ssl->s3->write_sequence, 8)) {
     return 0;
   }
 
   /* Fill in the length. */
   const size_t ciphertext_len =
-      SSL_AEAD_CTX_explicit_nonce_len(ssl->s3->aead_write_ctx) + in_len +
-      *out_suffix_len;
+      ssl->s3->aead_write_ctx->ExplicitNonceLen() + in_len + *out_suffix_len;
   if (ciphertext_len >= 1 << 15) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
     return 0;
@@ -430,10 +411,10 @@
      * will be placed in the prefix, as will four of the five bytes of the
      * record header for the main record. The final byte will replace the first
      * byte of the plaintext that was used in the small record. */
-    ret += ssl_cipher_get_record_split_len(ssl->s3->aead_write_ctx->cipher);
+    ret += ssl_cipher_get_record_split_len(ssl->s3->aead_write_ctx->cipher());
     ret += SSL3_RT_HEADER_LENGTH - 1;
   } else {
-    ret += SSL_AEAD_CTX_explicit_nonce_len(ssl->s3->aead_write_ctx);
+    ret += ssl->s3->aead_write_ctx->ExplicitNonceLen();
   }
   return ret;
 }
@@ -441,7 +422,7 @@
 /* tls_seal_scatter_record seals a new record of type |type| and body |in| and
  * splits it between |out_prefix|, |out|, and |out_suffix|. Exactly
  * |tls_seal_scatter_prefix_len| bytes are written to |out_prefix|, |in_len|
- * bytes to |out|, and up to 1 + |SSL_AEAD_CTX_max_overhead| bytes to
+ * bytes to |out|, and up to 1 + |SSLAEADContext::MaxOverhead| bytes to
  * |out_suffix|. |*out_suffix_len| is set to the actual number of bytes written
  * to |out_suffix|. It returns one on success and zero on error. If enabled,
  * |tls_seal_scatter_record| implements TLS 1.0 CBC 1/n-1 record splitting and
@@ -452,7 +433,7 @@
                                    const uint8_t *in, size_t in_len) {
   if (type == SSL3_RT_APPLICATION_DATA && in_len > 1 &&
       ssl_needs_record_splitting(ssl)) {
-    assert(SSL_AEAD_CTX_explicit_nonce_len(ssl->s3->aead_write_ctx) == 0);
+    assert(ssl->s3->aead_write_ctx->ExplicitNonceLen() == 0);
     const size_t prefix_len = SSL3_RT_HEADER_LENGTH;
 
     /* Write the 1-byte fragment into |out_prefix|. */
@@ -461,8 +442,7 @@
 
     /* TODO(martinkr): Make AEAD code not complain if max_suffix_len is lower
      * than |EVP_AEAD_max_overhead| but still sufficiently large. */
-    size_t split_max_suffix_len =
-        SSL_AEAD_CTX_max_suffix_len(ssl->s3->aead_write_ctx, 0);
+    size_t split_max_suffix_len = ssl->s3->aead_write_ctx->MaxSuffixLen(0);
     size_t split_suffix_len = 0;
     if (!do_seal_record(ssl, out_prefix, split_body, split_suffix,
                         &split_suffix_len, split_max_suffix_len, type, in, 1)) {
@@ -472,7 +452,7 @@
     size_t split_record_len = prefix_len + 1 + split_suffix_len;
 
     assert(SSL3_RT_HEADER_LENGTH + ssl_cipher_get_record_split_len(
-                                       ssl->s3->aead_write_ctx->cipher) ==
+                                       ssl->s3->aead_write_ctx->cipher()) ==
            split_record_len);
 
     /* Write the n-1-byte fragment. The header gets split between |out_prefix|
@@ -586,3 +566,25 @@
   OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_ALERT_TYPE);
   return ssl_open_record_error;
 }
+
+}  // namespace bssl
+
+using namespace bssl;
+
+size_t SSL_max_seal_overhead(const SSL *ssl) {
+  if (SSL_is_dtls(ssl)) {
+    return dtls_max_seal_overhead(ssl, dtls1_use_current_epoch);
+  }
+
+  size_t ret = SSL3_RT_HEADER_LENGTH;
+  ret += ssl->s3->aead_write_ctx->MaxOverhead();
+  /* TLS 1.3 needs an extra byte for the encrypted record type. */
+  if (!ssl->s3->aead_write_ctx->is_null_cipher() &&
+      ssl->s3->aead_write_ctx->version() >= TLS1_3_VERSION) {
+    ret += 1;
+  }
+  if (ssl_needs_record_splitting(ssl)) {
+    ret *= 2;
+  }
+  return ret;
+}
diff --git a/src/util/all_tests.go b/src/util/all_tests.go
index 235db05..d3f9203 100644
--- a/src/util/all_tests.go
+++ b/src/util/all_tests.go
@@ -162,7 +162,16 @@
 }
 
 func sdeOf(cpu, path string, args ...string) *exec.Cmd {
-	sdeArgs := []string{"-" + cpu, "--", path}
+	sdeArgs := []string{"-" + cpu}
+	// The kernel's vdso code for gettimeofday sometimes uses the RDTSCP
+	// instruction. Although SDE has a -chip_check_vsyscall flag that
+	// excludes such code by default, it does not seem to work. Instead,
+	// pass the -chip_check_exe_only flag which retains test coverage when
+	// statically linked and excludes the vdso.
+	if cpu == "p4p" || cpu == "pnr" || cpu == "mrm" || cpu == "slt" {
+		sdeArgs = append(sdeArgs, "-chip_check_exe_only")
+	}
+	sdeArgs = append(sdeArgs, "--", path)
 	sdeArgs = append(sdeArgs, args...)
 	return exec.Command(*sdePath, sdeArgs...)
 }