Remove deprecated split handshakes support

This is largely just deleting code, but a couple enums can now be
file-local.

Update-Note: The various handoff/handback APIs are removed. We believe
all remaining callers of this are now gone.

Fixed: 376969215
Change-Id: I26db9230cebb1ae21a49469bb301e63552e14633
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/103627
Reviewed-by: Adam Langley <agl@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Presubmit-BoringSSL-Verified: boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index cc330a0..d4ca791 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -487,9 +487,6 @@
 // See also `SSL_CTX_set_custom_verify`.
 #define SSL_ERROR_WANT_CERTIFICATE_VERIFY 16
 
-#define SSL_ERROR_HANDOFF 17
-#define SSL_ERROR_HANDBACK 18
-
 // SSL_ERROR_WANT_RENEGOTIATE indicates the operation is pending a response to
 // a renegotiation request from the server. The caller may call
 // `SSL_renegotiate` to schedule a renegotiation and retry the operation.
@@ -6919,63 +6916,6 @@
 BORINGSSL_MAKE_DELETER(SSL_SESSION, SSL_SESSION_free)
 BORINGSSL_MAKE_UP_REF(SSL_SESSION, SSL_SESSION_up_ref)
 
-
-// *** DEPRECATED EXPERIMENT — DO NOT USE ***
-//
-// Split handshakes.
-//
-// WARNING: This mechanism is deprecated and should not be used. It is very
-// fragile and difficult to use correctly. The relationship between
-// configuration options across the two halves is ill-defined and not
-// self-consistent. Additionally, version skew across the two halves risks
-// unusual behavior and connection failure. New development should use the
-// handshake hints API. Existing deployments should migrate to handshake hints
-// to reduce the risk of service outages.
-//
-// Split handshakes allows the handshake part of a TLS connection to be
-// performed in a different process (or on a different machine) than the data
-// exchange. This only applies to servers.
-//
-// In the first part of a split handshake, an `SSL` (where the `SSL_CTX` has
-// been configured with `SSL_CTX_set_handoff_mode`) is used normally. Once the
-// ClientHello message has been received, the handshake will stop and
-// `SSL_get_error` will indicate `SSL_ERROR_HANDOFF`. At this point (and only
-// at this point), `SSL_serialize_handoff` can be called to write the “handoff”
-// state of the connection.
-//
-// Elsewhere, a fresh `SSL` can be used with `SSL_apply_handoff` to continue
-// the connection. The connection from the client is fed into this `SSL`, and
-// the handshake resumed. When the handshake stops again and `SSL_get_error`
-// indicates `SSL_ERROR_HANDBACK`, `SSL_serialize_handback` should be called to
-// serialize the state of the handshake again.
-//
-// Back at the first location, a fresh `SSL` can be used with
-// `SSL_apply_handback`. Then the client's connection can be processed mostly
-// as normal.
-//
-// Lastly, when a connection is in the handoff state, whether or not
-// `SSL_serialize_handoff` is called, `SSL_decline_handoff` will move it back
-// into a normal state where the connection can proceed without impact.
-//
-// WARNING: Currently only works with TLS 1.0–1.2.
-// WARNING: The serialisation formats are not yet stable: version skew may be
-//     fatal.
-// WARNING: The handback data contains sensitive key material and must be
-//     protected.
-// WARNING: Some calls on the final `SSL` will not work. Just as an example,
-//     calls like `SSL_get0_session_id_context` and `SSL_get_privatekey` won't
-//     work because the certificate used for handshaking isn't available.
-// WARNING: `SSL_apply_handoff` may trigger “msg” callback calls.
-
-OPENSSL_EXPORT void SSL_CTX_set_handoff_mode(SSL_CTX *ctx, bool on);
-OPENSSL_EXPORT void SSL_set_handoff_mode(SSL *SSL, bool on);
-OPENSSL_EXPORT bool SSL_serialize_handoff(const SSL *ssl, CBB *out,
-                                          SSL_CLIENT_HELLO *out_hello);
-OPENSSL_EXPORT bool SSL_decline_handoff(SSL *ssl);
-OPENSSL_EXPORT bool SSL_apply_handoff(SSL *ssl, Span<const uint8_t> handoff);
-OPENSSL_EXPORT bool SSL_serialize_handback(const SSL *ssl, CBB *out);
-OPENSSL_EXPORT bool SSL_apply_handback(SSL *ssl, Span<const uint8_t> handback);
-
 // SSL_get_traffic_secrets sets `*out_read_traffic_secret` and
 // `*out_write_traffic_secret` to reference the current TLS 1.3 traffic secrets
 // for `ssl`. It returns true on success and false on error.
diff --git a/ssl/handoff.cc b/ssl/handoff.cc
index 7b1fc11..b455ef1 100644
--- a/ssl/handoff.cc
+++ b/ssl/handoff.cc
@@ -26,10 +26,7 @@
 
 BSSL_NAMESPACE_BEGIN
 
-constexpr int kHandoffVersion = 0;
-constexpr int kHandbackVersion = 0;
-
-static const CBS_ASN1_TAG kHandoffTagALPS = CBS_ASN1_CONTEXT_SPECIFIC | 0;
+static const CBS_ASN1_TAG kFeatureTagALPS = CBS_ASN1_CONTEXT_SPECIFIC | 0;
 
 // early_data_t represents the state of early data in a more compact way than
 // the 3 bits used by the implementation.
@@ -64,13 +61,13 @@
       return false;
     }
   }
-  // ALPS is a draft protocol and may change over time. The handoff structure
+  // ALPS is a draft protocol and may change over time. The features structure
   // contains a [0] IMPLICIT OCTET STRING OPTIONAL, containing a list of u16
   // ALPS versions that the binary supports. For now we name them by codepoint.
   // Once ALPS is finalized and past the support horizon, this field can be
   // removed.
   CBB alps;
