Migrate TLS 1.2 and below state machines to the new style.

Bug: 128
Change-Id: Ief3779b1c43dd34a154a0f1d2f94d0da756bc07a
Reviewed-on: https://boringssl-review.googlesource.com/19144
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/ssl/CMakeLists.txt b/ssl/CMakeLists.txt
index d9d2eb5..912327b 100644
--- a/ssl/CMakeLists.txt
+++ b/ssl/CMakeLists.txt
@@ -11,6 +11,7 @@
   d1_srtp.cc
   dtls_method.cc
   dtls_record.cc
+  handshake.cc
   handshake_client.cc
   handshake_server.cc
   s3_both.cc
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
new file mode 100644
index 0000000..ec6213f
--- /dev/null
+++ b/ssl/handshake.cc
@@ -0,0 +1,566 @@
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * This package is an SSL implementation written
+ * by Eric Young (eay@cryptsoft.com).
+ * The implementation was written so as to conform with Netscapes SSL.
+ *
+ * This library is free for commercial and non-commercial use as long as
+ * the following conditions are aheared to.  The following conditions
+ * apply to all code found in this distribution, be it the RC4, RSA,
+ * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
+ * included with this distribution is covered by the same copyright terms
+ * except that the holder is Tim Hudson (tjh@cryptsoft.com).
+ *
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * If this package is used in a product, Eric Young should be given attribution
+ * as the author of the parts of the library used.
+ * This can be in the form of a textual message at program startup or
+ * in documentation (online or textual) provided with the package.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *    "This product includes cryptographic software written by
+ *     Eric Young (eay@cryptsoft.com)"
+ *    The word 'cryptographic' can be left out if the rouines from the library
+ *    being used are not cryptographic related :-).
+ * 4. If you include any Windows specific code (or a derivative thereof) from
+ *    the apps directory (application code) you must include an acknowledgement:
+ *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * The licence and distribution terms for any publically available version or
+ * derivative of this code cannot be changed.  i.e. this code cannot simply be
+ * copied and put under another distribution licence
+ * [including the GNU Public Licence.]
+ */
+/* ====================================================================
+ * Copyright (c) 1998-2002 The OpenSSL Project.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ *    software must display the following acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+ *
+ * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please contact
+ *    openssl-core@openssl.org.
+ *
+ * 5. Products derived from this software may not be called "OpenSSL"
+ *    nor may "OpenSSL" appear in their names without prior written
+ *    permission of the OpenSSL Project.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This product includes cryptographic software written by Eric Young
+ * (eay@cryptsoft.com).  This product includes software written by Tim
+ * Hudson (tjh@cryptsoft.com). */
+/* ====================================================================
+ * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED.
+ * ECC cipher suite support in OpenSSL originally developed by
+ * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */
+
+#include <openssl/ssl.h>
+
+#include <assert.h>
+
+#include "../crypto/internal.h"
+#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),
+      received_custom_extension(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) {
+  UniquePtr<SSL_HANDSHAKE> hs = MakeUnique<SSL_HANDSHAKE>(ssl);
+  if (!hs ||
+      !hs->transcript.Init()) {
+    return nullptr;
+  }
+  return hs.release();
+}
+
+void ssl_handshake_free(SSL_HANDSHAKE *hs) { Delete(hs); }
+
+int ssl_check_message_type(SSL *ssl, const SSLMessage &msg, int type) {
+  if (msg.type != type) {
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
+    ERR_add_error_dataf("got type %d, wanted type %d", msg.type, type);
+    return 0;
+  }
+
+  return 1;
+}
+
+int ssl_add_message_cbb(SSL *ssl, CBB *cbb) {
+  uint8_t *msg;
+  size_t len;
+  if (!ssl->method->finish_message(ssl, cbb, &msg, &len) ||
+      !ssl->method->add_message(ssl, msg, len)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+size_t ssl_max_handshake_message_len(const SSL *ssl) {
+  /* kMaxMessageLen is the default maximum message size for handshakes which do
+   * not accept peer certificate chains. */
+  static const size_t kMaxMessageLen = 16384;
+
+  if (SSL_in_init(ssl)) {
+    if ((!ssl->server || (ssl->verify_mode & SSL_VERIFY_PEER)) &&
+        kMaxMessageLen < ssl->max_cert_list) {
+      return ssl->max_cert_list;
+    }
+    return kMaxMessageLen;
+  }
+
+  if (ssl3_protocol_version(ssl) < TLS1_3_VERSION) {
+    /* In TLS 1.2 and below, the largest acceptable post-handshake message is
+     * a HelloRequest. */
+    return 0;
+  }
+
+  if (ssl->server) {
+    /* The largest acceptable post-handshake message for a server is a
+     * KeyUpdate. We will never initiate post-handshake auth. */
+    return 1;
+  }
+
+  /* Clients must accept NewSessionTicket and CertificateRequest, so allow the
+   * default size. */
+  return kMaxMessageLen;
+}
+
+bool ssl_hash_message(SSL_HANDSHAKE *hs, const SSLMessage &msg) {
+  /* V2ClientHello messages are pre-hashed. */
+  if (msg.is_v2_hello) {
+    return true;
+  }
+
+  return hs->transcript.Update(CBS_data(&msg.raw), CBS_len(&msg.raw));
+}
+
+int ssl_parse_extensions(const CBS *cbs, uint8_t *out_alert,
+                         const SSL_EXTENSION_TYPE *ext_types,
+                         size_t num_ext_types, int ignore_unknown) {
+  /* Reset everything. */
+  for (size_t i = 0; i < num_ext_types; i++) {
+    *ext_types[i].out_present = 0;
+    CBS_init(ext_types[i].out_data, NULL, 0);
+  }
+
+  CBS copy = *cbs;
+  while (CBS_len(&copy) != 0) {
+    uint16_t type;
+    CBS data;
+    if (!CBS_get_u16(&copy, &type) ||
+        !CBS_get_u16_length_prefixed(&copy, &data)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_PARSE_TLSEXT);
+      *out_alert = SSL_AD_DECODE_ERROR;
+      return 0;
+    }
+
+    const SSL_EXTENSION_TYPE *ext_type = NULL;
+    for (size_t i = 0; i < num_ext_types; i++) {
+      if (type == ext_types[i].type) {
+        ext_type = &ext_types[i];
+        break;
+      }
+    }
+
+    if (ext_type == NULL) {
+      if (ignore_unknown) {
+        continue;
+      }
+      OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
+      *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
+      return 0;
+    }
+
+    /* Duplicate ext_types are forbidden. */
+    if (*ext_type->out_present) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DUPLICATE_EXTENSION);
+      *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+      return 0;
+    }
+
+    *ext_type->out_present = 1;
+    *ext_type->out_data = data;
+  }
+
+  return 1;
+}
+
+static void set_crypto_buffer(CRYPTO_BUFFER **dest, CRYPTO_BUFFER *src) {
+  /* TODO(davidben): Remove this helper once |SSL_SESSION| can use |UniquePtr|
+   * and |UniquePtr| has up_ref helpers. */
+  CRYPTO_BUFFER_free(*dest);
+  *dest = src;
+  if (src != nullptr) {
+    CRYPTO_BUFFER_up_ref(src);
+  }
+}
+
+enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  const SSL_SESSION *prev_session = ssl->s3->established_session;
+  if (prev_session != NULL) {
+    /* If renegotiating, the server must not change the server certificate. See
+     * https://mitls.org/pages/attacks/3SHAKE. We never resume on renegotiation,
+     * so this check is sufficient to ensure the reported peer certificate never
+     * changes on renegotiation. */
+    assert(!ssl->server);
+    if (sk_CRYPTO_BUFFER_num(prev_session->certs) !=
+        sk_CRYPTO_BUFFER_num(hs->new_session->certs)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_SERVER_CERT_CHANGED);
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      return ssl_verify_invalid;
+    }
+
+    for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(hs->new_session->certs); i++) {
+      const CRYPTO_BUFFER *old_cert =
+          sk_CRYPTO_BUFFER_value(prev_session->certs, i);
+      const CRYPTO_BUFFER *new_cert =
+          sk_CRYPTO_BUFFER_value(hs->new_session->certs, i);
+      if (CRYPTO_BUFFER_len(old_cert) != CRYPTO_BUFFER_len(new_cert) ||
+          OPENSSL_memcmp(CRYPTO_BUFFER_data(old_cert),
+                         CRYPTO_BUFFER_data(new_cert),
+                         CRYPTO_BUFFER_len(old_cert)) != 0) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_SERVER_CERT_CHANGED);
+        ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+        return ssl_verify_invalid;
+      }
+    }
+
+    /* The certificate is identical, so we may skip re-verifying the
+     * certificate. Since we only authenticated the previous one, copy other
+     * authentication from the established session and ignore what was newly
+     * received. */
+    set_crypto_buffer(&hs->new_session->ocsp_response,
+                      prev_session->ocsp_response);
+    set_crypto_buffer(&hs->new_session->signed_cert_timestamp_list,
+                      prev_session->signed_cert_timestamp_list);
+    hs->new_session->verify_result = prev_session->verify_result;
+    return ssl_verify_ok;
+  }
+
+  uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN;
+  enum ssl_verify_result_t ret;
+  if (ssl->custom_verify_callback != nullptr) {
+    ret = ssl->custom_verify_callback(ssl, &alert);
+    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;
+}
+
+enum ssl_hs_wait_t ssl_get_finished(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  SSLMessage msg;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
+  }
+
+  if (!ssl_check_message_type(ssl, msg, SSL3_MT_FINISHED)) {
+    return ssl_hs_error;
+  }
+
+  /* Snapshot the finished hash before incorporating the new message. */
+  uint8_t finished[EVP_MAX_MD_SIZE];
+  size_t finished_len;
+  if (!hs->transcript.GetFinishedMAC(finished, &finished_len,
+                                     SSL_get_session(ssl), !ssl->server,
+                                     ssl3_protocol_version(ssl)) ||
+      !ssl_hash_message(hs, msg)) {
+    return ssl_hs_error;
+  }
+
+  int finished_ok = CBS_mem_equal(&msg.body, finished, finished_len);
+#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
+  finished_ok = 1;
+#endif
+  if (!finished_ok) {
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DIGEST_CHECK_FAILED);
+    return ssl_hs_error;
+  }
+
+  /* Copy the Finished so we can use it for renegotiation checks. */
+  if (ssl->version != SSL3_VERSION) {
+    if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
+        finished_len > sizeof(ssl->s3->previous_server_finished)) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
+
+    if (ssl->server) {
+      OPENSSL_memcpy(ssl->s3->previous_client_finished, finished, finished_len);
+      ssl->s3->previous_client_finished_len = finished_len;
+    } else {
+      OPENSSL_memcpy(ssl->s3->previous_server_finished, finished, finished_len);
+      ssl->s3->previous_server_finished_len = finished_len;
+    }
+  }
+
+  ssl->method->next_message(ssl);
+  return ssl_hs_ok;
+}
+
+int ssl_run_handshake(SSL_HANDSHAKE *hs, int *out_early_return) {
+  SSL *const ssl = hs->ssl;
+  for (;;) {
+    /* Resolve the operation the handshake was waiting on. */
+    switch (hs->wait) {
+      case ssl_hs_error:
+        OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_HANDSHAKE_FAILURE);
+        return -1;
+
+      case ssl_hs_flush: {
+        int ret = ssl->method->flush_flight(ssl);
+        if (ret <= 0) {
+          return ret;
+        }
+        break;
+      }
+
+      case ssl_hs_read_server_hello:
+      case ssl_hs_read_message: {
+        int ret = ssl->method->read_message(ssl);
+        if (ret <= 0) {
+          uint32_t err = ERR_peek_error();
+          if (hs->wait == ssl_hs_read_server_hello &&
+              ERR_GET_LIB(err) == ERR_LIB_SSL &&
+              ERR_GET_REASON(err) == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE) {
+            /* Add a dedicated error code to the queue for a handshake_failure
+             * alert in response to ClientHello. This matches NSS's client
+             * behavior and gives a better error on a (probable) failure to
+             * negotiate initial parameters. Note: this error code comes after
+             * the original one.
+             *
+             * See https://crbug.com/446505. */
+            OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_FAILURE_ON_CLIENT_HELLO);
+          }
+          return ret;
+        }
+        break;
+      }
+
+      case ssl_hs_read_change_cipher_spec: {
+        int ret = ssl->method->read_change_cipher_spec(ssl);
+        if (ret <= 0) {
+          return ret;
+        }
+        break;
+      }
+
+      case ssl_hs_read_end_of_early_data: {
+        if (ssl->s3->hs->can_early_read) {
+          /* While we are processing early data, the handshake returns early. */
+          *out_early_return = 1;
+          return 1;
+        }
+        hs->wait = ssl_hs_ok;
+        break;
+      }
+
+      case ssl_hs_certificate_selection_pending:
+        ssl->rwstate = SSL_CERTIFICATE_SELECTION_PENDING;
+        hs->wait = ssl_hs_ok;
+        return -1;
+
+      case ssl_hs_x509_lookup:
+        ssl->rwstate = SSL_X509_LOOKUP;
+        hs->wait = ssl_hs_ok;
+        return -1;
+
+      case ssl_hs_channel_id_lookup:
+        ssl->rwstate = SSL_CHANNEL_ID_LOOKUP;
+        hs->wait = ssl_hs_ok;
+        return -1;
+
+      case ssl_hs_private_key_operation:
+        ssl->rwstate = SSL_PRIVATE_KEY_OPERATION;
+        hs->wait = ssl_hs_ok;
+        return -1;
+
+      case ssl_hs_pending_session:
+        ssl->rwstate = SSL_PENDING_SESSION;
+        hs->wait = ssl_hs_ok;
+        return -1;
+
+      case ssl_hs_pending_ticket:
+        ssl->rwstate = SSL_PENDING_TICKET;
+        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. */
+        hs->can_early_write = 0;
+        return -1;
+
+      case ssl_hs_early_return:
+        *out_early_return = 1;
+        hs->wait = ssl_hs_ok;
+        return 1;
+
+      case ssl_hs_ok:
+        break;
+    }
+
+    /* Run the state machine again. */
+    hs->wait = ssl->do_handshake(hs);
+    if (hs->wait == ssl_hs_error) {
+      /* Don't loop around to avoid a stray |SSL_R_SSL_HANDSHAKE_FAILURE| the
+       * first time around. */
+      return -1;
+    }
+    if (hs->wait == ssl_hs_ok) {
+      /* The handshake has completed. */
+      return 1;
+    }
+
+    /* Otherwise, loop to the beginning and resolve what was blocking the
+     * handshake. */
+  }
+}
+
+}  // namespace bssl
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index f018c0e..b1c48d8 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -4,21 +4,21 @@
  * This package is an SSL implementation written
  * by Eric Young (eay@cryptsoft.com).
  * The implementation was written so as to conform with Netscapes SSL.
- * 
+ *
  * This library is free for commercial and non-commercial use as long as
  * the following conditions are aheared to.  The following conditions
  * apply to all code found in this distribution, be it the RC4, RSA,
  * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
  * included with this distribution is covered by the same copyright terms
  * except that the holder is Tim Hudson (tjh@cryptsoft.com).
- * 
+ *
  * Copyright remains Eric Young's, and as such any Copyright notices in
  * the code are not to be removed.
  * If this package is used in a product, Eric Young should be given attribution
  * as the author of the parts of the library used.
  * This can be in the form of a textual message at program startup or
  * in documentation (online or textual) provided with the package.
- * 
+ *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
@@ -33,10 +33,10 @@
  *     Eric Young (eay@cryptsoft.com)"
  *    The word 'cryptographic' can be left out if the rouines from the library
  *    being used are not cryptographic related :-).
- * 4. If you include any Windows specific code (or a derivative thereof) from 
+ * 4. If you include any Windows specific code (or a derivative thereof) from
  *    the apps directory (application code) you must include an acknowledgement:
  *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -48,7 +48,7 @@
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
- * 
+ *
  * The licence and distribution terms for any publically available version or
  * derivative of this code cannot be changed.  i.e. this code cannot simply be
  * copied and put under another distribution licence