-  if (!CBB_add_asn1(out, &alps, kHandoffTagALPS) ||
+  if (!CBB_add_asn1(out, &alps, kFeatureTagALPS) ||
       !CBB_add_u16(&alps, TLSEXT_TYPE_application_settings_old) ||
       !CBB_add_u16(&alps, TLSEXT_TYPE_application_settings)) {
     return false;
@@ -78,52 +75,10 @@
   return CBB_flush(out);
 }
 
-bool SSL_serialize_handoff(const SSL *ssl, CBB *out,
-                           SSL_CLIENT_HELLO *out_hello) {
-  const auto *ssl_impl = FromOpaque(ssl);
-  const SSL3_STATE *const s3 = ssl_impl->s3;
-  if (!ssl_impl->server ||  //
-      s3->hs == nullptr ||  //
-      s3->rwstate != SSL_ERROR_HANDOFF) {
-    return false;
-  }
-
-  CBB seq;
-  SSLMessage msg;
-  Span<const uint8_t> transcript = s3->hs->transcript.buffer();
-
-  if (!CBB_add_asn1(out, &seq, CBS_ASN1_SEQUENCE) ||
-      !CBB_add_asn1_uint64(&seq, kHandoffVersion) ||
-      !CBB_add_asn1_octet_string(&seq, transcript.data(), transcript.size()) ||
-      !CBB_add_asn1_octet_string(&seq,
-                                 reinterpret_cast<uint8_t *>(s3->hs_buf->data),
-                                 s3->hs_buf->length) ||
-      !serialize_features(&seq) || !CBB_flush(out) ||
-      !ssl_impl->method->get_message(ssl_impl, &msg) ||
-      !SSL_parse_client_hello(ssl_impl, out_hello, CBS_data(&msg.body),
-                              CBS_len(&msg.body))) {
-    return false;
-  }
-
-  return true;
-}
-
-bool SSL_decline_handoff(SSL *ssl) {
-  auto *ssl_impl = FromOpaque(ssl);
-  const SSL3_STATE *const s3 = ssl_impl->s3;
-  if (!ssl_impl->server || s3->hs == nullptr ||
-      s3->rwstate != SSL_ERROR_HANDOFF) {
-    return false;
-  }
-
-  s3->hs->config->handoff = false;
-  return true;
-}
-
 // apply_remote_features reads a list of supported features from `in` and
 // (possibly) reconfigures `ssl` to disallow the negotiation of features whose
-// support has not been indicated.  (This prevents the handshake from
-// committing to features that are not supported on the handoff/handback side.)
+// support has not been indicated. (This prevents the hints from being generated
+// based on features that are not supported on the actual server.)
 static bool apply_remote_features(SSLImpl *ssl, CBS *in) {
   CBS ciphers;
   if (!CBS_get_asn1(in, &ciphers, CBS_ASN1_OCTETSTRING)) {
@@ -242,7 +197,7 @@
   CBS alps;
   CBS_init(&alps, nullptr, 0);
   if (!CBS_get_optional_asn1(in, &alps, /*out_present=*/nullptr,
-                             kHandoffTagALPS)) {
+                             kFeatureTagALPS)) {
     return false;
   }
   bool supports_alps = false;
@@ -268,551 +223,6 @@
   return true;
 }
 
-// uses_disallowed_feature returns true iff `ssl` enables a feature that
-// disqualifies it for split handshakes.
-static bool uses_disallowed_feature(const SSLImpl *ssl) {
-  return ssl->method->is_dtls || !ssl->config->cert->credentials.empty() ||
-         ssl->config->quic_transport_params.size() > 0 || ssl->ctx->ech_keys;
-}
-
-bool SSL_apply_handoff(SSL *ssl, Span<const uint8_t> handoff) {
-  auto *ssl_impl = FromOpaque(ssl);
-  if (uses_disallowed_feature(ssl_impl)) {
-    return false;
-  }
-
-  CBS seq, handoff_cbs(handoff);
-  uint64_t handoff_version;
-  if (!CBS_get_asn1(&handoff_cbs, &seq, CBS_ASN1_SEQUENCE) ||
-      !CBS_get_asn1_uint64(&seq, &handoff_version) ||
-      handoff_version != kHandoffVersion) {
-    return false;
-  }
-
-  CBS transcript, hs_buf;
-  if (!CBS_get_asn1(&seq, &transcript, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1(&seq, &hs_buf, CBS_ASN1_OCTETSTRING) ||
-      !apply_remote_features(ssl_impl, &seq)) {
-    return false;
-  }
-
-  SSL_set_accept_state(ssl_impl);
-
-  SSL3_STATE *const s3 = ssl_impl->s3;
-  s3->v2_hello_done = true;
-  s3->has_message = true;
-
-  s3->hs_buf.reset(BUF_MEM_new());
-  if (!s3->hs_buf ||
-      !BUF_MEM_append(s3->hs_buf.get(), CBS_data(&hs_buf), CBS_len(&hs_buf))) {
-    return false;
-  }
-
-  if (CBS_len(&transcript) != 0) {
-    s3->hs->transcript.Update(transcript);
-    s3->is_v2_hello = true;
-  }
-  s3->hs->handback = true;
-
-  return true;
-}
-
-bool SSL_serialize_handback(const SSL *ssl, CBB *out) {
-  const auto *ssl_impl = FromOpaque(ssl);
-  if (!ssl_impl->server || uses_disallowed_feature(ssl_impl)) {
-    return false;
-  }
-  const SSL3_STATE *const s3 = ssl_impl->s3;
-  SSL_HANDSHAKE *const hs = s3->hs.get();
-  handback_t type;
-  switch (hs->state) {
-    case state12_read_change_cipher_spec:
-      type = handback_after_session_resumption;
-      break;
-    case state12_read_client_certificate:
-      type = handback_after_ecdhe;
-      break;
-    case state12_finish_server_handshake:
-      type = handback_after_handshake;
-      break;
-    case state12_tls13:
-      if (hs->tls13_state != state13_send_half_rtt_ticket) {
-        return false;
-      }
-      type = handback_tls13;
-      break;
-    default:
-      return false;
-  }
-
-  size_t hostname_len = 0;
-  if (s3->hostname) {
-    hostname_len = strlen(s3->hostname.get());
-  }
-
-  Span<const uint8_t> transcript;
-  if (type != handback_after_handshake) {
-    transcript = s3->hs->transcript.buffer();
-  }
-  size_t write_iv_len = 0;
-  const uint8_t *write_iv = nullptr;
-  if ((type == handback_after_session_resumption ||
-       type == handback_after_handshake) &&
-      ssl_impl->s3->version == TLS1_VERSION &&
-      SSL_CIPHER_is_block_cipher(s3->aead_write_ctx->cipher()) &&
-      !s3->aead_write_ctx->GetIV(&write_iv, &write_iv_len)) {
-    return false;
-  }
-  size_t read_iv_len = 0;
-  const uint8_t *read_iv = nullptr;
-  if (type == handback_after_handshake &&                         //
-      ssl_impl->s3->version == TLS1_VERSION &&                    //
-      SSL_CIPHER_is_block_cipher(s3->aead_read_ctx->cipher()) &&  //
-      !s3->aead_read_ctx->GetIV(&read_iv, &read_iv_len)) {
-    return false;
-  }
-
-  // TODO(mab): make sure everything is serialized.
-  CBB seq, key_share;
-  const SSLSession *session;
-  if (type == handback_tls13) {
-    session = hs->new_session.get();
-  } else {
-    session =
-        s3->session_reused ? ssl_impl->session.get() : hs->new_session.get();
-  }
-  uint8_t read_sequence[8], write_sequence[8];
-  CRYPTO_store_u64_be(read_sequence, s3->read_sequence);
-  CRYPTO_store_u64_be(write_sequence, s3->write_sequence);
-  static const uint8_t kUnusedChannelID[64] = {0};
-  if (!CBB_add_asn1(out, &seq, CBS_ASN1_SEQUENCE) ||
-      !CBB_add_asn1_uint64(&seq, kHandbackVersion) ||
-      !CBB_add_asn1_uint64(&seq, type) ||
-      !CBB_add_asn1_octet_string(&seq, read_sequence, sizeof(read_sequence)) ||
-      !CBB_add_asn1_octet_string(&seq, write_sequence,
-                                 sizeof(write_sequence)) ||
-      !CBB_add_asn1_octet_string(&seq, s3->server_random,
-                                 sizeof(s3->server_random)) ||
-      !CBB_add_asn1_octet_string(&seq, s3->client_random,
-                                 sizeof(s3->client_random)) ||
-      !CBB_add_asn1_octet_string(&seq, read_iv, read_iv_len) ||
-      !CBB_add_asn1_octet_string(&seq, write_iv, write_iv_len) ||
-      !CBB_add_asn1_bool(&seq, s3->session_reused) ||
-      !CBB_add_asn1_bool(&seq, hs->channel_id_negotiated) ||
-      !ssl_session_serialize(session, &seq) ||
-      !CBB_add_asn1_octet_string(&seq, s3->next_proto_negotiated.data(),
-                                 s3->next_proto_negotiated.size()) ||
-      !CBB_add_asn1_octet_string(&seq, s3->alpn_selected.data(),
-                                 s3->alpn_selected.size()) ||
-      !CBB_add_asn1_octet_string(
-          &seq, reinterpret_cast<uint8_t *>(s3->hostname.get()),
-          hostname_len) ||
-      !CBB_add_asn1_octet_string(&seq, kUnusedChannelID,
-                                 sizeof(kUnusedChannelID)) ||
-      // These two fields were historically `token_binding_negotiated` and
-      // `negotiated_token_binding_param`.
-      !CBB_add_asn1_bool(&seq, 0) ||  //
-      !CBB_add_asn1_uint64(&seq, 0) ||
-      !CBB_add_asn1_bool(&seq, s3->hs->next_proto_neg_seen) ||
-      !CBB_add_asn1_bool(&seq, s3->hs->cert_request) ||
-      !CBB_add_asn1_bool(&seq, s3->hs->extended_master_secret) ||
-      !CBB_add_asn1_bool(&seq, s3->hs->ticket_expected) ||
-      !CBB_add_asn1_uint64(&seq, SSL_CIPHER_get_id(s3->hs->new_cipher)) ||
-      !CBB_add_asn1_octet_string(&seq, transcript.data(), transcript.size()) ||
-      !CBB_add_asn1(&seq, &key_share, CBS_ASN1_SEQUENCE)) {
-    return false;
-  }
-  if (type == handback_after_ecdhe) {
-    CBB private_key;
-    if (!CBB_add_asn1_uint64(&key_share, s3->hs->key_shares[0]->GroupID()) ||
-        !CBB_add_asn1(&key_share, &private_key, CBS_ASN1_OCTETSTRING) ||
-        !s3->hs->key_shares[0]->SerializePrivateKey(&private_key) ||
-        !CBB_flush(&key_share)) {
-      return false;
-    }
-  }
-  if (type == handback_tls13) {
-    early_data_t early_data;
-    // Check early data invariants.
-    if (ssl_impl->enable_early_data ==
-        (s3->early_data_reason == ssl_early_data_disabled)) {
-      return false;
-    }
-    if (hs->early_data_offered) {
-      if (s3->early_data_accepted && !s3->skip_early_data) {
-        early_data = early_data_accepted;
-      } else if (!s3->early_data_accepted && !s3->skip_early_data) {
-        early_data = early_data_rejected_hrr;
-      } else if (!s3->early_data_accepted && s3->skip_early_data) {
-        early_data = early_data_skipped;
-      } else {
-        return false;
-      }
-    } else if (!s3->early_data_accepted && !s3->skip_early_data) {
-      early_data = early_data_not_offered;
-    } else {
-      return false;
-    }
-    if (!CBB_add_asn1_octet_string(&seq, hs->client_traffic_secret_0.data(),
-                                   hs->client_traffic_secret_0.size()) ||
-        !CBB_add_asn1_octet_string(&seq, hs->server_traffic_secret_0.data(),
-                                   hs->server_traffic_secret_0.size()) ||
-        !CBB_add_asn1_octet_string(&seq, hs->client_handshake_secret.data(),
-                                   hs->client_handshake_secret.size()) ||
-        !CBB_add_asn1_octet_string(&seq, hs->server_handshake_secret.data(),
-                                   hs->server_handshake_secret.size()) ||
-        !CBB_add_asn1_octet_string(&seq, hs->secret.data(),
-                                   hs->secret.size()) ||
-        !CBB_add_asn1_octet_string(&seq, s3->exporter_secret.data(),
-                                   s3->exporter_secret.size()) ||
-        !CBB_add_asn1_bool(&seq, s3->used_hello_retry_request) ||
-        !CBB_add_asn1_bool(&seq, hs->accept_psk_mode) ||
-        !CBB_add_asn1_int64(&seq, s3->ticket_age_skew) ||
-        !CBB_add_asn1_uint64(&seq, s3->early_data_reason) ||
-        !CBB_add_asn1_uint64(&seq, early_data)) {
-      return false;
-    }
-    if (early_data == early_data_accepted &&
-        !CBB_add_asn1_octet_string(&seq, hs->early_traffic_secret.data(),
-                                   hs->early_traffic_secret.size())) {
-      return false;
-    }
-
-    if (session->has_application_settings) {
-      uint16_t alps_codepoint = TLSEXT_TYPE_application_settings_old;
-      if (hs->config->alps_use_new_codepoint) {
-        alps_codepoint = TLSEXT_TYPE_application_settings;
-      }
-      if (!CBB_add_asn1_uint64(&seq, alps_codepoint)) {
-        return false;
-      }
-    }
-  }
-  return CBB_flush(out);
-}
-
-static bool CopyExact(Span<uint8_t> out, const CBS *in) {
-  if (CBS_len(in) != out.size()) {
-    return false;
-  }
-  OPENSSL_memcpy(out.data(), CBS_data(in), out.size());
-  return true;
-}
-
-bool SSL_apply_handback(SSL *ssl, Span<const uint8_t> handback) {
-  auto *ssl_impl = FromOpaque(ssl);
-  if (ssl_impl->do_handshake != nullptr ||  //
-      ssl_impl->method->is_dtls) {
-    return false;
-  }
-
-  SSL3_STATE *const s3 = ssl_impl->s3;
-  uint64_t handback_version, unused_token_binding_param, cipher, type_u64,
-      alps_codepoint;
-
-  CBS seq, read_seq, write_seq, server_rand, client_rand, read_iv, write_iv,
-      next_proto, alpn, hostname, unused_channel_id, transcript, key_share;
-  int session_reused, channel_id_negotiated, cert_request,
-      extended_master_secret, ticket_expected, unused_token_binding,
-      next_proto_neg_seen;
-  SSLSession *session = nullptr;
-
-  CBS handback_cbs(handback);
-  if (!CBS_get_asn1(&handback_cbs, &seq, CBS_ASN1_SEQUENCE) ||  //
-      !CBS_get_asn1_uint64(&seq, &handback_version) ||          //
-      handback_version != kHandbackVersion ||                   //
-      !CBS_get_asn1_uint64(&seq, &type_u64) ||                  //
-      type_u64 > handback_max_value) {
-    return false;
-  }
-
-  handback_t type = static_cast<handback_t>(type_u64);
-  if (!CBS_get_asn1(&seq, &read_seq, CBS_ASN1_OCTETSTRING) ||
-      CBS_len(&read_seq) != sizeof(s3->read_sequence) ||
-      !CBS_get_asn1(&seq, &write_seq, CBS_ASN1_OCTETSTRING) ||
-      CBS_len(&write_seq) != sizeof(s3->write_sequence) ||
-      !CBS_get_asn1(&seq, &server_rand, CBS_ASN1_OCTETSTRING) ||
-      CBS_len(&server_rand) != sizeof(s3->server_random) ||
-      !CBS_copy_bytes(&server_rand, s3->server_random,
-                      sizeof(s3->server_random)) ||
-      !CBS_get_asn1(&seq, &client_rand, CBS_ASN1_OCTETSTRING) ||
-      CBS_len(&client_rand) != sizeof(s3->client_random) ||
-      !CBS_copy_bytes(&client_rand, s3->client_random,
-                      sizeof(s3->client_random)) ||
-      !CBS_get_asn1(&seq, &read_iv, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1(&seq, &write_iv, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1_bool(&seq, &session_reused) ||
-      !CBS_get_asn1_bool(&seq, &channel_id_negotiated)) {
-    return false;
-  }
-
-  s3->hs = ssl_handshake_new(ssl_impl);
-  if (!s3->hs) {
-    return false;
-  }
-  SSL_HANDSHAKE *const hs = s3->hs.get();
-  if (!session_reused || type == handback_tls13) {
-    hs->new_session = SSL_SESSION_parse(&seq, ssl_impl->ctx->x509_method,
-                                        ssl_impl->ctx->pool.get());
-    session = hs->new_session.get();
-  } else {
-    ssl_impl->session = SSL_SESSION_parse(&seq, ssl_impl->ctx->x509_method,
-                                          ssl_impl->ctx->pool.get());
-    session = ssl_impl->session.get();
-  }
-
-  // Split handshakes only support X.509 certificates.
-  if (session != nullptr && session->peer_cert_type != kDefaultCertType) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_CERTIFICATE);
-    return false;
-  }
-
-  if (!session || !CBS_get_asn1(&seq, &next_proto, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1(&seq, &alpn, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1(&seq, &hostname, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1(&seq, &unused_channel_id, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1_bool(&seq, &unused_token_binding) ||
-      !CBS_get_asn1_uint64(&seq, &unused_token_binding_param) ||
-      !CBS_get_asn1_bool(&seq, &next_proto_neg_seen) ||
-      !CBS_get_asn1_bool(&seq, &cert_request) ||
-      !CBS_get_asn1_bool(&seq, &extended_master_secret) ||
-      !CBS_get_asn1_bool(&seq, &ticket_expected) ||
-      !CBS_get_asn1_uint64(&seq, &cipher)) {
-    return false;
-  }
-  if ((hs->new_cipher =
-           SSL_get_cipher_by_value(static_cast<uint16_t>(cipher))) == nullptr) {
-    return false;
-  }
-  if (!CBS_get_asn1(&seq, &transcript, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1(&seq, &key_share, CBS_ASN1_SEQUENCE)) {
-    return false;
-  }
-  CBS client_handshake_secret, server_handshake_secret, client_traffic_secret_0,
-      server_traffic_secret_0, secret, exporter_secret, early_traffic_secret;
-  if (type == handback_tls13) {
-    int used_hello_retry_request, accept_psk_mode;
-    uint64_t early_data, early_data_reason;
-    int64_t ticket_age_skew;
-    if (!CBS_get_asn1(&seq, &client_traffic_secret_0, CBS_ASN1_OCTETSTRING) ||
-        !CBS_get_asn1(&seq, &server_traffic_secret_0, CBS_ASN1_OCTETSTRING) ||
-        !CBS_get_asn1(&seq, &client_handshake_secret, CBS_ASN1_OCTETSTRING) ||
-        !CBS_get_asn1(&seq, &server_handshake_secret, CBS_ASN1_OCTETSTRING) ||
-        !CBS_get_asn1(&seq, &secret, CBS_ASN1_OCTETSTRING) ||
-        !CBS_get_asn1(&seq, &exporter_secret, CBS_ASN1_OCTETSTRING) ||
-        !CBS_get_asn1_bool(&seq, &used_hello_retry_request) ||
-        !CBS_get_asn1_bool(&seq, &accept_psk_mode) ||
-        !CBS_get_asn1_int64(&seq, &ticket_age_skew) ||
-        !CBS_get_asn1_uint64(&seq, &early_data_reason) ||
-        early_data_reason > ssl_early_data_reason_max_value ||
-        !CBS_get_asn1_uint64(&seq, &early_data) ||
-        early_data > early_data_max_value) {
-      return false;
-    }
-    early_data_t early_data_type = static_cast<early_data_t>(early_data);
-    if (early_data_type == early_data_accepted &&
-        !CBS_get_asn1(&seq, &early_traffic_secret, CBS_ASN1_OCTETSTRING)) {
-      return false;
-    }
-
-    if (session->has_application_settings) {
-      // Making it optional to keep compatibility with older handshakers.
-      // Older handshakers won't send the field.
-      if (CBS_len(&seq) == 0) {
-        hs->config->alps_use_new_codepoint = false;
-      } else {
-        if (!CBS_get_asn1_uint64(&seq, &alps_codepoint)) {
-          return false;
-        }
-
-        if (alps_codepoint == TLSEXT_TYPE_application_settings) {
-          hs->config->alps_use_new_codepoint = true;
-        } else if (alps_codepoint == TLSEXT_TYPE_application_settings_old) {
-          hs->config->alps_use_new_codepoint = false;
-        } else {
-          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ALPS_CODEPOINT);
-          return false;
-        }
-      }
-    }
-
-    if (ticket_age_skew > std::numeric_limits<int32_t>::max() ||
-        ticket_age_skew < std::numeric_limits<int32_t>::min()) {
-      return false;
-    }
-    s3->ticket_age_skew = static_cast<int32_t>(ticket_age_skew);
-    s3->used_hello_retry_request = used_hello_retry_request;
-    hs->accept_psk_mode = accept_psk_mode;
-
-    s3->early_data_reason =
-        static_cast<ssl_early_data_reason_t>(early_data_reason);
-    ssl_impl->enable_early_data =
-        s3->early_data_reason != ssl_early_data_disabled;
-    s3->skip_early_data = false;
-    s3->early_data_accepted = false;
-    hs->early_data_offered = false;
-    switch (early_data_type) {
-      case early_data_not_offered:
-        break;
-      case early_data_accepted:
-        s3->early_data_accepted = true;
-        hs->early_data_offered = true;
-        hs->can_early_write = true;
-        hs->can_early_read = true;
-        hs->in_early_data = true;
-        break;
-      case early_data_rejected_hrr:
-        hs->early_data_offered = true;
-        break;
-      case early_data_skipped:
-        s3->skip_early_data = true;
-        hs->early_data_offered = true;
-        break;
-      default:
-        return false;
-    }
-  } else {
-    s3->early_data_reason = ssl_early_data_protocol_version;
-  }
-
-  ssl_impl->s3->version = session->ssl_version;
-  if (!ssl_method_supports_version(ssl_impl->method, ssl_impl->s3->version) ||
-      session->cipher != hs->new_cipher ||
-      ssl_protocol_version(ssl_impl) <
-          SSL_CIPHER_get_min_version(session->cipher) ||
-      SSL_CIPHER_get_max_version(session->cipher) <
-          ssl_protocol_version(ssl_impl)) {
-    return false;
-  }
-  ssl_impl->do_handshake = ssl_server_handshake;
-  ssl_impl->server = true;
-  switch (type) {
-    case handback_after_session_resumption:
-      hs->state = state12_read_change_cipher_spec;
-      if (!session_reused) {
-        return false;
-      }
-      break;
-    case handback_after_ecdhe:
-      hs->state = state12_read_client_certificate;
-      if (session_reused) {
-        return false;
-      }
-      break;
-    case handback_after_handshake:
-      hs->state = state12_finish_server_handshake;
-      break;
-    case handback_tls13:
-      hs->state = state12_tls13;
-      hs->tls13_state = state13_send_half_rtt_ticket;
-      break;
-    default:
-      return false;
-  }
-  s3->session_reused = session_reused;
-  hs->channel_id_negotiated = channel_id_negotiated;
-  if (!s3->next_proto_negotiated.CopyFrom(next_proto) ||
-      !s3->alpn_selected.CopyFrom(alpn)) {
-    return false;
-  }
-
-  const size_t hostname_len = CBS_len(&hostname);
-  if (hostname_len == 0) {
-    s3->hostname.reset();
-  } else {
-    char *hostname_str = nullptr;
-    if (!CBS_strdup(&hostname, &hostname_str)) {
-      return false;
-    }
-    s3->hostname.reset(hostname_str);
-  }
-
-  hs->next_proto_neg_seen = next_proto_neg_seen;
-  hs->wait = ssl_hs_flush;
-  hs->extended_master_secret = extended_master_secret;
-  hs->ticket_expected = ticket_expected;
-  hs->cert_request = cert_request;
-
-  if (type != handback_after_handshake &&
-      (!hs->transcript.Init() ||
-       !hs->transcript.InitHash(ssl_protocol_version(ssl_impl),
-                                hs->new_cipher) ||
-       !hs->transcript.Update(transcript))) {
-    return false;
-  }
-  if (type == handback_tls13) {
-    if (!hs->client_traffic_secret_0.TryCopyFrom(client_traffic_secret_0) ||
-        !hs->server_traffic_secret_0.TryCopyFrom(server_traffic_secret_0) ||
-        !hs->client_handshake_secret.TryCopyFrom(client_handshake_secret) ||
-        !hs->server_handshake_secret.TryCopyFrom(server_handshake_secret) ||
-        !hs->secret.TryCopyFrom(secret) ||
-        !s3->exporter_secret.TryCopyFrom(exporter_secret)) {
-      return false;
-    }
-
-    if (s3->early_data_accepted &&
-        !hs->early_traffic_secret.TryCopyFrom(early_traffic_secret)) {
-      return false;
-    }
-  }
-  Array<uint8_t> key_block;
-  switch (type) {
-    case handback_after_session_resumption:
-      // The write keys are installed after server Finished, but the client
-      // keys must wait for ChangeCipherSpec.
-      if (!tls1_configure_aead(ssl_impl, evp_aead_seal, &key_block, session,
-                               write_iv)) {
-        return false;
-      }
-      break;
-    case handback_after_ecdhe:
-      // The premaster secret is not yet computed, so install no keys.
-      break;
-    case handback_after_handshake:
-      // The handshake is complete, so both keys are installed.
-      if (!tls1_configure_aead(ssl_impl, evp_aead_seal, &key_block, session,
-                               write_iv) ||
-          !tls1_configure_aead(ssl_impl, evp_aead_open, &key_block, session,
-                               read_iv)) {
-        return false;
-      }
-      break;
-    case handback_tls13:
-      // After server Finished, the application write keys are installed, but
-      // none of the read keys. The read keys are installed in the state machine
-      // immediately after processing handback.
-      if (!tls13_set_traffic_key(ssl_impl, ssl_encryption_application,
-                                 evp_aead_seal, hs->new_session.get(),
-                                 hs->server_traffic_secret_0)) {
-        return false;
-      }
-      break;
-  }
-  uint8_t read_sequence[8], write_sequence[8];
-  if (!CopyExact(read_sequence, &read_seq) ||
-      !CopyExact(write_sequence, &write_seq)) {
-    return false;
-  }
-  s3->read_sequence = CRYPTO_load_u64_be(read_sequence);
-  s3->write_sequence = CRYPTO_load_u64_be(write_sequence);
-  if (type == handback_after_ecdhe) {
-    uint64_t group_id;
-    CBS private_key;
-    if (!CBS_get_asn1_uint64(&key_share, &group_id) ||  //
-        group_id > 0xffff ||
-        !CBS_get_asn1(&key_share, &private_key, CBS_ASN1_OCTETSTRING)) {
-      return false;
-    }
-    UniquePtr<SSLKeyShare> ssl_key_share = SSLKeyShare::Create(group_id);
-    if (ssl_key_share == nullptr ||
-        !ssl_key_share->DeserializePrivateKey(&private_key) ||
-        !hs->key_shares.TryPushBack(std::move(ssl_key_share))) {
-      return false;
-    }
-  }
-  return true;  // Trailing data allowed for extensibility.
-}
-
 BSSL_NAMESPACE_END
 
 using namespace bssl;
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index 6aa1327..d2b22b5 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -54,7 +54,6 @@
       ticket_expected(false),
       extended_master_secret(false),
       pending_private_key_op(false),
-      handback(false),
       cert_compression_negotiated(false),
       apply_jdk11_workaround(false),
       can_release_private_key(false),
@@ -618,21 +617,6 @@
         hs->wait = ssl_hs_ok;
         return -1;
 
-      case ssl_hs_handoff:
-        ssl->s3->rwstate = SSL_ERROR_HANDOFF;
-        hs->wait = ssl_hs_ok;
-        return -1;
-
-      case ssl_hs_handback: {
-        int ret = ssl->method->flush(ssl);
-        if (ret <= 0) {
-          return ret;
-        }
-        ssl->s3->rwstate = SSL_ERROR_HANDBACK;
-        hs->wait = ssl_hs_handback;
-        return -1;
-      }
-
         // The following cases are associated with callback APIs which expect to
         // be called each time the state machine runs. Thus they set `hs->wait`
         // to `ssl_hs_ok` so that, next time, we re-enter the state machine and
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index 60da4fb..1e64252 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -43,6 +43,31 @@
 
 BSSL_NAMESPACE_BEGIN
 
+enum tls12_server_hs_state_t {
+  state12_start_accept = 0,
+  state12_read_client_hello,
+  state12_read_client_hello_after_ech,
+  state12_cert_callback,
+  state12_tls13,
+  state12_select_parameters,
+  state12_send_server_hello,
+  state12_send_server_certificate,
+  state12_send_server_key_exchange,
+  state12_send_server_hello_done,
+  state12_read_client_certificate,
+  state12_verify_client_certificate,
+  state12_read_client_key_exchange,
+  state12_read_client_certificate_verify,
+  state12_read_change_cipher_spec,
+  state12_process_change_cipher_spec,
+  state12_read_next_proto,
+  state12_read_channel_id,
+  state12_read_client_finished,
+  state12_send_server_finished,
+  state12_finish_server_handshake,
+  state12_done,
+};
+
 bool ssl_client_cipher_list_contains_cipher(
     const SSL_CLIENT_HELLO *client_hello, uint16_t id) {
   CBS cipher_suites;
@@ -478,10 +503,6 @@
     return ssl_hs_error;
   }
 
-  if (hs->config->handoff) {
-    return ssl_hs_handoff;
-  }
-
   uint8_t alert = SSL_AD_DECODE_ERROR;
   // We check for rejection status in case we've rewound the state machine after
   // determining `ClientHelloInner` is invalid.
@@ -810,9 +831,7 @@
     return ssl_hs_error;
   }
 
-  // Handback includes the whole handshake transcript, so we cannot free the
-  // transcript buffer in the handback case.
-  if (!hs->cert_request && !hs->handback) {
+  if (!hs->cert_request) {
     hs->transcript.FreeBuffer();
   }
 
@@ -1143,9 +1162,6 @@
 static enum ssl_hs_wait_t do_read_client_certificate(SSL_HANDSHAKE *hs) {
   SSLImpl *const ssl = hs->ssl;
 
-  if (hs->handback && hs->new_cipher->algorithm_mkey == SSL_kECDHE) {
-    return ssl_hs_handback;
-  }
   if (!hs->cert_request) {
     hs->state = state12_verify_client_certificate;
     return ssl_hs_ok;
@@ -1535,9 +1551,6 @@
 }
 
 static enum ssl_hs_wait_t do_read_change_cipher_spec(SSL_HANDSHAKE *hs) {
-  if (hs->handback && hs->ssl->session != nullptr) {
-    return ssl_hs_handback;
-  }
   hs->state = state12_process_change_cipher_spec;
   return ssl_hs_read_change_cipher_spec;
 }
@@ -1690,10 +1703,6 @@
 static enum ssl_hs_wait_t do_finish_server_handshake(SSL_HANDSHAKE *hs) {
   SSLImpl *const ssl = hs->ssl;
 
-  if (hs->handback) {
-    return ssl_hs_handback;
-  }
-
   ssl->method->on_handshake_complete(ssl);
 
   // If we aren't retaining peer certificates then we can discard it now.
diff --git a/ssl/internal.h b/ssl/internal.h
index 8efc21e..dc5b619 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1695,8 +1695,6 @@
   ssl_hs_read_message,
   ssl_hs_flush,
   ssl_hs_certificate_selection_pending,
-  ssl_hs_handoff,
-  ssl_hs_handback,
   ssl_hs_x509_lookup,
   ssl_hs_private_key_operation,
   ssl_hs_pending_session,
@@ -1721,61 +1719,6 @@
   ssl_grease_last_index = ssl_grease_signature_algorithm,
 };
 
-enum tls12_server_hs_state_t {
-  state12_start_accept = 0,
-  state12_read_client_hello,
-  state12_read_client_hello_after_ech,
-  state12_cert_callback,
-  state12_tls13,
-  state12_select_parameters,
-  state12_send_server_hello,
-  state12_send_server_certificate,
-  state12_send_server_key_exchange,
-  state12_send_server_hello_done,
-  state12_read_client_certificate,
-  state12_verify_client_certificate,
-  state12_read_client_key_exchange,
-  state12_read_client_certificate_verify,
-  state12_read_change_cipher_spec,
-  state12_process_change_cipher_spec,
-  state12_read_next_proto,
-  state12_read_channel_id,
-  state12_read_client_finished,
-  state12_send_server_finished,
-  state12_finish_server_handshake,
-  state12_done,
-};
-
-enum tls13_server_hs_state_t {
-  state13_select_parameters = 0,
-  state13_select_session,
-  state13_send_hello_retry_request,
-  state13_read_second_client_hello,
-  state13_send_server_hello,
-  state13_send_server_certificate_verify,
-  state13_send_server_finished,
-  state13_send_half_rtt_ticket,
-  state13_read_second_client_flight,
-  state13_process_end_of_early_data,
-  state13_read_client_encrypted_extensions,
-  state13_read_client_certificate,
-  state13_read_client_certificate_verify,
-  state13_read_channel_id,
-  state13_read_client_finished,
-  state13_send_new_session_ticket,
-  state13_done,
-};
-
-// handback_t lists the points in the state machine where a handback can occur.
-// These are the different points at which key material is no longer needed.
-enum handback_t {
-  handback_after_session_resumption = 0,
-  handback_after_ecdhe = 1,
-  handback_after_handshake = 2,
-  handback_tls13 = 3,
-  handback_max_value = handback_tls13,
-};
-
 // SSL_HANDSHAKE_HINTS contains handshake hints for a connection. See
 // `SSL_request_handshake_hints` and related functions.
 struct SSL_HANDSHAKE_HINTS {
@@ -2120,12 +2063,6 @@
   // in progress.
   bool pending_private_key_op : 1;
 
-  // handback indicates that a server should pause the handshake after
-  // finishing operations that require private key material, in such a way that
-  // `SSL_get_error` returns `SSL_ERROR_HANDBACK`.  It is set by
-  // `SSL_apply_handoff`.
-  bool handback : 1;
-
   // cert_compression_negotiated is true iff `cert_compression_alg_id` is valid.
   bool cert_compression_negotiated : 1;
 
@@ -3621,12 +3558,6 @@
   // session space. Only effective on the server side.
   bool retain_only_sha256_of_client_certs : 1;
 
-  // handoff indicates that a server should stop after receiving the
-  // ClientHello and pause the handshake in such a way that `SSL_get_error`
-  // returns `SSL_ERROR_HANDOFF`. This is copied in `SSL_new` from the `SSL_CTX`
-  // element of the same name and may be cleared if the handoff is declined.
-  bool handoff : 1;
-
   // shed_handshake_config indicates that the handshake config (this object!)
   // should be freed after the handshake completes.
   bool shed_handshake_config : 1;
@@ -4271,11 +4202,6 @@
   // `SSL_MODE_ENABLE_FALSE_START` is enabled) is allowed without ALPN.
   bool false_start_allowed_without_alpn : 1;
 
-  // handoff indicates that a server should stop after receiving the
-  // ClientHello and pause the handshake in such a way that `SSL_get_error`
-  // returns `SSL_ERROR_HANDOFF`.
-  bool handoff : 1;
-
   // If enable_early_data is true, early data can be sent and accepted.
   bool enable_early_data : 1;
 
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index b59029f..4f98d81 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -270,10 +270,6 @@
 #endif
 }
 
-void SSL_CTX_set_handoff_mode(SSL_CTX *ctx, bool on) {
-  FromOpaque(ctx)->handoff = on;
-}
-
 static bool ssl_can_renegotiate(const SSLImpl *ssl) {
   if (ssl->server || SSL_is_dtls(ssl)) {
     return false;
@@ -316,14 +312,6 @@
   ssl->config.reset();
 }
 
-void SSL_set_handoff_mode(SSL *ssl, bool on) {
-  auto *ssl_impl = FromOpaque(ssl);
-  if (!ssl_impl->config) {
-    return;
-  }
-  ssl_impl->config->handoff = on;
-}
-
 bool SSL_get_traffic_secrets(const SSL *ssl,
                              Span<const uint8_t> *out_read_traffic_secret,
                              Span<const uint8_t> *out_write_traffic_secret) {
@@ -385,7 +373,6 @@
       permute_extensions(false),
       allow_unknown_alpn_protos(false),
       false_start_allowed_without_alpn(false),
-      handoff(false),
       enable_early_data(false),
       resumption_across_names_enabled(false) {
   CRYPTO_new_ex_data(&ex_data);
@@ -559,7 +546,6 @@
   ssl->config->signed_cert_timestamps_enabled =
       ctx_impl->signed_cert_timestamps_enabled;
   ssl->config->ocsp_stapling_enabled = ctx_impl->ocsp_stapling_enabled;
-  ssl->config->handoff = ctx_impl->handoff;
   ssl->quic_method = ctx_impl->quic_method;
 
   if (!ssl->method->ssl_new(ssl.get()) ||
@@ -578,7 +564,6 @@
       ocsp_stapling_enabled(false),
       channel_id_enabled(false),
       retain_only_sha256_of_client_certs(false),
-      handoff(false),
       shed_handshake_config(false),
       jdk11_workaround(false),
       quic_use_legacy_codepoint(false),
@@ -1243,8 +1228,6 @@
   switch (ssl_impl->s3->rwstate) {
     case SSL_ERROR_PENDING_SESSION:
     case SSL_ERROR_PENDING_CERTIFICATE:
-    case SSL_ERROR_HANDOFF:
-    case SSL_ERROR_HANDBACK:
     case SSL_ERROR_WANT_X509_LOOKUP:
     case SSL_ERROR_WANT_PRIVATE_KEY_OPERATION:
     case SSL_ERROR_PENDING_TICKET:
@@ -1331,10 +1314,6 @@
       return "EARLY_DATA_REJECTED";
     case SSL_ERROR_WANT_CERTIFICATE_VERIFY:
       return "WANT_CERTIFICATE_VERIFY";
-    case SSL_ERROR_HANDOFF:
-      return "HANDOFF";
-    case SSL_ERROR_HANDBACK:
-      return "HANDBACK";
     case SSL_ERROR_WANT_RENEGOTIATE:
       return "WANT_RENEGOTIATE";
     case SSL_ERROR_HANDSHAKE_HINTS_READY:
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index 25d0ca3..7fc7be5 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -6976,219 +6976,6 @@
   EXPECT_TRUE(SSL_get_app_data(server.get()) == XORCompressFunc);
 }
 
-void MoveBIOs(SSL *dest, SSL *src) {
-  BIO *rbio = SSL_get_rbio(src);
-  BIO_up_ref(rbio);
-  SSL_set0_rbio(dest, rbio);
-
-  BIO *wbio = SSL_get_wbio(src);
-  BIO_up_ref(wbio);
-  SSL_set0_wbio(dest, wbio);
-
-  SSL_set0_rbio(src, nullptr);
-  SSL_set0_wbio(src, nullptr);
-}
-
-void VerifyHandoff(bool use_new_alps_codepoint) {
-  static const uint8_t alpn[] = {0x03, 'f', 'o', 'o'};
-  static const uint8_t proto[] = {'f', 'o', 'o'};
-  static const uint8_t alps[] = {0x04, 'a', 'l', 'p', 's'};
-
-  bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
-  bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
-  bssl::UniquePtr<SSL_CTX> handshaker_ctx(
-      CreateContextWithTestCertificate(TLS_method()));
-  ASSERT_TRUE(client_ctx);
-  ASSERT_TRUE(server_ctx);
-  ASSERT_TRUE(handshaker_ctx);
-
-  if (!use_new_alps_codepoint) {
-    SetUpExpectedOldCodePoint(server_ctx.get());
-  } else {
-    SetUpExpectedNewCodePoint(server_ctx.get());
-  }
-
-  SSL_CTX_set_session_cache_mode(client_ctx.get(), SSL_SESS_CACHE_CLIENT);
-  SSL_CTX_sess_set_new_cb(client_ctx.get(), SaveLastSession);
-  SSL_CTX_set_handoff_mode(server_ctx.get(), true);
-  uint8_t keys[48];
-  SSL_CTX_get_tlsext_ticket_keys(server_ctx.get(), &keys, sizeof(keys));
-  SSL_CTX_set_tlsext_ticket_keys(handshaker_ctx.get(), &keys, sizeof(keys));
-  SSL_CTX_set_custom_verify(client_ctx.get(), SSL_VERIFY_PEER,
-                            AcceptAnyCertificate);
-
-  for (bool early_data : {false, true}) {
-    SCOPED_TRACE(early_data);
-    for (bool is_resume : {false, true}) {
-      SCOPED_TRACE(is_resume);
-      bssl::UniquePtr<SSL> client, server;
-      ASSERT_TRUE(CreateClientAndServer(&client, &server, client_ctx.get(),
-                                        server_ctx.get()));
-      SSL_set_early_data_enabled(client.get(), early_data);
-
-      // Set up client ALPS settings.
-      SSL_set_alps_use_new_codepoint(client.get(), use_new_alps_codepoint);
-      ASSERT_TRUE(SSL_set_alpn_protos(client.get(), alpn, sizeof(alpn)) == 0);
-      ASSERT_TRUE(SSL_add_application_settings(client.get(), proto,
-                                               sizeof(proto), nullptr, 0));
-      if (is_resume) {
-        ASSERT_TRUE(g_last_session);
-        SSL_set_session(client.get(), g_last_session.get());
-        if (early_data) {
-          EXPECT_GT(FromOpaque(g_last_session.get())->ticket_max_early_data,
-                    0u);
-        }
-      }
-
-
-      int client_ret = SSL_do_handshake(client.get());
-      int client_err = SSL_get_error(client.get(), client_ret);
-
-      uint8_t byte_written;
-      if (early_data && is_resume) {
-        ASSERT_EQ(client_err, 0);
-        EXPECT_TRUE(SSL_in_early_data(client.get()));
-        // Attempt to write early data.
-        byte_written = 43;
-        EXPECT_EQ(SSL_write(client.get(), &byte_written, 1), 1);
-      } else {
-        ASSERT_EQ(client_err, SSL_ERROR_WANT_READ);
-      }
-
-      int server_ret = SSL_do_handshake(server.get());
-      int server_err = SSL_get_error(server.get(), server_ret);
-      ASSERT_EQ(server_err, SSL_ERROR_HANDOFF);
-
-      ScopedCBB cbb;
-      Array<uint8_t> handoff;
-      SSL_CLIENT_HELLO hello;
-      ASSERT_TRUE(CBB_init(cbb.get(), 256));
-      ASSERT_TRUE(SSL_serialize_handoff(server.get(), cbb.get(), &hello));
-      ASSERT_TRUE(CBBFinishArray(cbb.get(), &handoff));
-
-      bssl::UniquePtr<SSL> handshaker(SSL_new(handshaker_ctx.get()));
-      ASSERT_TRUE(handshaker);
-      // Note split handshakes determines 0-RTT support, for both the current
-      // handshake and newly-issued tickets, entirely by `handshaker`. There is
-      // no need to call `SSL_set_early_data_enabled` on `server`.
-      SSL_set_early_data_enabled(handshaker.get(), 1);
-
-      // Set up handshaker ALPS settings.
-      SSL_set_alps_use_new_codepoint(handshaker.get(), use_new_alps_codepoint);
-      SSL_CTX_set_alpn_select_cb(
-          handshaker_ctx.get(),
-          [](SSL *ssl, const uint8_t **out, uint8_t *out_len, const uint8_t *in,
-             unsigned in_len, void *arg) -> int {
-            return SSL_select_next_proto(const_cast<uint8_t **>(out), out_len,
-                                         in, in_len, alpn,
-                                         sizeof(alpn)) == OPENSSL_NPN_NEGOTIATED
-                       ? SSL_TLSEXT_ERR_OK
-                       : SSL_TLSEXT_ERR_NOACK;
-          },
-          nullptr);
-      ASSERT_TRUE(SSL_add_application_settings(
-          handshaker.get(), proto, sizeof(proto), alps, sizeof(alps)));
-
-      ASSERT_TRUE(SSL_apply_handoff(handshaker.get(), handoff));
-
-      MoveBIOs(handshaker.get(), server.get());
-
-      int handshake_ret = SSL_do_handshake(handshaker.get());
-      int handshake_err = SSL_get_error(handshaker.get(), handshake_ret);
-      ASSERT_EQ(handshake_err, SSL_ERROR_HANDBACK);
-
-      // Double-check that additional calls to `SSL_do_handshake` continue
-      // to get `SSL_ERROR_HANDBACK`.
-      handshake_ret = SSL_do_handshake(handshaker.get());
-      handshake_err = SSL_get_error(handshaker.get(), handshake_ret);
-      ASSERT_EQ(handshake_err, SSL_ERROR_HANDBACK);
-
-      ScopedCBB cbb_handback;
-      Array<uint8_t> handback;
-      ASSERT_TRUE(CBB_init(cbb_handback.get(), 1024));
-      ASSERT_TRUE(SSL_serialize_handback(handshaker.get(), cbb_handback.get()));
-      ASSERT_TRUE(CBBFinishArray(cbb_handback.get(), &handback));
-
-      bssl::UniquePtr<SSL> server2(SSL_new(server_ctx.get()));
-      ASSERT_TRUE(server2);
-      ASSERT_TRUE(SSL_apply_handback(server2.get(), handback));
-
-      MoveBIOs(server2.get(), handshaker.get());
-      ASSERT_TRUE(CompleteHandshakes(client.get(), server2.get()));
-      EXPECT_EQ(is_resume, SSL_session_reused(client.get()));
-      // Verify application settings.
-      ASSERT_TRUE(SSL_has_application_settings(client.get()));
-
-      if (early_data && is_resume) {
-        // In this case, one byte of early data has already been written above.
-        EXPECT_TRUE(SSL_early_data_accepted(client.get()));
-      } else {
-        byte_written = 42;
-        EXPECT_EQ(SSL_write(client.get(), &byte_written, 1), 1);
-      }
-      uint8_t byte;
-      EXPECT_EQ(SSL_read(server2.get(), &byte, 1), 1);
-      EXPECT_EQ(byte_written, byte);
-
-      byte = 44;
-      EXPECT_EQ(SSL_write(server2.get(), &byte, 1), 1);
-      EXPECT_EQ(SSL_read(client.get(), &byte, 1), 1);
-      EXPECT_EQ(44, byte);
-    }
-  }
-}
-
-TEST(SSLTest, Handoff) {
-  for (bool use_new_alps_codepoint : {false, true}) {
-    SCOPED_TRACE(use_new_alps_codepoint);
-    VerifyHandoff(use_new_alps_codepoint);
-  }
-}
-
-TEST(SSLTest, HandoffDeclined) {
-  bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
-  bssl::UniquePtr<SSL_CTX> server_ctx(
-      CreateContextWithTestCertificate(TLS_method()));
-  ASSERT_TRUE(client_ctx);
-  ASSERT_TRUE(server_ctx);
-
-  SSL_CTX_set_handoff_mode(server_ctx.get(), true);
-  ASSERT_TRUE(SSL_CTX_set_max_proto_version(server_ctx.get(), TLS1_2_VERSION));
-  SSL_CTX_set_custom_verify(client_ctx.get(), SSL_VERIFY_PEER,
-                            AcceptAnyCertificate);
-
-  bssl::UniquePtr<SSL> client, server;
-  ASSERT_TRUE(CreateClientAndServer(&client, &server, client_ctx.get(),
-                                    server_ctx.get()));
-
-  int client_ret = SSL_do_handshake(client.get());
-  int client_err = SSL_get_error(client.get(), client_ret);
-  ASSERT_EQ(client_err, SSL_ERROR_WANT_READ);
-
-  int server_ret = SSL_do_handshake(server.get());
-  int server_err = SSL_get_error(server.get(), server_ret);
-  ASSERT_EQ(server_err, SSL_ERROR_HANDOFF);
-
-  ScopedCBB cbb;
-  SSL_CLIENT_HELLO hello;
-  ASSERT_TRUE(CBB_init(cbb.get(), 256));
-  ASSERT_TRUE(SSL_serialize_handoff(server.get(), cbb.get(), &hello));
-
-  ASSERT_TRUE(SSL_decline_handoff(server.get()));
-
-  ASSERT_TRUE(CompleteHandshakes(client.get(), server.get()));
-
-  uint8_t byte = 42;
-  EXPECT_EQ(SSL_write(client.get(), &byte, 1), 1);
-  EXPECT_EQ(SSL_read(server.get(), &byte, 1), 1);
-  EXPECT_EQ(42, byte);
-
-  byte = 43;
-  EXPECT_EQ(SSL_write(server.get(), &byte, 1), 1);
-  EXPECT_EQ(SSL_read(client.get(), &byte, 1), 1);
-  EXPECT_EQ(43, byte);
-}
-
 static std::string SigAlgsToString(Span<const uint16_t> sigalgs) {
   std::string ret = "{";
 
@@ -7338,87 +7125,6 @@
   }
 }
 
-TEST(SSLTest, ApplyHandoffRemovesUnsupportedCiphers) {
-  bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
-  ASSERT_TRUE(server_ctx);
-  bssl::UniquePtr<SSL> server(SSL_new(server_ctx.get()));
-  ASSERT_TRUE(server);
-
-  // handoff is a handoff message that has been artificially modified to pretend
-  // that only TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f) is supported. When
-  // it is applied to `server`, all ciphers but that one should be removed.
-  //
-  // To make a new one of these, try sticking this in the `Handoff` test above:
-  //
-  // hexdump(stderr, "", handoff.data(), handoff.size());
-  // sed -e 's/\(..\)/0x\1, /g'
-  //
-  // and modify serialize_features() to emit only cipher 0x0A.
-
-  uint8_t handoff[] = {
-      0x30, 0x81, 0x9a, 0x02, 0x01, 0x00, 0x04, 0x00, 0x04, 0x81, 0x82, 0x01,
-      0x00, 0x00, 0x7e, 0x03, 0x03, 0x30, 0x8e, 0x8f, 0x79, 0xd2, 0x87, 0x39,
-      0xc2, 0x23, 0x23, 0x13, 0xca, 0x3c, 0x80, 0x44, 0xfd, 0x80, 0x83, 0x62,
-      0x3c, 0xcc, 0xf8, 0x76, 0xd3, 0x62, 0xbb, 0x54, 0xe3, 0xc4, 0x39, 0x24,
-      0xa5, 0x00, 0x00, 0x1e, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
-      0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14,
-      0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
-      0x00, 0x37, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00,
-      0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00,
-      0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,
-      0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08,
-      0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x04, 0x02, 0xc0,
-      0x2f, 0x04, 0x0a, 0x00, 0x15, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00,
-      0x1d,
-  };
-
-  EXPECT_LT(1u, sk_SSL_CIPHER_num(SSL_get_ciphers(server.get())));
-  ASSERT_TRUE(SSL_apply_handoff(server.get(), handoff));
-  EXPECT_EQ(1u, sk_SSL_CIPHER_num(SSL_get_ciphers(server.get())));
-}
-
-TEST(SSLTest, ApplyHandoffRemovesUnsupportedCurves) {
-  bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
-  ASSERT_TRUE(server_ctx);
-  bssl::UniquePtr<SSL> server(SSL_new(server_ctx.get()));
-  ASSERT_TRUE(server);
-
-  // handoff is a handoff message that has been artificially modified to pretend
-  // that only one ECDH group is supported.  When it is applied to `server`, all
-  // groups but that one should be removed.
-  //
-  // See `ApplyHandoffRemovesUnsupportedCiphers` for how to make a new one of
-  // these.
-  uint8_t handoff[] = {
-      0x30, 0x81, 0xc0, 0x02, 0x01, 0x00, 0x04, 0x00, 0x04, 0x81, 0x82, 0x01,
-      0x00, 0x00, 0x7e, 0x03, 0x03, 0x98, 0x30, 0xce, 0xd9, 0xb0, 0xdf, 0x5f,
-      0x82, 0x05, 0x4a, 0x43, 0x67, 0x7e, 0xdb, 0x6a, 0x4f, 0x21, 0x18, 0x4e,
-      0x0d, 0x94, 0x63, 0x18, 0x8b, 0x54, 0x89, 0xdb, 0x8b, 0x1d, 0x84, 0xbc,
-      0x09, 0x00, 0x00, 0x1e, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
-      0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14,
-      0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
-      0x00, 0x37, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00,
-      0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00,
-      0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,
-      0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08,
-      0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x04, 0x30, 0x00,
-      0x02, 0x00, 0x0a, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x8c, 0x00, 0x8d, 0x00,
-      0x9c, 0x00, 0x9d, 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x09, 0xc0,
-      0x0a, 0xc0, 0x13, 0xc0, 0x14, 0xc0, 0x2b, 0xc0, 0x2c, 0xc0, 0x2f, 0xc0,
-      0x30, 0xc0, 0x35, 0xc0, 0x36, 0xcc, 0xa8, 0xcc, 0xa9, 0xcc, 0xac, 0x04,
-      0x02, 0x00, 0x17,
-  };
-
-  // The default list of groups is used before applying the handoff.
-  EXPECT_THAT(FromOpaque(server.get())->config->supported_group_list,
-              ElementsAreArray({SSL_GROUP_X25519_MLKEM768, SSL_GROUP_X25519,
-                                SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}));
-  ASSERT_TRUE(SSL_apply_handoff(server.get(), handoff));
-  EXPECT_EQ(1u, FromOpaque(server.get())->config->supported_group_list.size());
-  EXPECT_EQ(SSL_GROUP_SECP256R1,
-            FromOpaque(server.get())->config->supported_group_list[0]);
-}
-
 TEST(SSLTest, ZeroSizedWiteFlushesHandshakeMessages) {
   // If there are pending handshake messages, an `SSL_write` of zero bytes
   // should flush them.
diff --git a/ssl/tls13_enc.cc b/ssl/tls13_enc.cc
index 0fc44ca..7308c3c 100644
--- a/ssl/tls13_enc.cc
+++ b/ssl/tls13_enc.cc
@@ -68,11 +68,7 @@
     return false;
   }
 
-  // Handback includes the whole handshake transcript, so we cannot free the
-  // transcript buffer in the handback case.
-  if (!hs->handback) {
-    hs->transcript.FreeBuffer();
-  }
+  hs->transcript.FreeBuffer();
   return hkdf_extract_to_secret(hs, hs->transcript, psk);
 }
 
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index bb0d4c6..8aaea22 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -36,6 +36,26 @@
 
 BSSL_NAMESPACE_BEGIN
 
+enum tls13_server_hs_state_t {
+  state13_select_parameters = 0,
+  state13_select_session,
+  state13_send_hello_retry_request,
+  state13_read_second_client_hello,
+  state13_send_server_hello,
+  state13_send_server_certificate_verify,
+  state13_send_server_finished,
+  state13_send_half_rtt_ticket,
+  state13_read_second_client_flight,
+  state13_process_end_of_early_data,
+  state13_read_client_encrypted_extensions,
+  state13_read_client_certificate,
+  state13_read_client_certificate_verify,
+  state13_read_channel_id,
+  state13_read_client_finished,
+  state13_send_new_session_ticket,
+  state13_done,
+};
+
 static const uint8_t kZeroes[EVP_MAX_MD_SIZE] = {0};
 
 // Allow a minute of ticket age skew in either direction. This covers
@@ -1155,7 +1175,7 @@
   }
 
   hs->tls13_state = state13_send_half_rtt_ticket;
-  return hs->handback ? ssl_hs_handback : ssl_hs_ok;
+  return ssl_hs_ok;
 }
 
 static enum ssl_hs_wait_t do_send_half_rtt_ticket(SSL_HANDSHAKE *hs) {