@@ -62,7 +62,7 @@
  * are met:
  *
  * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer. 
+ *    notice, this list of conditions and the following disclaimer.
  *
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in
@@ -110,7 +110,7 @@
 /* ====================================================================
  * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED.
  *
- * Portions of the attached software ("Contribution") are developed by 
+ * Portions of the attached software ("Contribution") are developed by
  * SUN MICROSYSTEMS, INC., and are contributed to the OpenSSL project.
  *
  * The Contribution is licensed pursuant to the OpenSSL open source
@@ -150,6 +150,7 @@
 #include <openssl/ssl.h>
 
 #include <assert.h>
+#include <limits.h>
 #include <string.h>
 
 #include <utility>
@@ -172,377 +173,32 @@
 
 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_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);
-static int ssl3_send_client_certificate(SSL_HANDSHAKE *hs);
-static int ssl3_send_client_key_exchange(SSL_HANDSHAKE *hs);
-static int ssl3_send_cert_verify(SSL_HANDSHAKE *hs);
-static int ssl3_send_next_proto(SSL_HANDSHAKE *hs);
-static int ssl3_send_channel_id(SSL_HANDSHAKE *hs);
-static int ssl3_get_new_session_ticket(SSL_HANDSHAKE *hs);
-
-int ssl3_connect(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  int ret = -1;
-
-  assert(ssl->handshake_func == ssl3_connect);
-  assert(!ssl->server);
-
-  for (;;) {
-    int state = hs->state;
-
-    switch (hs->state) {
-      case SSL_ST_INIT:
-        ssl_do_info_callback(ssl, SSL_CB_HANDSHAKE_START, 1);
-        hs->state = SSL3_ST_CW_CLNT_HELLO_A;
-        break;
-
-      case SSL3_ST_CW_CLNT_HELLO_A:
-        ret = ssl3_send_client_hello(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (!SSL_is_dtls(ssl) || ssl->d1->send_cookie) {
-          if (hs->early_data_offered) {
-            if (!tls13_init_early_key_schedule(hs) ||
-                !tls13_advance_key_schedule(hs, ssl->session->master_key,
-                                            ssl->session->master_key_length) ||
-                !tls13_derive_early_secrets(hs) ||
-                !tls13_set_traffic_key(ssl, evp_aead_seal,
-                                       hs->early_traffic_secret,
-                                       hs->hash_len)) {
-              ret = -1;
-              goto end;
-            }
-            hs->next_state = SSL3_ST_WRITE_EARLY_DATA;
-          } else {
-            hs->next_state = SSL3_ST_CR_SRVR_HELLO_A;
-          }
-        } else {
-          hs->next_state = DTLS1_ST_CR_HELLO_VERIFY_REQUEST_A;
-        }
-        hs->state = SSL3_ST_CW_FLUSH;
-        break;
-
-      case DTLS1_ST_CR_HELLO_VERIFY_REQUEST_A:
-        assert(SSL_is_dtls(ssl));
-        ret = dtls1_get_hello_verify_request(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        if (ssl->d1->send_cookie) {
-          hs->state = SSL3_ST_CW_CLNT_HELLO_A;
-        } else {
-          hs->state = SSL3_ST_CR_SRVR_HELLO_A;
-        }
-        break;
-
-      case SSL3_ST_WRITE_EARLY_DATA:
-        /* Stash the early data session, so connection properties may be queried
-         * out of it. */
-        hs->in_early_data = 1;
-        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;
-        ret = 1;
-        goto end;
-
-      case SSL3_ST_CR_SRVR_HELLO_A:
-        ret = ssl3_get_server_hello(hs);
-        if (hs->state == SSL_ST_TLS13) {
-          break;
-        }
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (ssl->session != NULL) {
-          hs->state = SSL3_ST_CR_SESSION_TICKET_A;
-        } else {
-          hs->state = SSL3_ST_CR_CERT_A;
-        }
-        break;
-
-      case SSL3_ST_CR_CERT_A:
-        if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-          ret = ssl3_get_server_certificate(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CR_CERT_STATUS_A;
-        break;
-
-      case SSL3_ST_CR_CERT_STATUS_A:
-        if (hs->certificate_status_expected) {
-          ret = ssl3_get_cert_status(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_VERIFY_SERVER_CERT;
-        break;
-
-      case SSL3_ST_VERIFY_SERVER_CERT:
-        if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-          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;
-        break;
-
-      case SSL3_ST_CR_KEY_EXCH_A:
-        ret = ssl3_get_server_key_exchange(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_CR_CERT_REQ_A;
-        break;
-
-      case SSL3_ST_CR_CERT_REQ_A:
-        if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-          ret = ssl3_get_certificate_request(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CR_SRVR_DONE_A;
-        break;
-
-      case SSL3_ST_CR_SRVR_DONE_A:
-        ret = ssl3_get_server_hello_done(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_CW_CERT_A;
-        break;
-
-      case SSL3_ST_CW_CERT_A:
-        if (hs->cert_request) {
-          ret = ssl3_send_client_certificate(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CW_KEY_EXCH_A;
-        break;
-
-      case SSL3_ST_CW_KEY_EXCH_A:
-        ret = ssl3_send_client_key_exchange(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_CW_CERT_VRFY_A;
-        break;
-
-      case SSL3_ST_CW_CERT_VRFY_A:
-        if (hs->cert_request && ssl_has_certificate(ssl)) {
-          ret = ssl3_send_cert_verify(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CW_CHANGE;
-        break;
-
-      case SSL3_ST_CW_CHANGE:
-        if (!ssl->method->add_change_cipher_spec(ssl) ||
-            !tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_CLIENT_WRITE)) {
-          ret = -1;
-          goto end;
-        }
-
-        hs->state = SSL3_ST_CW_NEXT_PROTO_A;
-        break;
-
-      case SSL3_ST_CW_NEXT_PROTO_A:
-        if (hs->next_proto_neg_seen) {
-          ret = ssl3_send_next_proto(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CW_CHANNEL_ID_A;
-        break;
-
-      case SSL3_ST_CW_CHANNEL_ID_A:
-        if (ssl->s3->tlsext_channel_id_valid) {
-          ret = ssl3_send_channel_id(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CW_FINISHED_A;
-        break;
-
-      case SSL3_ST_CW_FINISHED_A:
-        ret = ssl3_send_finished(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_CW_FLUSH;
-
-        if (ssl->session != NULL) {
-          hs->next_state = SSL3_ST_FINISH_CLIENT_HANDSHAKE;
-        } else {
-          /* This is a non-resumption handshake. If it involves ChannelID, then
-           * record the handshake hashes at this point in the session so that
-           * any resumption of this session with ChannelID can sign those
-           * hashes. */
-          ret = tls1_record_handshake_hashes_for_channel_id(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-          if ((SSL_get_mode(ssl) & SSL_MODE_ENABLE_FALSE_START) &&
-              ssl3_can_false_start(ssl) &&
-              /* No False Start on renegotiation (would complicate the state
-               * machine). */
-              !ssl->s3->initial_handshake_complete) {
-            hs->next_state = SSL3_ST_FALSE_START;
-          } else {
-            hs->next_state = SSL3_ST_CR_SESSION_TICKET_A;
-          }
-        }
-        break;
-
-      case SSL3_ST_FALSE_START:
-        hs->state = SSL3_ST_CR_SESSION_TICKET_A;
-        hs->in_false_start = 1;
-        hs->can_early_write = 1;
-        ret = 1;
-        goto end;
-
-      case SSL3_ST_CR_SESSION_TICKET_A:
-        if (hs->ticket_expected) {
-          ret = ssl3_get_new_session_ticket(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CR_CHANGE;
-        break;
-
-      case SSL3_ST_CR_CHANGE:
-        ret = ssl->method->read_change_cipher_spec(ssl);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (!tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_CLIENT_READ)) {
-          ret = -1;
-          goto end;
-        }
-        hs->state = SSL3_ST_CR_FINISHED_A;
-        break;
-
-      case SSL3_ST_CR_FINISHED_A:
-        ret = ssl3_get_finished(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (ssl->session != NULL) {
-          hs->state = SSL3_ST_CW_CHANGE;
-        } else {
-          hs->state = SSL3_ST_FINISH_CLIENT_HANDSHAKE;
-        }
-        break;
-
-      case SSL3_ST_CW_FLUSH:
-        ret = ssl->method->flush_flight(ssl);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = hs->next_state;
-        break;
-
-      case SSL_ST_TLS13: {
-        int early_return = 0;
-        ret = tls13_handshake(hs, &early_return);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (early_return) {
-          ret = 1;
-          goto end;
-        }
-
-        hs->state = SSL3_ST_FINISH_CLIENT_HANDSHAKE;
-        break;
-      }
-
-      case SSL3_ST_FINISH_CLIENT_HANDSHAKE:
-        ssl->method->on_handshake_complete(ssl);
-
-        SSL_SESSION_free(ssl->s3->established_session);
-        if (ssl->session != NULL) {
-          SSL_SESSION_up_ref(ssl->session);
-          ssl->s3->established_session = ssl->session;
-        } else {
-          /* We make a copy of the session in order to maintain the immutability
-           * of the new established_session due to False Start. The caller may
-           * have taken a reference to the temporary session. */
-          ssl->s3->established_session =
-              SSL_SESSION_dup(hs->new_session.get(), SSL_SESSION_DUP_ALL)
-                  .release();
-          if (ssl->s3->established_session == NULL) {
-            ret = -1;
-            goto end;
-          }
-          /* Renegotiations do not participate in session resumption. */
-          if (!ssl->s3->initial_handshake_complete) {
-            ssl->s3->established_session->not_resumable = 0;
-          }
-
-          hs->new_session.reset();
-        }
-
-        hs->state = SSL_ST_OK;
-        break;
-
-      case SSL_ST_OK: {
-        ssl->s3->initial_handshake_complete = 1;
-        ssl_update_cache(hs, SSL_SESS_CACHE_CLIENT);
-
-        ret = 1;
-        ssl_do_info_callback(ssl, SSL_CB_HANDSHAKE_DONE, 1);
-        goto end;
-      }
-
-      default:
-        OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_STATE);
-        ret = -1;
-        goto end;
-    }
-
-    if (hs->state != state) {
-      ssl_do_info_callback(ssl, SSL_CB_CONNECT_LOOP, 1);
-    }
-  }
-
-end:
-  ssl_do_info_callback(ssl, SSL_CB_CONNECT_EXIT, ret);
-  return ret;
-}
+enum ssl_client_hs_state_t {
+  state_start_connect = 0,
+  state_send_client_hello,
+  state_enter_early_data,
+  state_read_hello_verify_request,
+  state_read_server_hello,
+  state_tls13,
+  state_read_server_certificate,
+  state_read_certificate_status,
+  state_verify_server_certificate,
+  state_read_server_key_exchange,
+  state_read_certificate_request,
+  state_read_server_hello_done,
+  state_send_client_certificate,
+  state_send_client_key_exchange,
+  state_send_client_certificate_verify,
+  state_send_second_client_flight,
+  state_send_channel_id,
+  state_send_client_finished,
+  state_finish_flight,
+  state_read_session_ticket,
+  state_process_change_cipher_spec,
+  state_read_server_finished,
+  state_finish_client_handshake,
+  state_done,
+};
 
 /* ssl_get_client_disabled sets |*out_mask_a| and |*out_mask_k| to masks of
  * disabled algorithms. */
@@ -702,99 +358,6 @@
   return ssl->method->add_message(ssl, msg, len);
 }
 
-static int ssl3_send_client_hello(SSL_HANDSHAKE *hs) {
-  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 (!hs->transcript.Init()) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
-  }
-
-  /* Freeze the version range. */
-  if (!ssl_get_version_range(ssl, &hs->min_version, &hs->max_version)) {
-    return -1;
-  }
-
-  /* Always advertise the ClientHello version from the original maximum version,
-   * even on renegotiation. The static RSA key exchange uses this field, and
-   * some servers fail when it changes across handshakes. */
-  if (SSL_is_dtls(hs->ssl)) {
-    hs->client_version =
-        hs->max_version >= TLS1_2_VERSION ? DTLS1_2_VERSION : DTLS1_VERSION;
-  } else {
-    hs->client_version =
-        hs->max_version >= TLS1_2_VERSION ? TLS1_2_VERSION : hs->max_version;
-  }
-
-  /* If the configured session has expired or was created at a disabled
-   * version, drop it. */
-  if (ssl->session != NULL) {
-    if (ssl->session->is_server ||
-        !ssl_supports_version(hs, ssl->session->ssl_version) ||
-        (ssl->session->session_id_length == 0 &&
-         ssl->session->tlsext_ticklen == 0) ||
-        ssl->session->not_resumable ||
-        !ssl_session_is_time_valid(ssl, ssl->session)) {
-      ssl_set_session(ssl, NULL);
-    }
-  }
-
-  /* If resending the ClientHello in DTLS after a HelloVerifyRequest, don't
-   * renegerate the client_random. The random must be reused. */
-  if ((!SSL_is_dtls(ssl) || !ssl->d1->send_cookie) &&
-      !RAND_bytes(ssl->s3->client_random, sizeof(ssl->s3->client_random))) {
-    return -1;
-  }
-
-  /* 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)) {
-      return -1;
-    }
-  }
-
-  if (!ssl_write_client_hello(hs)) {
-    return -1;
-  }
-
-  return 1;
-}
-
-static int dtls1_get_hello_verify_request(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
-  }
-
-  if (msg.type != DTLS1_MT_HELLO_VERIFY_REQUEST) {
-    ssl->d1->send_cookie = false;
-    return 1;
-  }
-
-  CBS hello_verify_request = msg.body, cookie;
-  uint16_t server_version;
-  if (!CBS_get_u16(&hello_verify_request, &server_version) ||
-      !CBS_get_u8_length_prefixed(&hello_verify_request, &cookie) ||
-      CBS_len(&cookie) > sizeof(ssl->d1->cookie) ||
-      CBS_len(&hello_verify_request) != 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
-  }
-
-  OPENSSL_memcpy(ssl->d1->cookie, CBS_data(&cookie), CBS_len(&cookie));
-  ssl->d1->cookie_len = CBS_len(&cookie);
-
-  ssl->d1->send_cookie = true;
-  ssl->method->next_message(ssl);
-  return 1;
-}
-
 static int parse_server_version(SSL_HANDSHAKE *hs, uint16_t *out,
                                 const SSLMessage &msg) {
   SSL *const ssl = hs->ssl;
@@ -866,34 +429,160 @@
   return 1;
 }
 
-static int ssl3_get_server_hello(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_start_connect(SSL_HANDSHAKE *hs) {
+  ssl_do_info_callback(hs->ssl, SSL_CB_HANDSHAKE_START, 1);
+  hs->state = state_send_client_hello;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_send_client_hello(SSL_HANDSHAKE *hs) {
+  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 (!hs->transcript.Init()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return ssl_hs_error;
+  }
+
+  /* Freeze the version range. */
+  if (!ssl_get_version_range(ssl, &hs->min_version, &hs->max_version)) {
+    return ssl_hs_error;
+  }
+
+  /* Always advertise the ClientHello version from the original maximum version,
+   * even on renegotiation. The static RSA key exchange uses this field, and
+   * some servers fail when it changes across handshakes. */
+  if (SSL_is_dtls(hs->ssl)) {
+    hs->client_version =
+        hs->max_version >= TLS1_2_VERSION ? DTLS1_2_VERSION : DTLS1_VERSION;
+  } else {
+    hs->client_version =
+        hs->max_version >= TLS1_2_VERSION ? TLS1_2_VERSION : hs->max_version;
+  }
+
+  /* If the configured session has expired or was created at a disabled
+   * version, drop it. */
+  if (ssl->session != NULL) {
+    if (ssl->session->is_server ||
+        !ssl_supports_version(hs, ssl->session->ssl_version) ||
+        (ssl->session->session_id_length == 0 &&
+         ssl->session->tlsext_ticklen == 0) ||
+        ssl->session->not_resumable ||
+        !ssl_session_is_time_valid(ssl, ssl->session)) {
+      ssl_set_session(ssl, NULL);
+    }
+  }
+
+  /* If resending the ClientHello in DTLS after a HelloVerifyRequest, don't
+   * renegerate the client_random. The random must be reused. */
+  if ((!SSL_is_dtls(ssl) || !ssl->d1->send_cookie) &&
+      !RAND_bytes(ssl->s3->client_random, sizeof(ssl->s3->client_random))) {
+    return ssl_hs_error;
+  }
+
+  /* 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)) {
+      return ssl_hs_error;
+    }
+  }
+
+  if (!ssl_write_client_hello(hs)) {
+    return ssl_hs_error;
+  }
+
+  hs->state = state_enter_early_data;
+  return ssl_hs_flush;
+}
+
+static enum ssl_hs_wait_t do_enter_early_data(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  if (SSL_is_dtls(ssl) && !ssl->d1->send_cookie) {
+    hs->state = state_read_hello_verify_request;
+    return ssl_hs_ok;
+  }
+
+  if (!hs->early_data_offered) {
+    hs->state = state_read_server_hello;
+    return ssl_hs_ok;
+  }
+
+  if (!tls13_init_early_key_schedule(hs) ||
+      !tls13_advance_key_schedule(hs, ssl->session->master_key,
+                                  ssl->session->master_key_length) ||
+      !tls13_derive_early_secrets(hs) ||
+      !tls13_set_traffic_key(ssl, evp_aead_seal, hs->early_traffic_secret,
+                             hs->hash_len)) {
+    return ssl_hs_error;
+  }
+
+  /* Stash the early data session, so connection properties may be queried out
+   * of it. */
+  hs->in_early_data = 1;
+  SSL_SESSION_up_ref(ssl->session);
+  hs->early_session.reset(ssl->session);
+  hs->can_early_write = 1;
+
+  hs->state = state_read_server_hello;
+  return ssl_hs_early_return;
+}
+
+static enum ssl_hs_wait_t do_read_hello_verify_request(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  assert(SSL_is_dtls(ssl));
+
+  SSLMessage msg;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
+  }
+
+  if (msg.type != DTLS1_MT_HELLO_VERIFY_REQUEST) {
+    ssl->d1->send_cookie = false;
+    hs->state = state_read_server_hello;
+    return ssl_hs_ok;
+  }
+
+  CBS hello_verify_request = msg.body, cookie;
+  uint16_t server_version;
+  if (!CBS_get_u16(&hello_verify_request, &server_version) ||
+      !CBS_get_u8_length_prefixed(&hello_verify_request, &cookie) ||
+      CBS_len(&cookie) > sizeof(ssl->d1->cookie) ||
+      CBS_len(&hello_verify_request) != 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+    return ssl_hs_error;
+  }
+
+  OPENSSL_memcpy(ssl->d1->cookie, CBS_data(&cookie), CBS_len(&cookie));
+  ssl->d1->cookie_len = CBS_len(&cookie);
+
+  ssl->d1->send_cookie = true;
+  ssl->method->next_message(ssl);
+  hs->state = state_send_client_hello;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    uint32_t err = ERR_peek_error();
-    if (ERR_GET_LIB(err) == ERR_LIB_SSL &&
-        ERR_GET_REASON(err) == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE) {
-      /* Add a dedicated error code to the queue for a handshake_failure alert
-       * in response to ClientHello. This matches NSS's client behavior and
-       * gives a better error on a (probable) failure to negotiate initial
-       * parameters. Note: this error code comes after the original one.
-       *
-       * See https://crbug.com/446505. */
-      OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_FAILURE_ON_CLIENT_HELLO);
-    }
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_server_hello;
   }
 
   uint16_t server_version;
   if (!parse_server_version(hs, &server_version, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   if (!ssl_supports_version(hs, server_version)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_PROTOCOL_VERSION);
-    return -1;
+    return ssl_hs_error;
   }
 
   assert(ssl->s3->have_version == ssl->s3->initial_handshake_complete);
@@ -905,25 +594,24 @@
   } else if (server_version != ssl->version) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_PROTOCOL_VERSION);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
-    hs->state = SSL_ST_TLS13;
-    hs->do_tls13_handshake = tls13_client_handshake;
-    return 1;
+    hs->state = state_tls13;
+    return ssl_hs_ok;
   }
 
   if (hs->early_data_offered) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_VERSION_ON_EARLY_DATA);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_PROTOCOL_VERSION);
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl_clear_tls13_state(hs);
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_SERVER_HELLO)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS server_hello = msg.body, server_random, session_id;
@@ -937,7 +625,7 @@
       !CBS_get_u8(&server_hello, &compression_method)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* Copy over the server random. */
@@ -958,7 +646,7 @@
     ssl_set_session(ssl, NULL);
     if (!ssl_get_new_session(hs, 0 /* client */)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
     /* Note: session_id could be empty. */
     hs->new_session->session_id_length = CBS_len(&session_id);
@@ -971,7 +659,7 @@
     /* unknown cipher */
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CIPHER_RETURNED);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* The cipher must be allowed in the selected version and enabled. */
@@ -983,26 +671,26 @@
       !sk_SSL_CIPHER_find(SSL_get_ciphers(ssl), NULL, cipher)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl->session != NULL) {
     if (ssl->session->ssl_version != ssl->version) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_VERSION_NOT_RETURNED);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return -1;
+      return ssl_hs_error;
     }
     if (ssl->session->cipher != cipher) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_CIPHER_NOT_RETURNED);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return -1;
+      return ssl_hs_error;
     }
     if (!ssl_session_is_context_valid(ssl, ssl->session)) {
       /* This is actually a client application bug. */
       OPENSSL_PUT_ERROR(SSL,
                         SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return -1;
+      return ssl_hs_error;
     }
   } else {
     hs->new_session->cipher = cipher;
@@ -1014,7 +702,7 @@
   if (!hs->transcript.InitHash(ssl3_protocol_version(ssl), hs->new_cipher) ||
       !ssl_hash_message(hs, msg)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* If doing a full handshake, the server may request a client certificate
@@ -1029,13 +717,13 @@
   if (compression_method != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_COMPRESSION_ALGORITHM);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* TLS extensions */
   if (!ssl_parse_serverhello_tlsext(hs, &server_hello)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_PARSE_TLSEXT);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* There should be nothing left over in the record. */
@@ -1043,7 +731,7 @@
     /* wrong packet length */
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl->session != NULL &&
@@ -1054,24 +742,46 @@
       OPENSSL_PUT_ERROR(SSL, SSL_R_RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION);
     }
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+
+  if (ssl->session != NULL) {
+    hs->state = state_read_session_ticket;
+    return ssl_hs_ok;
+  }
+
+  hs->state = state_read_server_certificate;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_server_certificate(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_tls13(SSL_HANDSHAKE *hs) {
+  enum ssl_hs_wait_t wait = tls13_client_handshake(hs);
+  if (wait == ssl_hs_ok) {
+    hs->state = state_finish_client_handshake;
+    return ssl_hs_ok;
+  }
+
+  return wait;
+}
+
+static enum ssl_hs_wait_t do_read_server_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (!ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
+    hs->state = state_read_certificate_status;
+    return ssl_hs_ok;
+  }
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_CERTIFICATE) ||
       !ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS body = msg.body;
@@ -1080,7 +790,7 @@
   if (!ssl_parse_cert_chain(&alert, &chain, &hs->peer_pubkey, NULL, &body,
                             ssl->ctx->pool)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-    return -1;
+    return ssl_hs_error;
   }
   sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
   hs->new_session->certs = chain.release();
@@ -1090,36 +800,44 @@
       !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;
+    return ssl_hs_error;
   }
 
   if (!ssl_check_leaf_certificate(
           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;
+    return ssl_hs_error;
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+
+  hs->state = state_read_certificate_status;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_cert_status(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_certificate_status(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (!hs->certificate_status_expected) {
+    hs->state = state_verify_server_certificate;
+    return ssl_hs_ok;
+  }
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (msg.type != SSL3_MT_CERTIFICATE_STATUS) {
     /* A server may send status_request in ServerHello and then change
      * its mind about sending CertificateStatus. */
-    return 1;
+    hs->state = state_verify_server_certificate;
+    return ssl_hs_ok;
   }
 
   if (!ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS certificate_status = msg.body, ocsp_response;
@@ -1131,7 +849,7 @@
       CBS_len(&certificate_status) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   CRYPTO_BUFFER_free(hs->new_session->ocsp_response);
@@ -1139,19 +857,40 @@
       CRYPTO_BUFFER_new_from_CBS(&ocsp_response, ssl->ctx->pool);
   if (hs->new_session->ocsp_response == nullptr) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+
+  hs->state = state_verify_server_certificate;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_server_key_exchange(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_verify_server_certificate(SSL_HANDSHAKE *hs) {
+  if (!ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
+    hs->state = state_read_server_key_exchange;
+    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->state = state_verify_server_certificate;
+      return ssl_hs_certificate_verify;
+  }
+
+  hs->state = state_read_server_key_exchange;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_server_key_exchange(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (msg.type != SSL3_MT_SERVER_KEY_EXCHANGE) {
@@ -1159,14 +898,15 @@
     if (ssl_cipher_requires_server_key_exchange(hs->new_cipher)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-      return -1;
+      return ssl_hs_error;
     }
 
-    return 1;
+    hs->state = state_read_certificate_request;
+    return ssl_hs_ok;
   }
 
   if (!ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   uint32_t alg_k = hs->new_cipher->algorithm_mkey;
@@ -1180,7 +920,7 @@
                                      &psk_identity_hint)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
 
     /* Store PSK identity hint for later use, hint is used in
@@ -1194,7 +934,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);
-      return -1;
+      return ssl_hs_error;
     }
 
     /* Save non-empty identity hints as a C string. Empty identity hints we
@@ -1207,7 +947,7 @@
         !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);
-      return -1;
+      return ssl_hs_error;
     }
     hs->peer_psk_identity_hint.reset(raw);
   }
@@ -1223,7 +963,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);
-      return -1;
+      return ssl_hs_error;
     }
     hs->new_session->group_id = group_id;
 
@@ -1231,19 +971,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);
-      return -1;
+      return ssl_hs_error;
     }
 
     /* Initialize ECDH and save the peer public key for later. */
     hs->key_share = SSLKeyShare::Create(group_id);
     if (!hs->key_share ||
         !CBS_stow(&point, &hs->peer_key, &hs->peer_key_len)) {
-      return -1;
+      return ssl_hs_error;
     }
   } 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);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* At this point, |server_key_exchange| contains the signature, if any, while
@@ -1260,19 +1000,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);
-        return -1;
+        return ssl_hs_error;
       }
       uint8_t alert = SSL_AD_DECODE_ERROR;
       if (!tls12_check_peer_sigalg(ssl, &alert, signature_algorithm)) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-        return -1;
+        return ssl_hs_error;
       }
       hs->new_session->peer_signature_algorithm = signature_algorithm;
     } else if (!tls1_get_legacy_signature_algorithm(&signature_algorithm,
                                                     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;
+      return ssl_hs_error;
     }
 
     /* The last field in |server_key_exchange| is the signature. */
@@ -1281,7 +1021,7 @@
         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);
-      return -1;
+      return ssl_hs_error;
     }
 
     ScopedCBB transcript;
@@ -1298,7 +1038,7 @@
         !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);
-      return -1;
+      return ssl_hs_error;
     }
 
     int sig_ok = ssl_public_key_verify(
@@ -1314,7 +1054,7 @@
       /* bad signature */
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SIGNATURE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
   } else {
     /* PSK ciphers are the only supported certificate-less ciphers. */
@@ -1323,32 +1063,39 @@
     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);
-      return -1;
+      return ssl_hs_error;
     }
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_read_certificate_request;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_certificate_request(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_certificate_request(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (!ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
+    hs->state = state_read_server_hello_done;
+    return ssl_hs_ok;
+  }
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (msg.type == SSL3_MT_SERVER_HELLO_DONE) {
     /* If we get here we don't need the handshake buffer as we won't be doing
      * client auth. */
     hs->transcript.FreeBuffer();
-    return 1;
+    hs->state = state_read_server_hello_done;
+    return ssl_hs_ok;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_CERTIFICATE_REQUEST) ||
       !ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   /* Get the certificate types. */
@@ -1356,13 +1103,13 @@
   if (!CBS_get_u8_length_prefixed(&body, &certificate_types)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (!CBS_stow(&certificate_types, &hs->certificate_types,
                 &hs->num_certificate_types)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl3_protocol_version(ssl) >= TLS1_2_VERSION) {
@@ -1371,7 +1118,7 @@
         !tls1_parse_peer_sigalgs(hs, &supported_signature_algorithms)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
   }
 
@@ -1380,59 +1127,68 @@
       ssl_parse_client_CA_list(ssl, &alert, &body);
   if (!ca_names) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (CBS_len(&body) != 0) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   hs->cert_request = 1;
   hs->ca_names = std::move(ca_names);
   ssl->ctx->x509_method->hs_flush_cached_ca_names(hs);
+
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_read_server_hello_done;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_server_hello_done(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_server_hello_done(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_SERVER_HELLO_DONE) ||
       !ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   /* ServerHelloDone is empty. */
   if (CBS_len(&msg.body) != 0) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_send_client_certificate;
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_client_certificate(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_client_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  /* The peer didn't request a certificate. */
+  if (!hs->cert_request) {
+    hs->state = state_send_client_key_exchange;
+    return ssl_hs_ok;
+  }
+
   /* Call cert_cb to update the certificate. */
-  if (ssl->cert->cert_cb) {
-    int ret = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg);
-    if (ret < 0) {
-      ssl->rwstate = SSL_X509_LOOKUP;
-      return -1;
-    }
-    if (ret == 0) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR);
+  if (ssl->cert->cert_cb != NULL) {
+    int rv = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg);
+    if (rv == 0) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return -1;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR);
+      return ssl_hs_error;
+    }
+    if (rv < 0) {
+      hs->state = state_send_client_certificate;
+      return ssl_hs_x509_lookup;
     }
   }
 
@@ -1444,29 +1200,33 @@
     if (ssl->version == SSL3_VERSION) {
       if (!ssl->method->add_alert(ssl, SSL3_AL_WARNING,
                                   SSL_AD_NO_CERTIFICATE)) {
-        return -1;
+        return ssl_hs_error;
       }
-      return 1;
+      hs->state = state_send_client_key_exchange;
+      return ssl_hs_ok;
     }
   }
 
   if (!ssl_on_certificate_selected(hs) ||
       !ssl3_output_cert_chain(ssl)) {
-    return -1;
+    return ssl_hs_error;
   }
-  return 1;
+
+
+  hs->state = state_send_client_key_exchange;
+  return ssl_hs_ok;
 }
 
 static_assert(sizeof(size_t) >= sizeof(unsigned),
               "size_t is smaller than unsigned");
 
-static int ssl3_send_client_key_exchange(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_client_key_exchange(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   ScopedCBB cbb;
   CBB body;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                  SSL3_MT_CLIENT_KEY_EXCHANGE)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   uint8_t *pms = NULL;
@@ -1628,36 +1388,43 @@
   OPENSSL_cleanse(pms, pms_len);
   OPENSSL_free(pms);
 
-  return 1;
+  hs->state = state_send_client_certificate_verify;
+  return ssl_hs_ok;
 
 err:
   if (pms != NULL) {
     OPENSSL_cleanse(pms, pms_len);
     OPENSSL_free(pms);
   }
-  return -1;
+  return ssl_hs_error;
+
 }
 
-static int ssl3_send_cert_verify(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_client_certificate_verify(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  assert(ssl_has_private_key(ssl));
 
+  if (!hs->cert_request || !ssl_has_certificate(ssl)) {
+    hs->state = state_send_second_client_flight;
+    return ssl_hs_ok;
+  }
+
+  assert(ssl_has_private_key(ssl));
   ScopedCBB cbb;
   CBB body, child;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                  SSL3_MT_CERTIFICATE_VERIFY)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   uint16_t signature_algorithm;
   if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
-    return -1;
+    return ssl_hs_error;
   }
   if (ssl3_protocol_version(ssl) >= TLS1_2_VERSION) {
     /* Write out the digest type in TLS 1.2. */
     if (!CBB_add_u16(&body, signature_algorithm)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
   }
 
@@ -1666,7 +1433,7 @@
   uint8_t *ptr;
   if (!CBB_add_u16_length_prefixed(&body, &child) ||
       !CBB_reserve(&child, &ptr, max_sig_len)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   size_t sig_len = max_sig_len;
@@ -1675,21 +1442,21 @@
   if (ssl3_protocol_version(ssl) == SSL3_VERSION) {
     if (ssl->cert->key_method != NULL) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL_FOR_CUSTOM_KEY);
-      return -1;
+      return ssl_hs_error;
     }
 
     uint8_t digest[EVP_MAX_MD_SIZE];
     size_t digest_len;
     if (!hs->transcript.GetSSL3CertVerifyHash(
             digest, &digest_len, hs->new_session.get(), signature_algorithm)) {
-      return -1;
+      return ssl_hs_error;
     }
 
     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;
+      return ssl_hs_error;
     }
   } else {
     switch (ssl_private_key_sign(
@@ -1698,53 +1465,70 @@
       case ssl_private_key_success:
         break;
       case ssl_private_key_failure:
-        return -1;
+        return ssl_hs_error;
       case ssl_private_key_retry:
-        ssl->rwstate = SSL_PRIVATE_KEY_OPERATION;
-        return -1;
+        hs->state = state_send_client_certificate_verify;
+        return ssl_hs_private_key_operation;
     }
   }
 
   if (!CBB_did_write(&child, sig_len) ||
       !ssl_add_message_cbb(ssl, cbb.get())) {
-    return -1;
+    return ssl_hs_error;
   }
 
   /* The handshake buffer is no longer necessary. */
   hs->transcript.FreeBuffer();
-  return 1;
+
+  hs->state = state_send_second_client_flight;
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_next_proto(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_second_client_flight(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  static const uint8_t kZero[32] = {0};
-  size_t padding_len = 32 - ((ssl->s3->next_proto_negotiated_len + 2) % 32);
 
-  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.get())) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
+  if (!ssl->method->add_change_cipher_spec(ssl) ||
+      !tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_CLIENT_WRITE)) {
+    return ssl_hs_error;
   }
 
-  return 1;
+  if (hs->next_proto_neg_seen) {
+    static const uint8_t kZero[32] = {0};
+    size_t padding_len = 32 - ((ssl->s3->next_proto_negotiated_len + 2) % 32);
+
+    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.get())) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
+  }
+
+  hs->state = state_send_channel_id;
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_channel_id(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_channel_id(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (!ssl->s3->tlsext_channel_id_valid) {
+    hs->state = state_send_client_finished;
+    return ssl_hs_ok;
+  }
+
   if (!ssl_do_channel_id_callback(ssl)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl->tlsext_channel_id_private == NULL) {
-    ssl->rwstate = SSL_CHANNEL_ID_LOOKUP;
-    return -1;
+    hs->state = state_send_channel_id;
+    return ssl_hs_channel_id_lookup;
   }
 
   ScopedCBB cbb;
@@ -1753,23 +1537,67 @@
       !tls1_write_channel_id(hs, &body) ||
       !ssl_add_message_cbb(ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
-  return 1;
+  hs->state = state_send_client_finished;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_new_session_ticket(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_client_finished(SSL_HANDSHAKE *hs) {
+  if (!ssl3_send_finished(hs)) {
+    return ssl_hs_error;
+  }
+
+  hs->state = state_finish_flight;
+  return ssl_hs_flush;
+}
+
+static enum ssl_hs_wait_t do_finish_flight(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+  if (ssl->session != NULL) {
+    hs->state = state_finish_client_handshake;
+    return ssl_hs_ok;
+  }
+
+  /* This is a full handshake. If it involves ChannelID, then record the
+   * handshake hashes at this point in the session so that any resumption of
+   * this session with ChannelID can sign those hashes. */
+  if (!tls1_record_handshake_hashes_for_channel_id(hs)) {
+    return ssl_hs_error;
+  }
+
+  hs->state = state_read_session_ticket;
+
+  if ((SSL_get_mode(ssl) & SSL_MODE_ENABLE_FALSE_START) &&
+      ssl3_can_false_start(ssl) &&
+      /* No False Start on renegotiation (would complicate the state
+       * machine). */
+      !ssl->s3->initial_handshake_complete) {
+    hs->in_false_start = 1;
+    hs->can_early_write = 1;
+    return ssl_hs_early_return;
+  }
+
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_session_ticket(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  if (!hs->ticket_expected) {
+    hs->state = state_process_change_cipher_spec;
+    return ssl_hs_read_change_cipher_spec;
+  }
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_NEW_SESSION_TICKET) ||
       !ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS new_session_ticket = msg.body, ticket;
@@ -1779,7 +1607,7 @@
       CBS_len(&new_session_ticket) != 0) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (CBS_len(&ticket) == 0) {
@@ -1788,7 +1616,8 @@
      * |ssl_update_cache| so is cleared here to avoid an unnecessary update. */
     hs->ticket_expected = 0;
     ssl->method->next_message(ssl);
-    return 1;
+    hs->state = state_process_change_cipher_spec;
+    return ssl_hs_read_change_cipher_spec;
   }
 
   SSL_SESSION *session = hs->new_session.get();
@@ -1802,7 +1631,7 @@
     if (!renewed_session) {
       /* This should never happen. */
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
     session = renewed_session.get();
   }
@@ -1812,7 +1641,7 @@
 
   if (!CBS_stow(&ticket, &session->tlsext_tick, &session->tlsext_ticklen)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return -1;
+    return ssl_hs_error;
   }
   session->tlsext_tick_lifetime_hint = tlsext_tick_lifetime_hint;
 
@@ -1822,7 +1651,7 @@
   if (!EVP_Digest(CBS_data(&ticket), CBS_len(&ticket),
                   session->session_id, &session->session_id_length,
                   EVP_sha256(), NULL)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   if (renewed_session) {
@@ -1832,7 +1661,217 @@
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_process_change_cipher_spec;
+  return ssl_hs_read_change_cipher_spec;
 }
 
-}  // namespace bssl
+static enum ssl_hs_wait_t do_process_change_cipher_spec(SSL_HANDSHAKE *hs) {
+  if (!tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_CLIENT_READ)) {
+    return ssl_hs_error;
+  }
+
+  hs->state = state_read_server_finished;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_server_finished(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  enum ssl_hs_wait_t wait = ssl_get_finished(hs);
+  if (wait != ssl_hs_ok) {
+    return wait;
+  }
+
+  if (ssl->session != NULL) {
+    hs->state = state_send_second_client_flight;
+    return ssl_hs_ok;
+  }
+
+  hs->state = state_finish_client_handshake;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_finish_client_handshake(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  ssl->method->on_handshake_complete(ssl);
+
+  SSL_SESSION_free(ssl->s3->established_session);
+  if (ssl->session != NULL) {
+    SSL_SESSION_up_ref(ssl->session);
+    ssl->s3->established_session = ssl->session;
+  } else {
+    /* We make a copy of the session in order to maintain the immutability
+     * of the new established_session due to False Start. The caller may
+     * have taken a reference to the temporary session. */
+    ssl->s3->established_session =
+        SSL_SESSION_dup(hs->new_session.get(), SSL_SESSION_DUP_ALL)
+        .release();
+    if (ssl->s3->established_session == NULL) {
+      return ssl_hs_error;
+    }
+    /* Renegotiations do not participate in session resumption. */
+    if (!ssl->s3->initial_handshake_complete) {
+      ssl->s3->established_session->not_resumable = 0;
+    }
+
+    hs->new_session.reset();
+  }
+
+  ssl->s3->initial_handshake_complete = 1;
+  ssl_update_cache(hs, SSL_SESS_CACHE_CLIENT);
+
+  hs->state = state_done;
+  return ssl_hs_ok;
+}
+
+enum ssl_hs_wait_t ssl_client_handshake(SSL_HANDSHAKE *hs) {
+  while (hs->state != state_done) {
+    enum ssl_hs_wait_t ret = ssl_hs_error;
+    enum ssl_client_hs_state_t state =
+        static_cast<enum ssl_client_hs_state_t>(hs->state);
+    switch (state) {
+      case state_start_connect:
+        ret = do_start_connect(hs);
+        break;
+      case state_send_client_hello:
+        ret = do_send_client_hello(hs);
+        break;
+      case state_enter_early_data:
+        ret = do_enter_early_data(hs);
+        break;
+      case state_read_hello_verify_request:
+        ret = do_read_hello_verify_request(hs);
+        break;
+      case state_read_server_hello:
+        ret = do_read_server_hello(hs);
+        break;
+      case state_tls13:
+        ret = do_tls13(hs);
+        break;
+      case state_read_server_certificate:
+        ret = do_read_server_certificate(hs);
+        break;
+      case state_read_certificate_status:
+        ret = do_read_certificate_status(hs);
+        break;
+      case state_verify_server_certificate:
+        ret = do_verify_server_certificate(hs);
+        break;
+      case state_read_server_key_exchange:
+        ret = do_read_server_key_exchange(hs);
+        break;
+      case state_read_certificate_request:
+        ret = do_read_certificate_request(hs);
+        break;
+      case state_read_server_hello_done:
+        ret = do_read_server_hello_done(hs);
+        break;
+      case state_send_client_certificate:
+        ret = do_send_client_certificate(hs);
+        break;
+      case state_send_client_key_exchange:
+        ret = do_send_client_key_exchange(hs);
+        break;
+      case state_send_client_certificate_verify:
+        ret = do_send_client_certificate_verify(hs);
+        break;
+      case state_send_second_client_flight:
+        ret = do_send_second_client_flight(hs);
+        break;
+      case state_send_channel_id:
+        ret = do_send_channel_id(hs);
+        break;
+      case state_send_client_finished:
+        ret = do_send_client_finished(hs);
+        break;
+      case state_finish_flight:
+        ret = do_finish_flight(hs);
+        break;
+      case state_read_session_ticket:
+        ret = do_read_session_ticket(hs);
+        break;
+      case state_process_change_cipher_spec:
+        ret = do_process_change_cipher_spec(hs);
+        break;
+      case state_read_server_finished:
+        ret = do_read_server_finished(hs);
+        break;
+      case state_finish_client_handshake:
+        ret = do_finish_client_handshake(hs);
+        break;
+      case state_done:
+        ret = ssl_hs_ok;
+        break;
+    }
+
+    if (hs->state != state) {
+      ssl_do_info_callback(hs->ssl, SSL_CB_CONNECT_LOOP, 1);
+    }
+
+    if (ret != ssl_hs_ok) {
+      return ret;
+    }
+  }
+
+  ssl_do_info_callback(hs->ssl, SSL_CB_HANDSHAKE_DONE, 1);
+  return ssl_hs_ok;
+}
+
+const char *ssl_client_handshake_state(SSL_HANDSHAKE *hs) {
+  enum ssl_client_hs_state_t state =
+      static_cast<enum ssl_client_hs_state_t>(hs->state);
+  switch (state) {
+    case state_start_connect:
+      return "TLS client start_connect";
+    case state_send_client_hello:
+      return "TLS client send_client_hello";
+    case state_enter_early_data:
+      return "TLS client enter_early_data";
+    case state_read_hello_verify_request:
+      return "TLS client read_hello_verify_request";
+    case state_read_server_hello:
+      return "TLS client read_server_hello";
+    case state_tls13:
+      return tls13_client_handshake_state(hs);
+    case state_read_server_certificate:
+      return "TLS client read_server_certificate";
+    case state_read_certificate_status:
+      return "TLS client read_certificate_status";
+    case state_verify_server_certificate:
+      return "TLS client verify_server_certificate";
+    case state_read_server_key_exchange:
+      return "TLS client read_server_key_exchange";
+    case state_read_certificate_request:
+      return "TLS client read_certificate_request";
+    case state_read_server_hello_done:
+      return "TLS client read_server_hello_done";
+    case state_send_client_certificate:
+      return "TLS client send_client_certificate";
+    case state_send_client_key_exchange:
+      return "TLS client send_client_key_exchange";
+    case state_send_client_certificate_verify:
+      return "TLS client send_client_certificate_verify";
+    case state_send_second_client_flight:
+      return "TLS client send_second_client_flight";
+    case state_send_channel_id:
+      return "TLS client send_channel_id";
+    case state_send_client_finished:
+      return "TLS client send_client_finished";
+    case state_finish_flight:
+      return "TLS client finish_flight";
+    case state_read_session_ticket:
+      return "TLS client read_session_ticket";
+    case state_process_change_cipher_spec:
+      return "TLS client process_change_cipher_spec";
+    case state_read_server_finished:
+      return "TLS client read_server_finished";
+    case state_finish_client_handshake:
+      return "TLS client finish_client_handshake";
+    case state_done:
+      return "TLS client done";
+  }
+
+  return "TLS client unknown";
+}
+
+}
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index 2d5b85e..b1df9f9 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -4,21 +4,21 @@
  * This package is an SSL implementation written
  * by Eric Young (eay@cryptsoft.com).
  * The implementation was written so as to conform with Netscapes SSL.
- * 
+ *
  * This library is free for commercial and non-commercial use as long as
  * the following conditions are aheared to.  The following conditions
  * apply to all code found in this distribution, be it the RC4, RSA,
  * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
  * included with this distribution is covered by the same copyright terms
  * except that the holder is Tim Hudson (tjh@cryptsoft.com).
- * 
+ *
  * Copyright remains Eric Young's, and as such any Copyright notices in
  * the code are not to be removed.
  * If this package is used in a product, Eric Young should be given attribution
  * as the author of the parts of the library used.
  * This can be in the form of a textual message at program startup or
  * in documentation (online or textual) provided with the package.
- * 
+ *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
@@ -33,10 +33,10 @@
  *     Eric Young (eay@cryptsoft.com)"
  *    The word 'cryptographic' can be left out if the rouines from the library
  *    being used are not cryptographic related :-).
- * 4. If you include any Windows specific code (or a derivative thereof) from 
+ * 4. If you include any Windows specific code (or a derivative thereof) from
  *    the apps directory (application code) you must include an acknowledgement:
  *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -48,7 +48,7 @@
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
- * 
+ *
  * The licence and distribution terms for any publically available version or
  * derivative of this code cannot be changed.  i.e. this code cannot simply be
  * copied and put under another distribution licence
@@ -62,7 +62,7 @@
  * are met:
  *
  * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer. 
+ *    notice, this list of conditions and the following disclaimer.
  *
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in
@@ -110,7 +110,7 @@
 /* ====================================================================
  * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED.
  *
- * Portions of the attached software ("Contribution") are developed by 
+ * Portions of the attached software ("Contribution") are developed by
  * SUN MICROSYSTEMS, INC., and are contributed to the OpenSSL project.
  *
  * The Contribution is licensed pursuant to the OpenSSL open source
@@ -172,284 +172,29 @@
 
 namespace bssl {
 
-static int ssl3_read_client_hello(SSL_HANDSHAKE *hs);
-static int ssl3_select_certificate(SSL_HANDSHAKE *hs);
-static int ssl3_select_parameters(SSL_HANDSHAKE *hs);
-static int ssl3_send_server_hello(SSL_HANDSHAKE *hs);
-static int ssl3_send_server_certificate(SSL_HANDSHAKE *hs);
-static int ssl3_send_server_key_exchange(SSL_HANDSHAKE *hs);
-static int ssl3_send_server_hello_done(SSL_HANDSHAKE *hs);
-static int ssl3_get_client_certificate(SSL_HANDSHAKE *hs);
-static int ssl3_get_client_key_exchange(SSL_HANDSHAKE *hs);
-static int ssl3_get_cert_verify(SSL_HANDSHAKE *hs);
-static int ssl3_get_next_proto(SSL_HANDSHAKE *hs);
-static int ssl3_get_channel_id(SSL_HANDSHAKE *hs);
-static int ssl3_send_server_finished(SSL_HANDSHAKE *hs);
-
-int ssl3_accept(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  int ret = -1;
-
-  assert(ssl->handshake_func == ssl3_accept);
-  assert(ssl->server);
-
-  for (;;) {
-    int state = hs->state;
-
-    switch (hs->state) {
-      case SSL_ST_INIT:
-        ssl_do_info_callback(ssl, SSL_CB_HANDSHAKE_START, 1);
-        hs->state = SSL3_ST_SR_CLNT_HELLO_A;
-        break;
-
-      case SSL3_ST_SR_CLNT_HELLO_A:
-        ret = ssl3_read_client_hello(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_SR_CLNT_HELLO_B;
-        break;
-
-      case SSL3_ST_SR_CLNT_HELLO_B:
-        ret = ssl3_select_certificate(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        if (hs->state != SSL_ST_TLS13) {
-          hs->state = SSL3_ST_SR_CLNT_HELLO_C;
-        }
-        break;
-
-      case SSL3_ST_SR_CLNT_HELLO_C:
-        ret = ssl3_select_parameters(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_SW_SRVR_HELLO_A;
-        break;
-
-      case SSL3_ST_SW_SRVR_HELLO_A:
-        ret = ssl3_send_server_hello(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        if (ssl->session != NULL) {
-          hs->state = SSL3_ST_SW_FINISHED_A;
-        } else {
-          hs->state = SSL3_ST_SW_CERT_A;
-        }
-        break;
-
-      case SSL3_ST_SW_CERT_A:
-        ret = ssl3_send_server_certificate(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_SW_KEY_EXCH_A;
-        break;
-
-      case SSL3_ST_SW_KEY_EXCH_A:
-        if (hs->server_params_len > 0) {
-          ret = ssl3_send_server_key_exchange(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-
-        hs->state = SSL3_ST_SW_SRVR_DONE_A;
-        break;
-
-      case SSL3_ST_SW_SRVR_DONE_A:
-        ret = ssl3_send_server_hello_done(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->next_state = SSL3_ST_SR_CERT_A;
-        hs->state = SSL3_ST_SW_FLUSH;
-        break;
-
-      case SSL3_ST_SR_CERT_A:
-        if (hs->cert_request) {
-          ret = ssl3_get_client_certificate(hs);
-          if (ret <= 0) {
-            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;
-
-      case SSL3_ST_SR_KEY_EXCH_A:
-        ret = ssl3_get_client_key_exchange(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_SR_CERT_VRFY_A;
-        break;
-
-      case SSL3_ST_SR_CERT_VRFY_A:
-        ret = ssl3_get_cert_verify(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        hs->state = SSL3_ST_SR_CHANGE;
-        break;
-
-      case SSL3_ST_SR_CHANGE:
-        ret = ssl->method->read_change_cipher_spec(ssl);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (!tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_SERVER_READ)) {
-          ret = -1;
-          goto end;
-        }
-
-        hs->state = SSL3_ST_SR_NEXT_PROTO_A;
-        break;
-
-      case SSL3_ST_SR_NEXT_PROTO_A:
-        if (hs->next_proto_neg_seen) {
-          ret = ssl3_get_next_proto(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_SR_CHANNEL_ID_A;
-        break;
-
-      case SSL3_ST_SR_CHANNEL_ID_A:
-        if (ssl->s3->tlsext_channel_id_valid) {
-          ret = ssl3_get_channel_id(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_SR_FINISHED_A;
-        break;
-
-      case SSL3_ST_SR_FINISHED_A:
-        ret = ssl3_get_finished(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (ssl->session != NULL) {
-          hs->state = SSL_ST_OK;
-        } else {
-          hs->state = SSL3_ST_SW_FINISHED_A;
-        }
-
-        /* If this is a full handshake with ChannelID then record the handshake
-         * hashes in |hs->new_session| in case we need them to verify a
-         * ChannelID signature on a resumption of this session in the future. */
-        if (ssl->session == NULL && ssl->s3->tlsext_channel_id_valid) {
-          ret = tls1_record_handshake_hashes_for_channel_id(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        break;
-
-      case SSL3_ST_SW_FINISHED_A:
-        ret = ssl3_send_server_finished(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_SW_FLUSH;
-        if (ssl->session != NULL) {
-          hs->next_state = SSL3_ST_SR_CHANGE;
-        } else {
-          hs->next_state = SSL_ST_OK;
-        }
-        break;
-
-      case SSL3_ST_SW_FLUSH:
-        ret = ssl->method->flush_flight(ssl);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        hs->state = hs->next_state;
-        break;
-
-      case SSL_ST_TLS13: {
-        int early_return = 0;
-        ret = tls13_handshake(hs, &early_return);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (early_return) {
-          ret = 1;
-          goto end;
-        }
-
-        hs->state = SSL_ST_OK;
-        break;
-      }
-
-      case SSL_ST_OK:
-        ssl->method->on_handshake_complete(ssl);
-
-        /* If we aren't retaining peer certificates then we can discard it
-         * now. */
-        if (hs->new_session != NULL &&
-            ssl->retain_only_sha256_of_client_certs) {
-          sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
-          hs->new_session->certs = NULL;
-          ssl->ctx->x509_method->session_clear(hs->new_session.get());
-        }
-
-        SSL_SESSION_free(ssl->s3->established_session);
-        if (ssl->session != NULL) {
-          SSL_SESSION_up_ref(ssl->session);
-          ssl->s3->established_session = ssl->session;
-        } else {
-          ssl->s3->established_session = hs->new_session.release();
-          ssl->s3->established_session->not_resumable = 0;
-        }
-
-        ssl->s3->initial_handshake_complete = 1;
-        ssl_update_cache(hs, SSL_SESS_CACHE_SERVER);
-
-        ssl_do_info_callback(ssl, SSL_CB_HANDSHAKE_DONE, 1);
-        ret = 1;
-        goto end;
-
-      default:
-        OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_STATE);
-        ret = -1;
-        goto end;
-    }
-
-    if (hs->state != state) {
-      ssl_do_info_callback(ssl, SSL_CB_ACCEPT_LOOP, 1);
-    }
-  }
-
-end:
-  ssl_do_info_callback(ssl, SSL_CB_ACCEPT_EXIT, ret);
-  return ret;
-}
+enum ssl_server_hs_state_t {
+  state_start_accept = 0,
+  state_read_client_hello,
+  state_select_certificate,
+  state_tls13,
+  state_select_parameters,
+  state_send_server_hello,
+  state_send_server_certificate,
+  state_send_server_key_exchange,
+  state_send_server_hello_done,
+  state_read_client_certificate,
+  state_verify_client_certificate,
+  state_read_client_key_exchange,
+  state_read_client_certificate_verify,
+  state_read_change_cipher_spec,
+  state_process_change_cipher_spec,
+  state_read_next_proto,
+  state_read_channel_id,
+  state_read_client_finished,
+  state_send_server_finished,
+  state_finish_server_handshake,
+  state_done,
+};
 
 int ssl_client_cipher_list_contains_cipher(const SSL_CLIENT_HELLO *client_hello,
                                            uint16_t id) {
@@ -677,36 +422,42 @@
   return nullptr;
 }
 
-static int ssl3_read_client_hello(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_start_accept(SSL_HANDSHAKE *hs) {
+  ssl_do_info_callback(hs->ssl, SSL_CB_HANDSHAKE_START, 1);
+  hs->state = state_read_client_hello;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
+
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_CLIENT_HELLO)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   SSL_CLIENT_HELLO client_hello;
   if (!ssl_client_hello_init(ssl, &client_hello, msg)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* Run the early callback. */
   if (ssl->ctx->select_certificate_cb != NULL) {
     switch (ssl->ctx->select_certificate_cb(&client_hello)) {
       case ssl_select_cert_retry:
-        ssl->rwstate = SSL_CERTIFICATE_SELECTION_PENDING;
-        return -1;
+        return ssl_hs_certificate_selection_pending;
 
       case ssl_select_cert_error:
         /* Connection rejected. */
         OPENSSL_PUT_ERROR(SSL, SSL_R_CONNECTION_REJECTED);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-        return -1;
+        return ssl_hs_error;
 
       default:
         /* fallthrough */;
@@ -715,19 +466,19 @@
 
   /* Freeze the version range after the early callback. */
   if (!ssl_get_version_range(ssl, &hs->min_version, &hs->max_version)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   uint8_t alert = SSL_AD_DECODE_ERROR;
   if (!negotiate_version(hs, &alert, &client_hello)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-    return -1;
+    return ssl_hs_error;
   }
 
   hs->client_version = client_hello.version;
   if (client_hello.random_len != SSL3_RANDOM_SIZE) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
   OPENSSL_memcpy(ssl->s3->client_random, client_hello.random,
                  client_hello.random_len);
@@ -740,24 +491,25 @@
        client_hello.compression_methods_len != 1)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_COMPRESSION_LIST);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* TLS extensions. */
   if (!ssl_parse_clienthello_tlsext(hs, &client_hello)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_PARSE_TLSEXT);
-    return -1;
+    return ssl_hs_error;
   }
 
-  return 1;
+  hs->state = state_select_certificate;
+  return ssl_hs_ok;
 }
 
-static int ssl3_select_certificate(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_select_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   /* Call |cert_cb| to update server certificates if required. */
@@ -766,28 +518,26 @@
     if (rv == 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
     if (rv < 0) {
-      ssl->rwstate = SSL_X509_LOOKUP;
-      return -1;
+      return ssl_hs_x509_lookup;
     }
   }
 
   if (!ssl_on_certificate_selected(hs)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
     /* Jump to the TLS 1.3 state machine. */
-    hs->state = SSL_ST_TLS13;
-    hs->do_tls13_handshake = tls13_server_handshake;
-    return 1;
+    hs->state = state_tls13;
+    return ssl_hs_ok;
   }
 
   SSL_CLIENT_HELLO client_hello;
   if (!ssl_client_hello_init(ssl, &client_hello, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   /* Negotiate the cipher suite. This must be done after |cert_cb| so the
@@ -797,22 +547,34 @@
   if (hs->new_cipher == NULL) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-    return -1;
+    return ssl_hs_error;
   }
 
-  return 1;
+  hs->state = state_select_parameters;
+  return ssl_hs_ok;
 }
 
-static int ssl3_select_parameters(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+static enum ssl_hs_wait_t do_tls13(SSL_HANDSHAKE *hs) {
+  enum ssl_hs_wait_t wait = tls13_server_handshake(hs);
+  if (wait == ssl_hs_ok) {
+    hs->state = state_finish_server_handshake;
+    return ssl_hs_ok;
   }
+
+  return wait;
+}
+
+static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  SSLMessage msg;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
+  }
+
   SSL_CLIENT_HELLO client_hello;
   if (!ssl_client_hello_init(ssl, &client_hello, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   /* Determine whether we are doing session resumption. */
@@ -823,13 +585,11 @@
     case ssl_session_success:
       break;
     case ssl_session_error:
-      return -1;
+      return ssl_hs_error;
     case ssl_session_retry:
-      ssl->rwstate = SSL_PENDING_SESSION;
-      return -1;
+      return ssl_hs_pending_session;
     case ssl_session_ticket_retry:
-      ssl->rwstate = SSL_PENDING_TICKET;
-      return -1;
+      return ssl_hs_pending_ticket;
   }
 
   if (session) {
@@ -838,7 +598,7 @@
        * is fatal to the connection. */
       OPENSSL_PUT_ERROR(SSL, SSL_R_RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-      return -1;
+      return ssl_hs_error;
     }
 
     if (!ssl_session_is_resumable(hs, session.get()) ||
@@ -858,7 +618,7 @@
     hs->ticket_expected = tickets_supported;
     ssl_set_session(ssl, NULL);
     if (!ssl_get_new_session(hs, 1 /* server */)) {
-      return -1;
+      return ssl_hs_error;
     }
 
     /* Clear the session ID if we want the session to be single-use. */
@@ -872,7 +632,7 @@
     /* Connection rejected for DOS reasons. */
     OPENSSL_PUT_ERROR(SSL, SSL_R_CONNECTION_REJECTED);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl->session == NULL) {
@@ -884,7 +644,7 @@
       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;
+        return ssl_hs_error;
       }
     }
 
@@ -912,7 +672,7 @@
   uint8_t alert = SSL_AD_DECODE_ERROR;
   if (!ssl_negotiate_alpn(hs, &alert, &client_hello)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* Now that all parameters are known, initialize the handshake hash and hash
@@ -920,7 +680,7 @@
   if (!hs->transcript.InitHash(ssl3_protocol_version(ssl), hs->new_cipher) ||
       !ssl_hash_message(hs, msg)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* Release the handshake buffer if client authentication isn't required. */
@@ -929,10 +689,12 @@
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+
+  hs->state = state_send_server_hello;
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_server_hello(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
   /* We only accept ChannelIDs on connections with ECDHE in order to avoid a
@@ -957,7 +719,7 @@
   ssl->s3->server_random[2] = now.tv_sec >> 8;
   ssl->s3->server_random[3] = now.tv_sec;
   if (!RAND_bytes(ssl->s3->server_random + 4, SSL3_RANDOM_SIZE - 4)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   /* TODO(davidben): Implement the TLS 1.1 and 1.2 downgrade sentinels once TLS
@@ -981,24 +743,29 @@
       !ssl_add_serverhello_tlsext(hs, &body) ||
       !ssl_add_message_cbb(ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
-  return 1;
+  if (ssl->session != NULL) {
+    hs->state = state_send_server_finished;
+  } else {
+    hs->state = state_send_server_certificate;
+  }
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_server_certificate(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_server_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   ScopedCBB cbb;
 
   if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
     if (!ssl_has_certificate(ssl)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET);
-      return -1;
+      return ssl_hs_error;
     }
 
     if (!ssl3_output_cert_chain(ssl)) {
-      return -1;
+      return ssl_hs_error;
     }
 
     if (hs->certificate_status_expected) {
@@ -1012,7 +779,7 @@
                          CRYPTO_BUFFER_len(ssl->cert->ocsp_response)) ||
           !ssl_add_message_cbb(ssl, cbb.get())) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-        return -1;
+        return ssl_hs_error;
       }
     }
   }
@@ -1029,7 +796,7 @@
     if (!CBB_init(cbb.get(), SSL3_RANDOM_SIZE * 2 + 128) ||
         !CBB_add_bytes(cbb.get(), ssl->s3->client_random, SSL3_RANDOM_SIZE) ||
         !CBB_add_bytes(cbb.get(), ssl->s3->server_random, SSL3_RANDOM_SIZE)) {
-      return -1;
+      return ssl_hs_error;
     }
 
     /* PSK ciphers begin with an identity hint. */
@@ -1039,7 +806,7 @@
       if (!CBB_add_u16_length_prefixed(cbb.get(), &child) ||
           !CBB_add_bytes(&child, (const uint8_t *)ssl->psk_identity_hint,
                          len)) {
-        return -1;
+        return ssl_hs_error;
       }
     }
 
@@ -1049,7 +816,7 @@
       if (!tls1_get_shared_group(hs, &group_id)) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-        return -1;
+        return ssl_hs_error;
        }
       hs->new_session->group_id = group_id;
 
@@ -1060,22 +827,29 @@
           !CBB_add_u16(cbb.get(), group_id) ||
           !CBB_add_u8_length_prefixed(cbb.get(), &child) ||
           !hs->key_share->Offer(&child)) {
-        return -1;
+        return ssl_hs_error;
       }
     } else {
       assert(alg_k & SSL_kPSK);
     }
 
     if (!CBB_finish(cbb.get(), &hs->server_params, &hs->server_params_len)) {
-      return -1;
+      return ssl_hs_error;
     }
   }
 
-  return 1;
+  hs->state = state_send_server_key_exchange;
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_server_key_exchange(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_server_key_exchange(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (hs->server_params_len == 0) {
+    hs->state = state_send_server_hello_done;
+    return ssl_hs_ok;
+  }
+
   ScopedCBB cbb;
   CBB body, child;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
@@ -1084,26 +858,26 @@
       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)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   /* 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);
-      return -1;
+      return ssl_hs_error;
     }
 
     /* Determine the signature algorithm. */
     uint16_t signature_algorithm;
     if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
-      return -1;
+      return ssl_hs_error;
     }
     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);
-        return -1;
+        return ssl_hs_error;
       }
     }
 
@@ -1112,7 +886,7 @@
     uint8_t *ptr;
     if (!CBB_add_u16_length_prefixed(&body, &child) ||
         !CBB_reserve(&child, &ptr, max_sig_len)) {
-      return -1;
+      return ssl_hs_error;
     }
 
     size_t sig_len;
@@ -1121,30 +895,31 @@
                                  hs->server_params_len)) {
       case ssl_private_key_success:
         if (!CBB_did_write(&child, sig_len)) {
-          return -1;
+          return ssl_hs_error;
         }
         break;
       case ssl_private_key_failure:
-        return -1;
+        return ssl_hs_error;
       case ssl_private_key_retry:
-        ssl->rwstate = SSL_PRIVATE_KEY_OPERATION;
-        return -1;
+        return ssl_hs_private_key_operation;
     }
   }
 
   if (!ssl_add_message_cbb(ssl, cbb.get())) {
-    return -1;
+    return ssl_hs_error;
   }
 
   OPENSSL_free(hs->server_params);
   hs->server_params = NULL;
   hs->server_params_len = 0;
 
-  return 1;
+  hs->state = state_send_server_hello_done;
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_server_hello_done(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_server_hello_done(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
   ScopedCBB cbb;
   CBB body;
 
@@ -1162,7 +937,7 @@
         !ssl_add_client_CA_list(ssl, &body) ||
         !ssl_add_message_cbb(ssl, cbb.get())) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
   }
 
@@ -1170,20 +945,24 @@
                                  SSL3_MT_SERVER_HELLO_DONE) ||
       !ssl_add_message_cbb(ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
-  return 1;
+  hs->state = state_read_client_certificate;
+  return ssl_hs_flush;
 }
 
-static int ssl3_get_client_certificate(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_client_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  assert(hs->cert_request);
+
+  if (!hs->cert_request) {
+    hs->state = state_verify_client_certificate;
+    return ssl_hs_ok;
+  }
 
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (msg.type != SSL3_MT_CERTIFICATE) {
@@ -1194,22 +973,23 @@
       if (ssl->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-        return -1;
+        return ssl_hs_error;
       }
 
       /* OpenSSL returns X509_V_OK when no certificates are received. This is
        * classed by them as a bug, but it's assumed by at least NGINX. */
       hs->new_session->verify_result = X509_V_OK;
-      return 1;
+      hs->state = state_verify_client_certificate;
+      return ssl_hs_ok;
     }
 
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (!ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS certificate_msg = msg.body;
@@ -1221,7 +1001,7 @@
                                 : NULL,
                             &certificate_msg, ssl->ctx->pool)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-    return -1;
+    return ssl_hs_error;
   }
   sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
   hs->new_session->certs = chain.release();
@@ -1230,7 +1010,7 @@
       !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;
+    return ssl_hs_error;
   }
 
   if (sk_CRYPTO_BUFFER_num(hs->new_session->certs) == 0) {
@@ -1242,14 +1022,14 @@
     if (ssl->version == SSL3_VERSION) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATES_RETURNED);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-      return -1;
+      return ssl_hs_error;
     }
 
     if (ssl->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
       /* Fail for TLS only if we required a certificate */
       OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-      return -1;
+      return ssl_hs_error;
     }
 
     /* OpenSSL returns X509_V_OK when no certificates are received. This is
@@ -1261,23 +1041,41 @@
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_verify_client_certificate;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_client_key_exchange(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_verify_client_certificate(SSL_HANDSHAKE *hs) {
+  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:
+        return ssl_hs_error;
+      case ssl_verify_retry:
+        return ssl_hs_certificate_verify;
+    }
+  }
+
+  hs->state = state_read_client_key_exchange;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_client_key_exchange(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  ssl_hs_wait_t ret = ssl_hs_error;
   uint8_t *premaster_secret = NULL;
   size_t premaster_secret_len = 0;
   uint8_t *decrypt_buf = NULL;
 
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_CLIENT_KEY_EXCHANGE)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS client_key_exchange = msg.body;
@@ -1346,7 +1144,7 @@
       case ssl_private_key_failure:
         goto err;
       case ssl_private_key_retry:
-        ssl->rwstate = SSL_PRIVATE_KEY_OPERATION;
+        ret = ssl_hs_private_key_operation;
         goto err;
     }
 
@@ -1496,10 +1294,9 @@
   }
   hs->new_session->extended_master_secret = hs->extended_master_secret;
 
-  OPENSSL_cleanse(premaster_secret, premaster_secret_len);
-  OPENSSL_free(premaster_secret);
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_read_client_certificate_verify;
+  ret = ssl_hs_ok;
 
 err:
   if (premaster_secret != NULL) {
@@ -1508,10 +1305,11 @@
   }
   OPENSSL_free(decrypt_buf);
 
-  return -1;
+  return ret;
+
 }
 
-static int ssl3_get_cert_verify(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_client_certificate_verify(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
   /* Only RSA and ECDSA client certificates are supported, so a
@@ -1519,17 +1317,17 @@
    * */
   if (!hs->peer_pubkey) {
     hs->transcript.FreeBuffer();
-    return 1;
+    hs->state = state_read_change_cipher_spec;
+    return ssl_hs_ok;
   }
 
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_CERTIFICATE_VERIFY)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS certificate_verify = msg.body, signature;
@@ -1540,19 +1338,19 @@
     if (!CBS_get_u16(&certificate_verify, &signature_algorithm)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
     uint8_t alert = SSL_AD_DECODE_ERROR;
     if (!tls12_check_peer_sigalg(ssl, &alert, signature_algorithm)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-      return -1;
+      return ssl_hs_error;
     }
     hs->new_session->peer_signature_algorithm = signature_algorithm;
   } else if (!tls1_get_legacy_signature_algorithm(&signature_algorithm,
                                                   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;
+    return ssl_hs_error;
   }
 
   /* Parse and verify the signature. */
@@ -1560,7 +1358,7 @@
       CBS_len(&certificate_verify) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   int sig_ok;
@@ -1571,7 +1369,7 @@
     size_t digest_len;
     if (!hs->transcript.GetSSL3CertVerifyHash(
             digest, &digest_len, hs->new_session.get(), signature_algorithm)) {
-      return -1;
+      return ssl_hs_error;
     }
 
     UniquePtr<EVP_PKEY_CTX> pctx(
@@ -1594,33 +1392,51 @@
   if (!sig_ok) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SIGNATURE);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* The handshake buffer is no longer necessary, and we may hash the current
    * message.*/
   hs->transcript.FreeBuffer();
   if (!ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_read_change_cipher_spec;
+  return ssl_hs_ok;
 }
 
-/* ssl3_get_next_proto reads a Next Protocol Negotiation handshake message. It
- * sets the next_proto member in s if found */
-static int ssl3_get_next_proto(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_change_cipher_spec(SSL_HANDSHAKE *hs) {
+  hs->state = state_process_change_cipher_spec;
+  return ssl_hs_read_change_cipher_spec;
+}
+
+static enum ssl_hs_wait_t do_process_change_cipher_spec(SSL_HANDSHAKE *hs) {
+  if (!tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_SERVER_READ)) {
+    return ssl_hs_error;
+  }
+
+  hs->state = state_read_next_proto;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_next_proto(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (!hs->next_proto_neg_seen) {
+    hs->state = state_read_channel_id;
+    return ssl_hs_ok;
+  }
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_NEXT_PROTO) ||
       !ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS next_protocol = msg.body, selected_protocol, padding;
@@ -1629,37 +1445,68 @@
       CBS_len(&next_protocol) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (!CBS_stow(&selected_protocol, &ssl->s3->next_proto_negotiated,
                 &ssl->s3->next_proto_negotiated_len)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_read_channel_id;
+  return ssl_hs_ok;
 }
 
-/* ssl3_get_channel_id reads and verifies a ClientID handshake message. */
-static int ssl3_get_channel_id(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_channel_id(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (!ssl->s3->tlsext_channel_id_valid) {
+    hs->state = state_read_client_finished;
+    return ssl_hs_ok;
+  }
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_CHANNEL_ID) ||
       !tls1_verify_channel_id(hs, msg) ||
       !ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
+
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_read_client_finished;
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_server_finished(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_client_finished(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  enum ssl_hs_wait_t wait = ssl_get_finished(hs);
+  if (wait != ssl_hs_ok) {
+    return wait;
+  }
+
+  if (ssl->session != NULL) {
+    hs->state = state_finish_server_handshake;
+  } else {
+    hs->state = state_send_server_finished;
+  }
+
+  /* If this is a full handshake with ChannelID then record the handshake
+   * hashes in |hs->new_session| in case we need them to verify a
+   * ChannelID signature on a resumption of this session in the future. */
+  if (ssl->session == NULL && ssl->s3->tlsext_channel_id_valid &&
+      !tls1_record_handshake_hashes_for_channel_id(hs)) {
+    return ssl_hs_error;
+  }
+
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_send_server_finished(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
   if (hs->ticket_expected) {
@@ -1674,7 +1521,7 @@
        * the timeout. */
       session_copy = SSL_SESSION_dup(ssl->session, SSL_SESSION_INCLUDE_NONAUTH);
       if (!session_copy) {
-        return -1;
+        return ssl_hs_error;
       }
 
       ssl_session_rebase_time(ssl, session_copy.get());
@@ -1689,16 +1536,186 @@
         !CBB_add_u16_length_prefixed(&body, &ticket) ||
         !ssl_encrypt_ticket(ssl, &ticket, session) ||
         !ssl_add_message_cbb(ssl, cbb.get())) {
-      return -1;
+      return ssl_hs_error;
     }
   }
 
   if (!ssl->method->add_change_cipher_spec(ssl) ||
-      !tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_SERVER_WRITE)) {
-    return -1;
+      !tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_SERVER_WRITE) ||
+      !ssl3_send_finished(hs)) {
+    return ssl_hs_error;
   }
 
-  return ssl3_send_finished(hs);
+  if (ssl->session != NULL) {
+    hs->state = state_read_change_cipher_spec;
+  } else {
+    hs->state = state_finish_server_handshake;
+  }
+  return ssl_hs_flush;
 }
 
-}  // namespace bssl
+static enum ssl_hs_wait_t do_finish_server_handshake(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  ssl->method->on_handshake_complete(ssl);
+
+  /* If we aren't retaining peer certificates then we can discard it
+   * now. */
+  if (hs->new_session != NULL && ssl->retain_only_sha256_of_client_certs) {
+    sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
+    hs->new_session->certs = NULL;
+    ssl->ctx->x509_method->session_clear(hs->new_session.get());
+  }
+
+  SSL_SESSION_free(ssl->s3->established_session);
+  if (ssl->session != NULL) {
+    SSL_SESSION_up_ref(ssl->session);
+    ssl->s3->established_session = ssl->session;
+  } else {
+    ssl->s3->established_session = hs->new_session.release();
+    ssl->s3->established_session->not_resumable = 0;
+  }
+
+  ssl->s3->initial_handshake_complete = 1;
+  ssl_update_cache(hs, SSL_SESS_CACHE_SERVER);
+
+  hs->state = state_done;
+  return ssl_hs_ok;
+}
+
+enum ssl_hs_wait_t ssl_server_handshake(SSL_HANDSHAKE *hs) {
+  while (hs->state != state_done) {
+    enum ssl_hs_wait_t ret = ssl_hs_error;
+    enum ssl_server_hs_state_t state =
+        static_cast<enum ssl_server_hs_state_t>(hs->state);
+    switch (state) {
+      case state_start_accept:
+        ret = do_start_accept(hs);
+        break;
+      case state_read_client_hello:
+        ret = do_read_client_hello(hs);
+        break;
+      case state_select_certificate:
+        ret = do_select_certificate(hs);
+        break;
+      case state_tls13:
+        ret = do_tls13(hs);
+        break;
+      case state_select_parameters:
+        ret = do_select_parameters(hs);
+        break;
+      case state_send_server_hello:
+        ret = do_send_server_hello(hs);
+        break;
+      case state_send_server_certificate:
+        ret = do_send_server_certificate(hs);
+        break;
+      case state_send_server_key_exchange:
+        ret = do_send_server_key_exchange(hs);
+        break;
+      case state_send_server_hello_done:
+        ret = do_send_server_hello_done(hs);
+        break;
+      case state_read_client_certificate:
+        ret = do_read_client_certificate(hs);
+        break;
+      case state_verify_client_certificate:
+        ret = do_verify_client_certificate(hs);
+        break;
+      case state_read_client_key_exchange:
+        ret = do_read_client_key_exchange(hs);
+        break;
+      case state_read_client_certificate_verify:
+        ret = do_read_client_certificate_verify(hs);
+        break;
+      case state_read_change_cipher_spec:
+        ret = do_read_change_cipher_spec(hs);
+        break;
+      case state_process_change_cipher_spec:
+        ret = do_process_change_cipher_spec(hs);
+        break;
+      case state_read_next_proto:
+        ret = do_read_next_proto(hs);
+        break;
+      case state_read_channel_id:
+        ret = do_read_channel_id(hs);
+        break;
+      case state_read_client_finished:
+        ret = do_read_client_finished(hs);
+        break;
+      case state_send_server_finished:
+        ret = do_send_server_finished(hs);
+        break;
+      case state_finish_server_handshake:
+        ret = do_finish_server_handshake(hs);
+        break;
+      case state_done:
+        ret = ssl_hs_ok;
+        break;
+    }
+
+    if (hs->state != state) {
+      ssl_do_info_callback(hs->ssl, SSL_CB_ACCEPT_LOOP, 1);
+    }
+
+    if (ret != ssl_hs_ok) {
+      return ret;
+    }
+  }
+
+  ssl_do_info_callback(hs->ssl, SSL_CB_HANDSHAKE_DONE, 1);
+  return ssl_hs_ok;
+}
+
+const char *ssl_server_handshake_state(SSL_HANDSHAKE *hs) {
+  enum ssl_server_hs_state_t state =
+      static_cast<enum ssl_server_hs_state_t>(hs->state);
+  switch (state) {
+    case state_start_accept:
+      return "TLS server start_accept";
+    case state_read_client_hello:
+      return "TLS server read_client_hello";
+    case state_select_certificate:
+      return "TLS server select_certificate";
+    case state_tls13:
+      return tls13_server_handshake_state(hs);
+    case state_select_parameters:
+      return "TLS server select_parameters";
+    case state_send_server_hello:
+      return "TLS server send_server_hello";
+    case state_send_server_certificate:
+      return "TLS server send_server_certificate";
+    case state_send_server_key_exchange:
+      return "TLS server send_server_key_exchange";
+    case state_send_server_hello_done:
+      return "TLS server send_server_hello_done";
+    case state_read_client_certificate:
+      return "TLS server read_client_certificate";
+    case state_verify_client_certificate:
+      return "TLS server verify_client_certificate";
+    case state_read_client_key_exchange:
+      return "TLS server read_client_key_exchange";
+    case state_read_client_certificate_verify:
+      return "TLS server read_client_certificate_verify";
+    case state_read_change_cipher_spec:
+      return "TLS server read_change_cipher_spec";
+    case state_process_change_cipher_spec:
+      return "TLS server process_change_cipher_spec";
+    case state_read_next_proto:
+      return "TLS server read_next_proto";
+    case state_read_channel_id:
+      return "TLS server read_channel_id";
+    case state_read_client_finished:
+      return "TLS server read_client_finished";
+    case state_send_server_finished:
+      return "TLS server send_server_finished";
+    case state_finish_server_handshake:
+      return "TLS server finish_server_handshake";
+    case state_done:
+      return "TLS server done";
+  }
+
+  return "TLS server unknown";
+}
+
+}
diff --git a/ssl/internal.h b/ssl/internal.h
index b9c3998..d3efdae 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -844,11 +844,6 @@
  * in a handshake message for |ssl|. */
 size_t ssl_max_handshake_message_len(const SSL *ssl);
 
-/* ssl_read_message reads a message for the old |BIO|-based state machine. On
- * success, it returns one and sets |*out| to the current message. Otherwise, it
- * returns <= 0. */
-int ssl_read_message(SSL *ssl, SSLMessage *out);
-
 /* dtls_clear_incoming_messages releases all buffered incoming messages. */
 void dtls_clear_incoming_messages(SSL *ssl);
 
@@ -1070,12 +1065,16 @@
 enum ssl_hs_wait_t {
   ssl_hs_error,
   ssl_hs_ok,
+  ssl_hs_read_server_hello,
   ssl_hs_read_message,
   ssl_hs_flush,
+  ssl_hs_certificate_selection_pending,
   ssl_hs_x509_lookup,
   ssl_hs_channel_id_lookup,
   ssl_hs_private_key_operation,
+  ssl_hs_pending_session,
   ssl_hs_pending_ticket,
+  ssl_hs_early_return,
   ssl_hs_early_data_rejected,
   ssl_hs_read_end_of_early_data,
   ssl_hs_read_change_cipher_spec,
@@ -1090,23 +1089,16 @@
   /* ssl is a non-owning pointer to the parent |SSL| object. */
   SSL *ssl;
 
-  /* do_tls13_handshake runs the TLS 1.3 handshake. On completion, it returns
-   * |ssl_hs_ok|. Otherwise, it returns a value corresponding to what operation
-   * is needed to progress. */
-  enum ssl_hs_wait_t (*do_tls13_handshake)(SSL_HANDSHAKE *hs);
-
-  /* wait contains the operation |do_tls13_handshake| is currently blocking on
-   * or |ssl_hs_ok| if none. */
+  /* wait contains the operation the handshake is currently blocking on or
+   * |ssl_hs_ok| if none. */
   enum ssl_hs_wait_t wait = ssl_hs_ok;
 
-  /* state contains one of the SSL3_ST_* values. */
-  int state = SSL_ST_INIT;
-
-  /* next_state is used when SSL_ST_FLUSH_DATA is entered */
-  int next_state = 0;
+  /* state is the internal state for the TLS 1.2 and below handshake. Its
+   * values depend on |do_handshake| but the starting state is always zero. */
+  int 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. */
+   * depend on |do_handshake| but the starting state is always zero. */
   int tls13_state = 0;
 
   /* min_version is the minimum accepted protocol version, taking account both
@@ -1326,18 +1318,22 @@
  * one. Otherwise, it sends an alert and returns zero. */
 int ssl_check_message_type(SSL *ssl, const SSLMessage &msg, int type);
 
-/* tls13_handshake runs the TLS 1.3 handshake. It returns one on success and <=
- * 0 on error. It sets |out_early_return| to one if we've completed the
- * handshake early. */
-int tls13_handshake(SSL_HANDSHAKE *hs, int *out_early_return);
+/* ssl_run_handshake runs the TLS handshake. It returns one on success and <= 0
+ * on error. It sets |out_early_return| to one if we've completed the handshake
+ * early. */
+int ssl_run_handshake(SSL_HANDSHAKE *hs, int *out_early_return);
 
-/* The following are implementations of |do_tls13_handshake| for the client and
+/* The following are implementations of |do_handshake| for the client and
  * server. */
+enum ssl_hs_wait_t ssl_client_handshake(SSL_HANDSHAKE *hs);
+enum ssl_hs_wait_t ssl_server_handshake(SSL_HANDSHAKE *hs);
 enum ssl_hs_wait_t tls13_client_handshake(SSL_HANDSHAKE *hs);
 enum ssl_hs_wait_t tls13_server_handshake(SSL_HANDSHAKE *hs);
 
-/* The following functions return human-readable representations of the TLS 1.3
+/* The following functions return human-readable representations of the TLS
  * handshake states for debugging. */
+const char *ssl_client_handshake_state(SSL_HANDSHAKE *hs);
+const char *ssl_server_handshake_state(SSL_HANDSHAKE *hs);
 const char *tls13_client_handshake_state(SSL_HANDSHAKE *hs);
 const char *tls13_server_handshake_state(SSL_HANDSHAKE *hs);
 
@@ -1918,7 +1914,10 @@
   BIO *rbio; /* used by SSL_read */
   BIO *wbio; /* used by SSL_write */
 
-  int (*handshake_func)(SSL_HANDSHAKE *hs);
+  /* do_handshake runs the handshake. On completion, it returns |ssl_hs_ok|.
+   * Otherwise, it returns a value corresponding to what operation is needed to
+   * progress. */
+  enum ssl_hs_wait_t (*do_handshake)(SSL_HANDSHAKE *hs);
 
   BUF_MEM *init_buf; /* buffer used during init */
 
@@ -2160,7 +2159,7 @@
 
 void ssl_update_cache(SSL_HANDSHAKE *hs, int mode);
 
-int ssl3_get_finished(SSL_HANDSHAKE *hs);
+enum ssl_hs_wait_t ssl_get_finished(SSL_HANDSHAKE *hs);
 int ssl3_send_alert(SSL *ssl, int level, int desc);
 bool ssl3_get_message(SSL *ssl, SSLMessage *out);
 int ssl3_read_message(SSL *ssl);
@@ -2179,8 +2178,6 @@
 
 int ssl3_new(SSL *ssl);
 void ssl3_free(SSL *ssl);
-int ssl3_accept(SSL_HANDSHAKE *hs);
-int ssl3_connect(SSL_HANDSHAKE *hs);
 
 int ssl3_init_message(SSL *ssl, CBB *cbb, CBB *body, uint8_t type);
 int ssl3_finish_message(SSL *ssl, CBB *cbb, uint8_t **out_msg, size_t *out_len);
diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc
index f51af69..11da692 100644
--- a/ssl/s3_both.cc
+++ b/ssl/s3_both.cc
@@ -132,73 +132,6 @@
 
 namespace bssl {
 
-SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
-    : ssl(ssl_arg),
-      scts_requested(0),
-      needs_psk_binder(0),
-      received_hello_retry_request(0),
-      received_custom_extension(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) {
-  UniquePtr<SSL_HANDSHAKE> hs = MakeUnique<SSL_HANDSHAKE>(ssl);
-  if (!hs ||
-      !hs->transcript.Init()) {
-    return nullptr;
-  }
-  return hs.release();
-}
-
-void ssl_handshake_free(SSL_HANDSHAKE *hs) { Delete(hs); }
-
-int ssl_check_message_type(SSL *ssl, const SSLMessage &msg, int type) {
-  if (msg.type != type) {
-    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
-    ERR_add_error_dataf("got type %d, wanted type %d", msg.type, type);
-    return 0;
-  }
-
-  return 1;
-}
-
 static int add_record_to_flight(SSL *ssl, uint8_t type, const uint8_t *in,
                                 size_t in_len) {
   /* We'll never add a flight while in the process of writing it out. */
@@ -316,17 +249,6 @@
   return 1;
 }
 
-int ssl_add_message_cbb(SSL *ssl, CBB *cbb) {
-  uint8_t *msg;
-  size_t len;
-  if (!ssl->method->finish_message(ssl, cbb, &msg, &len) ||
-      !ssl->method->add_message(ssl, msg, len)) {
-    return 0;
-  }
-
-  return 1;
-}
-
 int ssl3_flush_flight(SSL *ssl) {
   if (ssl->s3->pending_flight == NULL) {
     return 1;
@@ -396,7 +318,7 @@
     if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
         finished_len > sizeof(ssl->s3->previous_server_finished)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return -1;
+      return 0;
     }
 
     if (ssl->server) {
@@ -414,65 +336,12 @@
       !CBB_add_bytes(&body, finished, finished_len) ||
       !ssl_add_message_cbb(ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
+    return 0;
   }
 
   return 1;
 }
 
-int ssl3_get_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
-  }
-
-  if (!ssl_check_message_type(ssl, msg, SSL3_MT_FINISHED)) {
-    return -1;
-  }
-
-  /* Snapshot the finished hash before incorporating the new message. */
-  uint8_t finished[EVP_MAX_MD_SIZE];
-  size_t finished_len;
-  if (!hs->transcript.GetFinishedMAC(finished, &finished_len,
-                                     SSL_get_session(ssl), !ssl->server,
-                                     ssl3_protocol_version(ssl)) ||
-      !ssl_hash_message(hs, msg)) {
-    return -1;
-  }
-
-  int finished_ok = CBS_mem_equal(&msg.body, finished, finished_len);
-#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  finished_ok = 1;
-#endif
-  if (!finished_ok) {
-    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DIGEST_CHECK_FAILED);
-    return -1;
-  }
-
-  /* Copy the Finished so we can use it for renegotiation checks. */
-  if (ssl->version != SSL3_VERSION) {
-    if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
-        finished_len > sizeof(ssl->s3->previous_server_finished)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return -1;
-    }
-
-    if (ssl->server) {
-      OPENSSL_memcpy(ssl->s3->previous_client_finished, finished, finished_len);
-      ssl->s3->previous_client_finished_len = finished_len;
-    } else {
-      OPENSSL_memcpy(ssl->s3->previous_server_finished, finished, finished_len);
-      ssl->s3->previous_server_finished_len = finished_len;
-    }
-  }
-
-  ssl->method->next_message(ssl);
-  return 1;
-}
-
 int ssl3_output_cert_chain(SSL *ssl) {
   ScopedCBB cbb;
   CBB body;
@@ -486,46 +355,6 @@
   return 1;
 }
 
-size_t ssl_max_handshake_message_len(const SSL *ssl) {
-  /* kMaxMessageLen is the default maximum message size for handshakes which do
-   * not accept peer certificate chains. */
-  static const size_t kMaxMessageLen = 16384;
-
-  if (SSL_in_init(ssl)) {
-    if ((!ssl->server || (ssl->verify_mode & SSL_VERIFY_PEER)) &&
-        kMaxMessageLen < ssl->max_cert_list) {
-      return ssl->max_cert_list;
-    }
-    return kMaxMessageLen;
-  }
-
-  if (ssl3_protocol_version(ssl) < TLS1_3_VERSION) {
-    /* In TLS 1.2 and below, the largest acceptable post-handshake message is
-     * a HelloRequest. */
-    return 0;
-  }
-
-  if (ssl->server) {
-    /* The largest acceptable post-handshake message for a server is a
-     * KeyUpdate. We will never initiate post-handshake auth. */
-    return 1;
-  }
-
-  /* Clients must accept NewSessionTicket and CertificateRequest, so allow the
-   * default size. */
-  return kMaxMessageLen;
-}
-
-int ssl_read_message(SSL *ssl, SSLMessage *out) {
-  while (!ssl->method->get_message(ssl, out)) {
-    int ret = ssl->method->read_message(ssl);
-    if (ret <= 0) {
-      return ret;
-    }
-  }
-  return 1;
-}
-
 static int extend_handshake_buffer(SSL *ssl, size_t length) {
   if (!BUF_MEM_reserve(ssl->init_buf, length)) {
     return -1;
@@ -769,15 +598,6 @@
   return extend_handshake_buffer(ssl, bytes_needed);
 }
 
-bool ssl_hash_message(SSL_HANDSHAKE *hs, const SSLMessage &msg) {
-  /* V2ClientHello messages are pre-hashed. */
-  if (msg.is_v2_hello) {
-    return true;
-  }
-
-  return hs->transcript.Update(CBS_data(&msg.raw), CBS_len(&msg.raw));
-}
-
 void ssl3_next_message(SSL *ssl) {
   SSLMessage msg;
   if (!ssl3_get_message(ssl, &msg) ||
@@ -801,154 +621,4 @@
   }
 }
 
-int ssl_parse_extensions(const CBS *cbs, uint8_t *out_alert,
-                         const SSL_EXTENSION_TYPE *ext_types,
-                         size_t num_ext_types, int ignore_unknown) {
-  /* Reset everything. */
-  for (size_t i = 0; i < num_ext_types; i++) {
-    *ext_types[i].out_present = 0;
-    CBS_init(ext_types[i].out_data, NULL, 0);
-  }
-
-  CBS copy = *cbs;
-  while (CBS_len(&copy) != 0) {
-    uint16_t type;
-    CBS data;
-    if (!CBS_get_u16(&copy, &type) ||
-        !CBS_get_u16_length_prefixed(&copy, &data)) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_PARSE_TLSEXT);
-      *out_alert = SSL_AD_DECODE_ERROR;
-      return 0;
-    }
-
-    const SSL_EXTENSION_TYPE *ext_type = NULL;
-    for (size_t i = 0; i < num_ext_types; i++) {
-      if (type == ext_types[i].type) {
-        ext_type = &ext_types[i];
-        break;
-      }
-    }
-
-    if (ext_type == NULL) {
-      if (ignore_unknown) {
-        continue;
-      }
-      OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
-      *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
-      return 0;
-    }
-
-    /* Duplicate ext_types are forbidden. */
-    if (*ext_type->out_present) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_DUPLICATE_EXTENSION);
-      *out_alert = SSL_AD_ILLEGAL_PARAMETER;
-      return 0;
-    }
-
-    *ext_type->out_present = 1;
-    *ext_type->out_data = data;
-  }
-
-  return 1;
-}
-
-static void set_crypto_buffer(CRYPTO_BUFFER **dest, CRYPTO_BUFFER *src) {
-  /* TODO(davidben): Remove this helper once |SSL_SESSION| can use |UniquePtr|
-   * and |UniquePtr| has up_ref helpers. */
-  CRYPTO_BUFFER_free(*dest);
-  *dest = src;
-  if (src != nullptr) {
-    CRYPTO_BUFFER_up_ref(src);
-  }
-}
-
-enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  const SSL_SESSION *prev_session = ssl->s3->established_session;
-  if (prev_session != NULL) {
-    /* If renegotiating, the server must not change the server certificate. See
-     * https://mitls.org/pages/attacks/3SHAKE. We never resume on renegotiation,
-     * so this check is sufficient to ensure the reported peer certificate never
-     * changes on renegotiation. */
-    assert(!ssl->server);
-    if (sk_CRYPTO_BUFFER_num(prev_session->certs) !=
-        sk_CRYPTO_BUFFER_num(hs->new_session->certs)) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_SERVER_CERT_CHANGED);
-      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return ssl_verify_invalid;
-    }
-
-    for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(hs->new_session->certs); i++) {
-      const CRYPTO_BUFFER *old_cert =
-          sk_CRYPTO_BUFFER_value(prev_session->certs, i);
-      const CRYPTO_BUFFER *new_cert =
-          sk_CRYPTO_BUFFER_value(hs->new_session->certs, i);
-      if (CRYPTO_BUFFER_len(old_cert) != CRYPTO_BUFFER_len(new_cert) ||
-          OPENSSL_memcmp(CRYPTO_BUFFER_data(old_cert),
-                         CRYPTO_BUFFER_data(new_cert),
-                         CRYPTO_BUFFER_len(old_cert)) != 0) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_SERVER_CERT_CHANGED);
-        ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-        return ssl_verify_invalid;
-      }
-    }
-
-    /* The certificate is identical, so we may skip re-verifying the
-     * certificate. Since we only authenticated the previous one, copy other
-     * authentication from the established session and ignore what was newly
-     * received. */
-    set_crypto_buffer(&hs->new_session->ocsp_response,
-                      prev_session->ocsp_response);
-    set_crypto_buffer(&hs->new_session->signed_cert_timestamp_list,
-                      prev_session->signed_cert_timestamp_list);
-    hs->new_session->verify_result = prev_session->verify_result;
-    return ssl_verify_ok;
-  }
-
-  uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN;
-  enum ssl_verify_result_t ret;
-  if (ssl->custom_verify_callback != nullptr) {
-    ret = ssl->custom_verify_callback(ssl, &alert);
-    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/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 9ecd7df..6305356 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -729,12 +729,12 @@
 
 void SSL_set_connect_state(SSL *ssl) {
   ssl->server = 0;
-  ssl->handshake_func = ssl3_connect;
+  ssl->do_handshake = ssl_client_handshake;
 }
 
 void SSL_set_accept_state(SSL *ssl) {
   ssl->server = 1;
-  ssl->handshake_func = ssl3_accept;
+  ssl->do_handshake = ssl_server_handshake;
 }
 
 void SSL_set0_rbio(SSL *ssl, BIO *rbio) {
@@ -788,7 +788,7 @@
 int SSL_do_handshake(SSL *ssl) {
   ssl_reset_error_state(ssl);
 
-  if (ssl->handshake_func == NULL) {
+  if (ssl->do_handshake == NULL) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_CONNECTION_TYPE_NOT_SET);
     return -1;
   }
@@ -797,20 +797,19 @@
     return 1;
   }
 
-  if (ssl->s3->hs == NULL) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
-  }
-
   /* Run the handshake. */
-  assert(ssl->s3->hs != NULL);
-  int ret = ssl->handshake_func(ssl->s3->hs);
+  SSL_HANDSHAKE *hs = ssl->s3->hs;
+
+  int early_return = 0;
+  int ret = ssl_run_handshake(hs, &early_return);
+  ssl_do_info_callback(
+      ssl, ssl->server ? SSL_CB_ACCEPT_EXIT : SSL_CB_CONNECT_EXIT, ret);
   if (ret <= 0) {
     return ret;
   }
 
   /* Destroy the handshake object if the handshake has completely finished. */
-  if (!SSL_in_init(ssl)) {
+  if (!early_return) {
     ssl_handshake_free(ssl->s3->hs);
     ssl->s3->hs = NULL;
   }
@@ -819,7 +818,7 @@
 }
 
 int SSL_connect(SSL *ssl) {
-  if (ssl->handshake_func == NULL) {
+  if (ssl->do_handshake == NULL) {
     /* Not properly initialized yet */
     SSL_set_connect_state(ssl);
   }
@@ -828,7 +827,7 @@
 }
 
 int SSL_accept(SSL *ssl) {
-  if (ssl->handshake_func == NULL) {
+  if (ssl->do_handshake == NULL) {
     /* Not properly initialized yet */
     SSL_set_accept_state(ssl);
   }
@@ -902,7 +901,7 @@
 static int ssl_read_impl(SSL *ssl, void *buf, int num, int peek) {
   ssl_reset_error_state(ssl);
 
-  if (ssl->handshake_func == NULL) {
+  if (ssl->do_handshake == NULL) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);
     return -1;
   }
@@ -958,7 +957,7 @@
 int SSL_write(SSL *ssl, const void *buf, int num) {
   ssl_reset_error_state(ssl);
 
-  if (ssl->handshake_func == NULL) {
+  if (ssl->do_handshake == NULL) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);
     return -1;
   }
@@ -991,7 +990,7 @@
 int SSL_shutdown(SSL *ssl) {
   ssl_reset_error_state(ssl);
 
-  if (ssl->handshake_func == NULL) {
+  if (ssl->do_handshake == NULL) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);
     return -1;
   }
@@ -2337,8 +2336,7 @@
 }
 
 int SSL_in_init(const SSL *ssl) {
-  SSL_HANDSHAKE *hs = ssl->s3->hs;
-  return hs != NULL && hs->state != SSL_ST_OK;
+  return ssl->s3->hs != NULL;
 }
 
 int SSL_in_false_start(const SSL *ssl) {
diff --git a/ssl/ssl_session.cc b/ssl/ssl_session.cc
index 6bacc80..07329d0 100644
--- a/ssl/ssl_session.cc
+++ b/ssl/ssl_session.cc
@@ -1066,7 +1066,7 @@
   /* SSL_set_session may only be called before the handshake has started. */
   if (ssl->s3->initial_handshake_complete ||
       ssl->s3->hs == NULL ||
-      ssl->s3->hs->state != SSL_ST_INIT) {
+      ssl->s3->hs->state != 0) {
     abort();
   }
 
diff --git a/ssl/ssl_stat.cc b/ssl/ssl_stat.cc
index 31cce4d..37e0324 100644
--- a/ssl/ssl_stat.cc
+++ b/ssl/ssl_stat.cc
@@ -88,217 +88,17 @@
 #include "internal.h"
 
 
-static int ssl_state(const SSL *ssl) {
-  if (ssl->s3->hs == NULL) {
-    assert(ssl->s3->initial_handshake_complete);
-    return SSL_ST_OK;
-  }
-
-  return ssl->s3->hs->state;
-}
-
 const char *SSL_state_string_long(const SSL *ssl) {
-  switch (ssl_state(ssl)) {
-    case SSL_ST_ACCEPT:
-      return "before accept initialization";
-
-    case SSL_ST_CONNECT:
-      return "before connect initialization";
-
-    case SSL_ST_OK:
-      return "SSL negotiation finished successfully";
-
-    case SSL_ST_RENEGOTIATE:
-      return "SSL renegotiate ciphers";
-
-    /* SSLv3 additions */
-    case SSL3_ST_CW_CLNT_HELLO_A:
-      return "SSLv3 write client hello A";
-
-    case SSL3_ST_CR_SRVR_HELLO_A:
-      return "SSLv3 read server hello A";
-
-    case SSL3_ST_CR_CERT_A:
-      return "SSLv3 read server certificate A";
-
-    case SSL3_ST_CR_KEY_EXCH_A:
-      return "SSLv3 read server key exchange A";
-
-    case SSL3_ST_CR_CERT_REQ_A:
-      return "SSLv3 read server certificate request A";
-
-    case SSL3_ST_CR_SESSION_TICKET_A:
-      return "SSLv3 read server session ticket A";
-
-    case SSL3_ST_CR_SRVR_DONE_A:
-      return "SSLv3 read server done A";
-
-    case SSL3_ST_CW_CERT_A:
-      return "SSLv3 write client certificate A";
-
-    case SSL3_ST_CW_KEY_EXCH_A:
-      return "SSLv3 write client key exchange A";
-
-    case SSL3_ST_CW_CERT_VRFY_A:
-      return "SSLv3 write certificate verify A";
-
-    case SSL3_ST_CW_CHANGE:
-      return "SSLv3 write change cipher spec";
-
-    case SSL3_ST_CW_FINISHED_A:
-    case SSL3_ST_SW_FINISHED_A:
-      return "SSLv3 write finished A";
-
-    case SSL3_ST_CR_CHANGE:
-    case SSL3_ST_SR_CHANGE:
-      return "SSLv3 read change cipher spec";
-
-    case SSL3_ST_CR_FINISHED_A:
-    case SSL3_ST_SR_FINISHED_A:
-      return "SSLv3 read finished A";
-
-    case SSL3_ST_CW_FLUSH:
-    case SSL3_ST_SW_FLUSH:
-      return "SSLv3 flush data";
-
-    case SSL3_ST_SR_CLNT_HELLO_A:
-      return "SSLv3 read client hello A";
-
-    case SSL3_ST_SR_CLNT_HELLO_B:
-      return "SSLv3 read client hello B";
-
-    case SSL3_ST_SR_CLNT_HELLO_C:
-      return "SSLv3 read client hello C";
-
-    case SSL3_ST_SW_SRVR_HELLO_A:
-      return "SSLv3 write server hello A";
-
-    case SSL3_ST_SW_CERT_A:
-      return "SSLv3 write certificate A";
-
-    case SSL3_ST_SW_KEY_EXCH_A:
-      return "SSLv3 write key exchange A";
-
-    case SSL3_ST_SW_SRVR_DONE_A:
-      return "SSLv3 write server done A";
-
-    case SSL3_ST_SR_CERT_A:
-      return "SSLv3 read client certificate A";
-
-    case SSL3_ST_SR_KEY_EXCH_A:
-      return "SSLv3 read client key exchange A";
-
-    case SSL3_ST_SR_CERT_VRFY_A:
-      return "SSLv3 read certificate verify A";
-
-    /* DTLS */
-    case DTLS1_ST_CR_HELLO_VERIFY_REQUEST_A:
-      return "DTLS1 read hello verify request A";
-
-    case SSL_ST_TLS13:
-      return ssl->server ? tls13_server_handshake_state(ssl->s3->hs)
-                         : tls13_client_handshake_state(ssl->s3->hs);
-
-    default:
-      return "unknown state";
+  if (ssl->s3->hs == NULL) {
+    return "SSL negotiation finished successfully";
   }
+
+  return ssl->server ? ssl_server_handshake_state(ssl->s3->hs)
+                     : ssl_client_handshake_state(ssl->s3->hs);
 }
 
 const char *SSL_state_string(const SSL *ssl) {
-  switch (ssl_state(ssl)) {
-    case SSL_ST_ACCEPT:
-      return "AINIT ";
-
-    case SSL_ST_CONNECT:
-      return "CINIT ";
-
-    case SSL_ST_OK:
-      return "SSLOK ";
-
-    /* SSLv3 additions */
-    case SSL3_ST_SW_FLUSH:
-    case SSL3_ST_CW_FLUSH:
-      return "3FLUSH";
-
-    case SSL3_ST_CW_CLNT_HELLO_A:
-      return "3WCH_A";
-
-    case SSL3_ST_CR_SRVR_HELLO_A:
-      return "3RSH_A";
-
-    case SSL3_ST_CR_CERT_A:
-      return "3RSC_A";
-
-    case SSL3_ST_CR_KEY_EXCH_A:
-      return "3RSKEA";
-
-    case SSL3_ST_CR_CERT_REQ_A:
-      return "3RCR_A";
-
-    case SSL3_ST_CR_SRVR_DONE_A:
-      return "3RSD_A";
-
-    case SSL3_ST_CW_CERT_A:
-      return "3WCC_A";
-
-    case SSL3_ST_CW_KEY_EXCH_A:
-      return "3WCKEA";
-
-    case SSL3_ST_CW_CERT_VRFY_A:
-      return "3WCV_A";
-
-    case SSL3_ST_CW_CHANGE:
-      return "3WCCS_";
-
-    case SSL3_ST_SW_FINISHED_A:
-    case SSL3_ST_CW_FINISHED_A:
-      return "3WFINA";
-
-    case SSL3_ST_CR_CHANGE:
-    case SSL3_ST_SR_CHANGE:
-      return "3RCCS_";
-
-    case SSL3_ST_SR_FINISHED_A:
-    case SSL3_ST_CR_FINISHED_A:
-      return "3RFINA";
-
-    case SSL3_ST_SR_CLNT_HELLO_A:
-      return "3RCH_A";
-
-    case SSL3_ST_SR_CLNT_HELLO_B:
-      return "3RCH_B";
-
-    case SSL3_ST_SR_CLNT_HELLO_C:
-      return "3RCH_C";
-
-    case SSL3_ST_SW_SRVR_HELLO_A:
-      return "3WSH_A";
-
-    case SSL3_ST_SW_CERT_A:
-      return "3WSC_A";
-
-    case SSL3_ST_SW_KEY_EXCH_A:
-      return "3WSKEA";
-
-    case SSL3_ST_SW_SRVR_DONE_A:
-      return "3WSD_A";
-
-    case SSL3_ST_SR_CERT_A:
-      return "3RCC_A";
-
-    case SSL3_ST_SR_KEY_EXCH_A:
-      return "3RCKEA";
-
-    case SSL3_ST_SR_CERT_VRFY_A:
-      return "3RCV_A";
-
-    /* DTLS */
-    case DTLS1_ST_CR_HELLO_VERIFY_REQUEST_A:
-      return "DRCHVA";
-
-    default:
-      return "UNKWN ";
-  }
+  return "!!!!!!";
 }
 
 const char *SSL_alert_type_string_long(int value) {
diff --git a/ssl/ssl_x509.cc b/ssl/ssl_x509.cc
index 98a5b8c..7e9e51f 100644
--- a/ssl/ssl_x509.cc
+++ b/ssl/ssl_x509.cc
@@ -1143,9 +1143,9 @@
   /* For historical reasons, this function is used both to query configuration
    * state on a server as well as handshake state on a client. However, whether
    * |ssl| is a client or server is not known until explicitly configured with
-   * |SSL_set_connect_state|. If |handshake_func| is NULL, |ssl| is in an
+   * |SSL_set_connect_state|. If |do_handshake| is NULL, |ssl| is in an
    * indeterminate mode and |ssl->server| is unset. */
-  if (ssl->handshake_func != NULL && !ssl->server) {
+  if (ssl->do_handshake != NULL && !ssl->server) {
     if (ssl->s3->hs != NULL) {
       return buffer_names_to_x509(ssl->s3->hs->ca_names.get(),
                                   &ssl->s3->hs->cached_x509_ca_names);
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index e50710a..7527a98 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -3483,7 +3483,7 @@
    * handshake hashes that we wish to record are for the original, full
    * handshake. */
   if (ssl->session != NULL) {
-    return -1;
+    return 0;
   }
 
   static_assert(
@@ -3493,7 +3493,7 @@
   size_t digest_len;
   if (!hs->transcript.GetHash(hs->new_session->original_handshake_hash,
                               &digest_len)) {
-    return -1;
+    return 0;
   }
 
   static_assert(EVP_MAX_MD_SIZE <= 0xff,
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
index 6e7260e..509079d 100644
--- a/ssl/tls13_both.cc
+++ b/ssl/tls13_both.cc
@@ -37,101 +37,6 @@
  * without being able to return application data. */
 static const uint8_t kMaxKeyUpdates = 32;
 
-int tls13_handshake(SSL_HANDSHAKE *hs, int *out_early_return) {
-  SSL *const ssl = hs->ssl;
-  for (;;) {
-    /* Resolve the operation the handshake was waiting on. */
-    switch (hs->wait) {
-      case ssl_hs_error:
-        OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_HANDSHAKE_FAILURE);
-        return -1;
-
-      case ssl_hs_flush: {
-        int ret = ssl->method->flush_flight(ssl);
-        if (ret <= 0) {
-          return ret;
-        }
-        break;
-      }
-
-      case ssl_hs_read_message: {
-        int ret = ssl->method->read_message(ssl);
-        if (ret <= 0) {
-          return ret;
-        }
-        break;
-      }
-
-      case ssl_hs_read_change_cipher_spec: {
-        int ret = ssl->method->read_change_cipher_spec(ssl);
-        if (ret <= 0) {
-          return ret;
-        }
-        break;
-      }
-
-      case ssl_hs_read_end_of_early_data: {
-        if (ssl->s3->hs->can_early_read) {
-          /* While we are processing early data, the handshake returns early. */
-          *out_early_return = 1;
-          return 1;
-        }
-        hs->wait = ssl_hs_ok;
-        break;
-      }
-
-      case ssl_hs_x509_lookup:
-        ssl->rwstate = SSL_X509_LOOKUP;
-        hs->wait = ssl_hs_ok;
-        return -1;
-
-      case ssl_hs_channel_id_lookup:
-        ssl->rwstate = SSL_CHANNEL_ID_LOOKUP;
-        hs->wait = ssl_hs_ok;
-        return -1;
-
-      case ssl_hs_private_key_operation:
-        ssl->rwstate = SSL_PRIVATE_KEY_OPERATION;
-        hs->wait = ssl_hs_ok;
-        return -1;
-
-      case ssl_hs_pending_ticket:
-        ssl->rwstate = SSL_PENDING_TICKET;
-        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. */
-        hs->can_early_write = 0;
-        return -1;
-
-      case ssl_hs_ok:
-        break;
-    }
-
-    /* Run the state machine again. */
-    hs->wait = hs->do_tls13_handshake(hs);
-    if (hs->wait == ssl_hs_error) {
-      /* Don't loop around to avoid a stray |SSL_R_SSL_HANDSHAKE_FAILURE| the
-       * first time around. */
-      return -1;
-    }
-    if (hs->wait == ssl_hs_ok) {
-      /* The handshake has completed. */
-      return 1;
-    }
-
-    /* Otherwise, loop to the beginning and resolve what was blocking the
-     * handshake. */
-  }
-}
-
 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) {
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc
index 6608404..8221368 100644
--- a/ssl/tls13_client.cc
+++ b/ssl/tls13_client.cc
@@ -733,7 +733,7 @@
         break;
     }
 
-    if (hs->state != state) {
+    if (hs->tls13_state != state) {
       ssl_do_info_callback(hs->ssl, SSL_CB_CONNECT_LOOP, 1);
     }
 
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index 894fa87..781d59e 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -922,7 +922,7 @@
         break;
     }
 
-    if (hs->state != state) {
+    if (hs->tls13_state != state) {
       ssl_do_info_callback(hs->ssl, SSL_CB_ACCEPT_LOOP, 1);
     }