Turn SSL into an opaque struct

This avoids squatting on ::ssl_st::ssl_st() and ::ssl_st::~ssl_st()
symbols. All that's left now is SSL_SESSION.

Bug: 500444613
Change-Id: Ia415b58cfec41b819d32d2cb876a2b19697f5abb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/98467
Reviewed-by: Lily Chen <chlily@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/fuzz/decode_client_hello_inner.cc b/fuzz/decode_client_hello_inner.cc
index 42f9270..6291712 100644
--- a/fuzz/decode_client_hello_inner.cc
+++ b/fuzz/decode_client_hello_inner.cc
@@ -47,7 +47,7 @@
   uint8_t alert_unused;
   bssl::Array<uint8_t> client_hello_inner;
   bssl::ssl_decode_client_hello_inner(
-      ssl.get(), &alert_unused, &client_hello_inner, encoded_client_hello_inner,
-      &client_hello_outer);
+      bssl::FromOpaque(ssl.get()), &alert_unused, &client_hello_inner,
+      encoded_client_hello_inner, &client_hello_outer);
   return 0;
 }
diff --git a/ssl/d1_both.cc b/ssl/d1_both.cc
index 7b63d43..ccc2f52 100644
--- a/ssl/d1_both.cc
+++ b/ssl/d1_both.cc
@@ -210,7 +210,7 @@
 
 // dtls1_is_current_message_complete returns whether the current handshake
 // message is complete.
-static bool dtls1_is_current_message_complete(const SSL *ssl) {
+static bool dtls1_is_current_message_complete(const SSLImpl *ssl) {
   size_t idx = ssl->d1->handshake_read_seq % SSL_MAX_HANDSHAKE_FLIGHT;
   DTLSIncomingMessage *frag = ssl->d1->incoming_messages[idx].get();
   return frag != nullptr && frag->reassembly.IsComplete();
@@ -221,7 +221,7 @@
 // queue. Otherwise, it checks `msg_hdr` is consistent with the existing one. It
 // returns NULL on failure. The caller does not take ownership of the result.
 static DTLSIncomingMessage *dtls1_get_incoming_message(
-    SSL *ssl, uint8_t *out_alert, const struct hm_header_st *msg_hdr) {
+    SSLImpl *ssl, uint8_t *out_alert, const struct hm_header_st *msg_hdr) {
   if (msg_hdr->seq < ssl->d1->handshake_read_seq ||
       msg_hdr->seq - ssl->d1->handshake_read_seq >= SSL_MAX_HANDSHAKE_FLIGHT) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
@@ -252,7 +252,7 @@
   return ssl->d1->incoming_messages[idx].get();
 }
 
-bool dtls1_process_handshake_fragments(SSL *ssl, uint8_t *out_alert,
+bool dtls1_process_handshake_fragments(SSLImpl *ssl, uint8_t *out_alert,
                                        DTLSRecordNumber record_number,
                                        Span<const uint8_t> record) {
   bool implicit_ack = false;
@@ -372,7 +372,7 @@
   return true;
 }
 
-ssl_open_record_t dtls1_open_handshake(SSL *ssl, size_t *out_consumed,
+ssl_open_record_t dtls1_open_handshake(SSLImpl *ssl, size_t *out_consumed,
                                        uint8_t *out_alert, Span<uint8_t> in) {
   uint8_t type;
   DTLSRecordNumber record_number;
@@ -433,7 +433,7 @@
   }
 }
 
-bool dtls1_get_message(const SSL *ssl, SSLMessage *out) {
+bool dtls1_get_message(const SSLImpl *ssl, SSLMessage *out) {
   if (!dtls1_is_current_message_complete(ssl)) {
     return false;
   }
@@ -451,7 +451,7 @@
   return true;
 }
 
-void dtls1_next_message(SSL *ssl) {
+void dtls1_next_message(SSLImpl *ssl) {
   assert(ssl->s3->has_message);
   assert(dtls1_is_current_message_complete(ssl));
   size_t index = ssl->d1->handshake_read_seq % SSL_MAX_HANDSHAKE_FLIGHT;
@@ -468,7 +468,7 @@
   }
 }
 
-bool dtls_has_unprocessed_handshake_data(const SSL *ssl) {
+bool dtls_has_unprocessed_handshake_data(const SSLImpl *ssl) {
   size_t current = ssl->d1->handshake_read_seq % SSL_MAX_HANDSHAKE_FLIGHT;
   for (size_t i = 0; i < SSL_MAX_HANDSHAKE_FLIGHT; i++) {
     // Skip the current message.
@@ -499,7 +499,8 @@
   return true;
 }
 
-ssl_open_record_t dtls1_open_change_cipher_spec(SSL *ssl, size_t *out_consumed,
+ssl_open_record_t dtls1_open_change_cipher_spec(SSLImpl *ssl,
+                                                size_t *out_consumed,
                                                 uint8_t *out_alert,
                                                 Span<uint8_t> in) {
   if (!ssl->d1->has_change_cipher_spec) {
@@ -519,7 +520,7 @@
 
 // Sending handshake messages.
 
-void dtls_clear_outgoing_messages(SSL *ssl) {
+void dtls_clear_outgoing_messages(SSLImpl *ssl) {
   ssl->d1->outgoing_messages.clear();
   ssl->d1->sent_records = nullptr;
   ssl->d1->outgoing_written = 0;
@@ -530,7 +531,7 @@
   dtls_clear_unused_write_epochs(ssl);
 }
 
-void dtls_clear_unused_write_epochs(SSL *ssl) {
+void dtls_clear_unused_write_epochs(SSLImpl *ssl) {
   ssl->d1->extra_write_epochs.EraseIf(
       [ssl](const UniquePtr<DTLSWriteEpoch> &write_epoch) -> bool {
         // Non-current epochs may be discarded once there are no incomplete
@@ -547,7 +548,7 @@
       });
 }
 
-bool dtls1_init_message(const SSL *ssl, CBB *cbb, CBB *body, uint8_t type) {
+bool dtls1_init_message(const SSLImpl *ssl, CBB *cbb, CBB *body, uint8_t type) {
   // Pick a modest size hint to save most of the `realloc` calls.
   if (!CBB_init(cbb, 64) ||                                   //
       !CBB_add_u8(cbb, type) ||                               //
@@ -561,7 +562,8 @@
   return true;
 }
 
-bool dtls1_finish_message(const SSL *ssl, CBB *cbb, Array<uint8_t> *out_msg) {
+bool dtls1_finish_message(const SSLImpl *ssl, CBB *cbb,
+                          Array<uint8_t> *out_msg) {
   if (!CBBFinishArray(cbb, out_msg) ||
       out_msg->size() < DTLS1_HM_HEADER_LENGTH) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
@@ -577,7 +579,7 @@
 
 // add_outgoing adds a new handshake message or ChangeCipherSpec to the current
 // outgoing flight. It returns true on success and false on error.
-static bool add_outgoing(SSL *ssl, bool is_ccs, Array<uint8_t> data) {
+static bool add_outgoing(SSLImpl *ssl, bool is_ccs, Array<uint8_t> data) {
   if (ssl->d1->outgoing_messages_complete) {
     // If we've begun writing a new flight, we received the peer flight. Discard
     // the timer and the our flight.
@@ -628,11 +630,11 @@
   return true;
 }
 
-bool dtls1_add_message(SSL *ssl, Array<uint8_t> data) {
+bool dtls1_add_message(SSLImpl *ssl, Array<uint8_t> data) {
   return add_outgoing(ssl, false /* handshake */, std::move(data));
 }
 
-bool dtls1_add_change_cipher_spec(SSL *ssl) {
+bool dtls1_add_change_cipher_spec(SSLImpl *ssl) {
   // DTLS 1.3 disables compatibility mode, which means that DTLS 1.3 never sends
   // a ChangeCipherSpec message.
   if (ssl_protocol_version(ssl) > TLS1_2_VERSION) {
@@ -643,7 +645,7 @@
 
 // dtls1_update_mtu updates the current MTU from the BIO, ensuring it is above
 // the minimum.
-static void dtls1_update_mtu(SSL *ssl) {
+static void dtls1_update_mtu(SSLImpl *ssl) {
   // TODO(davidben): No consumer implements `BIO_CTRL_DGRAM_SET_MTU` and the
   // only `BIO_CTRL_DGRAM_QUERY_MTU` implementation could use
   // `SSL_set_mtu`. Does this need to be so complex?
@@ -677,7 +679,7 @@
 // this record, it returns `seal_continue` and the caller should loop again.
 // Otherwise, it returns `seal_flush` and the packet is complete (either because
 // there are no more messages or the packet is full).
-static seal_result_t seal_next_record(SSL *ssl, Span<uint8_t> out,
+static seal_result_t seal_next_record(SSLImpl *ssl, Span<uint8_t> out,
                                       size_t *out_len) {
   *out_len = 0;
 
@@ -847,7 +849,7 @@
 // seal_next_packet writes as much of the next flight as possible to `out` and
 // advances `ssl->d1->outgoing_written` and `ssl->d1->outgoing_offset` as
 // appropriate.
-static bool seal_next_packet(SSL *ssl, Span<uint8_t> out, size_t *out_len) {
+static bool seal_next_packet(SSLImpl *ssl, Span<uint8_t> out, size_t *out_len) {
   size_t total = 0;
   for (;;) {
     size_t len;
@@ -872,7 +874,7 @@
   return true;
 }
 
-static int send_flight(SSL *ssl) {
+static int send_flight(SSLImpl *ssl) {
   if (ssl->s3->write_shutdown != ssl_shutdown_none) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_PROTOCOL_IS_SHUTDOWN);
     return -1;
@@ -928,7 +930,7 @@
   return 1;
 }
 
-void dtls1_finish_flight(SSL *ssl) {
+void dtls1_finish_flight(SSLImpl *ssl) {
   if (ssl->d1->outgoing_messages.empty() ||
       ssl->d1->outgoing_messages_complete) {
     return;  // Nothing to do.
@@ -953,12 +955,12 @@
   dtls1_stop_timer(ssl);
 }
 
-void dtls1_schedule_ack(SSL *ssl) {
+void dtls1_schedule_ack(SSLImpl *ssl) {
   ssl->d1->ack_timer.Stop();
   ssl->d1->sending_ack = !ssl->d1->records_to_ack.empty();
 }
 
-static int send_ack(SSL *ssl) {
+static int send_ack(SSLImpl *ssl) {
   assert(ssl_protocol_version(ssl) >= TLS1_3_VERSION);
 
   // Ensure we don't send so many ACKs that we overflow the MTU. There is a
@@ -1017,7 +1019,7 @@
   return 1;
 }
 
-int dtls1_flush(SSL *ssl) {
+int dtls1_flush(SSLImpl *ssl) {
   // Send the pending ACK, if any.
   if (ssl->d1->sending_ack) {
     int ret = send_ack(ssl);
diff --git a/ssl/d1_lib.cc b/ssl/d1_lib.cc
index f6f89ca..765fc16 100644
--- a/ssl/d1_lib.cc
+++ b/ssl/d1_lib.cc
@@ -63,7 +63,7 @@
   return true;
 }
 
-bool dtls1_new(SSL *ssl) {
+bool dtls1_new(SSLImpl *ssl) {
   if (!tls_new(ssl)) {
     return false;
   }
@@ -77,7 +77,7 @@
   return true;
 }
 
-void dtls1_free(SSL *ssl) {
+void dtls1_free(SSLImpl *ssl) {
   tls_free(ssl);
 
   if (ssl == nullptr) {
@@ -124,7 +124,7 @@
   return remain_us;
 }
 
-void dtls1_stop_timer(SSL *ssl) {
+void dtls1_stop_timer(SSLImpl *ssl) {
   ssl->d1->num_timeouts = 0;
   ssl->d1->retransmit_timer.Stop();
   ssl->d1->timeout_duration_ms = ssl->initial_timeout_duration_ms;
@@ -135,15 +135,16 @@
 using namespace bssl;
 
 void DTLSv1_set_initial_timeout_duration(SSL *ssl, uint32_t duration_ms) {
-  if (!SSL_is_dtls(ssl)) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!SSL_is_dtls(ssl_impl)) {
     return;
   }
 
   // Modify the initial value for next flight.
-  ssl->initial_timeout_duration_ms = duration_ms;
+  ssl_impl->initial_timeout_duration_ms = duration_ms;
 
   // Retransmit timer increase by factor of 2 at each timeout.
-  uint32_t timeout_duration_ms = duration_ms << ssl->d1->num_timeouts;
+  uint32_t timeout_duration_ms = duration_ms << ssl_impl->d1->num_timeouts;
   if (timeout_duration_ms < duration_ms) {
     timeout_duration_ms = uint32_t{60000};
   } else {
@@ -151,30 +152,32 @@
   }
 
   // Modify the value used for next timeout.
-  ssl->d1->timeout_duration_ms = timeout_duration_ms;
+  ssl_impl->d1->timeout_duration_ms = timeout_duration_ms;
 
   // Modify retransmit timer.
-  if (ssl->d1->retransmit_timer.IsSet()) {
-    ssl->d1->retransmit_timer.UpdateDuration(uint64_t{timeout_duration_ms} *
-                                             1000);
+  if (ssl_impl->d1->retransmit_timer.IsSet()) {
+    ssl_impl->d1->retransmit_timer.UpdateDuration(
+        uint64_t{timeout_duration_ms} * 1000);
   }
 
   // Modify ack timer.
-  if (ssl->d1->ack_timer.IsSet()) {
-    ssl->d1->ack_timer.UpdateDuration(uint64_t{timeout_duration_ms} * 1000 / 4);
+  if (ssl_impl->d1->ack_timer.IsSet()) {
+    ssl_impl->d1->ack_timer.UpdateDuration(uint64_t{timeout_duration_ms} *
+                                           1000 / 4);
   }
 }
 
 int DTLSv1_get_timeout(const SSL *ssl, struct timeval *out) {
-  if (!SSL_is_dtls(ssl)) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!SSL_is_dtls(ssl_impl)) {
     return 0;
   }
 
-  OPENSSL_timeval now = ssl_ctx_get_current_time(ssl->ctx.get());
+  OPENSSL_timeval now = ssl_ctx_get_current_time(ssl_impl->ctx.get());
   uint64_t remaining_usec =
-      ssl->d1->retransmit_timer.MicrosecondsRemaining(now);
-  remaining_usec =
-      std::min(remaining_usec, ssl->d1->ack_timer.MicrosecondsRemaining(now));
+      ssl_impl->d1->retransmit_timer.MicrosecondsRemaining(now);
+  remaining_usec = std::min(remaining_usec,
+                            ssl_impl->d1->ack_timer.MicrosecondsRemaining(now));
   if (remaining_usec == DTLSTimer::kNever) {
     return 0;  // No timeout is set.
   }
@@ -195,39 +198,41 @@
 }
 
 int DTLSv1_handle_timeout(SSL *ssl) {
-  ssl_reset_error_state(ssl);
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_reset_error_state(ssl_impl);
 
-  if (!SSL_is_dtls(ssl)) {
+  if (!SSL_is_dtls(ssl_impl)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return -1;
   }
 
-  if (!ssl->d1->ack_timer.IsSet() && !ssl->d1->retransmit_timer.IsSet()) {
+  if (!ssl_impl->d1->ack_timer.IsSet() &&
+      !ssl_impl->d1->retransmit_timer.IsSet()) {
     // No timers are running. Don't bother querying the clock.
     return 0;
   }
 
-  OPENSSL_timeval now = ssl_ctx_get_current_time(ssl->ctx.get());
+  OPENSSL_timeval now = ssl_ctx_get_current_time(ssl_impl->ctx.get());
   bool any_timer_expired = false;
-  if (ssl->d1->ack_timer.IsExpired(now)) {
+  if (ssl_impl->d1->ack_timer.IsExpired(now)) {
     any_timer_expired = true;
-    ssl->d1->sending_ack = true;
-    ssl->d1->ack_timer.Stop();
+    ssl_impl->d1->sending_ack = true;
+    ssl_impl->d1->ack_timer.Stop();
   }
 
-  if (ssl->d1->retransmit_timer.IsExpired(now)) {
+  if (ssl_impl->d1->retransmit_timer.IsExpired(now)) {
     any_timer_expired = true;
-    ssl->d1->sending_flight = true;
-    ssl->d1->retransmit_timer.Stop();
+    ssl_impl->d1->sending_flight = true;
+    ssl_impl->d1->retransmit_timer.Stop();
 
-    ssl->d1->num_timeouts++;
+    ssl_impl->d1->num_timeouts++;
     // Reduce MTU after 2 unsuccessful retransmissions.
-    if (ssl->d1->num_timeouts > DTLS1_MTU_TIMEOUTS &&
-        !(SSL_get_options(ssl) & SSL_OP_NO_QUERY_MTU)) {
-      long mtu = BIO_ctrl(ssl->wbio.get(), BIO_CTRL_DGRAM_GET_FALLBACK_MTU, 0,
-                          nullptr);
+    if (ssl_impl->d1->num_timeouts > DTLS1_MTU_TIMEOUTS &&
+        !(SSL_get_options(ssl_impl) & SSL_OP_NO_QUERY_MTU)) {
+      long mtu = BIO_ctrl(ssl_impl->wbio.get(), BIO_CTRL_DGRAM_GET_FALLBACK_MTU,
+                          0, nullptr);
       if (mtu >= 0 && mtu <= (1 << 30) && (unsigned)mtu >= dtls1_min_mtu()) {
-        ssl->d1->mtu = (unsigned)mtu;
+        ssl_impl->d1->mtu = (unsigned)mtu;
       }
     }
   }
@@ -236,5 +241,5 @@
     return 0;
   }
 
-  return dtls1_flush(ssl);
+  return dtls1_flush(ssl_impl);
 }
diff --git a/ssl/d1_pkt.cc b/ssl/d1_pkt.cc
index e09e479..e08ad80 100644
--- a/ssl/d1_pkt.cc
+++ b/ssl/d1_pkt.cc
@@ -32,7 +32,7 @@
 
 BSSL_NAMESPACE_BEGIN
 
-ssl_open_record_t dtls1_process_ack(SSL *ssl, uint8_t *out_alert,
+ssl_open_record_t dtls1_process_ack(SSLImpl *ssl, uint8_t *out_alert,
                                     DTLSRecordNumber ack_record_number,
                                     Span<const uint8_t> data) {
   // As a DTLS-1.3-capable client, it is possible to receive an ACK before we
@@ -157,7 +157,7 @@
   return ssl_open_record_discard;
 }
 
-ssl_open_record_t dtls1_open_app_data(SSL *ssl, Span<uint8_t> *out,
+ssl_open_record_t dtls1_open_app_data(SSLImpl *ssl, Span<uint8_t> *out,
                                       size_t *out_consumed, uint8_t *out_alert,
                                       Span<uint8_t> in) {
   assert(!SSL_in_init(ssl));
@@ -234,7 +234,7 @@
   return ssl_open_record_success;
 }
 
-int dtls1_write_app_data(SSL *ssl, bool *out_needs_handshake,
+int dtls1_write_app_data(SSLImpl *ssl, bool *out_needs_handshake,
                          size_t *out_bytes_written, Span<const uint8_t> in) {
   assert(!SSL_in_init(ssl));
   *out_needs_handshake = false;
@@ -265,7 +265,7 @@
   return 1;
 }
 
-int dtls1_write_record(SSL *ssl, int type, Span<const uint8_t> in,
+int dtls1_write_record(SSLImpl *ssl, int type, Span<const uint8_t> in,
                        uint16_t epoch) {
   SSLBuffer *buf = &ssl->s3->write_buffer;
   assert(in.size() <= SSL3_RT_MAX_PLAIN_LENGTH);
@@ -298,7 +298,7 @@
   return 1;
 }
 
-int dtls1_dispatch_alert(SSL *ssl) {
+int dtls1_dispatch_alert(SSLImpl *ssl) {
   int ret = dtls1_write_record(ssl, SSL3_RT_ALERT, ssl->s3->send_alert,
                                ssl->d1->write_epoch.epoch());
   if (ret <= 0) {
diff --git a/ssl/d1_srtp.cc b/ssl/d1_srtp.cc
index ab1f73d..be2378a 100644
--- a/ssl/d1_srtp.cc
+++ b/ssl/d1_srtp.cc
@@ -92,27 +92,29 @@
 }
 
 int SSL_set_srtp_profiles(SSL *ssl, const char *profiles) {
-  return ssl->config != nullptr &&
-         ssl_ctx_make_profiles(profiles, &ssl->config->srtp_profiles);
+  auto *ssl_impl = FromOpaque(ssl);
+  return ssl_impl->config != nullptr &&
+         ssl_ctx_make_profiles(profiles, &ssl_impl->config->srtp_profiles);
 }
 
 const STACK_OF(SRTP_PROTECTION_PROFILE) *SSL_get_srtp_profiles(const SSL *ssl) {
-  if (ssl == nullptr) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl == nullptr) {
     return nullptr;
   }
 
-  if (ssl->config == nullptr) {
+  if (ssl_impl->config == nullptr) {
     assert(0);
     return nullptr;
   }
 
-  return ssl->config->srtp_profiles != nullptr
-             ? ssl->config->srtp_profiles.get()
-             : ssl->ctx->srtp_profiles.get();
+  return ssl_impl->config->srtp_profiles != nullptr
+             ? ssl_impl->config->srtp_profiles.get()
+             : ssl_impl->ctx->srtp_profiles.get();
 }
 
 const SRTP_PROTECTION_PROFILE *SSL_get_selected_srtp_profile(SSL *ssl) {
-  return ssl->s3->srtp_profile;
+  return FromOpaque(ssl)->s3->srtp_profile;
 }
 
 int SSL_CTX_set_tlsext_use_srtp(SSL_CTX *ctx, const char *profiles) {
diff --git a/ssl/dtls_method.cc b/ssl/dtls_method.cc
index 9a3c925..a35e192 100644
--- a/ssl/dtls_method.cc
+++ b/ssl/dtls_method.cc
@@ -25,7 +25,7 @@
 
 using namespace bssl;
 
-static void dtls1_on_handshake_complete(SSL *ssl) {
+static void dtls1_on_handshake_complete(SSLImpl *ssl) {
   if (ssl_protocol_version(ssl) <= TLS1_2_VERSION) {
     // Stop the reply timer left by the last flight we sent. In DTLS 1.2, the
     // retransmission timer ends when the handshake completes. If we sent the
@@ -40,7 +40,7 @@
   }
 }
 
-static bool next_epoch(const SSL *ssl, uint16_t *out,
+static bool next_epoch(const SSLImpl *ssl, uint16_t *out,
                        ssl_encryption_level_t level, uint16_t prev) {
   switch (level) {
     case ssl_encryption_initial:
@@ -68,7 +68,7 @@
   return false;
 }
 
-static bool dtls1_set_read_state(SSL *ssl, ssl_encryption_level_t level,
+static bool dtls1_set_read_state(SSLImpl *ssl, ssl_encryption_level_t level,
                                  UniquePtr<SSLAEADContext> aead_ctx,
                                  Span<const uint8_t> traffic_secret) {
   // Cipher changes are forbidden if the current epoch has leftover data.
@@ -108,7 +108,7 @@
   return true;
 }
 
-static bool dtls1_set_write_state(SSL *ssl, ssl_encryption_level_t level,
+static bool dtls1_set_write_state(SSLImpl *ssl, ssl_encryption_level_t level,
                                   UniquePtr<SSLAEADContext> aead_ctx,
                                   Span<const uint8_t> traffic_secret) {
   uint16_t epoch;
diff --git a/ssl/dtls_record.cc b/ssl/dtls_record.cc
index c0fe668..6b4292a 100644
--- a/ssl/dtls_record.cc
+++ b/ssl/dtls_record.cc
@@ -56,7 +56,7 @@
   }
 }
 
-static uint16_t dtls_record_version(const SSL *ssl) {
+static uint16_t dtls_record_version(const SSLImpl *ssl) {
   if (ssl->s3->version == 0) {
     // Before the version is determined, outgoing records use dTLS 1.0 for
     // historical compatibility requirements.
@@ -68,7 +68,7 @@
                                                      : ssl->s3->version;
 }
 
-static uint64_t dtls_aead_sequence(const SSL *ssl, DTLSRecordNumber num) {
+static uint64_t dtls_aead_sequence(const SSLImpl *ssl, DTLSRecordNumber num) {
   // DTLS 1.3 uses the sequence number with the AEAD, while DTLS 1.2 uses the
   // combined value. If the version is not known, the epoch is unencrypted and
   // the value is ignored.
@@ -119,7 +119,7 @@
   return seqnum;
 }
 
-DTLSReadEpoch *dtls_get_read_epoch(const SSL *ssl, uint16_t epoch) {
+DTLSReadEpoch *dtls_get_read_epoch(const SSLImpl *ssl, uint16_t epoch) {
   if (epoch == ssl->d1->read_epoch.epoch) {
     return &ssl->d1->read_epoch;
   }
@@ -134,7 +134,7 @@
   return nullptr;
 }
 
-DTLSWriteEpoch *dtls_get_write_epoch(const SSL *ssl, uint16_t epoch) {
+DTLSWriteEpoch *dtls_get_write_epoch(const SSLImpl *ssl, uint16_t epoch) {
   if (ssl->d1->write_epoch.epoch() == epoch) {
     return &ssl->d1->write_epoch;
   }
@@ -160,14 +160,14 @@
   uint16_t version = 0;
 };
 
-static bool use_dtls13_record_header(const SSL *ssl, uint16_t epoch) {
+static bool use_dtls13_record_header(const SSLImpl *ssl, uint16_t epoch) {
   // Plaintext records in DTLS 1.3 also use the DTLSPlaintext structure for
   // backwards compatibility.
   return ssl->s3->version != 0 && ssl_protocol_version(ssl) > TLS1_2_VERSION &&
          epoch > 0;
 }
 
-static bool parse_dtls13_record(SSL *ssl, CBS *in, ParsedDTLSRecord *out) {
+static bool parse_dtls13_record(SSLImpl *ssl, CBS *in, ParsedDTLSRecord *out) {
   if (out->type & 0x10) {
     // Connection ID bit set, which we didn't negotiate.
     return false;
@@ -232,7 +232,7 @@
   return true;
 }
 
-static bool parse_dtls12_record(SSL *ssl, CBS *in, ParsedDTLSRecord *out) {
+static bool parse_dtls12_record(SSLImpl *ssl, CBS *in, ParsedDTLSRecord *out) {
   uint64_t epoch_and_seq;
   if (!CBS_get_u16(in, &out->version) ||  //
       !CBS_get_u64(in, &epoch_and_seq) ||
@@ -264,7 +264,7 @@
   return true;
 }
 
-static bool parse_dtls_record(SSL *ssl, CBS *cbs, ParsedDTLSRecord *out) {
+static bool parse_dtls_record(SSLImpl *ssl, CBS *cbs, ParsedDTLSRecord *out) {
   CBS copy = *cbs;
   if (!CBS_get_u8(cbs, &out->type)) {
     return false;
@@ -289,7 +289,7 @@
   return true;
 }
 
-enum ssl_open_record_t dtls_open_record(SSL *ssl, uint8_t *out_type,
+enum ssl_open_record_t dtls_open_record(SSLImpl *ssl, uint8_t *out_type,
                                         DTLSRecordNumber *out_number,
                                         Span<uint8_t> *out,
                                         size_t *out_consumed,
@@ -424,7 +424,7 @@
   return ssl_open_record_success;
 }
 
-size_t dtls_record_header_write_len(const SSL *ssl, uint16_t epoch) {
+size_t dtls_record_header_write_len(const SSLImpl *ssl, uint16_t epoch) {
   if (!use_dtls13_record_header(ssl, epoch)) {
     return DTLS_PLAINTEXT_RECORD_HEADER_LENGTH;
   }
@@ -436,7 +436,7 @@
   return DTLS1_3_RECORD_HEADER_WRITE_LENGTH;
 }
 
-size_t dtls_max_seal_overhead(const SSL *ssl, uint16_t epoch) {
+size_t dtls_max_seal_overhead(const SSLImpl *ssl, uint16_t epoch) {
   DTLSWriteEpoch *write_epoch = dtls_get_write_epoch(ssl, epoch);
   if (write_epoch == nullptr) {
     return 0;
@@ -450,7 +450,7 @@
   return ret;
 }
 
-size_t dtls_seal_prefix_len(const SSL *ssl, uint16_t epoch) {
+size_t dtls_seal_prefix_len(const SSLImpl *ssl, uint16_t epoch) {
   DTLSWriteEpoch *write_epoch = dtls_get_write_epoch(ssl, epoch);
   if (write_epoch == nullptr) {
     return 0;
@@ -459,7 +459,8 @@
          write_epoch->aead->ExplicitNonceLen();
 }
 
-size_t dtls_seal_max_input_len(const SSL *ssl, uint16_t epoch, size_t max_out) {
+size_t dtls_seal_max_input_len(const SSLImpl *ssl, uint16_t epoch,
+                               size_t max_out) {
   DTLSWriteEpoch *write_epoch = dtls_get_write_epoch(ssl, epoch);
   if (write_epoch == nullptr) {
     return 0;
@@ -477,7 +478,7 @@
   return max_out;
 }
 
-bool dtls_seal_record(SSL *ssl, DTLSRecordNumber *out_number, uint8_t *out,
+bool dtls_seal_record(SSLImpl *ssl, DTLSRecordNumber *out_number, uint8_t *out,
                       size_t *out_len, size_t max_out, uint8_t type,
                       const uint8_t *in, size_t in_len, uint16_t epoch) {
   const size_t prefix = dtls_seal_prefix_len(ssl, epoch);
diff --git a/ssl/encrypted_client_hello.cc b/ssl/encrypted_client_hello.cc
index eacc375..46ba196 100644
--- a/ssl/encrypted_client_hello.cc
+++ b/ssl/encrypted_client_hello.cc
@@ -90,7 +90,7 @@
   return true;
 }
 
-static bool is_valid_client_hello_inner(SSL *ssl, uint8_t *out_alert,
+static bool is_valid_client_hello_inner(SSLImpl *ssl, uint8_t *out_alert,
                                         Span<const uint8_t> body) {
   // See RFC 9849, section 7.1.
   SSL_CLIENT_HELLO client_hello;
@@ -135,7 +135,7 @@
 }
 
 bool ssl_decode_client_hello_inner(
-    SSL *ssl, uint8_t *out_alert, Array<uint8_t> *out_client_hello_inner,
+    SSLImpl *ssl, uint8_t *out_alert, Array<uint8_t> *out_client_hello_inner,
     Span<const uint8_t> encoded_client_hello_inner,
     const SSL_CLIENT_HELLO *client_hello_outer) {
   SSL_CLIENT_HELLO client_hello_inner;
@@ -784,7 +784,7 @@
 }
 
 bool ssl_encrypt_client_hello(SSL_HANDSHAKE *hs, Span<const uint8_t> enc) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (!hs->selected_ech_config) {
     return setup_ech_grease(hs);
   }
@@ -894,15 +894,17 @@
 using namespace bssl;
 
 void SSL_set_enable_ech_grease(SSL *ssl, int enable) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->ech_grease_enabled = !!enable;
+  ssl_impl->config->ech_grease_enabled = !!enable;
 }
 
 int SSL_set1_ech_config_list(SSL *ssl, const uint8_t *ech_config_list,
                              size_t ech_config_list_len) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
 
@@ -911,19 +913,20 @@
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ECH_CONFIG_LIST);
     return 0;
   }
-  return ssl->config->client_ech_config_list.CopyFrom(span);
+  return ssl_impl->config->client_ech_config_list.CopyFrom(span);
 }
 
 void SSL_get0_ech_name_override(const SSL *ssl, const char **out_name,
                                 size_t *out_name_len) {
+  const auto *ssl_impl = FromOpaque(ssl);
   // When ECH is rejected, we use the public name. Note that, if
   // `SSL_CTX_set_reverify_on_resume` is enabled, we reverify the certificate
   // before the 0-RTT point. If also offering ECH, we verify as if
   // ClientHelloInner was accepted and do not override. This works because, at
   // this point, `ech_status` will be `ssl_ech_none`. See the
   // ECH-Client-Reject-EarlyDataReject-OverrideNameOnRetry tests in runner.go.
-  const SSL_HANDSHAKE *hs = ssl->s3->hs.get();
-  if (!ssl->server && hs && ssl->s3->ech_status == ssl_ech_rejected) {
+  const SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get();
+  if (!ssl_impl->server && hs && ssl_impl->s3->ech_status == ssl_ech_rejected) {
     *out_name = reinterpret_cast<const char *>(
         hs->selected_ech_config->public_name.data());
     *out_name_len = hs->selected_ech_config->public_name.size();
@@ -936,7 +939,8 @@
 void SSL_get0_ech_retry_configs(const SSL *ssl,
                                 const uint8_t **out_retry_configs,
                                 size_t *out_retry_configs_len) {
-  const SSL_HANDSHAKE *hs = ssl->s3->hs.get();
+  const auto *ssl_impl = FromOpaque(ssl);
+  const SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get();
   if (!hs || !hs->ech_authenticated_reject) {
     // It is an error to call this function except in response to
     // `SSL_R_ECH_REJECTED`. Returning an empty string risks the caller
@@ -1083,12 +1087,13 @@
 }
 
 int SSL_ech_accepted(const SSL *ssl) {
-  if (SSL_in_early_data(ssl) && !ssl->server) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (SSL_in_early_data(ssl_impl) && !ssl_impl->server) {
     // In the client early data state, we report properties as if the server
     // accepted early data. The server can only accept early data with
     // ClientHelloInner.
-    return ssl->s3->hs->selected_ech_config != nullptr;
+    return ssl_impl->s3->hs->selected_ech_config != nullptr;
   }
 
-  return ssl->s3->ech_status == ssl_ech_accepted;
+  return ssl_impl->s3->ech_status == ssl_ech_accepted;
 }
diff --git a/ssl/extensions.cc b/ssl/extensions.cc
index 1f29be1..022714a 100644
--- a/ssl/extensions.cc
+++ b/ssl/extensions.cc
@@ -115,10 +115,10 @@
   }
 }
 
-bool ssl_parse_client_hello_with_trailing_data(const SSL *ssl, CBS *cbs,
+bool ssl_parse_client_hello_with_trailing_data(const SSLImpl *ssl, CBS *cbs,
                                                SSL_CLIENT_HELLO *out) {
   OPENSSL_memset(out, 0, sizeof(*out));
-  out->ssl = const_cast<SSL *>(ssl);
+  out->ssl = const_cast<SSLImpl *>(ssl);
 
   CBS copy = *cbs;
   CBS random, session_id;
@@ -207,7 +207,7 @@
 }
 
 bool tls1_get_shared_group(SSL_HANDSHAKE *hs, uint16_t *out_group_id) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   assert(ssl->server);
 
   // Clients are not required to send a supported_groups extension. In this
@@ -523,7 +523,7 @@
 static bool ext_sni_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
                                     CBB *out_compressible,
                                     ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   // If offering ECH, send the public name instead of the configured name.
   Span<const uint8_t> hostname;
   if (type == ssl_client_hello_outer) {
@@ -613,7 +613,7 @@
 
 static bool ext_ech_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                       CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr) {
     return true;
   }
@@ -665,7 +665,7 @@
 }
 
 static bool ext_ech_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) < TLS1_3_VERSION ||
       ssl->s3->ech_status == ssl_ech_accepted ||  //
       hs->ech_keys == nullptr) {
@@ -700,7 +700,7 @@
 static bool ext_ri_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
                                    CBB *out_compressible,
                                    ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   // Renegotiation indication is not necessary in TLS 1.3.
   if (hs->min_version >= TLS1_3_VERSION ||  //
       type == ssl_client_hello_inner) {
@@ -725,7 +725,7 @@
 
 static bool ext_ri_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                      CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     if (contents != nullptr) {
       *out_alert = SSL_AD_ILLEGAL_PARAMETER;
@@ -804,7 +804,7 @@
 
 static bool ext_ri_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                      CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // Renegotiation isn't supported as a server so this function should never be
   // called after the initial handshake.
   assert(!ssl->s3->initial_handshake_complete);
@@ -839,7 +839,7 @@
 }
 
 static bool ext_ri_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // Renegotiation isn't supported as a server so this function should never be
   // called after the initial handshake.
   assert(!ssl->s3->initial_handshake_complete);
@@ -880,7 +880,7 @@
 
 static bool ext_ems_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                       CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (contents != nullptr) {
     if (ssl_protocol_version(ssl) >= TLS1_3_VERSION ||  //
@@ -942,7 +942,7 @@
 static bool ext_ticket_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
                                        CBB *out_compressible,
                                        ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   // TLS 1.3 uses a different ticket extension.
   if (hs->min_version >= TLS1_3_VERSION || type == ssl_client_hello_inner ||
       SSL_get_options(ssl) & SSL_OP_NO_TICKET) {
@@ -973,7 +973,7 @@
 
 static bool ext_ticket_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                          CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr) {
     return true;
   }
@@ -1091,7 +1091,7 @@
 
 static bool ext_ocsp_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                        CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr) {
     return true;
   }
@@ -1134,7 +1134,7 @@
 }
 
 static bool ext_ocsp_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION ||
       !hs->ocsp_stapling_requested || ssl->s3->session_reused ||
       !ssl_cipher_uses_certificate_auth(hs->new_cipher) ||
@@ -1156,7 +1156,7 @@
 static bool ext_npn_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
                                     CBB *out_compressible,
                                     ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   if (ssl->ctx->next_proto_select_cb == nullptr ||
       // Do not allow NPN to change on renegotiation.
       ssl->s3->initial_handshake_complete ||
@@ -1176,7 +1176,7 @@
 
 static bool ext_npn_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                       CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr) {
     return true;
   }
@@ -1228,7 +1228,7 @@
 
 static bool ext_npn_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                       CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return true;
   }
@@ -1249,7 +1249,7 @@
 }
 
 static bool ext_npn_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // `next_proto_neg_seen` might have been cleared when an ALPN extension was
   // parsed.
   if (!hs->next_proto_neg_seen) {
@@ -1299,7 +1299,7 @@
 
 static bool ext_sct_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                       CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr) {
     return true;
   }
@@ -1351,7 +1351,7 @@
 }
 
 static bool ext_sct_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   assert(hs->scts_requested);
   // The extension shouldn't be sent when resuming sessions.
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION || ssl->s3->session_reused ||
@@ -1379,7 +1379,7 @@
 static bool ext_alpn_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
                                      CBB *out_compressible,
                                      ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   if (hs->config->alpn_client_proto_list.empty() && SSL_is_quic(ssl)) {
     // ALPN MUST be used with QUIC.
     OPENSSL_PUT_ERROR(SSL, SSL_R_NO_APPLICATION_PROTOCOL);
@@ -1407,7 +1407,7 @@
 
 static bool ext_alpn_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                        CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr) {
     if (SSL_is_quic(ssl)) {
       // ALPN is required when QUIC is used.
@@ -1503,7 +1503,7 @@
 
 bool ssl_negotiate_alpn(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                         const SSL_CLIENT_HELLO *client_hello) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   CBS contents;
   if (ssl->ctx->alpn_select_cb == nullptr ||
       !ssl_client_hello_get_extension(
@@ -1574,7 +1574,7 @@
 }
 
 static bool ext_alpn_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl->s3->alpn_selected.empty()) {
     return true;
   }
@@ -1601,7 +1601,7 @@
 static bool ext_channel_id_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
                                            CBB *out_compressible,
                                            ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   if (!hs->config->channel_id_private || SSL_is_dtls(ssl) ||
       // Don't offer Channel ID in ClientHelloOuter. ClientHelloOuter handshakes
       // are not authenticated for the name that can learn the Channel ID.
@@ -1644,7 +1644,7 @@
 static bool ext_channel_id_parse_clienthello(SSL_HANDSHAKE *hs,
                                              uint8_t *out_alert,
                                              CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr || !hs->config->channel_id_enabled ||
       SSL_is_dtls(ssl)) {
     return true;
@@ -1679,7 +1679,7 @@
 static bool ext_srtp_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
                                      CBB *out_compressible,
                                      ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   const STACK_OF(SRTP_PROTECTION_PROFILE) *profiles =
       SSL_get_srtp_profiles(ssl);
   if (profiles == nullptr ||                            //
@@ -1711,7 +1711,7 @@
 
 static bool ext_srtp_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                        CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr) {
     return true;
   }
@@ -1754,7 +1754,7 @@
 
 static bool ext_srtp_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                        CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // DTLS-SRTP is only defined for DTLS.
   if (contents == nullptr || !SSL_is_dtls(ssl)) {
     return true;
@@ -1795,7 +1795,7 @@
 }
 
 static bool ext_srtp_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl->s3->srtp_profile == nullptr) {
     return true;
   }
@@ -1880,7 +1880,7 @@
 }
 
 static bool ext_ec_point_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return true;
   }
@@ -1902,7 +1902,7 @@
 // https://tools.ietf.org/html/rfc8446#section-4.2.11
 
 bool ssl_setup_pre_shared_keys(SSL_HANDSHAKE *hs) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   if (hs->max_version < TLS1_3_VERSION) {
     return true;
   }
@@ -1977,7 +1977,7 @@
                                                CBB *out_extensions,
                                                size_t *out_psk_len,
                                                ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   if (!should_offer_psk(hs, type)) {
     *out_psk_len = 0;
     // Discard empty extensions blocks.
@@ -2280,7 +2280,7 @@
 static bool ext_early_data_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
                                            CBB *out_compressible,
                                            ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   // The second ClientHello never offers early data, and we must have already
   // filled in `early_data_reason` by this point.
   if (ssl->s3->used_hello_retry_request) {
@@ -2308,7 +2308,7 @@
 static bool ext_early_data_parse_serverhello(SSL_HANDSHAKE *hs,
                                              uint8_t *out_alert,
                                              CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr) {
     if (hs->early_data_offered && !ssl->s3->used_hello_retry_request) {
       ssl->s3->early_data_reason = ssl->s3->session_reused
@@ -2346,7 +2346,7 @@
 static bool ext_early_data_parse_clienthello(SSL_HANDSHAKE *hs,
                                              uint8_t *out_alert,
                                              CBS *contents) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr || ssl_protocol_version(ssl) < TLS1_3_VERSION) {
     return true;
   }
@@ -2380,7 +2380,7 @@
 // https://tools.ietf.org/html/rfc8446#section-4.2.8
 
 bool ssl_setup_key_shares(SSL_HANDSHAKE *hs, uint16_t override_group_id) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   hs->key_shares.clear();
   hs->key_share_bytes.Reset();
 
@@ -2644,7 +2644,7 @@
 static bool ext_supported_versions_add_clienthello(
     const SSL_HANDSHAKE *hs, CBB *out, CBB *out_compressible,
     ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   if (hs->max_version <= TLS1_2_VERSION) {
     return true;
   }
@@ -2713,7 +2713,7 @@
                                                  CBB *out,
                                                  CBB *out_compressible,
                                                  ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   // In PAKE mode, supported_groups and key_share are not used.
   if (hs->pake_prover != nullptr) {
     return true;
@@ -2884,7 +2884,7 @@
 }
 
 static bool ext_server_padding_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) < TLS1_3_VERSION ||
       !hs->client_requested_server_padding_size ||
       !hs->config->server_padding_enabled) {
@@ -2997,7 +2997,7 @@
 }
 
 static bool ext_trust_anchors_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) < TLS1_3_VERSION) {
     return true;
   }
@@ -3131,7 +3131,7 @@
 static bool ext_quic_transport_params_parse_serverhello_impl(
     SSL_HANDSHAKE *hs, uint8_t *out_alert, CBS *contents,
     bool used_legacy_codepoint) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr) {
     if (used_legacy_codepoint != hs->config->quic_use_legacy_codepoint) {
       // Silently ignore because we expect the other QUIC codepoint.
@@ -3167,7 +3167,7 @@
 static bool ext_quic_transport_params_parse_clienthello_impl(
     SSL_HANDSHAKE *hs, uint8_t *out_alert, CBS *contents,
     bool used_legacy_codepoint) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (!contents) {
     if (!SSL_is_quic(ssl)) {
       if (hs->config->quic_transport_params.empty()) {
@@ -3633,7 +3633,7 @@
                                           CBB *out_compressible,
                                           ssl_client_hello_type_t type,
                                           bool use_new_codepoint) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   if (  // ALPS requires TLS 1.3.
       hs->max_version < TLS1_3_VERSION ||
       // Do not offer ALPS without ALPN.
@@ -3690,7 +3690,7 @@
 static bool ext_alps_parse_serverhello_impl(SSL_HANDSHAKE *hs,
                                             uint8_t *out_alert, CBS *contents,
                                             bool use_new_codepoint) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (contents == nullptr) {
     return true;
   }
@@ -3732,7 +3732,7 @@
 
 static bool ext_alps_add_serverhello_impl(SSL_HANDSHAKE *hs, CBB *out,
                                           bool use_new_codepoint) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // If early data is accepted, we omit the ALPS extension. It is implicitly
   // carried over from the previous connection.
   if (hs->new_session == nullptr ||
@@ -3774,7 +3774,7 @@
 
 bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                         const SSL_CLIENT_HELLO *client_hello) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl->s3->alpn_selected.empty()) {
     return true;
   }
@@ -4366,7 +4366,7 @@
   // are written to `extensions` and copied to `extensions_encoded`. Compressed
   // extensions are buffered in `compressed` and written to the end. (ECH can
   // only compress contiguous extensions.)
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   bssl::ScopedCBB compressed, outer_extensions;
   CBB extensions, extensions_encoded;
   if (!CBB_add_u16_length_prefixed(out, &extensions) ||
@@ -4482,7 +4482,7 @@
   // header.
   size_t msg_len = SSL3_HM_HEADER_LENGTH + CBB_len(out);
   assert(out_encoded == nullptr);  // Only ClientHelloInner needs two outputs.
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   CBB extensions;
   if (!CBB_add_u16_length_prefixed(out, &extensions)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
@@ -4590,7 +4590,7 @@
 }
 
 bool ssl_add_serverhello_tlsext(SSL_HANDSHAKE *hs, CBB *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   CBB extensions;
   if (!CBB_add_u16_length_prefixed(out, &extensions)) {
     goto err;
@@ -4690,7 +4690,7 @@
 
 bool ssl_parse_clienthello_tlsext(SSL_HANDSHAKE *hs,
                                   const SSL_CLIENT_HELLO *client_hello) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   int alert = SSL_AD_DECODE_ERROR;
   if (!ssl_scan_clienthello_tlsext(hs, client_hello, &alert)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
@@ -4776,7 +4776,7 @@
 }
 
 static bool ssl_check_clienthello_tlsext(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   int ret = SSL_TLSEXT_ERR_NOACK;
   int al = SSL_AD_UNRECOGNIZED_NAME;
   if (ssl->ctx->servername_callback != nullptr) {
@@ -4802,7 +4802,7 @@
 }
 
 static bool ssl_check_serverhello_tlsext(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // ALPS and ALPN have a dependency between each other, so we defer checking
   // consistency to after the callbacks run.
   if (hs->new_session != nullptr && hs->new_session->has_application_settings) {
@@ -4832,7 +4832,7 @@
 }
 
 bool ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, const CBS *cbs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   int alert = SSL_AD_DECODE_ERROR;
   if (!ssl_scan_serverhello_tlsext(hs, cbs, &alert)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
@@ -4995,7 +4995,7 @@
     SSL_HANDSHAKE *hs, UniquePtr<SSL_SESSION> *out_session,
     bool *out_renew_ticket, Span<const uint8_t> ticket,
     Span<const uint8_t> session_id, bool save_ticket) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   *out_renew_ticket = false;
   out_session->reset();
 
@@ -5122,7 +5122,7 @@
 
 bool tls1_choose_signature_algorithm(SSL_HANDSHAKE *hs,
                                      const SSLCredential *cred, uint16_t *out) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (!cred->UsesPrivateKey()) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
     return false;
@@ -5175,7 +5175,7 @@
 }
 
 bool tls1_verify_channel_id(SSL_HANDSHAKE *hs, const SSLMessage &msg) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // A Channel ID handshake message is structured to contain multiple
   // extensions, but the only one that can be present is Channel ID.
   uint16_t extension_type;
@@ -5278,7 +5278,7 @@
 }
 
 bool tls1_channel_id_hash(SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     Array<uint8_t> msg;
     if (!tls13_get_cert_verify_signature_input(hs, &msg,
@@ -5319,7 +5319,7 @@
 }
 
 bool tls1_record_handshake_hashes_for_channel_id(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // This function should never be called for a resumed session because the
   // handshake hashes that we wish to record are for the original, full
   // handshake.
@@ -5366,7 +5366,7 @@
 int SSL_parse_client_hello(const SSL *ssl, SSL_CLIENT_HELLO *out,
                            const uint8_t *in, size_t len) {
   CBS cbs = Span(in, len);
-  if (!ssl_parse_client_hello_with_trailing_data(ssl, &cbs, out)) {
+  if (!ssl_parse_client_hello_with_trailing_data(FromOpaque(ssl), &cbs, out)) {
     return 0;
   }
   if (CBS_len(&cbs) != 0) {
diff --git a/ssl/handoff.cc b/ssl/handoff.cc
index b00514e..c8d9897 100644
--- a/ssl/handoff.cc
+++ b/ssl/handoff.cc
@@ -80,8 +80,9 @@
 
 bool SSL_serialize_handoff(const SSL *ssl, CBB *out,
                            SSL_CLIENT_HELLO *out_hello) {
-  const SSL3_STATE *const s3 = ssl->s3;
-  if (!ssl->server ||       //
+  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;
@@ -98,8 +99,8 @@
                                  reinterpret_cast<uint8_t *>(s3->hs_buf->data),
                                  s3->hs_buf->length) ||
       !serialize_features(&seq) || !CBB_flush(out) ||
-      !ssl->method->get_message(ssl, &msg) ||
-      !SSL_parse_client_hello(ssl, out_hello, CBS_data(&msg.body),
+      !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;
   }
@@ -108,8 +109,10 @@
 }
 
 bool SSL_decline_handoff(SSL *ssl) {
-  const SSL3_STATE *const s3 = ssl->s3;
-  if (!ssl->server || s3->hs == nullptr || s3->rwstate != SSL_ERROR_HANDOFF) {
+  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;
   }
 
@@ -121,7 +124,7 @@
 // (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.)
-static bool apply_remote_features(SSL *ssl, CBS *in) {
+static bool apply_remote_features(SSLImpl *ssl, CBS *in) {
   CBS ciphers;
   if (!CBS_get_asn1(in, &ciphers, CBS_ASN1_OCTETSTRING)) {
     return false;
@@ -267,13 +270,14 @@
 
 // uses_disallowed_feature returns true iff `ssl` enables a feature that
 // disqualifies it for split handshakes.
-static bool uses_disallowed_feature(const SSL *ssl) {
+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) {
-  if (uses_disallowed_feature(ssl)) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (uses_disallowed_feature(ssl_impl)) {
     return false;
   }
 
@@ -288,13 +292,13 @@
   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, &seq)) {
+      !apply_remote_features(ssl_impl, &seq)) {
     return false;
   }
 
-  SSL_set_accept_state(ssl);
+  SSL_set_accept_state(ssl_impl);
 
-  SSL3_STATE *const s3 = ssl->s3;
+  SSL3_STATE *const s3 = ssl_impl->s3;
   s3->v2_hello_done = true;
   s3->has_message = true;
 
@@ -314,10 +318,11 @@
 }
 
 bool SSL_serialize_handback(const SSL *ssl, CBB *out) {
-  if (!ssl->server || uses_disallowed_feature(ssl)) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->server || uses_disallowed_feature(ssl_impl)) {
     return false;
   }
-  const SSL3_STATE *const s3 = ssl->s3;
+  const SSL3_STATE *const s3 = ssl_impl->s3;
   SSL_HANDSHAKE *const hs = s3->hs.get();
   handback_t type;
   switch (hs->state) {
@@ -353,7 +358,7 @@
   const uint8_t *write_iv = nullptr;
   if ((type == handback_after_session_resumption ||
        type == handback_after_handshake) &&
-      ssl->s3->version == TLS1_VERSION &&
+      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;
@@ -361,7 +366,7 @@
   size_t read_iv_len = 0;
   const uint8_t *read_iv = nullptr;
   if (type == handback_after_handshake &&                         //
-      ssl->s3->version == TLS1_VERSION &&                         //
+      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;
@@ -373,7 +378,8 @@
   if (type == handback_tls13) {
     session = hs->new_session.get();
   } else {
-    session = s3->session_reused ? ssl->session.get() : hs->new_session.get();
+    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);
@@ -428,7 +434,7 @@
   if (type == handback_tls13) {
     early_data_t early_data;
     // Check early data invariants.
-    if (ssl->enable_early_data ==
+    if (ssl_impl->enable_early_data ==
         (s3->early_data_reason == ssl_early_data_disabled)) {
       return false;
     }
@@ -494,12 +500,13 @@
 }
 
 bool SSL_apply_handback(SSL *ssl, Span<const uint8_t> handback) {
-  if (ssl->do_handshake != nullptr ||  //
-      ssl->method->is_dtls) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->do_handshake != nullptr ||  //
+      ssl_impl->method->is_dtls) {
     return false;
   }
 
-  SSL3_STATE *const s3 = ssl->s3;
+  SSL3_STATE *const s3 = ssl_impl->s3;
   uint64_t handback_version, unused_token_binding_param, cipher, type_u64,
       alps_codepoint;
 
@@ -539,19 +546,19 @@
     return false;
   }
 
-  s3->hs = ssl_handshake_new(ssl);
+  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->ctx->x509_method, ssl->ctx->pool.get());
+    hs->new_session = SSL_SESSION_parse(&seq, ssl_impl->ctx->x509_method,
+                                        ssl_impl->ctx->pool.get());
     session = hs->new_session.get();
   } else {
-    ssl->session =
-        SSL_SESSION_parse(&seq, ssl->ctx->x509_method, ssl->ctx->pool.get());
-    session = ssl->session.get();
+    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.
@@ -639,7 +646,8 @@
 
     s3->early_data_reason =
         static_cast<ssl_early_data_reason_t>(early_data_reason);
-    ssl->enable_early_data = s3->early_data_reason != ssl_early_data_disabled;
+    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;
@@ -667,15 +675,17 @@
     s3->early_data_reason = ssl_early_data_protocol_version;
   }
 
-  ssl->s3->version = session->ssl_version;
-  if (!ssl_method_supports_version(ssl->method, ssl->s3->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) < SSL_CIPHER_get_min_version(session->cipher) ||
-      SSL_CIPHER_get_max_version(session->cipher) < ssl_protocol_version(ssl)) {
+      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->do_handshake = ssl_server_handshake;
-  ssl->server = true;
+  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;
@@ -725,7 +735,8 @@
 
   if (type != handback_after_handshake &&
       (!hs->transcript.Init() ||
-       !hs->transcript.InitHash(ssl_protocol_version(ssl), hs->new_cipher) ||
+       !hs->transcript.InitHash(ssl_protocol_version(ssl_impl),
+                                hs->new_cipher) ||
        !hs->transcript.Update(transcript))) {
     return false;
   }
@@ -749,7 +760,7 @@
     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, evp_aead_seal, &key_block, session,
+      if (!tls1_configure_aead(ssl_impl, evp_aead_seal, &key_block, session,
                                write_iv)) {
         return false;
       }
@@ -759,9 +770,9 @@
       break;
     case handback_after_handshake:
       // The handshake is complete, so both keys are installed.
-      if (!tls1_configure_aead(ssl, evp_aead_seal, &key_block, session,
+      if (!tls1_configure_aead(ssl_impl, evp_aead_seal, &key_block, session,
                                write_iv) ||
-          !tls1_configure_aead(ssl, evp_aead_open, &key_block, session,
+          !tls1_configure_aead(ssl_impl, evp_aead_open, &key_block, session,
                                read_iv)) {
         return false;
       }
@@ -770,8 +781,8 @@
       // 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, ssl_encryption_application, evp_aead_seal,
-                                 hs->new_session.get(),
+      if (!tls13_set_traffic_key(ssl_impl, ssl_encryption_application,
+                                 evp_aead_seal, hs->new_session.get(),
                                  hs->server_traffic_secret_0)) {
         return false;
       }
@@ -821,7 +832,8 @@
                                 size_t client_hello_len,
                                 const uint8_t *capabilities,
                                 size_t capabilities_len) {
-  if (SSL_is_dtls(ssl) || ssl->s3->hs == nullptr) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (SSL_is_dtls(ssl_impl) || ssl_impl->s3->hs == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
@@ -832,22 +844,23 @@
       MakeUnique<SSL_HANDSHAKE_HINTS>();
   if (pending_hints == nullptr ||
       !CBS_get_asn1(&cbs, &seq, CBS_ASN1_SEQUENCE) ||
-      !apply_remote_features(ssl, &seq)) {
+      !apply_remote_features(ssl_impl, &seq)) {
     return 0;
   }
 
-  SSL3_STATE *const s3 = ssl->s3;
+  SSL3_STATE *const s3 = ssl_impl->s3;
   s3->v2_hello_done = true;
   s3->has_message = true;
 
   Array<uint8_t> client_hello_msg;
   ScopedCBB client_hello_cbb;
   CBB client_hello_body;
-  if (!ssl->method->init_message(ssl, client_hello_cbb.get(),
-                                 &client_hello_body, SSL3_MT_CLIENT_HELLO) ||
+  if (!ssl_impl->method->init_message(ssl_impl, client_hello_cbb.get(),
+                                      &client_hello_body,
+                                      SSL3_MT_CLIENT_HELLO) ||
       !CBB_add_bytes(&client_hello_body, client_hello, client_hello_len) ||
-      !ssl->method->finish_message(ssl, client_hello_cbb.get(),
-                                   &client_hello_msg)) {
+      !ssl_impl->method->finish_message(ssl_impl, client_hello_cbb.get(),
+                                        &client_hello_msg)) {
     return 0;
   }
 
@@ -936,8 +949,9 @@
 static const CBS_ASN1_TAG kIgnoreTicketTag = CBS_ASN1_CONTEXT_SPECIFIC | 10;
 
 int SSL_serialize_handshake_hints(const SSL *ssl, CBB *out) {
-  const SSL_HANDSHAKE *hs = ssl->s3->hs.get();
-  if (!ssl->server || hs == nullptr || hs->pending_hints == nullptr) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  const SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get();
+  if (!ssl_impl->server || hs == nullptr || hs->pending_hints == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
@@ -1064,7 +1078,8 @@
 }
 
 int SSL_set_handshake_hints(SSL *ssl, const uint8_t *hints, size_t hints_len) {
-  if (SSL_is_dtls(ssl) || ssl->s3->hs == nullptr) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (SSL_is_dtls(ssl_impl) || ssl_impl->s3->hs == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
@@ -1195,6 +1210,6 @@
     return 0;
   }
 
-  ssl->s3->hs->provided_hints = std::move(hints_obj);
+  ssl_impl->s3->hs->provided_hints = std::move(hints_obj);
   return 1;
 }
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index 09a1763..5103fe9 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -31,7 +31,7 @@
 
 BSSL_NAMESPACE_BEGIN
 
-SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
+SSL_HANDSHAKE::SSL_HANDSHAKE(SSLImpl *ssl_arg)
     : ssl(ssl_arg),
       transcript(SSL_is_dtls(ssl_arg)),
       inner_transcript(SSL_is_dtls(ssl_arg)),
@@ -99,7 +99,7 @@
   return true;
 }
 
-UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSL *ssl) {
+UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSLImpl *ssl) {
   UniquePtr<SSL_HANDSHAKE> hs = MakeUnique<SSL_HANDSHAKE>(ssl);
   if (!hs || !hs->transcript.Init()) {
     return nullptr;
@@ -112,7 +112,7 @@
   return hs;
 }
 
-bool ssl_check_message_type(SSL *ssl, const SSLMessage &msg, int type) {
+bool ssl_check_message_type(SSLImpl *ssl, const SSLMessage &msg, int type) {
   if (msg.type != type) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
@@ -123,7 +123,7 @@
   return true;
 }
 
-bool ssl_add_message_cbb(SSL *ssl, CBB *cbb) {
+bool ssl_add_message_cbb(SSLImpl *ssl, CBB *cbb) {
   Array<uint8_t> msg;
   if (!ssl->method->finish_message(ssl, cbb, &msg) ||
       !ssl->method->add_message(ssl, std::move(msg))) {
@@ -133,7 +133,7 @@
   return true;
 }
 
-size_t ssl_max_handshake_message_len(const SSL *ssl) {
+size_t ssl_max_handshake_message_len(const SSLImpl *ssl) {
   // kMaxMessageLen is the default maximum message size for handshakes which do
   // not accept peer certificate chains.
   static const size_t kMaxMessageLen = 16384;
@@ -265,7 +265,7 @@
 }
 
 enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   const SSL_SESSION *prev_session = ssl->s3->established_session.get();
   if (prev_session != nullptr) {
     // If renegotiating, the server must not change the server certificate. See
@@ -349,7 +349,7 @@
 // 4. We only support custom verify callbacks.
 enum ssl_verify_result_t ssl_reverify_peer_cert(SSL_HANDSHAKE *hs,
                                                 bool send_alert) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   assert(ssl->s3->established_session == nullptr);
   assert(hs->config->verify_mode != SSL_VERIFY_NONE);
 
@@ -392,7 +392,7 @@
 }
 
 enum ssl_hs_wait_t ssl_get_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -446,7 +446,7 @@
 }
 
 bool ssl_send_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   const SSL_SESSION *session = ssl_handshake_session(hs);
 
   uint8_t finished_buf[EVP_MAX_MD_SIZE];
@@ -524,7 +524,7 @@
 }
 
 int ssl_run_handshake(SSL_HANDSHAKE *hs, bool *out_early_return) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   for (;;) {
     // If a timeout during the handshake triggered a DTLS ACK or retransmit, we
     // resolve that first. E.g., if `ssl_hs_private_key_operation` is slow, the
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index af70197..a4a53cd 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -93,7 +93,7 @@
 
 static bool ssl_write_client_cipher_list(const SSL_HANDSHAKE *hs, CBB *out,
                                          ssl_client_hello_type_t type) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   uint32_t mask_a, mask_k;
   ssl_get_client_disabled(hs, &mask_a, &mask_k);
 
@@ -182,7 +182,7 @@
                                                CBB *cbb,
                                                ssl_client_hello_type_t type,
                                                bool empty_session_id) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   CBB child;
   if (!CBB_add_u16(cbb, hs->client_version) ||
       !CBB_add_bytes(cbb,
@@ -217,7 +217,7 @@
 }
 
 bool ssl_add_client_hello(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   ScopedCBB cbb;
   CBB body;
   ssl_client_hello_type_t type = hs->selected_ech_config
@@ -274,7 +274,7 @@
 // offer early data, and some other reason code otherwise.
 static ssl_early_data_reason_t should_offer_early_data(
     const SSL_HANDSHAKE *hs) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   assert(!ssl->server);
   if (!ssl->enable_early_data) {
     return ssl_early_data_disabled;
@@ -351,7 +351,7 @@
 }
 
 static enum ssl_hs_wait_t do_start_connect(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   ssl_do_info_callback(ssl, SSL_CB_HANDSHAKE_START, 1);
   // `session_reused` must be reset in case this is a renegotiation.
@@ -455,7 +455,7 @@
 }
 
 static enum ssl_hs_wait_t do_enter_early_data(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (!hs->early_data_offered) {
     hs->state = state_read_server_hello;
     return ssl_hs_ok;
@@ -474,7 +474,7 @@
 
 static enum ssl_hs_wait_t do_early_reverify_server_certificate(
     SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl->ctx->reverify_on_resume) {
     // Don't send an alert on error. The alert would be in the clear, which the
     // server is not expecting anyway. Alerts in between ClientHello and
@@ -516,7 +516,7 @@
 
 static bool handle_hello_verify_request(SSL_HANDSHAKE *hs,
                                         const SSLMessage &msg) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   assert(SSL_is_dtls(ssl));
   assert(msg.type == DTLS1_MT_HELLO_VERIFY_REQUEST);
   assert(!hs->received_hello_verify_request);
@@ -580,7 +580,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_server_hello;
@@ -841,7 +841,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_server_certificate(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (!ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
     hs->state = state_read_certificate_status;
@@ -901,7 +901,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_certificate_status(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (!hs->certificate_status_expected) {
     hs->state = state_verify_server_certificate;
@@ -987,7 +987,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_server_key_exchange(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -1162,7 +1162,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_certificate_request(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (!ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
     hs->state = state_read_server_hello_done;
@@ -1234,7 +1234,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_server_hello_done(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -1306,7 +1306,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_client_certificate(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   // The peer didn't request a certificate.
   if (!hs->cert_request) {
@@ -1382,7 +1382,7 @@
               "size_t is smaller than unsigned");
 
 static enum ssl_hs_wait_t do_send_client_key_exchange(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   ScopedCBB cbb;
   CBB body;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
@@ -1538,7 +1538,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_client_certificate_verify(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (!hs->cert_request || hs->credential == nullptr) {
     hs->state = state_send_client_finished;
@@ -1595,7 +1595,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_client_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   hs->can_release_private_key = true;
   if (!ssl->method->add_change_cipher_spec(ssl) ||
       !tls1_change_cipher_state(hs, evp_aead_seal)) {
@@ -1641,7 +1641,7 @@
 }
 
 static bool can_false_start(const SSL_HANDSHAKE *hs) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
 
   // False Start bypasses the Finished check's downgrade protection. This can
   // enable attacks where we send data under weaker settings than supported
@@ -1680,7 +1680,7 @@
 }
 
 static enum ssl_hs_wait_t do_finish_flight(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl->session != nullptr) {
     hs->state = state_finish_client_handshake;
     return ssl_hs_ok;
@@ -1708,7 +1708,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_session_ticket(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (!hs->ticket_expected) {
     hs->state = state_process_change_cipher_spec;
@@ -1786,7 +1786,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_server_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   enum ssl_hs_wait_t wait = ssl_get_finished(hs);
   if (wait != ssl_hs_ok) {
     return wait;
@@ -1802,7 +1802,7 @@
 }
 
 static enum ssl_hs_wait_t do_finish_client_handshake(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl->s3->ech_status == ssl_ech_rejected) {
     // Release the retry configs.
     hs->ech_authenticated_reject = true;
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index 2046db0..64ea061 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -65,7 +65,7 @@
 
 static bool negotiate_version(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                               const SSL_CLIENT_HELLO *client_hello) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   assert(ssl->s3->version == 0);
   CBS supported_versions, versions;
   if (ssl_client_hello_get_extension(client_hello, &supported_versions,
@@ -158,7 +158,7 @@
 static const SSL_CIPHER *choose_cipher(SSL_HANDSHAKE *hs,
                                        const STACK_OF(SSL_CIPHER) *client_pref,
                                        uint32_t mask_k, uint32_t mask_a) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   const STACK_OF(SSL_CIPHER) *prio, *allow;
   // in_group_flags will either be NULL, or will point to an array of bytes
   // which indicate equal-preference groups in the `prio` stack. See the
@@ -404,7 +404,7 @@
 
 static bool decrypt_ech(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                         const SSL_CLIENT_HELLO *client_hello) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   CBS body;
   if (!ssl_client_hello_get_extension(client_hello, &body,
                                       TLSEXT_TYPE_encrypted_client_hello)) {
@@ -482,7 +482,7 @@
 
 static bool extract_sni(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                         const SSL_CLIENT_HELLO *client_hello) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   CBS sni;
   if (!ssl_client_hello_get_extension(client_hello, &sni,
                                       TLSEXT_TYPE_server_name)) {
@@ -532,7 +532,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
@@ -588,7 +588,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_client_hello_after_ech(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   SSLMessage msg_unused;
   SSL_CLIENT_HELLO client_hello;
@@ -674,7 +674,7 @@
 }
 
 static enum ssl_hs_wait_t do_cert_callback(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   // Call `cert_cb` to update server certificates if required.
   if (hs->config->cert->cert_cb != nullptr) {
@@ -733,7 +733,7 @@
 }
 
 static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   SSL_CLIENT_HELLO client_hello;
   if (!hs->GetClientHello(&msg, &client_hello)) {
@@ -913,7 +913,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   // We only accept ChannelIDs on connections with ECDHE in order to avoid a
   // known attack while we fix ChannelID itself.
@@ -1003,7 +1003,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_server_certificate(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   ScopedCBB cbb;
 
   if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
@@ -1114,7 +1114,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_server_key_exchange(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (hs->server_params.size() == 0) {
     hs->state = state12_send_server_hello_done;
@@ -1184,7 +1184,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_server_hello_done(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (hs->pending_hints != nullptr) {
     return ssl_hs_hints_ready;
   }
@@ -1221,7 +1221,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_client_certificate(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (hs->handback && hs->new_cipher->algorithm_mkey == SSL_kECDHE) {
     return ssl_hs_handback;
@@ -1317,7 +1317,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_client_key_exchange(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -1532,7 +1532,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_client_certificate_verify(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   // Only RSA and ECDSA client certificates are supported, so a
   // CertificateVerify is required if and only if there's a client certificate.
@@ -1632,7 +1632,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_next_proto(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (!hs->next_proto_neg_seen) {
     hs->state = state12_read_channel_id;
@@ -1668,7 +1668,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_channel_id(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (!hs->channel_id_negotiated) {
     hs->state = state12_read_client_finished;
@@ -1692,7 +1692,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_client_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   enum ssl_hs_wait_t wait = ssl_get_finished(hs);
   if (wait != ssl_hs_ok) {
     return wait;
@@ -1716,7 +1716,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_server_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (hs->ticket_expected) {
     const SSL_SESSION *session;
@@ -1768,7 +1768,7 @@
 }
 
 static enum ssl_hs_wait_t do_finish_server_handshake(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (hs->handback) {
     return ssl_hs_handback;
diff --git a/ssl/internal.h b/ssl/internal.h
index bdd6320..30b2269 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -58,6 +58,7 @@
 
 DECLARE_OPAQUE_STRUCT(ssl_credential_st, SSLCredential)
 DECLARE_OPAQUE_STRUCT(ssl_ctx_st, SSLContext)
+DECLARE_OPAQUE_STRUCT(ssl_st, SSLImpl)
 DECLARE_OPAQUE_STRUCT(ssl_ech_keys_st, SSLECHKeys)
 
 BSSL_NAMESPACE_BEGIN
@@ -186,11 +187,11 @@
 // ssl_has_final_version returns whether `ssl` has determined the final version.
 // This may be used to distinguish the predictive 0-RTT version from the final
 // one.
-bool ssl_has_final_version(const SSL *ssl);
+bool ssl_has_final_version(const SSLImpl *ssl);
 
 // ssl_protocol_version returns `ssl`'s protocol version. It is an error to
 // call this function before the version is determined.
-uint16_t ssl_protocol_version(const SSL *ssl);
+uint16_t ssl_protocol_version(const SSLImpl *ssl);
 
 // Cipher suites.
 
@@ -716,7 +717,7 @@
 //
 // TODO(davidben): Expose this as part of public API once the high-level
 // buffer-free APIs are available.
-size_t ssl_record_prefix_len(const SSL *ssl);
+size_t ssl_record_prefix_len(const SSLImpl *ssl);
 
 enum ssl_open_record_t {
   ssl_open_record_success,
@@ -749,7 +750,7 @@
 //
 // On failure or fatal alert, it returns `ssl_open_record_error` and sets
 // `*out_alert` to an alert to emit, or zero if no alert should be emitted.
-enum ssl_open_record_t tls_open_record(SSL *ssl, uint8_t *out_type,
+enum ssl_open_record_t tls_open_record(SSLImpl *ssl, uint8_t *out_type,
                                        Span<uint8_t> *out, size_t *out_consumed,
                                        uint8_t *out_alert, Span<uint8_t> in);
 
@@ -757,7 +758,7 @@
 // `ssl_open_record_partial` if `in` was empty and sets `*out_consumed` to
 // zero. The caller should read one packet and try again. On success,
 // `*out_number` is set to the record number of the record.
-enum ssl_open_record_t dtls_open_record(SSL *ssl, uint8_t *out_type,
+enum ssl_open_record_t dtls_open_record(SSLImpl *ssl, uint8_t *out_type,
                                         DTLSRecordNumber *out_number,
                                         Span<uint8_t> *out,
                                         size_t *out_consumed,
@@ -765,7 +766,7 @@
 
 // ssl_needs_record_splitting returns one if `ssl`'s current outgoing cipher
 // state needs record-splitting and zero otherwise.
-bool ssl_needs_record_splitting(const SSL *ssl);
+bool ssl_needs_record_splitting(const SSLImpl *ssl);
 
 // tls_seal_record seals a new record of type `type` and body `in` and writes it
 // to `out`. At most `max_out` bytes will be written. It returns true on success
@@ -778,36 +779,38 @@
 // bytes to `out`.
 //
 // `in` and `out` may not alias.
-bool tls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
-                     uint8_t type, const uint8_t *in, size_t in_len);
+bool tls_seal_record(SSLImpl *ssl, uint8_t *out, size_t *out_len,
+                     size_t max_out, uint8_t type, const uint8_t *in,
+                     size_t in_len);
 
 // dtls_record_header_write_len returns the length of the record header that
 // will be written at `epoch`.
-size_t dtls_record_header_write_len(const SSL *ssl, uint16_t epoch);
+size_t dtls_record_header_write_len(const SSLImpl *ssl, uint16_t epoch);
 
 // dtls_max_seal_overhead returns the maximum overhead, in bytes, of sealing a
 // record.
-size_t dtls_max_seal_overhead(const SSL *ssl, uint16_t epoch);
+size_t dtls_max_seal_overhead(const SSLImpl *ssl, uint16_t epoch);
 
 // dtls_seal_prefix_len returns the number of bytes of prefix to reserve in
 // front of the plaintext when sealing a record in-place.
-size_t dtls_seal_prefix_len(const SSL *ssl, uint16_t epoch);
+size_t dtls_seal_prefix_len(const SSLImpl *ssl, uint16_t epoch);
 
 // dtls_seal_max_input_len returns the maximum number of input bytes that can
 // fit in a record of up to `max_out` bytes, or zero if none may fit.
-size_t dtls_seal_max_input_len(const SSL *ssl, uint16_t epoch, size_t max_out);
+size_t dtls_seal_max_input_len(const SSLImpl *ssl, uint16_t epoch,
+                               size_t max_out);
 
 // dtls_get_read_epoch and dtls_get_write_epoch return the epoch corresponding
 // to `epoch` or nullptr if there is none.
-DTLSReadEpoch *dtls_get_read_epoch(const SSL *ssl, uint16_t epoch);
-DTLSWriteEpoch *dtls_get_write_epoch(const SSL *ssl, uint16_t epoch);
+DTLSReadEpoch *dtls_get_read_epoch(const SSLImpl *ssl, uint16_t epoch);
+DTLSWriteEpoch *dtls_get_write_epoch(const SSLImpl *ssl, uint16_t epoch);
 
 // dtls_seal_record implements `tls_seal_record` for DTLS. `epoch` selects which
 // epoch's cipher state to use. Unlike `tls_seal_record`, `in` and `out` may
 // alias but, if they do, `in` must be exactly `dtls_seal_prefix_len` bytes
 // ahead of `out`. On success, `*out_number` is set to the record number of the
 // record.
-bool dtls_seal_record(SSL *ssl, DTLSRecordNumber *out_number, uint8_t *out,
+bool dtls_seal_record(SSLImpl *ssl, DTLSRecordNumber *out_number, uint8_t *out,
                       size_t *out_len, size_t max_out, uint8_t type,
                       const uint8_t *in, size_t in_len, uint16_t epoch);
 
@@ -815,7 +818,7 @@
 // state. It returns one of `ssl_open_record_discard`, `ssl_open_record_error`,
 // `ssl_open_record_close_notify`, or `ssl_open_record_fatal_alert` as
 // appropriate.
-enum ssl_open_record_t ssl_process_alert(SSL *ssl, uint8_t *out_alert,
+enum ssl_open_record_t ssl_process_alert(SSLImpl *ssl, uint8_t *out_alert,
                                          Span<const uint8_t> in);
 
 
@@ -845,12 +848,12 @@
 
 // ssl_pkey_supports_algorithm returns whether `pkey` may be used to sign
 // `sigalg`.
-bool ssl_pkey_supports_algorithm(const SSL *ssl, EVP_PKEY *pkey,
+bool ssl_pkey_supports_algorithm(const SSLImpl *ssl, EVP_PKEY *pkey,
                                  uint16_t sigalg, bool is_verify);
 
 // ssl_public_key_verify verifies that the `signature` is valid for the public
 // key `pkey` and input `in`, using the signature algorithm `sigalg`.
-bool ssl_public_key_verify(SSL *ssl, Span<const uint8_t> signature,
+bool ssl_public_key_verify(SSLImpl *ssl, Span<const uint8_t> signature,
                            uint16_t sigalg, EVP_PKEY *pkey,
                            Span<const uint8_t> in);
 
@@ -959,42 +962,42 @@
 
 // ssl_max_handshake_message_len returns the maximum number of bytes permitted
 // in a handshake message for `ssl`.
-size_t ssl_max_handshake_message_len(const SSL *ssl);
+size_t ssl_max_handshake_message_len(const SSLImpl *ssl);
 
 // tls_can_accept_handshake_data returns whether `ssl` is able to accept more
 // data into handshake buffer.
-bool tls_can_accept_handshake_data(const SSL *ssl, uint8_t *out_alert);
+bool tls_can_accept_handshake_data(const SSLImpl *ssl, uint8_t *out_alert);
 
 // tls_has_unprocessed_handshake_data returns whether there is buffered
 // handshake data that has not been consumed by `get_message`.
-bool tls_has_unprocessed_handshake_data(const SSL *ssl);
+bool tls_has_unprocessed_handshake_data(const SSLImpl *ssl);
 
 // tls_append_handshake_data appends `data` to the handshake buffer. It returns
 // true on success and false on allocation failure.
-bool tls_append_handshake_data(SSL *ssl, Span<const uint8_t> data);
+bool tls_append_handshake_data(SSLImpl *ssl, Span<const uint8_t> data);
 
 // dtls_has_unprocessed_handshake_data behaves like
 // `tls_has_unprocessed_handshake_data` for DTLS.
-bool dtls_has_unprocessed_handshake_data(const SSL *ssl);
+bool dtls_has_unprocessed_handshake_data(const SSLImpl *ssl);
 
 // tls_flush_pending_hs_data flushes any handshake plaintext data.
-bool tls_flush_pending_hs_data(SSL *ssl);
+bool tls_flush_pending_hs_data(SSLImpl *ssl);
 
 // dtls_clear_outgoing_messages releases all buffered outgoing messages.
-void dtls_clear_outgoing_messages(SSL *ssl);
+void dtls_clear_outgoing_messages(SSLImpl *ssl);
 
 // dtls_clear_unused_write_epochs releases any write epochs that are no longer
 // needed.
-void dtls_clear_unused_write_epochs(SSL *ssl);
+void dtls_clear_unused_write_epochs(SSLImpl *ssl);
 
 
 // Callbacks.
 
 // ssl_do_info_callback calls `ssl`'s info callback, if set.
-void ssl_do_info_callback(const SSL *ssl, int type, int value);
+void ssl_do_info_callback(const SSLImpl *ssl, int type, int value);
 
 // ssl_do_msg_callback calls `ssl`'s message callback, if set.
-void ssl_do_msg_callback(const SSL *ssl, int is_write, int content_type,
+void ssl_do_msg_callback(const SSLImpl *ssl, int is_write, int content_type,
                          Span<const uint8_t> in);
 
 
@@ -1059,19 +1062,19 @@
 //
 // It is an error to call `ssl_read_buffer_extend_to` in DTLS when the buffer is
 // non-empty.
-int ssl_read_buffer_extend_to(SSL *ssl, size_t len);
+int ssl_read_buffer_extend_to(SSLImpl *ssl, size_t len);
 
 // ssl_handle_open_record handles the result of passing `ssl->s3->read_buffer`
 // to a record-processing function. If `ret` is a success or if the caller
 // should retry, it returns one and sets `*out_retry`. Otherwise, it returns <=
 // 0.
-int ssl_handle_open_record(SSL *ssl, bool *out_retry, ssl_open_record_t ret,
+int ssl_handle_open_record(SSLImpl *ssl, bool *out_retry, ssl_open_record_t ret,
                            size_t consumed, uint8_t alert);
 
 // ssl_write_buffer_flush flushes the write buffer to the transport. It returns
 // one on success and <= 0 on error. For DTLS, whether or not the write
 // succeeds, the write buffer will be cleared.
-int ssl_write_buffer_flush(SSL *ssl);
+int ssl_write_buffer_flush(SSLImpl *ssl);
 
 
 // Certificate functions.
@@ -1135,7 +1138,7 @@
 // it returns a newly-allocated `CRYPTO_BUFFER` list and advances
 // `cbs`. Otherwise, it returns nullptr and sets `*out_alert` to an alert to
 // send to the peer.
-UniquePtr<STACK_OF(CRYPTO_BUFFER)> SSL_parse_CA_list(SSL *ssl,
+UniquePtr<STACK_OF(CRYPTO_BUFFER)> SSL_parse_CA_list(SSLImpl *ssl,
                                                      uint8_t *out_alert,
                                                      CBS *cbs);
 
@@ -1182,7 +1185,7 @@
 // tls13_set_traffic_key sets the read or write traffic keys to
 // `traffic_secret`. The version and cipher suite are determined from `session`.
 // It returns true on success and false on error.
-bool tls13_set_traffic_key(SSL *ssl, enum ssl_encryption_level_t level,
+bool tls13_set_traffic_key(SSLImpl *ssl, enum ssl_encryption_level_t level,
                            enum evp_aead_direction_t direction,
                            const SSL_SESSION *session,
                            Span<const uint8_t> traffic_secret);
@@ -1197,7 +1200,8 @@
 
 // tls13_rotate_traffic_key derives the next read or write traffic secret. It
 // returns true on success and false on error.
-bool tls13_rotate_traffic_key(SSL *ssl, enum evp_aead_direction_t direction);
+bool tls13_rotate_traffic_key(SSLImpl *ssl,
+                              enum evp_aead_direction_t direction);
 
 // tls13_derive_application_secrets derives the initial application data traffic
 // and exporter secrets based on the handshake transcripts and `master_secret`.
@@ -1209,7 +1213,7 @@
 
 // tls13_export_keying_material provides an exporter interface to use the
 // `exporter_secret`.
-bool tls13_export_keying_material(const SSL *ssl, Span<uint8_t> out,
+bool tls13_export_keying_material(const SSLImpl *ssl, Span<uint8_t> out,
                                   Span<const uint8_t> secret,
                                   std::string_view label,
                                   Span<const uint8_t> context);
@@ -1349,7 +1353,7 @@
 //
 // This function is exported for fuzzing.
 OPENSSL_EXPORT bool ssl_decode_client_hello_inner(
-    SSL *ssl, uint8_t *out_alert, Array<uint8_t> *out_client_hello_inner,
+    SSLImpl *ssl, uint8_t *out_alert, Array<uint8_t> *out_client_hello_inner,
     Span<const uint8_t> encoded_client_hello_inner,
     const SSL_CLIENT_HELLO *client_hello_outer);
 
@@ -1366,7 +1370,7 @@
 
 // ssl_ech_confirmation_signal_hello_offset returns the offset of the ECH
 // confirmation signal in a ServerHello message, including the handshake header.
-size_t ssl_ech_confirmation_signal_hello_offset(const SSL *ssl);
+size_t ssl_ech_confirmation_signal_hello_offset(const SSLImpl *ssl);
 
 // ssl_ech_accept_confirmation computes the server's ECH acceptance signal,
 // writing it to `out`. The transcript portion is the concatenation of
@@ -1766,12 +1770,12 @@
 };
 
 struct SSL_HANDSHAKE {
-  explicit SSL_HANDSHAKE(SSL *ssl);
+  explicit SSL_HANDSHAKE(SSLImpl *ssl);
   ~SSL_HANDSHAKE();
   static constexpr bool kAllowUniquePtr = true;
 
   // ssl is a non-owning pointer to the parent `SSL` object.
-  SSL *ssl;
+  SSLImpl *ssl;
 
   // config is a non-owning pointer to the handshake configuration.
   SSL_CONFIG *config;
@@ -2162,11 +2166,11 @@
 // so many tickets.
 constexpr size_t kMaxTickets = 16;
 
-UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSL *ssl);
+UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSLImpl *ssl);
 
 // ssl_check_message_type checks if `msg` has type `type`. If so it returns
 // one. Otherwise, it sends an alert and returns zero.
-bool ssl_check_message_type(SSL *ssl, const SSLMessage &msg, int type);
+bool ssl_check_message_type(SSLImpl *ssl, const SSLMessage &msg, int type);
 
 // 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
@@ -2189,11 +2193,11 @@
 
 // tls13_add_key_update queues a KeyUpdate message on `ssl`. `request_type` must
 // be one of `SSL_KEY_UPDATE_REQUESTED` or `SSL_KEY_UPDATE_NOT_REQUESTED`.
-bool tls13_add_key_update(SSL *ssl, int request_type);
+bool tls13_add_key_update(SSLImpl *ssl, int request_type);
 
 // tls13_post_handshake processes a post-handshake message. It returns true on
 // success and false on failure.
-bool tls13_post_handshake(SSL *ssl, const SSLMessage &msg);
+bool tls13_post_handshake(SSLImpl *ssl, const SSLMessage &msg);
 
 bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg,
                                bool allow_anonymous);
@@ -2213,8 +2217,9 @@
 enum ssl_private_key_result_t tls13_add_certificate_verify(SSL_HANDSHAKE *hs);
 
 bool tls13_add_finished(SSL_HANDSHAKE *hs);
-bool tls13_process_new_session_ticket(SSL *ssl, const SSLMessage &msg);
-UniquePtr<SSL_SESSION> tls13_create_session_with_ticket(SSL *ssl, CBS *body);
+bool tls13_process_new_session_ticket(SSLImpl *ssl, const SSLMessage &msg);
+UniquePtr<SSL_SESSION> tls13_create_session_with_ticket(SSLImpl *ssl,
+                                                        CBS *body);
 
 // ssl_setup_extension_permutation computes a ClientHello extension permutation
 // for `hs`, if applicable. It returns true on success and false on error.
@@ -2467,13 +2472,13 @@
 
 // ssl_log_secret logs `secret` with label `label`, if logging is enabled for
 // `ssl`. It returns true on success and false on failure.
-bool ssl_log_secret(const SSL *ssl, const char *label,
+bool ssl_log_secret(const SSLImpl *ssl, const char *label,
                     Span<const uint8_t> secret);
 
 
 // ClientHello functions.
 
-bool ssl_parse_client_hello_with_trailing_data(const SSL *ssl, CBS *cbs,
+bool ssl_parse_client_hello_with_trailing_data(const SSLImpl *ssl, CBS *cbs,
                                                SSL_CLIENT_HELLO *out);
 
 bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello,
@@ -2593,25 +2598,26 @@
 // `SSL_PROTOCOL_METHOD` abstracts between TLS and DTLS.
 struct SSL_PROTOCOL_METHOD {
   bool is_dtls;
-  bool (*ssl_new)(SSL *ssl);
-  void (*ssl_free)(SSL *ssl);
+  bool (*ssl_new)(SSLImpl *ssl);
+  void (*ssl_free)(SSLImpl *ssl);
   // get_message sets `*out` to the current handshake message and returns true
   // if one has been received. It returns false if more input is needed.
-  bool (*get_message)(const SSL *ssl, SSLMessage *out);
+  bool (*get_message)(const SSLImpl *ssl, SSLMessage *out);
   // next_message is called to release the current handshake message.
-  void (*next_message)(SSL *ssl);
+  void (*next_message)(SSLImpl *ssl);
   // has_unprocessed_handshake_data returns whether there is buffered
   // handshake data that has not been consumed by `get_message`.
-  bool (*has_unprocessed_handshake_data)(const SSL *ssl);
+  bool (*has_unprocessed_handshake_data)(const SSLImpl *ssl);
   // Use the `ssl_open_handshake` wrapper.
-  ssl_open_record_t (*open_handshake)(SSL *ssl, size_t *out_consumed,
+  ssl_open_record_t (*open_handshake)(SSLImpl *ssl, size_t *out_consumed,
                                       uint8_t *out_alert, Span<uint8_t> in);
   // Use the `ssl_open_change_cipher_spec` wrapper.
-  ssl_open_record_t (*open_change_cipher_spec)(SSL *ssl, size_t *out_consumed,
+  ssl_open_record_t (*open_change_cipher_spec)(SSLImpl *ssl,
+                                               size_t *out_consumed,
                                                uint8_t *out_alert,
                                                Span<uint8_t> in);
   // Use the `ssl_open_app_data` wrapper.
-  ssl_open_record_t (*open_app_data)(SSL *ssl, Span<uint8_t> *out,
+  ssl_open_record_t (*open_app_data)(SSLImpl *ssl, Span<uint8_t> *out,
                                      size_t *out_consumed, uint8_t *out_alert,
                                      Span<uint8_t> in);
   // write_app_data encrypts and writes `in` as application data. On success, it
@@ -2619,33 +2625,33 @@
   // written. Otherwise, it returns <= 0 and sets `*out_needs_handshake` to
   // whether the operation failed because the caller needs to drive the
   // handshake.
-  int (*write_app_data)(SSL *ssl, bool *out_needs_handshake,
+  int (*write_app_data)(SSLImpl *ssl, bool *out_needs_handshake,
                         size_t *out_bytes_written, Span<const uint8_t> in);
-  int (*dispatch_alert)(SSL *ssl);
+  int (*dispatch_alert)(SSLImpl *ssl);
   // init_message begins a new handshake message of type `type`. `cbb` is the
   // root CBB to be passed into `finish_message`. `*body` is set to a child CBB
   // the caller should write to. It returns true on success and false on error.
-  bool (*init_message)(const SSL *ssl, CBB *cbb, CBB *body, uint8_t type);
+  bool (*init_message)(const SSLImpl *ssl, CBB *cbb, CBB *body, uint8_t type);
   // finish_message finishes a handshake message. It sets `*out_msg` to the
   // serialized message. It returns true on success and false on error.
-  bool (*finish_message)(const SSL *ssl, CBB *cbb, Array<uint8_t> *out_msg);
+  bool (*finish_message)(const SSLImpl *ssl, CBB *cbb, Array<uint8_t> *out_msg);
   // add_message adds a handshake message to the pending flight. It returns
   // true on success and false on error.
-  bool (*add_message)(SSL *ssl, Array<uint8_t> msg);
+  bool (*add_message)(SSLImpl *ssl, Array<uint8_t> msg);
   // add_change_cipher_spec adds a ChangeCipherSpec record to the pending
   // flight. It returns true on success and false on error.
-  bool (*add_change_cipher_spec)(SSL *ssl);
+  bool (*add_change_cipher_spec)(SSLImpl *ssl);
   // finish_flight marks the pending flight as finished and ready to send.
   // `flush` must be called to write it.
-  void (*finish_flight)(SSL *ssl);
+  void (*finish_flight)(SSLImpl *ssl);
   // schedule_ack schedules a DTLS 1.3 ACK to be sent, without an ACK delay.
   // `flush` must be called to write it.
-  void (*schedule_ack)(SSL *ssl);
+  void (*schedule_ack)(SSLImpl *ssl);
   // flush writes any scheduled data to the transport. It returns one on success
   // and <= 0 on error.
-  int (*flush)(SSL *ssl);
+  int (*flush)(SSLImpl *ssl);
   // on_handshake_complete is called when the handshake is complete.
-  void (*on_handshake_complete)(SSL *ssl);
+  void (*on_handshake_complete)(SSLImpl *ssl);
   // set_read_state sets `ssl`'s read cipher state and level to `aead_ctx` and
   // `level`. In QUIC, `aead_ctx` is a placeholder object. In TLS 1.3,
   // `traffic_secret` is the original traffic secret. This function returns true
@@ -2653,7 +2659,7 @@
   //
   // TODO(crbug.com/371998381): Take the traffic secrets as input and let the
   // function create the SSLAEADContext.
-  bool (*set_read_state)(SSL *ssl, ssl_encryption_level_t level,
+  bool (*set_read_state)(SSLImpl *ssl, ssl_encryption_level_t level,
                          UniquePtr<SSLAEADContext> aead_ctx,
                          Span<const uint8_t> traffic_secret);
   // set_write_state sets `ssl`'s write cipher state and level to `aead_ctx` and
@@ -2663,7 +2669,7 @@
   //
   // TODO(crbug.com/371998381): Take the traffic secrets as input and let the
   // function create the SSLAEADContext.
-  bool (*set_write_state)(SSL *ssl, ssl_encryption_level_t level,
+  bool (*set_write_state)(SSLImpl *ssl, ssl_encryption_level_t level,
                           UniquePtr<SSLAEADContext> aead_ctx,
                           Span<const uint8_t> traffic_secret);
 };
@@ -2672,12 +2678,13 @@
 
 // ssl_open_handshake processes a record from `in` for reading a handshake
 // message.
-ssl_open_record_t ssl_open_handshake(SSL *ssl, size_t *out_consumed,
+ssl_open_record_t ssl_open_handshake(SSLImpl *ssl, size_t *out_consumed,
                                      uint8_t *out_alert, Span<uint8_t> in);
 
 // ssl_open_change_cipher_spec processes a record from `in` for reading a
 // ChangeCipherSpec.
-ssl_open_record_t ssl_open_change_cipher_spec(SSL *ssl, size_t *out_consumed,
+ssl_open_record_t ssl_open_change_cipher_spec(SSLImpl *ssl,
+                                              size_t *out_consumed,
                                               uint8_t *out_alert,
                                               Span<uint8_t> in);
 
@@ -2686,7 +2693,7 @@
 // input. If it encounters a post-handshake message, it returns
 // `ssl_open_record_discard`. The caller should then retry, after processing any
 // messages received with `get_message`.
-ssl_open_record_t ssl_open_app_data(SSL *ssl, Span<uint8_t> *out,
+ssl_open_record_t ssl_open_app_data(SSLImpl *ssl, Span<uint8_t> *out,
                                     size_t *out_consumed, uint8_t *out_alert,
                                     Span<uint8_t> in);
 
@@ -3345,11 +3352,11 @@
 struct SSL_CONFIG {
   static constexpr bool kAllowUniquePtr = true;
 
-  explicit SSL_CONFIG(SSL *ssl_arg);
+  explicit SSL_CONFIG(SSLImpl *ssl_arg);
   ~SSL_CONFIG();
 
   // ssl is a non-owning pointer to the parent `SSL` object.
-  SSL *const ssl = nullptr;
+  SSLImpl *const ssl = nullptr;
 
   // conf_max_version is the maximum acceptable version configured by
   // `SSL_set_max_proto_version`. Note this version is not normalized in DTLS
@@ -3604,7 +3611,7 @@
 
 // ssl_session_is_time_valid returns true if `session` is still valid and false
 // if it has expired.
-bool ssl_session_is_time_valid(const SSL *ssl, const SSL_SESSION *session);
+bool ssl_session_is_time_valid(const SSLImpl *ssl, const SSL_SESSION *session);
 
 // ssl_session_is_resumable returns whether `session` is resumable for `hs`.
 bool ssl_session_is_resumable(const SSL_HANDSHAKE *hs,
@@ -3623,7 +3630,7 @@
 // on the peer's certificate type) or a valid SHA-256 hash thereof.
 bool ssl_session_has_peer_cred(const SSL_SESSION *session);
 
-void ssl_set_session(SSL *ssl, SSL_SESSION *session);
+void ssl_set_session(SSLImpl *ssl, SSL_SESSION *session);
 
 // ssl_get_prev_session looks up the previous session based on `client_hello`.
 // On success, it sets `*out_session` to the session or nullptr if none was
@@ -3652,74 +3659,77 @@
 
 // ssl_session_rebase_time updates `session`'s start time to the current time,
 // adjusting the timeout so the expiration time is unchanged.
-void ssl_session_rebase_time(SSL *ssl, SSL_SESSION *session);
+void ssl_session_rebase_time(SSLImpl *ssl, SSL_SESSION *session);
 
 // ssl_session_renew_timeout calls `ssl_session_rebase_time` and renews
 // `session`'s timeout to `timeout` (measured from the current time). The
 // renewal is clamped to the session's auth_timeout.
-void ssl_session_renew_timeout(SSL *ssl, SSL_SESSION *session,
+void ssl_session_renew_timeout(SSLImpl *ssl, SSL_SESSION *session,
                                uint32_t timeout);
 
-void ssl_update_cache(SSL *ssl);
+void ssl_update_cache(SSLImpl *ssl);
 
-void ssl_send_alert(SSL *ssl, int level, int desc);
-int ssl_send_alert_impl(SSL *ssl, int level, int desc);
-bool tls_get_message(const SSL *ssl, SSLMessage *out);
-ssl_open_record_t tls_open_handshake(SSL *ssl, size_t *out_consumed,
+void ssl_send_alert(SSLImpl *ssl, int level, int desc);
+int ssl_send_alert_impl(SSLImpl *ssl, int level, int desc);
+bool tls_get_message(const SSLImpl *ssl, SSLMessage *out);
+ssl_open_record_t tls_open_handshake(SSLImpl *ssl, size_t *out_consumed,
                                      uint8_t *out_alert, Span<uint8_t> in);
-void tls_next_message(SSL *ssl);
+void tls_next_message(SSLImpl *ssl);
 
-int tls_dispatch_alert(SSL *ssl);
-ssl_open_record_t tls_open_app_data(SSL *ssl, Span<uint8_t> *out,
+int tls_dispatch_alert(SSLImpl *ssl);
+ssl_open_record_t tls_open_app_data(SSLImpl *ssl, Span<uint8_t> *out,
                                     size_t *out_consumed, uint8_t *out_alert,
                                     Span<uint8_t> in);
-ssl_open_record_t tls_open_change_cipher_spec(SSL *ssl, size_t *out_consumed,
+ssl_open_record_t tls_open_change_cipher_spec(SSLImpl *ssl,
+                                              size_t *out_consumed,
                                               uint8_t *out_alert,
                                               Span<uint8_t> in);
-int tls_write_app_data(SSL *ssl, bool *out_needs_handshake,
+int tls_write_app_data(SSLImpl *ssl, bool *out_needs_handshake,
                        size_t *out_bytes_written, Span<const uint8_t> in);
 
-bool tls_new(SSL *ssl);
-void tls_free(SSL *ssl);
+bool tls_new(SSLImpl *ssl);
+void tls_free(SSLImpl *ssl);
 
-bool tls_init_message(const SSL *ssl, CBB *cbb, CBB *body, uint8_t type);
-bool tls_finish_message(const SSL *ssl, CBB *cbb, Array<uint8_t> *out_msg);
-bool tls_add_message(SSL *ssl, Array<uint8_t> msg);
-bool tls_add_change_cipher_spec(SSL *ssl);
-int tls_flush(SSL *ssl);
+bool tls_init_message(const SSLImpl *ssl, CBB *cbb, CBB *body, uint8_t type);
+bool tls_finish_message(const SSLImpl *ssl, CBB *cbb, Array<uint8_t> *out_msg);
+bool tls_add_message(SSLImpl *ssl, Array<uint8_t> msg);
+bool tls_add_change_cipher_spec(SSLImpl *ssl);
+int tls_flush(SSLImpl *ssl);
 
-bool dtls1_init_message(const SSL *ssl, CBB *cbb, CBB *body, uint8_t type);
-bool dtls1_finish_message(const SSL *ssl, CBB *cbb, Array<uint8_t> *out_msg);
-bool dtls1_add_message(SSL *ssl, Array<uint8_t> msg);
-bool dtls1_add_change_cipher_spec(SSL *ssl);
-void dtls1_finish_flight(SSL *ssl);
-void dtls1_schedule_ack(SSL *ssl);
-int dtls1_flush(SSL *ssl);
+bool dtls1_init_message(const SSLImpl *ssl, CBB *cbb, CBB *body, uint8_t type);
+bool dtls1_finish_message(const SSLImpl *ssl, CBB *cbb,
+                          Array<uint8_t> *out_msg);
+bool dtls1_add_message(SSLImpl *ssl, Array<uint8_t> msg);
+bool dtls1_add_change_cipher_spec(SSLImpl *ssl);
+void dtls1_finish_flight(SSLImpl *ssl);
+void dtls1_schedule_ack(SSLImpl *ssl);
+int dtls1_flush(SSLImpl *ssl);
 
 // ssl_add_message_cbb finishes the handshake message in `cbb` and adds it to
 // the pending flight. It returns true on success and false on error.
-bool ssl_add_message_cbb(SSL *ssl, CBB *cbb);
+bool ssl_add_message_cbb(SSLImpl *ssl, CBB *cbb);
 
 // ssl_hash_message incorporates `msg` into the handshake hash. It returns true
 // on success and false on allocation failure.
 bool ssl_hash_message(SSL_HANDSHAKE *hs, const SSLMessage &msg);
 
-ssl_open_record_t dtls1_process_ack(SSL *ssl, uint8_t *out_alert,
+ssl_open_record_t dtls1_process_ack(SSLImpl *ssl, uint8_t *out_alert,
                                     DTLSRecordNumber ack_record_number,
                                     Span<const uint8_t> data);
-ssl_open_record_t dtls1_open_app_data(SSL *ssl, Span<uint8_t> *out,
+ssl_open_record_t dtls1_open_app_data(SSLImpl *ssl, Span<uint8_t> *out,
                                       size_t *out_consumed, uint8_t *out_alert,
                                       Span<uint8_t> in);
-ssl_open_record_t dtls1_open_change_cipher_spec(SSL *ssl, size_t *out_consumed,
+ssl_open_record_t dtls1_open_change_cipher_spec(SSLImpl *ssl,
+                                                size_t *out_consumed,
                                                 uint8_t *out_alert,
                                                 Span<uint8_t> in);
 
-int dtls1_write_app_data(SSL *ssl, bool *out_needs_handshake,
+int dtls1_write_app_data(SSLImpl *ssl, bool *out_needs_handshake,
                          size_t *out_bytes_written, Span<const uint8_t> in);
 
 // dtls1_write_record sends a record. It returns one on success and <= 0 on
 // error.
-int dtls1_write_record(SSL *ssl, int type, Span<const uint8_t> in,
+int dtls1_write_record(SSLImpl *ssl, int type, Span<const uint8_t> in,
                        uint16_t epoch);
 
 bool dtls1_parse_fragment(CBS *cbs, struct hm_header_st *out_hdr,
@@ -3733,28 +3743,28 @@
 // before failing the DTLS handshake.
 #define DTLS1_MAX_TIMEOUTS 12
 
-void dtls1_stop_timer(SSL *ssl);
+void dtls1_stop_timer(SSLImpl *ssl);
 
 unsigned int dtls1_min_mtu();
 
-bool dtls1_new(SSL *ssl);
-void dtls1_free(SSL *ssl);
+bool dtls1_new(SSLImpl *ssl);
+void dtls1_free(SSLImpl *ssl);
 
-bool dtls1_process_handshake_fragments(SSL *ssl, uint8_t *out_alert,
+bool dtls1_process_handshake_fragments(SSLImpl *ssl, uint8_t *out_alert,
                                        DTLSRecordNumber record_number,
                                        Span<const uint8_t> record);
-bool dtls1_get_message(const SSL *ssl, SSLMessage *out);
-ssl_open_record_t dtls1_open_handshake(SSL *ssl, size_t *out_consumed,
+bool dtls1_get_message(const SSLImpl *ssl, SSLMessage *out);
+ssl_open_record_t dtls1_open_handshake(SSLImpl *ssl, size_t *out_consumed,
                                        uint8_t *out_alert, Span<uint8_t> in);
-void dtls1_next_message(SSL *ssl);
-int dtls1_dispatch_alert(SSL *ssl);
+void dtls1_next_message(SSLImpl *ssl);
+int dtls1_dispatch_alert(SSLImpl *ssl);
 
 // tls1_configure_aead configures either the read or write direction AEAD (as
 // determined by `direction`) using the keys generated by the TLS KDF. The
 // `key_block_cache` argument is used to store the generated key block, if
 // empty. Otherwise it's assumed that the key block is already contained within
 // it. It returns true on success or false on error.
-bool tls1_configure_aead(SSL *ssl, evp_aead_direction_t direction,
+bool tls1_configure_aead(SSLImpl *ssl, evp_aead_direction_t direction,
                          Array<uint8_t> *key_block_cache,
                          const SSL_SESSION *session,
                          Span<const uint8_t> iv_override);
@@ -3832,19 +3842,19 @@
 bool tls1_record_handshake_hashes_for_channel_id(SSL_HANDSHAKE *hs);
 
 // ssl_can_write returns whether `ssl` is allowed to write.
-bool ssl_can_write(const SSL *ssl);
+bool ssl_can_write(const SSLImpl *ssl);
 
 // ssl_can_read returns whether `ssl` is allowed to read.
-bool ssl_can_read(const SSL *ssl);
+bool ssl_can_read(const SSLImpl *ssl);
 
 OPENSSL_timeval ssl_ctx_get_current_time(const SSLContext *ctx);
 
 // ssl_reset_error_state resets state for `SSL_get_error`.
-void ssl_reset_error_state(SSL *ssl);
+void ssl_reset_error_state(SSLImpl *ssl);
 
 // ssl_set_read_error sets `ssl`'s read half into an error state, saving the
 // current state of the error queue.
-void ssl_set_read_error(SSL *ssl);
+void ssl_set_read_error(SSLImpl *ssl);
 
 BSSL_NAMESPACE_END
 
@@ -4202,39 +4212,41 @@
   friend RefCounted;
   ~SSLContext();
 };
-BSSL_NAMESPACE_END
 
-struct ssl_st {
-  explicit ssl_st(bssl::SSLContext *ctx_arg);
-  ssl_st(const ssl_st &) = delete;
-  ssl_st &operator=(const ssl_st &) = delete;
-  ~ssl_st();
+class SSLImpl : public ssl_st {
+ public:
+  static constexpr bool kAllowUniquePtr = true;
+
+  explicit SSLImpl(SSLContext *ctx_arg);
+  SSLImpl(const SSLImpl &) = delete;
+  SSLImpl &operator=(const SSLImpl &) = delete;
+  ~SSLImpl();
 
   // method is the method table corresponding to the current protocol (DTLS or
   // TLS).
-  const bssl::SSL_PROTOCOL_METHOD *method = nullptr;
+  const SSL_PROTOCOL_METHOD *method = nullptr;
 
   // config is a container for handshake configuration.  Accesses to this field
   // should check for nullptr, since configuration may be shed after the
   // handshake completes.  (If you have the `SSL_HANDSHAKE` object at hand, use
   // that instead, and skip the null check.)
-  bssl::UniquePtr<bssl::SSL_CONFIG> config;
+  UniquePtr<SSL_CONFIG> config;
 
   uint16_t max_send_fragment = 0;
 
   // There are 2 BIO's even though they are normally both the same. This is so
   // data can be read and written to different handlers
 
-  bssl::UniquePtr<BIO> rbio;  // used by SSL_read
-  bssl::UniquePtr<BIO> wbio;  // used by SSL_write
+  UniquePtr<BIO> rbio;  // used by SSL_read
+  UniquePtr<BIO> wbio;  // used by SSL_write
 
   // do_handshake runs the handshake. On completion, it returns `ssl_hs_ok`.
   // Otherwise, it returns a value corresponding to what operation is needed to
   // progress.
-  bssl::ssl_hs_wait_t (*do_handshake)(bssl::SSL_HANDSHAKE *hs) = nullptr;
+  ssl_hs_wait_t (*do_handshake)(SSL_HANDSHAKE *hs) = nullptr;
 
-  bssl::SSL3_STATE *s3 = nullptr;   // TLS variables
-  bssl::DTLS1_STATE *d1 = nullptr;  // DTLS variables
+  SSL3_STATE *s3 = nullptr;   // TLS variables
+  DTLS1_STATE *d1 = nullptr;  // DTLS variables
 
   // callback that allows applications to peek at protocol messages
   void (*msg_callback)(int write_p, int version, int content_type,
@@ -4251,15 +4263,15 @@
 
   // session is the configured session to be offered by the client. This session
   // is immutable.
-  bssl::UniquePtr<SSL_SESSION> session;
+  UniquePtr<SSL_SESSION> session;
 
   void (*info_callback)(const SSL *ssl, int type, int value) = nullptr;
 
-  bssl::UniquePtr<bssl::SSLContext> ctx;
+  UniquePtr<SSLContext> ctx;
 
   // session_ctx is the `SSLContext` used for the session cache and related
   // settings.
-  bssl::UniquePtr<bssl::SSLContext> session_ctx;
+  UniquePtr<SSLContext> session_ctx;
 
   // extra application data
   CRYPTO_EX_DATA ex_data;
@@ -4267,7 +4279,7 @@
   uint32_t options = 0;  // protocol behaviour
   uint32_t mode = 0;     // API behaviour
   uint32_t max_cert_list = 0;
-  bssl::UniquePtr<char> hostname;
+  UniquePtr<char> hostname;
 
   // quic_method is the method table corresponding to the QUIC hooks.
   const SSL_QUIC_METHOD *quic_method = nullptr;
@@ -4291,6 +4303,7 @@
   // signal its sessions may be resumed across names in the server certificate.
   bool resumption_across_names_enabled : 1;
 };
+BSSL_NAMESPACE_END
 
 struct ssl_session_st : public bssl::RefCounted<ssl_session_st> {
   explicit ssl_session_st(const bssl::SSL_X509_METHOD *method);
diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc
index c08e381..d885da9 100644
--- a/ssl/s3_both.cc
+++ b/ssl/s3_both.cc
@@ -39,7 +39,7 @@
 
 BSSL_NAMESPACE_BEGIN
 
-static bool add_record_to_flight(SSL *ssl, uint8_t type,
+static bool add_record_to_flight(SSLImpl *ssl, uint8_t type,
                                  Span<const uint8_t> in) {
   // The caller should have flushed `pending_hs_data` first.
   assert(!ssl->s3->pending_hs_data);
@@ -73,7 +73,7 @@
   return true;
 }
 
-bool tls_init_message(const SSL *ssl, CBB *cbb, CBB *body, uint8_t type) {
+bool tls_init_message(const SSLImpl *ssl, CBB *cbb, CBB *body, uint8_t type) {
   // Pick a modest size hint to save most of the `realloc` calls.
   if (!CBB_init(cbb, 64) ||      //
       !CBB_add_u8(cbb, type) ||  //
@@ -86,11 +86,11 @@
   return true;
 }
 
-bool tls_finish_message(const SSL *ssl, CBB *cbb, Array<uint8_t> *out_msg) {
+bool tls_finish_message(const SSLImpl *ssl, CBB *cbb, Array<uint8_t> *out_msg) {
   return CBBFinishArray(cbb, out_msg);
 }
 
-bool tls_add_message(SSL *ssl, Array<uint8_t> msg) {
+bool tls_add_message(SSLImpl *ssl, Array<uint8_t> msg) {
   // Pack handshake data into the minimal number of records. This avoids
   // unnecessary encryption overhead, notably in TLS 1.3 where we send several
   // encrypted messages in a row. For now, we do not do this for the null
@@ -146,7 +146,7 @@
   return true;
 }
 
-bool tls_flush_pending_hs_data(SSL *ssl) {
+bool tls_flush_pending_hs_data(SSLImpl *ssl) {
   if (!ssl->s3->pending_hs_data || ssl->s3->pending_hs_data->length == 0) {
     return true;
   }
@@ -167,7 +167,7 @@
   return add_record_to_flight(ssl, SSL3_RT_HANDSHAKE, data);
 }
 
-bool tls_add_change_cipher_spec(SSL *ssl) {
+bool tls_add_change_cipher_spec(SSLImpl *ssl) {
   if (SSL_is_quic(ssl)) {
     return true;
   }
@@ -184,7 +184,7 @@
   return true;
 }
 
-int tls_flush(SSL *ssl) {
+int tls_flush(SSLImpl *ssl) {
   if (!tls_flush_pending_hs_data(ssl)) {
     return -1;
   }
@@ -255,7 +255,8 @@
   return 1;
 }
 
-static ssl_open_record_t read_v2_client_hello(SSL *ssl, size_t *out_consumed,
+static ssl_open_record_t read_v2_client_hello(SSLImpl *ssl,
+                                              size_t *out_consumed,
                                               Span<const uint8_t> in) {
   *out_consumed = 0;
   assert(in.size() >= SSL3_RT_HEADER_LENGTH);
@@ -376,7 +377,7 @@
   return ssl_open_record_success;
 }
 
-static bool parse_message(const SSL *ssl, SSLMessage *out,
+static bool parse_message(const SSLImpl *ssl, SSLMessage *out,
                           size_t *out_bytes_needed) {
   if (!ssl->s3->hs_buf) {
     *out_bytes_needed = 4;
@@ -404,7 +405,7 @@
   return true;
 }
 
-bool tls_get_message(const SSL *ssl, SSLMessage *out) {
+bool tls_get_message(const SSLImpl *ssl, SSLMessage *out) {
   size_t unused;
   if (!parse_message(ssl, out, &unused)) {
     return false;
@@ -418,7 +419,7 @@
   return true;
 }
 
-bool tls_can_accept_handshake_data(const SSL *ssl, uint8_t *out_alert) {
+bool tls_can_accept_handshake_data(const SSLImpl *ssl, uint8_t *out_alert) {
   // If there is a complete message, the caller must have consumed it first.
   SSLMessage msg;
   size_t bytes_needed;
@@ -438,7 +439,7 @@
   return true;
 }
 
-bool tls_has_unprocessed_handshake_data(const SSL *ssl) {
+bool tls_has_unprocessed_handshake_data(const SSLImpl *ssl) {
   size_t msg_len = 0;
   if (ssl->s3->has_message) {
     SSLMessage msg;
@@ -451,7 +452,7 @@
   return ssl->s3->hs_buf && ssl->s3->hs_buf->length > msg_len;
 }
 
-bool tls_append_handshake_data(SSL *ssl, Span<const uint8_t> data) {
+bool tls_append_handshake_data(SSLImpl *ssl, Span<const uint8_t> data) {
   // Re-create the handshake buffer if needed.
   if (!ssl->s3->hs_buf) {
     ssl->s3->hs_buf.reset(BUF_MEM_new());
@@ -460,7 +461,7 @@
          BUF_MEM_append(ssl->s3->hs_buf.get(), data.data(), data.size());
 }
 
-ssl_open_record_t tls_open_handshake(SSL *ssl, size_t *out_consumed,
+ssl_open_record_t tls_open_handshake(SSLImpl *ssl, size_t *out_consumed,
                                      uint8_t *out_alert, Span<uint8_t> in) {
   *out_consumed = 0;
   // Bypass the record layer for the first message to handle V2ClientHello.
@@ -528,7 +529,7 @@
   return ssl_open_record_success;
 }
 
-void tls_next_message(SSL *ssl) {
+void tls_next_message(SSLImpl *ssl) {
   SSLMessage msg;
   if (!tls_get_message(ssl, &msg) ||  //
       !ssl->s3->hs_buf ||             //
diff --git a/ssl/s3_lib.cc b/ssl/s3_lib.cc
index a58242a..f1f88b9 100644
--- a/ssl/s3_lib.cc
+++ b/ssl/s3_lib.cc
@@ -49,7 +49,7 @@
 
 SSL3_STATE::~SSL3_STATE() {}
 
-bool tls_new(SSL *ssl) {
+bool tls_new(SSLImpl *ssl) {
   UniquePtr<SSL3_STATE> s3 = MakeUnique<SSL3_STATE>();
   if (!s3) {
     return false;
@@ -76,7 +76,7 @@
   return true;
 }
 
-void tls_free(SSL *ssl) {
+void tls_free(SSLImpl *ssl) {
   if (ssl->s3 == nullptr) {
     return;
   }
diff --git a/ssl/s3_pkt.cc b/ssl/s3_pkt.cc
index 6eb797f..2724e32 100644
--- a/ssl/s3_pkt.cc
+++ b/ssl/s3_pkt.cc
@@ -32,10 +32,10 @@
 
 BSSL_NAMESPACE_BEGIN
 
-static int do_tls_write(SSL *ssl, size_t *out_bytes_written, uint8_t type,
+static int do_tls_write(SSLImpl *ssl, size_t *out_bytes_written, uint8_t type,
                         Span<const uint8_t> in);
 
-int tls_write_app_data(SSL *ssl, bool *out_needs_handshake,
+int tls_write_app_data(SSLImpl *ssl, bool *out_needs_handshake,
                        size_t *out_bytes_written, Span<const uint8_t> in) {
   assert(ssl_can_write(ssl));
   assert(!ssl->s3->aead_write_ctx->is_null_cipher());
@@ -114,7 +114,7 @@
 //
 // TODO(davidben): Is this alignment valuable? Record-splitting makes this a
 // mess.
-static size_t tls_seal_align_prefix_len(const SSL *ssl) {
+static size_t tls_seal_align_prefix_len(const SSLImpl *ssl) {
   size_t ret =
       SSL3_RT_HEADER_LENGTH + ssl->s3->aead_write_ctx->ExplicitNonceLen();
   if (ssl_needs_record_splitting(ssl)) {
@@ -127,7 +127,7 @@
 // do_tls_write writes an SSL record of the given type. On success, it sets
 // `*out_bytes_written` to number of bytes successfully written and returns one.
 // On error, it returns a value <= 0 from the underlying `BIO`.
-static int do_tls_write(SSL *ssl, size_t *out_bytes_written, uint8_t type,
+static int do_tls_write(SSLImpl *ssl, size_t *out_bytes_written, uint8_t type,
                         Span<const uint8_t> in) {
   // If there is a pending write, the retry must be consistent.
   if (!ssl->s3->pending_write.empty() &&
@@ -233,7 +233,7 @@
   return 1;
 }
 
-ssl_open_record_t tls_open_app_data(SSL *ssl, Span<uint8_t> *out,
+ssl_open_record_t tls_open_app_data(SSLImpl *ssl, Span<uint8_t> *out,
                                     size_t *out_consumed, uint8_t *out_alert,
                                     Span<uint8_t> in) {
   assert(ssl_can_read(ssl));
@@ -289,7 +289,8 @@
   return ssl_open_record_success;
 }
 
-ssl_open_record_t tls_open_change_cipher_spec(SSL *ssl, size_t *out_consumed,
+ssl_open_record_t tls_open_change_cipher_spec(SSLImpl *ssl,
+                                              size_t *out_consumed,
                                               uint8_t *out_alert,
                                               Span<uint8_t> in) {
   uint8_t type;
@@ -315,7 +316,7 @@
   return ssl_open_record_success;
 }
 
-void ssl_send_alert(SSL *ssl, int level, int desc) {
+void ssl_send_alert(SSLImpl *ssl, int level, int desc) {
   // This function is called in response to a fatal error from the peer. Ignore
   // any failures writing the alert and report only the original error. In
   // particular, if the transport uses `SSL_write`, our existing error will be
@@ -332,7 +333,7 @@
   ERR_restore_state(err_state.get());
 }
 
-int ssl_send_alert_impl(SSL *ssl, int level, int desc) {
+int ssl_send_alert_impl(SSLImpl *ssl, int level, int desc) {
   // It is illegal to send an alert when we've already sent a closing one.
   if (ssl->s3->write_shutdown != ssl_shutdown_none) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_PROTOCOL_IS_SHUTDOWN);
@@ -360,7 +361,7 @@
   return -1;
 }
 
-int tls_dispatch_alert(SSL *ssl) {
+int tls_dispatch_alert(SSLImpl *ssl) {
   if (SSL_is_quic(ssl)) {
     if (!ssl->quic_method->send_alert(ssl, ssl->s3->quic_write_level,
                                       ssl->s3->send_alert[1])) {
diff --git a/ssl/ssl_buffer.cc b/ssl/ssl_buffer.cc
index e6a2b04..9cb1298 100644
--- a/ssl/ssl_buffer.cc
+++ b/ssl/ssl_buffer.cc
@@ -116,7 +116,7 @@
   }
 }
 
-static int dtls_read_buffer_next_packet(SSL *ssl) {
+static int dtls_read_buffer_next_packet(SSLImpl *ssl) {
   SSLBuffer *buf = &ssl->s3->read_buffer;
 
   if (!buf->empty()) {
@@ -137,7 +137,7 @@
   return 1;
 }
 
-static int tls_read_buffer_extend_to(SSL *ssl, size_t len) {
+static int tls_read_buffer_extend_to(SSLImpl *ssl, size_t len) {
   SSLBuffer *buf = &ssl->s3->read_buffer;
 
   if (len > buf->cap()) {
@@ -161,7 +161,7 @@
   return 1;
 }
 
-int ssl_read_buffer_extend_to(SSL *ssl, size_t len) {
+int ssl_read_buffer_extend_to(SSLImpl *ssl, size_t len) {
   // `ssl_read_buffer_extend_to` implicitly discards any consumed data.
   ssl->s3->read_buffer.DiscardConsumed();
 
@@ -202,7 +202,7 @@
   return ret;
 }
 
-int ssl_handle_open_record(SSL *ssl, bool *out_retry, ssl_open_record_t ret,
+int ssl_handle_open_record(SSLImpl *ssl, bool *out_retry, ssl_open_record_t ret,
                            size_t consumed, uint8_t alert) {
   *out_retry = false;
   if (ret != ssl_open_record_partial) {
@@ -255,7 +255,7 @@
                   0xffff,
               "maximum DTLS write buffer is too large");
 
-static int tls_write_buffer_flush(SSL *ssl) {
+static int tls_write_buffer_flush(SSLImpl *ssl) {
   SSLBuffer *buf = &ssl->s3->write_buffer;
   while (!buf->empty()) {
     size_t written;
@@ -269,7 +269,7 @@
   return 1;
 }
 
-static int dtls_write_buffer_flush(SSL *ssl) {
+static int dtls_write_buffer_flush(SSLImpl *ssl) {
   SSLBuffer *buf = &ssl->s3->write_buffer;
   if (buf->empty()) {
     return 1;
@@ -288,7 +288,7 @@
   return 1;
 }
 
-int ssl_write_buffer_flush(SSL *ssl) {
+int ssl_write_buffer_flush(SSLImpl *ssl) {
   if (ssl->wbio == nullptr) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BIO_NOT_SET);
     return -1;
diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc
index 46ed23e..7455162 100644
--- a/ssl/ssl_cert.cc
+++ b/ssl/ssl_cert.cc
@@ -405,7 +405,7 @@
   return true;
 }
 
-UniquePtr<STACK_OF(CRYPTO_BUFFER)> SSL_parse_CA_list(SSL *ssl,
+UniquePtr<STACK_OF(CRYPTO_BUFFER)> SSL_parse_CA_list(SSLImpl *ssl,
                                                      uint8_t *out_alert,
                                                      CBS *cbs) {
   CRYPTO_BUFFER_POOL *const pool = ssl->ctx->pool.get();
@@ -542,10 +542,11 @@
 int SSL_set_chain_and_key(SSL *ssl, CRYPTO_BUFFER *const *certs,
                           size_t num_certs, EVP_PKEY *privkey,
                           const SSL_PRIVATE_KEY_METHOD *privkey_method) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return cert_set_chain_and_key(ssl->config->cert.get(), certs, num_certs,
+  return cert_set_chain_and_key(ssl_impl->config->cert.get(), certs, num_certs,
                                 privkey, privkey_method);
 }
 
@@ -557,11 +558,12 @@
 }
 
 void SSL_certs_clear(SSL *ssl) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
 
-  CERT *cert = ssl->config->cert.get();
+  CERT *cert = ssl_impl->config->cert.get();
   cert->x509_method->cert_clear(cert);
   cert->credentials.clear();
   cert->legacy_credential->ClearCertAndKey();
@@ -572,10 +574,11 @@
 }
 
 const STACK_OF(CRYPTO_BUFFER) *SSL_get0_chain(const SSL *ssl) {
-  if (!ssl->config) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return nullptr;
   }
-  return ssl->config->cert->legacy_credential->chain.get();
+  return ssl_impl->config->cert->legacy_credential->chain.get();
 }
 
 int SSL_CTX_use_certificate_ASN1(SSL_CTX *ctx, size_t der_len,
@@ -589,12 +592,13 @@
 }
 
 int SSL_use_certificate_ASN1(SSL *ssl, const uint8_t *der, size_t der_len) {
+  auto *ssl_impl = FromOpaque(ssl);
   UniquePtr<CRYPTO_BUFFER> buffer(CRYPTO_BUFFER_new(der, der_len, nullptr));
-  if (!buffer || !ssl->config) {
+  if (!buffer || !ssl_impl->config) {
     return 0;
   }
 
-  return ssl_set_cert(ssl->config->cert.get(), std::move(buffer));
+  return ssl_set_cert(ssl_impl->config->cert.get(), std::move(buffer));
 }
 
 void SSL_CTX_set_cert_cb(SSL_CTX *ctx, int (*cb)(SSL *ssl, void *arg),
@@ -603,10 +607,11 @@
 }
 
 void SSL_set_cert_cb(SSL *ssl, int (*cb)(SSL *ssl, void *arg), void *arg) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl_cert_set_cert_cb(ssl->config->cert.get(), cb, arg);
+  ssl_cert_set_cert_cb(ssl_impl->config->cert.get(), cb, arg);
 }
 
 const STACK_OF(CRYPTO_BUFFER) *SSL_get0_peer_certificates(const SSL *ssl) {
@@ -619,10 +624,11 @@
 }
 
 const STACK_OF(CRYPTO_BUFFER) *SSL_get0_server_requested_CAs(const SSL *ssl) {
-  if (ssl->s3->hs == nullptr) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->s3->hs == nullptr) {
     return nullptr;
   }
-  return ssl->s3->hs->ca_names.get();
+  return ssl_impl->s3->hs->ca_names.get();
 }
 
 int SSL_CTX_set_signed_cert_timestamp_list(SSL_CTX *ctx, const uint8_t *list,
@@ -635,13 +641,14 @@
 
 int SSL_set_signed_cert_timestamp_list(SSL *ssl, const uint8_t *list,
                                        size_t list_len) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
   UniquePtr<CRYPTO_BUFFER> buf(CRYPTO_BUFFER_new(list, list_len, nullptr));
   return buf != nullptr &&
          SSL_CREDENTIAL_set1_signed_cert_timestamp_list(
-             ssl->config->cert->legacy_credential.get(), buf.get());
+             ssl_impl->config->cert->legacy_credential.get(), buf.get());
 }
 
 int SSL_CTX_set_ocsp_response(SSL_CTX *ctx, const uint8_t *response,
@@ -655,14 +662,15 @@
 
 int SSL_set_ocsp_response(SSL *ssl, const uint8_t *response,
                           size_t response_len) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
   UniquePtr<CRYPTO_BUFFER> buf(
       CRYPTO_BUFFER_new(response, response_len, nullptr));
   return buf != nullptr &&
          SSL_CREDENTIAL_set1_ocsp_response(
-             ssl->config->cert->legacy_credential.get(), buf.get());
+             ssl_impl->config->cert->legacy_credential.get(), buf.get());
 }
 
 void SSL_CTX_set0_client_CAs(SSL_CTX *ctx, STACK_OF(CRYPTO_BUFFER) *name_list) {
@@ -672,18 +680,21 @@
 }
 
 void SSL_set0_client_CAs(SSL *ssl, STACK_OF(CRYPTO_BUFFER) *name_list) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     // `SSL_set0_client_CAs` is expected to take ownership of `name_list`.
     sk_CRYPTO_BUFFER_pop_free(name_list, CRYPTO_BUFFER_free);
     return;
   }
-  ssl->ctx->x509_method->ssl_flush_cached_client_CA(ssl->config.get());
-  ssl->config->client_CA.reset(name_list);
+  ssl_impl->ctx->x509_method->ssl_flush_cached_client_CA(
+      ssl_impl->config.get());
+  ssl_impl->config->client_CA.reset(name_list);
 }
 
 void SSL_set0_CA_names(SSL *ssl, STACK_OF(CRYPTO_BUFFER) *name_list) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->CA_names.reset(name_list);
+  ssl_impl->config->CA_names.reset(name_list);
 }
diff --git a/ssl/ssl_credential.cc b/ssl/ssl_credential.cc
index cd522d3..87bf24c 100644
--- a/ssl/ssl_credential.cc
+++ b/ssl/ssl_credential.cc
@@ -668,8 +668,9 @@
 }
 
 int SSL_add1_credential(SSL *ssl, const SSL_CREDENTIAL *cred) {
+  auto *ssl_impl = FromOpaque(ssl);
   auto *cred_impl = FromOpaque(cred);
-  if (ssl->config == nullptr) {
+  if (ssl_impl->config == nullptr) {
     return 0;
   }
 
@@ -677,14 +678,15 @@
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
-  return ssl->config->cert->credentials.Push(UpRef(cred_impl));
+  return ssl_impl->config->cert->credentials.Push(UpRef(cred_impl));
 }
 
 const SSL_CREDENTIAL *SSL_get0_selected_credential(const SSL *ssl) {
-  if (ssl->s3->hs == nullptr) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->s3->hs == nullptr) {
     return nullptr;
   }
-  return ssl->s3->hs->credential.get();
+  return ssl_impl->s3->hs->credential.get();
 }
 
 int SSL_CREDENTIAL_get_ex_new_index(long argl, void *argp,
diff --git a/ssl/ssl_file.cc b/ssl/ssl_file.cc
index 92143c3..c7d4645 100644
--- a/ssl/ssl_file.cc
+++ b/ssl/ssl_file.cc
@@ -130,6 +130,7 @@
 }
 
 int SSL_use_certificate_file(SSL *ssl, const char *file, int type) {
+  SSLContext *ctx = FromOpaque(SSL_get_SSL_CTX(ssl));
   UniquePtr<BIO> in(BIO_new_file(file, "rb"));
   if (in == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
@@ -143,9 +144,8 @@
     x.reset(d2i_X509_bio(in.get(), nullptr));
   } else if (type == SSL_FILETYPE_PEM) {
     reason_code = ERR_R_PEM_LIB;
-    x.reset(PEM_read_bio_X509(in.get(), nullptr,
-                              ssl->ctx->default_passwd_callback,
-                              ssl->ctx->default_passwd_callback_userdata));
+    x.reset(PEM_read_bio_X509(in.get(), nullptr, ctx->default_passwd_callback,
+                              ctx->default_passwd_callback_userdata));
   } else {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SSL_FILETYPE);
     return 0;
@@ -160,6 +160,7 @@
 }
 
 int SSL_use_RSAPrivateKey_file(SSL *ssl, const char *file, int type) {
+  SSLContext *ctx = FromOpaque(SSL_get_SSL_CTX(ssl));
   UniquePtr<BIO> in(BIO_new_file(file, "rb"));
   if (in == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
@@ -174,8 +175,8 @@
   } else if (type == SSL_FILETYPE_PEM) {
     reason_code = ERR_R_PEM_LIB;
     rsa.reset(PEM_read_bio_RSAPrivateKey(
-        in.get(), nullptr, ssl->ctx->default_passwd_callback,
-        ssl->ctx->default_passwd_callback_userdata));
+        in.get(), nullptr, ctx->default_passwd_callback,
+        ctx->default_passwd_callback_userdata));
   } else {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SSL_FILETYPE);
     return 0;
@@ -189,6 +190,7 @@
 }
 
 int SSL_use_PrivateKey_file(SSL *ssl, const char *file, int type) {
+  SSLContext *ctx = FromOpaque(SSL_get_SSL_CTX(ssl));
   UniquePtr<BIO> in(BIO_new_file(file, "rb"));
   if (in == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
@@ -199,9 +201,9 @@
   UniquePtr<EVP_PKEY> pkey;
   if (type == SSL_FILETYPE_PEM) {
     reason_code = ERR_R_PEM_LIB;
-    pkey.reset(PEM_read_bio_PrivateKey(
-        in.get(), nullptr, ssl->ctx->default_passwd_callback,
-        ssl->ctx->default_passwd_callback_userdata));
+    pkey.reset(PEM_read_bio_PrivateKey(in.get(), nullptr,
+                                       ctx->default_passwd_callback,
+                                       ctx->default_passwd_callback_userdata));
   } else if (type == SSL_FILETYPE_ASN1) {
     reason_code = ERR_R_ASN1_LIB;
     pkey.reset(d2i_PrivateKey_bio(in.get(), nullptr));
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index fd287f5..e05f91b 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -72,7 +72,7 @@
 static ExDataClass g_ex_data_class_ssl(/*with_app_data=*/true);
 static ExDataClass g_ex_data_class_ssl_ctx(/*with_app_data=*/true);
 
-void ssl_reset_error_state(SSL *ssl) {
+void ssl_reset_error_state(SSLImpl *ssl) {
   // Functions which use `SSL_get_error` must reset I/O and error state on
   // entry.
   ssl->s3->rwstate = SSL_ERROR_NONE;
@@ -80,12 +80,12 @@
   ERR_clear_system_error();
 }
 
-void ssl_set_read_error(SSL *ssl) {
+void ssl_set_read_error(SSLImpl *ssl) {
   ssl->s3->read_shutdown = ssl_shutdown_error;
   ssl->s3->read_error.reset(ERR_save_state());
 }
 
-static bool check_read_error(const SSL *ssl) {
+static bool check_read_error(const SSLImpl *ssl) {
   if (ssl->s3->read_shutdown == ssl_shutdown_error) {
     ERR_restore_state(ssl->s3->read_error.get());
     return false;
@@ -93,15 +93,15 @@
   return true;
 }
 
-bool ssl_can_write(const SSL *ssl) {
+bool ssl_can_write(const SSLImpl *ssl) {
   return !SSL_in_init(ssl) || ssl->s3->hs->can_early_write;
 }
 
-bool ssl_can_read(const SSL *ssl) {
+bool ssl_can_read(const SSLImpl *ssl) {
   return !SSL_in_init(ssl) || ssl->s3->hs->can_early_read;
 }
 
-ssl_open_record_t ssl_open_handshake(SSL *ssl, size_t *out_consumed,
+ssl_open_record_t ssl_open_handshake(SSLImpl *ssl, size_t *out_consumed,
                                      uint8_t *out_alert, Span<uint8_t> in) {
   *out_consumed = 0;
   if (!check_read_error(ssl)) {
@@ -115,7 +115,8 @@
   return ret;
 }
 
-ssl_open_record_t ssl_open_change_cipher_spec(SSL *ssl, size_t *out_consumed,
+ssl_open_record_t ssl_open_change_cipher_spec(SSLImpl *ssl,
+                                              size_t *out_consumed,
                                               uint8_t *out_alert,
                                               Span<uint8_t> in) {
   *out_consumed = 0;
@@ -131,7 +132,7 @@
   return ret;
 }
 
-ssl_open_record_t ssl_open_app_data(SSL *ssl, Span<uint8_t> *out,
+ssl_open_record_t ssl_open_app_data(SSLImpl *ssl, Span<uint8_t> *out,
                                     size_t *out_consumed, uint8_t *out_alert,
                                     Span<uint8_t> in) {
   *out_consumed = 0;
@@ -166,7 +167,7 @@
   return true;
 }
 
-bool ssl_log_secret(const SSL *ssl, const char *label,
+bool ssl_log_secret(const SSLImpl *ssl, const char *label,
                     Span<const uint8_t> secret) {
   if (ssl->ctx->keylog_callback == nullptr) {
     return true;
@@ -193,7 +194,7 @@
   return true;
 }
 
-void ssl_do_info_callback(const SSL *ssl, int type, int value) {
+void ssl_do_info_callback(const SSLImpl *ssl, int type, int value) {
   void (*cb)(const SSL *ssl, int type, int value) = nullptr;
   if (ssl->info_callback != nullptr) {
     cb = ssl->info_callback;
@@ -206,7 +207,7 @@
   }
 }
 
-void ssl_do_msg_callback(const SSL *ssl, int is_write, int content_type,
+void ssl_do_msg_callback(const SSLImpl *ssl, int is_write, int content_type,
                          Span<const uint8_t> in) {
   if (ssl->msg_callback == nullptr) {
     return;
@@ -228,7 +229,7 @@
   }
 
   ssl->msg_callback(is_write, version, content_type, in.data(), in.size(),
-                    const_cast<SSL *>(ssl), ssl->msg_callback_arg);
+                    const_cast<SSLImpl *>(ssl), ssl->msg_callback_arg);
 }
 
 OPENSSL_timeval ssl_ctx_get_current_time(const SSLContext *ctx) {
@@ -273,7 +274,7 @@
   FromOpaque(ctx)->handoff = on;
 }
 
-static bool ssl_can_renegotiate(const SSL *ssl) {
+static bool ssl_can_renegotiate(const SSLImpl *ssl) {
   if (ssl->server || SSL_is_dtls(ssl)) {
     return false;
   }
@@ -304,7 +305,7 @@
   return false;
 }
 
-static void ssl_maybe_shed_handshake_config(SSL *ssl) {
+static void ssl_maybe_shed_handshake_config(SSLImpl *ssl) {
   if (ssl->s3->hs != nullptr ||               //
       ssl->config == nullptr ||               //
       !ssl->config->shed_handshake_config ||  //
@@ -316,35 +317,37 @@
 }
 
 void SSL_set_handoff_mode(SSL *ssl, bool on) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->handoff = on;
+  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) {
+  const auto *ssl_impl = FromOpaque(ssl);
   // This API is not well-defined for DTLS, where multiple epochs may be alive
   // at once. Callers should use `SSL_get_dtls_*_traffic_secret` instead. In
   // QUIC, the application is already handed the traffic secret.
-  if (SSL_is_dtls(ssl) || SSL_is_quic(ssl)) {
+  if (SSL_is_dtls(ssl_impl) || SSL_is_quic(ssl_impl)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return false;
   }
 
-  if (!ssl->s3->initial_handshake_complete) {
+  if (!ssl_impl->s3->initial_handshake_complete) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_NOT_COMPLETE);
     return false;
   }
 
-  if (SSL_version(ssl) < TLS1_3_VERSION) {
+  if (SSL_version(ssl_impl) < TLS1_3_VERSION) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION);
     return false;
   }
 
-  *out_read_traffic_secret = ssl->s3->read_traffic_secret;
-  *out_write_traffic_secret = ssl->s3->write_traffic_secret;
+  *out_read_traffic_secret = ssl_impl->s3->read_traffic_secret;
+  *out_write_traffic_secret = ssl_impl->s3->write_traffic_secret;
   return true;
 }
 
@@ -356,8 +359,9 @@
 }
 
 void SSL_set_aes_hw_override_for_testing(SSL *ssl, bool override_value) {
-  ssl->config->aes_hw_override = true;
-  ssl->config->aes_hw_override_value = override_value;
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_impl->config->aes_hw_override = true;
+  ssl_impl->config->aes_hw_override_value = override_value;
 }
 
 BSSL_NAMESPACE_END
@@ -467,7 +471,7 @@
   }
 }
 
-ssl_st::ssl_st(SSLContext *ctx_arg)
+bssl::SSLImpl::SSLImpl(SSLContext *ctx_arg)
     : method(ctx_arg->method),
       max_send_fragment(ctx_arg->max_send_fragment),
       msg_callback(ctx_arg->msg_callback),
@@ -484,7 +488,7 @@
   CRYPTO_new_ex_data(&ex_data);
 }
 
-ssl_st::~ssl_st() {
+bssl::SSLImpl::~SSLImpl() {
   CRYPTO_free_ex_data(&g_ex_data_class_ssl, &ex_data);
   // `config` refers to `this`, so we must release it earlier.
   config.reset();
@@ -500,7 +504,7 @@
     return nullptr;
   }
 
-  UniquePtr<SSL> ssl = MakeUnique<SSL>(ctx_impl);
+  UniquePtr<SSLImpl> ssl = MakeUnique<SSLImpl>(ctx_impl);
   if (ssl == nullptr) {
     return nullptr;
   }
@@ -576,7 +580,7 @@
   return ssl.release();
 }
 
-SSL_CONFIG::SSL_CONFIG(SSL *ssl_arg)
+SSL_CONFIG::SSL_CONFIG(SSLImpl *ssl_arg)
     : ssl(ssl_arg),
       ech_grease_enabled(false),
       signed_cert_timestamps_enabled(false),
@@ -599,21 +603,23 @@
   }
 }
 
-void SSL_free(SSL *ssl) { Delete(ssl); }
+void SSL_free(SSL *ssl) { Delete(FromOpaque(ssl)); }
 
 void SSL_set_connect_state(SSL *ssl) {
-  ssl->server = false;
-  ssl->do_handshake = ssl_client_handshake;
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_impl->server = false;
+  ssl_impl->do_handshake = ssl_client_handshake;
 }
 
 void SSL_set_accept_state(SSL *ssl) {
-  ssl->server = true;
-  ssl->do_handshake = ssl_server_handshake;
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_impl->server = true;
+  ssl_impl->do_handshake = ssl_server_handshake;
 }
 
-void SSL_set0_rbio(SSL *ssl, BIO *rbio) { ssl->rbio.reset(rbio); }
+void SSL_set0_rbio(SSL *ssl, BIO *rbio) { FromOpaque(ssl)->rbio.reset(rbio); }
 
-void SSL_set0_wbio(SSL *ssl, BIO *wbio) { ssl->wbio.reset(wbio); }
+void SSL_set0_wbio(SSL *ssl, BIO *wbio) { FromOpaque(ssl)->wbio.reset(wbio); }
 
 void SSL_set_bio(SSL *ssl, BIO *rbio, BIO *wbio) {
   // For historical reasons, this function has many different cases in ownership
@@ -649,12 +655,13 @@
   SSL_set0_wbio(ssl, wbio);
 }
 
-BIO *SSL_get_rbio(const SSL *ssl) { return ssl->rbio.get(); }
+BIO *SSL_get_rbio(const SSL *ssl) { return FromOpaque(ssl)->rbio.get(); }
 
-BIO *SSL_get_wbio(const SSL *ssl) { return ssl->wbio.get(); }
+BIO *SSL_get_wbio(const SSL *ssl) { return FromOpaque(ssl)->wbio.get(); }
 
 size_t SSL_quic_max_handshake_flight_len(const SSL *ssl,
                                          enum ssl_encryption_level_t level) {
+  const auto *ssl_impl = FromOpaque(ssl);
   // Limits flights to 16K by default when there are no large
   // (certificate-carrying) messages.
   static const size_t kDefaultLimit = 16384;
@@ -666,18 +673,18 @@
       // QUIC does not send EndOfEarlyData.
       return 0;
     case ssl_encryption_handshake:
-      if (ssl->server) {
+      if (ssl_impl->server) {
         // Servers may receive Certificate message if configured to request
         // client certificates.
-        if (!!(ssl->config->verify_mode & SSL_VERIFY_PEER) &&
-            ssl->max_cert_list > kDefaultLimit) {
-          return ssl->max_cert_list;
+        if (!!(ssl_impl->config->verify_mode & SSL_VERIFY_PEER) &&
+            ssl_impl->max_cert_list > kDefaultLimit) {
+          return ssl_impl->max_cert_list;
         }
       } else {
         // Clients may receive both Certificate message and a CertificateRequest
         // message.
-        if (2 * ssl->max_cert_list > kDefaultLimit) {
-          return 2 * ssl->max_cert_list;
+        if (2 * ssl_impl->max_cert_list > kDefaultLimit) {
+          return 2 * ssl_impl->max_cert_list;
         }
       }
       return kDefaultLimit;
@@ -693,70 +700,74 @@
 
 enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl) {
   assert(SSL_is_quic(ssl));
-  return ssl->s3->quic_read_level;
+  return FromOpaque(ssl)->s3->quic_read_level;
 }
 
 enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl) {
   assert(SSL_is_quic(ssl));
-  return ssl->s3->quic_write_level;
+  return FromOpaque(ssl)->s3->quic_write_level;
 }
 
 int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level,
                           const uint8_t *data, size_t len) {
-  if (!SSL_is_quic(ssl)) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!SSL_is_quic(ssl_impl)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
 
-  if (level != ssl->s3->quic_read_level) {
+  if (level != ssl_impl->s3->quic_read_level) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_ENCRYPTION_LEVEL_RECEIVED);
     return 0;
   }
 
-  size_t new_len = (ssl->s3->hs_buf ? ssl->s3->hs_buf->length : 0) + len;
+  size_t new_len =
+      (ssl_impl->s3->hs_buf ? ssl_impl->s3->hs_buf->length : 0) + len;
   if (new_len < len ||
-      new_len > SSL_quic_max_handshake_flight_len(ssl, level)) {
+      new_len > SSL_quic_max_handshake_flight_len(ssl_impl, level)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_EXCESSIVE_MESSAGE_SIZE);
     return 0;
   }
 
-  return tls_append_handshake_data(ssl, Span(data, len));
+  return tls_append_handshake_data(ssl_impl, Span(data, len));
 }
 
 int SSL_do_handshake(SSL *ssl) {
-  ssl_reset_error_state(ssl);
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_reset_error_state(ssl_impl);
 
-  if (ssl->do_handshake == nullptr) {
+  if (ssl_impl->do_handshake == nullptr) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_CONNECTION_TYPE_NOT_SET);
     return -1;
   }
 
-  if (!SSL_in_init(ssl)) {
+  if (!SSL_in_init(ssl_impl)) {
     return 1;
   }
 
   // Run the handshake.
-  SSL_HANDSHAKE *hs = ssl->s3->hs.get();
+  SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get();
 
   bool early_return = false;
   int ret = ssl_run_handshake(hs, &early_return);
   ssl_do_info_callback(
-      ssl, ssl->server ? SSL_CB_ACCEPT_EXIT : SSL_CB_CONNECT_EXIT, ret);
+      ssl_impl, ssl_impl->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 (!early_return) {
-    ssl->s3->hs.reset();
-    ssl_maybe_shed_handshake_config(ssl);
+    ssl_impl->s3->hs.reset();
+    ssl_maybe_shed_handshake_config(ssl_impl);
   }
 
   return 1;
 }
 
 int SSL_connect(SSL *ssl) {
-  if (ssl->do_handshake == nullptr) {
+  if (FromOpaque(ssl)->do_handshake == nullptr) {
     // Not properly initialized yet
     SSL_set_connect_state(ssl);
   }
@@ -765,7 +776,7 @@
 }
 
 int SSL_accept(SSL *ssl) {
-  if (ssl->do_handshake == nullptr) {
+  if (FromOpaque(ssl)->do_handshake == nullptr) {
     // Not properly initialized yet
     SSL_set_accept_state(ssl);
   }
@@ -773,7 +784,7 @@
   return SSL_do_handshake(ssl);
 }
 
-static int ssl_do_post_handshake(SSL *ssl, const SSLMessage &msg) {
+static int ssl_do_post_handshake(SSLImpl *ssl, const SSLMessage &msg) {
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return tls13_post_handshake(ssl, msg);
   }
@@ -810,33 +821,34 @@
 }
 
 int SSL_process_quic_post_handshake(SSL *ssl) {
-  ssl_reset_error_state(ssl);
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_reset_error_state(ssl_impl);
 
-  if (!SSL_is_quic(ssl) || SSL_in_init(ssl)) {
+  if (!SSL_is_quic(ssl_impl) || SSL_in_init(ssl_impl)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
 
   // Replay post-handshake message errors.
-  if (!check_read_error(ssl)) {
+  if (!check_read_error(ssl_impl)) {
     return 0;
   }
 
   // Process any buffered post-handshake messages.
   SSLMessage msg;
-  while (ssl->method->get_message(ssl, &msg)) {
+  while (ssl_impl->method->get_message(ssl_impl, &msg)) {
     // Handle the post-handshake message and try again.
-    if (!ssl_do_post_handshake(ssl, msg)) {
-      ssl_set_read_error(ssl);
+    if (!ssl_do_post_handshake(ssl_impl, msg)) {
+      ssl_set_read_error(ssl_impl);
       return 0;
     }
-    ssl->method->next_message(ssl);
+    ssl_impl->method->next_message(ssl_impl);
   }
 
   return 1;
 }
 
-static int ssl_read_impl(SSL *ssl) {
+static int ssl_read_impl(SSLImpl *ssl) {
   ssl_reset_error_state(ssl);
 
   if (ssl->do_handshake == nullptr) {
@@ -916,27 +928,29 @@
 }
 
 int SSL_read(SSL *ssl, void *buf, int num) {
-  int ret = SSL_peek(ssl, buf, num);
+  auto *ssl_impl = FromOpaque(ssl);
+  int ret = SSL_peek(ssl_impl, buf, num);
   if (ret <= 0) {
     return ret;
   }
   // TODO(davidben): In DTLS, should the rest of the record be discarded?  DTLS
   // is not a stream. See https://crbug.com/boringssl/65.
-  ssl->s3->pending_app_data =
-      ssl->s3->pending_app_data.subspan(static_cast<size_t>(ret));
-  if (ssl->s3->pending_app_data.empty()) {
-    ssl->s3->read_buffer.DiscardConsumed();
+  ssl_impl->s3->pending_app_data =
+      ssl_impl->s3->pending_app_data.subspan(static_cast<size_t>(ret));
+  if (ssl_impl->s3->pending_app_data.empty()) {
+    ssl_impl->s3->read_buffer.DiscardConsumed();
   }
   return ret;
 }
 
 int SSL_peek(SSL *ssl, void *buf, int num) {
-  if (SSL_is_quic(ssl)) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (SSL_is_quic(ssl_impl)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return -1;
   }
 
-  int ret = ssl_read_impl(ssl);
+  int ret = ssl_read_impl(ssl_impl);
   if (ret <= 0) {
     return ret;
   }
@@ -944,20 +958,21 @@
     return num;
   }
   size_t todo =
-      std::min(ssl->s3->pending_app_data.size(), static_cast<size_t>(num));
-  OPENSSL_memcpy(buf, ssl->s3->pending_app_data.data(), todo);
+      std::min(ssl_impl->s3->pending_app_data.size(), static_cast<size_t>(num));
+  OPENSSL_memcpy(buf, ssl_impl->s3->pending_app_data.data(), todo);
   return static_cast<int>(todo);
 }
 
 int SSL_write(SSL *ssl, const void *buf, int num) {
-  ssl_reset_error_state(ssl);
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_reset_error_state(ssl_impl);
 
-  if (SSL_is_quic(ssl)) {
+  if (SSL_is_quic(ssl_impl)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return -1;
   }
 
-  if (ssl->do_handshake == nullptr) {
+  if (ssl_impl->do_handshake == nullptr) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);
     return -1;
   }
@@ -967,8 +982,8 @@
   bool needs_handshake = false;
   do {
     // If necessary, complete the handshake implicitly.
-    if (!ssl_can_write(ssl)) {
-      ret = SSL_do_handshake(ssl);
+    if (!ssl_can_write(ssl_impl)) {
+      ret = SSL_do_handshake(ssl_impl);
       if (ret < 0) {
         return ret;
       }
@@ -982,43 +997,45 @@
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_LENGTH);
       return -1;
     }
-    ret = ssl->method->write_app_data(
-        ssl, &needs_handshake, &bytes_written,
+    ret = ssl_impl->method->write_app_data(
+        ssl_impl, &needs_handshake, &bytes_written,
         Span(static_cast<const uint8_t *>(buf), static_cast<size_t>(num)));
   } while (needs_handshake);
   return ret <= 0 ? ret : static_cast<int>(bytes_written);
 }
 
 int SSL_key_update(SSL *ssl, int request_type) {
-  ssl_reset_error_state(ssl);
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_reset_error_state(ssl_impl);
 
-  if (ssl->do_handshake == nullptr) {
+  if (ssl_impl->do_handshake == nullptr) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);
     return 0;
   }
 
-  if (SSL_is_quic(ssl)) {
+  if (SSL_is_quic(ssl_impl)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
 
-  if (!ssl->s3->initial_handshake_complete) {
+  if (!ssl_impl->s3->initial_handshake_complete) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_NOT_COMPLETE);
     return 0;
   }
 
-  if (ssl_protocol_version(ssl) < TLS1_3_VERSION) {
+  if (ssl_protocol_version(ssl_impl) < TLS1_3_VERSION) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION);
     return 0;
   }
 
-  return tls13_add_key_update(ssl, request_type);
+  return tls13_add_key_update(ssl_impl, request_type);
 }
 
 int SSL_shutdown(SSL *ssl) {
-  ssl_reset_error_state(ssl);
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_reset_error_state(ssl_impl);
 
-  if (ssl->do_handshake == nullptr) {
+  if (ssl_impl->do_handshake == nullptr) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);
     return -1;
   }
@@ -1026,14 +1043,14 @@
   // If we are in the middle of a handshake, silently succeed. Consumers often
   // call this function before `SSL_free`, whether the handshake succeeded or
   // not. We assume the caller has already handled failed handshakes.
-  if (SSL_in_init(ssl)) {
+  if (SSL_in_init(ssl_impl)) {
     return 1;
   }
 
-  if (ssl->quiet_shutdown) {
+  if (ssl_impl->quiet_shutdown) {
     // Do nothing if configured not to send a close_notify.
-    ssl->s3->write_shutdown = ssl_shutdown_close_notify;
-    ssl->s3->read_shutdown = ssl_shutdown_close_notify;
+    ssl_impl->s3->write_shutdown = ssl_shutdown_close_notify;
+    ssl_impl->s3->read_shutdown = ssl_shutdown_close_notify;
     return 1;
   }
 
@@ -1041,75 +1058,80 @@
   // waits for a close_notify to come in. Perform exactly one action and return
   // whether or not it succeeds.
 
-  if (ssl->s3->write_shutdown != ssl_shutdown_close_notify) {
+  if (ssl_impl->s3->write_shutdown != ssl_shutdown_close_notify) {
     // Send a close_notify.
-    if (ssl_send_alert_impl(ssl, SSL3_AL_WARNING, SSL_AD_CLOSE_NOTIFY) <= 0) {
+    if (ssl_send_alert_impl(ssl_impl, SSL3_AL_WARNING, SSL_AD_CLOSE_NOTIFY) <=
+        0) {
       return -1;
     }
-  } else if (ssl->s3->alert_dispatch) {
+  } else if (ssl_impl->s3->alert_dispatch) {
     // Finish sending the close_notify.
-    if (ssl->method->dispatch_alert(ssl) <= 0) {
+    if (ssl_impl->method->dispatch_alert(ssl_impl) <= 0) {
       return -1;
     }
-  } else if (ssl->s3->read_shutdown != ssl_shutdown_close_notify) {
-    if (SSL_is_dtls(ssl)) {
+  } else if (ssl_impl->s3->read_shutdown != ssl_shutdown_close_notify) {
+    if (SSL_is_dtls(ssl_impl)) {
       // Bidirectional shutdown doesn't make sense for an unordered
       // transport. DTLS alerts also aren't delivered reliably, so we may even
       // time out because the peer never received our close_notify. Report to
       // the caller that the channel has fully shut down.
-      if (ssl->s3->read_shutdown == ssl_shutdown_error) {
-        ERR_restore_state(ssl->s3->read_error.get());
+      if (ssl_impl->s3->read_shutdown == ssl_shutdown_error) {
+        ERR_restore_state(ssl_impl->s3->read_error.get());
         return -1;
       }
-      ssl->s3->read_shutdown = ssl_shutdown_close_notify;
+      ssl_impl->s3->read_shutdown = ssl_shutdown_close_notify;
     } else {
       // Process records until an error, close_notify, or application data.
-      if (ssl_read_impl(ssl) > 0) {
+      if (ssl_read_impl(ssl_impl) > 0) {
         // We received some unexpected application data.
         OPENSSL_PUT_ERROR(SSL, SSL_R_APPLICATION_DATA_ON_SHUTDOWN);
         return -1;
       }
-      if (ssl->s3->read_shutdown != ssl_shutdown_close_notify) {
+      if (ssl_impl->s3->read_shutdown != ssl_shutdown_close_notify) {
         return -1;
       }
     }
   }
 
   // Return 0 for unidirectional shutdown and 1 for bidirectional shutdown.
-  return ssl->s3->read_shutdown == ssl_shutdown_close_notify;
+  return ssl_impl->s3->read_shutdown == ssl_shutdown_close_notify;
 }
 
 int SSL_send_fatal_alert(SSL *ssl, uint8_t alert) {
-  if (ssl->s3->alert_dispatch) {
-    if (ssl->s3->send_alert[0] != SSL3_AL_FATAL ||
-        ssl->s3->send_alert[1] != alert) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->s3->alert_dispatch) {
+    if (ssl_impl->s3->send_alert[0] != SSL3_AL_FATAL ||
+        ssl_impl->s3->send_alert[1] != alert) {
       // We are already attempting to write a different alert.
       OPENSSL_PUT_ERROR(SSL, SSL_R_PROTOCOL_IS_SHUTDOWN);
       return -1;
     }
-    return ssl->method->dispatch_alert(ssl);
+    return ssl_impl->method->dispatch_alert(ssl_impl);
   }
 
-  return ssl_send_alert_impl(ssl, SSL3_AL_FATAL, alert);
+  return ssl_send_alert_impl(ssl_impl, SSL3_AL_FATAL, alert);
 }
 
 int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
                                   size_t params_len) {
-  return ssl->config &&
-         ssl->config->quic_transport_params.CopyFrom(Span(params, params_len));
+  auto *ssl_impl = FromOpaque(ssl);
+  return ssl_impl->config && ssl_impl->config->quic_transport_params.CopyFrom(
+                                 Span(params, params_len));
 }
 
 void SSL_get_peer_quic_transport_params(const SSL *ssl,
                                         const uint8_t **out_params,
                                         size_t *out_params_len) {
-  *out_params = ssl->s3->peer_quic_transport_params.data();
-  *out_params_len = ssl->s3->peer_quic_transport_params.size();
+  const auto *ssl_impl = FromOpaque(ssl);
+  *out_params = ssl_impl->s3->peer_quic_transport_params.data();
+  *out_params_len = ssl_impl->s3->peer_quic_transport_params.size();
 }
 
 int SSL_set_quic_early_data_context(SSL *ssl, const uint8_t *context,
                                     size_t context_len) {
-  return ssl->config && ssl->config->quic_early_data_context.CopyFrom(
-                            Span(context, context_len));
+  auto *ssl_impl = FromOpaque(ssl);
+  return ssl_impl->config && ssl_impl->config->quic_early_data_context.CopyFrom(
+                                 Span(context, context_len));
 }
 
 void SSL_CTX_set_early_data_enabled(SSL_CTX *ctx, int enabled) {
@@ -1117,22 +1139,24 @@
 }
 
 void SSL_set_early_data_enabled(SSL *ssl, int enabled) {
-  ssl->enable_early_data = !!enabled;
+  FromOpaque(ssl)->enable_early_data = !!enabled;
 }
 
 int SSL_in_early_data(const SSL *ssl) {
-  if (ssl->s3->hs == nullptr) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->s3->hs == nullptr) {
     return 0;
   }
-  return ssl->s3->hs->in_early_data;
+  return ssl_impl->s3->hs->in_early_data;
 }
 
 int SSL_early_data_accepted(const SSL *ssl) {
-  return ssl->s3->early_data_accepted;
+  return FromOpaque(ssl)->s3->early_data_accepted;
 }
 
 void SSL_reset_early_data_reject(SSL *ssl) {
-  SSL_HANDSHAKE *hs = ssl->s3->hs.get();
+  auto *ssl_impl = FromOpaque(ssl);
+  SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get();
   if (hs == nullptr ||  //
       hs->wait != ssl_hs_early_data_rejected) {
     abort();
@@ -1145,12 +1169,12 @@
   // Discard any unfinished writes from the perspective of `SSL_write`'s
   // retry. The handshake will transparently flush out the pending record
   // (discarded by the server) to keep the framing correct.
-  ssl->s3->pending_write = {};
-  ssl->s3->unreported_bytes_written = 0;
+  ssl_impl->s3->pending_write = {};
+  ssl_impl->s3->unreported_bytes_written = 0;
 }
 
 enum ssl_early_data_reason_t SSL_get_early_data_reason(const SSL *ssl) {
-  return ssl->s3->early_data_reason;
+  return FromOpaque(ssl)->s3->early_data_reason;
 }
 
 const char *SSL_early_data_reason_string(enum ssl_early_data_reason_t reason) {
@@ -1200,6 +1224,7 @@
 }
 
 int SSL_get_error(const SSL *ssl, int ret_code) {
+  const auto *ssl_impl = FromOpaque(ssl);
   if (ret_code > 0) {
     return SSL_ERROR_NONE;
   }
@@ -1215,7 +1240,7 @@
   }
 
   if (ret_code == 0) {
-    if (ssl->s3->rwstate == SSL_ERROR_ZERO_RETURN) {
+    if (ssl_impl->s3->rwstate == SSL_ERROR_ZERO_RETURN) {
       return SSL_ERROR_ZERO_RETURN;
     }
     // An EOF was observed which violates the protocol, and the underlying
@@ -1224,7 +1249,7 @@
     return SSL_ERROR_SYSCALL;
   }
 
-  switch (ssl->s3->rwstate) {
+  switch (ssl_impl->s3->rwstate) {
     case SSL_ERROR_PENDING_SESSION:
     case SSL_ERROR_PENDING_CERTIFICATE:
     case SSL_ERROR_HANDOFF:
@@ -1236,13 +1261,13 @@
     case SSL_ERROR_WANT_CERTIFICATE_VERIFY:
     case SSL_ERROR_WANT_RENEGOTIATE:
     case SSL_ERROR_HANDSHAKE_HINTS_READY:
-      return ssl->s3->rwstate;
+      return ssl_impl->s3->rwstate;
 
     case SSL_ERROR_WANT_READ: {
-      if (SSL_is_quic(ssl)) {
+      if (SSL_is_quic(ssl_impl)) {
         return SSL_ERROR_WANT_READ;
       }
-      BIO *bio = SSL_get_rbio(ssl);
+      BIO *bio = SSL_get_rbio(ssl_impl);
       if (BIO_should_read(bio)) {
         return SSL_ERROR_WANT_READ;
       }
@@ -1261,7 +1286,7 @@
     }
 
     case SSL_ERROR_WANT_WRITE: {
-      BIO *bio = SSL_get_wbio(ssl);
+      BIO *bio = SSL_get_wbio(ssl_impl);
       if (BIO_should_write(bio)) {
         return SSL_ERROR_WANT_WRITE;
       }
@@ -1345,16 +1370,18 @@
 }
 
 uint32_t SSL_set_options(SSL *ssl, uint32_t options) {
-  ssl->options |= options;
-  return ssl->options;
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_impl->options |= options;
+  return ssl_impl->options;
 }
 
 uint32_t SSL_clear_options(SSL *ssl, uint32_t options) {
-  ssl->options &= ~options;
-  return ssl->options;
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_impl->options &= ~options;
+  return ssl_impl->options;
 }
 
-uint32_t SSL_get_options(const SSL *ssl) { return ssl->options; }
+uint32_t SSL_get_options(const SSL *ssl) { return FromOpaque(ssl)->options; }
 
 uint32_t SSL_CTX_set_mode(SSL_CTX *ctx, uint32_t mode) {
   auto *ctx_impl = FromOpaque(ctx);
@@ -1371,16 +1398,18 @@
 uint32_t SSL_CTX_get_mode(const SSL_CTX *ctx) { return FromOpaque(ctx)->mode; }
 
 uint32_t SSL_set_mode(SSL *ssl, uint32_t mode) {
-  ssl->mode |= mode;
-  return ssl->mode;
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_impl->mode |= mode;
+  return ssl_impl->mode;
 }
 
 uint32_t SSL_clear_mode(SSL *ssl, uint32_t mode) {
-  ssl->mode &= ~mode;
-  return ssl->mode;
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_impl->mode &= ~mode;
+  return ssl_impl->mode;
 }
 
-uint32_t SSL_get_mode(const SSL *ssl) { return ssl->mode; }
+uint32_t SSL_get_mode(const SSL *ssl) { return FromOpaque(ssl)->mode; }
 
 void SSL_CTX_set1_buffer_pool(SSL_CTX *ctx, CRYPTO_BUFFER_POOL *pool) {
   FromOpaque(ctx)->pool = UpRef(pool);
@@ -1397,25 +1426,26 @@
 
 int SSL_get_tls_unique(const SSL *ssl, uint8_t *out, size_t *out_len,
                        size_t max_out) {
+  const auto *ssl_impl = FromOpaque(ssl);
   *out_len = 0;
   OPENSSL_memset(out, 0, max_out);
 
   // tls-unique is not defined for TLS 1.3.
-  if (!ssl->s3->initial_handshake_complete ||
-      ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
+  if (!ssl_impl->s3->initial_handshake_complete ||
+      ssl_protocol_version(ssl_impl) >= TLS1_3_VERSION) {
     return 0;
   }
 
   // The tls-unique value is the first Finished message in the handshake, which
   // is the client's in a full handshake and the server's for a resumption. See
   // https://tools.ietf.org/html/rfc5929#section-3.1.
-  Span<const uint8_t> finished = ssl->s3->previous_client_finished;
-  if (ssl->session != nullptr) {
+  Span<const uint8_t> finished = ssl_impl->s3->previous_client_finished;
+  if (ssl_impl->session != nullptr) {
     // tls-unique is broken for resumed sessions unless EMS is used.
-    if (!ssl->session->extended_master_secret) {
+    if (!ssl_impl->session->extended_master_secret) {
       return 0;
     }
-    finished = ssl->s3->previous_server_finished;
+    finished = ssl_impl->s3->previous_server_finished;
   }
 
   *out_len = finished.size();
@@ -1445,20 +1475,23 @@
 
 int SSL_set_session_id_context(SSL *ssl, const uint8_t *sid_ctx,
                                size_t sid_ctx_len) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return set_session_id_context(ssl->config->cert.get(), sid_ctx, sid_ctx_len);
+  return set_session_id_context(ssl_impl->config->cert.get(), sid_ctx,
+                                sid_ctx_len);
 }
 
 const uint8_t *SSL_get0_session_id_context(const SSL *ssl, size_t *out_len) {
-  if (!ssl->config) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     *out_len = 0;
     return nullptr;
   }
-  *out_len = ssl->config->cert->sid_ctx.size();
-  return ssl->config->cert->sid_ctx.data();
+  *out_len = ssl_impl->config->cert->sid_ctx.size();
+  return ssl_impl->config->cert->sid_ctx.data();
 }
 
 int SSL_get_fd(const SSL *ssl) { return SSL_get_rfd(ssl); }
@@ -1542,57 +1575,61 @@
 }
 
 size_t SSL_get_finished(const SSL *ssl, void *buf, size_t count) {
-  if (!ssl->s3->initial_handshake_complete ||
-      ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->s3->initial_handshake_complete ||
+      ssl_protocol_version(ssl_impl) >= TLS1_3_VERSION) {
     return 0;
   }
 
-  if (ssl->server) {
-    return copy_finished(buf, count, ssl->s3->previous_server_finished);
+  if (ssl_impl->server) {
+    return copy_finished(buf, count, ssl_impl->s3->previous_server_finished);
   }
 
-  return copy_finished(buf, count, ssl->s3->previous_client_finished);
+  return copy_finished(buf, count, ssl_impl->s3->previous_client_finished);
 }
 
 size_t SSL_get_peer_finished(const SSL *ssl, void *buf, size_t count) {
-  if (!ssl->s3->initial_handshake_complete ||
-      ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->s3->initial_handshake_complete ||
+      ssl_protocol_version(ssl_impl) >= TLS1_3_VERSION) {
     return 0;
   }
 
-  if (ssl->server) {
-    return copy_finished(buf, count, ssl->s3->previous_client_finished);
+  if (ssl_impl->server) {
+    return copy_finished(buf, count, ssl_impl->s3->previous_client_finished);
   }
 
-  return copy_finished(buf, count, ssl->s3->previous_server_finished);
+  return copy_finished(buf, count, ssl_impl->s3->previous_server_finished);
 }
 
 int SSL_get_verify_mode(const SSL *ssl) {
-  if (!ssl->config) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     return -1;
   }
-  return ssl->config->verify_mode;
+  return ssl_impl->config->verify_mode;
 }
 
 int SSL_get_extms_support(const SSL *ssl) {
+  const auto *ssl_impl = FromOpaque(ssl);
   // TLS 1.3 does not require extended master secret and always reports as
   // supporting it.
-  if (ssl->s3->version == 0) {
+  if (ssl_impl->s3->version == 0) {
     return 0;
   }
-  if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
+  if (ssl_protocol_version(ssl_impl) >= TLS1_3_VERSION) {
     return 1;
   }
 
   // If the initial handshake completed, query the established session.
-  if (ssl->s3->established_session != nullptr) {
-    return ssl->s3->established_session->extended_master_secret;
+  if (ssl_impl->s3->established_session != nullptr) {
+    return ssl_impl->s3->established_session->extended_master_secret;
   }
 
   // Otherwise, query the in-progress handshake.
-  if (ssl->s3->hs != nullptr) {
-    return ssl->s3->hs->extended_master_secret;
+  if (ssl_impl->s3->hs != nullptr) {
+    return ssl_impl->s3->hs->extended_master_secret;
   }
   assert(0);
   return 0;
@@ -1607,11 +1644,11 @@
 int SSL_set_read_ahead(SSL *ssl, int yes) { return 1; }
 
 int SSL_pending(const SSL *ssl) {
-  return static_cast<int>(ssl->s3->pending_app_data.size());
+  return static_cast<int>(FromOpaque(ssl)->s3->pending_app_data.size());
 }
 
 int SSL_has_pending(const SSL *ssl) {
-  return SSL_pending(ssl) != 0 || !ssl->s3->read_buffer.empty();
+  return SSL_pending(ssl) != 0 || !FromOpaque(ssl)->s3->read_buffer.empty();
 }
 
 static bool has_cert_and_key(const SSLCredential *cred) {
@@ -1637,13 +1674,14 @@
 }
 
 int SSL_check_private_key(const SSL *ssl) {
-  if (!ssl->config) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
 
   // There is no need to actually check consistency because inconsistent values
   // can never be configured.
-  return has_cert_and_key(ssl->config->cert->legacy_credential.get());
+  return has_cert_and_key(ssl_impl->config->cert->legacy_credential.get());
 }
 
 long SSL_get_default_timeout(const SSL *ssl) {
@@ -1651,51 +1689,52 @@
 }
 
 int SSL_renegotiate(SSL *ssl) {
+  auto *ssl_impl = FromOpaque(ssl);
   // Caller-initiated renegotiation is not supported.
-  if (!ssl->s3->renegotiate_pending) {
+  if (!ssl_impl->s3->renegotiate_pending) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
 
-  if (!ssl_can_renegotiate(ssl)) {
+  if (!ssl_can_renegotiate(ssl_impl)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
     return 0;
   }
 
   // We should not have told the caller to release the private key.
-  assert(!SSL_can_release_private_key(ssl));
+  assert(!SSL_can_release_private_key(ssl_impl));
 
   // Renegotiation is only supported at quiescent points in the application
   // protocol, namely in HTTPS, just before reading the HTTP response.
   // Require the record-layer be idle and avoid complexities of sending a
   // handshake record while an application_data record is being written.
-  if (!ssl->s3->write_buffer.empty() ||
-      ssl->s3->write_shutdown != ssl_shutdown_none) {
+  if (!ssl_impl->s3->write_buffer.empty() ||
+      ssl_impl->s3->write_shutdown != ssl_shutdown_none) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
     return 0;
   }
 
   // Begin a new handshake.
-  if (ssl->s3->hs != nullptr) {
+  if (ssl_impl->s3->hs != nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return 0;
   }
-  ssl->s3->hs = ssl_handshake_new(ssl);
-  if (ssl->s3->hs == nullptr) {
+  ssl_impl->s3->hs = ssl_handshake_new(ssl_impl);
+  if (ssl_impl->s3->hs == nullptr) {
     return 0;
   }
 
-  ssl->s3->renegotiate_pending = false;
-  ssl->s3->total_renegotiations++;
+  ssl_impl->s3->renegotiate_pending = false;
+  ssl_impl->s3->total_renegotiations++;
   return 1;
 }
 
 int SSL_renegotiate_pending(SSL *ssl) {
-  return SSL_in_init(ssl) && ssl->s3->initial_handshake_complete;
+  return SSL_in_init(ssl) && FromOpaque(ssl)->s3->initial_handshake_complete;
 }
 
 int SSL_total_renegotiations(const SSL *ssl) {
-  return ssl->s3->total_renegotiations;
+  return FromOpaque(ssl)->s3->total_renegotiations;
 }
 
 size_t SSL_CTX_get_max_cert_list(const SSL_CTX *ctx) {
@@ -1709,13 +1748,15 @@
   FromOpaque(ctx)->max_cert_list = (uint32_t)max_cert_list;
 }
 
-size_t SSL_get_max_cert_list(const SSL *ssl) { return ssl->max_cert_list; }
+size_t SSL_get_max_cert_list(const SSL *ssl) {
+  return FromOpaque(ssl)->max_cert_list;
+}
 
 void SSL_set_max_cert_list(SSL *ssl, size_t max_cert_list) {
   if (max_cert_list > kMaxHandshakeSize) {
     max_cert_list = kMaxHandshakeSize;
   }
-  ssl->max_cert_list = (uint32_t)max_cert_list;
+  FromOpaque(ssl)->max_cert_list = (uint32_t)max_cert_list;
 }
 
 int SSL_CTX_set_max_send_fragment(SSL_CTX *ctx, size_t max_send_fragment) {
@@ -1737,7 +1778,7 @@
   if (max_send_fragment > SSL3_RT_MAX_PLAIN_LENGTH) {
     max_send_fragment = SSL3_RT_MAX_PLAIN_LENGTH;
   }
-  ssl->max_send_fragment = (uint16_t)max_send_fragment;
+  FromOpaque(ssl)->max_send_fragment = (uint16_t)max_send_fragment;
 
   return 1;
 }
@@ -1746,16 +1787,17 @@
   if (!SSL_is_dtls(ssl) || mtu < dtls1_min_mtu()) {
     return 0;
   }
-  ssl->d1->mtu = mtu;
+  FromOpaque(ssl)->d1->mtu = mtu;
   return 1;
 }
 
 int SSL_get_secure_renegotiation_support(const SSL *ssl) {
-  if (ssl->s3->version == 0) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->s3->version == 0) {
     return 0;
   }
-  return ssl_protocol_version(ssl) >= TLS1_3_VERSION ||
-         ssl->s3->send_connection_binding;
+  return ssl_protocol_version(ssl_impl) >= TLS1_3_VERSION ||
+         ssl_impl->s3->send_connection_binding;
 }
 
 size_t SSL_CTX_sess_number(const SSL_CTX *ctx) {
@@ -1948,13 +1990,14 @@
 
 int SSL_set1_group_ids_with_flags(SSL *ssl, const uint16_t *group_ids,
                                   const uint32_t *flags, size_t num_group_ids) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
   if (set_group_ids_and_flags(group_ids, flags, num_group_ids,
-                              &ssl->config->supported_group_list,
-                              &ssl->config->supported_group_list_flags)) {
-    clear_key_shares_if_invalid(ssl->config.get());
+                              &ssl_impl->config->supported_group_list,
+                              &ssl_impl->config->supported_group_list_flags)) {
+    clear_key_shares_if_invalid(ssl_impl->config.get());
     return 1;
   }
   return 0;
@@ -1993,14 +2036,15 @@
 }
 
 int SSL_set1_groups(SSL *ssl, const int *groups, size_t num_groups) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  if (ssl_nids_to_group_ids(&ssl->config->supported_group_list,
+  if (ssl_nids_to_group_ids(&ssl_impl->config->supported_group_list,
                             Span(groups, num_groups)) &&
-      ssl->config->supported_group_list_flags.Init(
-          ssl->config->supported_group_list.size())) {
-    clear_key_shares_if_invalid(ssl->config.get());
+      ssl_impl->config->supported_group_list_flags.Init(
+          ssl_impl->config->supported_group_list.size())) {
+    clear_key_shares_if_invalid(ssl_impl->config.get());
     return 1;
   }
   return 0;
@@ -2054,13 +2098,14 @@
 }
 
 int SSL_set1_groups_list(SSL *ssl, const char *groups) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  if (ssl_str_to_group_ids(&ssl->config->supported_group_list, groups) &&
-      ssl->config->supported_group_list_flags.Init(
-          ssl->config->supported_group_list.size())) {
-    clear_key_shares_if_invalid(ssl->config.get());
+  if (ssl_str_to_group_ids(&ssl_impl->config->supported_group_list, groups) &&
+      ssl_impl->config->supported_group_list_flags.Init(
+          ssl_impl->config->supported_group_list.size())) {
+    clear_key_shares_if_invalid(ssl_impl->config.get());
     return 1;
   }
   return 0;
@@ -2085,29 +2130,31 @@
 
 int SSL_set1_client_key_shares(SSL *ssl, const uint16_t *group_ids,
                                size_t num_group_ids) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
   auto requested_key_shares = Span(group_ids, num_group_ids);
   if (!validate_key_shares(requested_key_shares,
-                           ssl->config->supported_group_list)) {
+                           ssl_impl->config->supported_group_list)) {
     return 0;
   }
 
   assert(requested_key_shares.size() <= kNumNamedGroups);
-  ssl->config->client_key_share_selections.emplace();
-  ssl->config->client_key_share_selections->CopyFrom(requested_key_shares);
+  ssl_impl->config->client_key_share_selections.emplace();
+  ssl_impl->config->client_key_share_selections->CopyFrom(requested_key_shares);
   return 1;
 }
 
 int SSL_set1_server_supported_groups_hint(SSL *ssl,
                                           const uint16_t *server_groups,
                                           size_t num_server_groups) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
   auto span = Span(server_groups, num_server_groups);
-  return ssl->config->server_supported_groups_hint.CopyFrom(span);
+  return ssl_impl->config->server_supported_groups_hint.CopyFrom(span);
 }
 
 int SSL_CTX_set_tmp_dh(SSL_CTX *ctx, const DH *dh) { return 1; }
@@ -2130,13 +2177,15 @@
   if (ssl == nullptr) {
     return nullptr;
   }
-  if (ssl->config == nullptr) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->config == nullptr) {
+    assert(ssl_impl->config);
     return nullptr;
   }
 
-  return ssl->config->cipher_list ? ssl->config->cipher_list->ciphers.get()
-                                  : ssl->ctx->cipher_list->ciphers.get();
+  return ssl_impl->config->cipher_list
+             ? ssl_impl->config->cipher_list->ciphers.get()
+             : ssl_impl->ctx->cipher_list->ciphers.get();
 }
 
 const char *SSL_get_cipher_list(const SSL *ssl, int n) {
@@ -2176,24 +2225,26 @@
 }
 
 int SSL_set_cipher_list(SSL *ssl, const char *str) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  const bool has_aes_hw = ssl->config->aes_hw_override
-                              ? ssl->config->aes_hw_override_value
+  const bool has_aes_hw = ssl_impl->config->aes_hw_override
+                              ? ssl_impl->config->aes_hw_override_value
                               : EVP_has_aes_hardware();
-  return ssl_create_cipher_list(&ssl->config->cipher_list, has_aes_hw, str,
+  return ssl_create_cipher_list(&ssl_impl->config->cipher_list, has_aes_hw, str,
                                 false /* not strict */);
 }
 
 int SSL_set_strict_cipher_list(SSL *ssl, const char *str) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  const bool has_aes_hw = ssl->config->aes_hw_override
-                              ? ssl->config->aes_hw_override_value
+  const bool has_aes_hw = ssl_impl->config->aes_hw_override
+                              ? ssl_impl->config->aes_hw_override_value
                               : EVP_has_aes_hardware();
-  return ssl_create_cipher_list(&ssl->config->cipher_list, has_aes_hw, str,
+  return ssl_create_cipher_list(&ssl_impl->config->cipher_list, has_aes_hw, str,
                                 true /* strict */);
 }
 
@@ -2202,13 +2253,14 @@
     return nullptr;
   }
 
+  const auto *ssl_impl = FromOpaque(ssl);
   // Historically, `SSL_get_servername` was also the configuration getter
   // corresponding to `SSL_set_tlsext_host_name`.
-  if (ssl->hostname != nullptr) {
-    return ssl->hostname.get();
+  if (ssl_impl->hostname != nullptr) {
+    return ssl_impl->hostname.get();
   }
 
-  return ssl->s3->hostname.get();
+  return ssl_impl->s3->hostname.get();
 }
 
 int SSL_get_servername_type(const SSL *ssl) {
@@ -2229,11 +2281,12 @@
 void SSL_set_custom_verify(
     SSL *ssl, int mode,
     enum ssl_verify_result_t (*callback)(SSL *ssl, uint8_t *out_alert)) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->verify_mode = mode;
-  ssl->config->custom_verify_callback = callback;
+  ssl_impl->config->verify_mode = mode;
+  ssl_impl->config->custom_verify_callback = callback;
 }
 
 void SSL_CTX_enable_signed_cert_timestamps(SSL_CTX *ctx) {
@@ -2241,10 +2294,11 @@
 }
 
 void SSL_enable_signed_cert_timestamps(SSL *ssl) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->signed_cert_timestamps_enabled = true;
+  ssl_impl->config->signed_cert_timestamps_enabled = true;
 }
 
 void SSL_CTX_enable_ocsp_stapling(SSL_CTX *ctx) {
@@ -2252,16 +2306,18 @@
 }
 
 void SSL_enable_ocsp_stapling(SSL *ssl) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->ocsp_stapling_enabled = true;
+  ssl_impl->config->ocsp_stapling_enabled = true;
 }
 
 void SSL_get0_signed_cert_timestamp_list(const SSL *ssl, const uint8_t **out,
                                          size_t *out_len) {
   SSL_SESSION *session = SSL_get_session(ssl);
-  if (ssl->server || !session || !session->signed_cert_timestamp_list) {
+  if (FromOpaque(ssl)->server || !session ||
+      !session->signed_cert_timestamp_list) {
     *out_len = 0;
     *out = nullptr;
     return;
@@ -2274,7 +2330,7 @@
 void SSL_get0_ocsp_response(const SSL *ssl, const uint8_t **out,
                             size_t *out_len) {
   SSL_SESSION *session = SSL_get_session(ssl);
-  if (ssl->server || !session || !session->ocsp_response) {
+  if (FromOpaque(ssl)->server || !session || !session->ocsp_response) {
     *out_len = 0;
     *out = nullptr;
     return;
@@ -2285,7 +2341,8 @@
 }
 
 int SSL_set_tlsext_host_name(SSL *ssl, const char *name) {
-  ssl->hostname.reset();
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_impl->hostname.reset();
   if (name == nullptr) {
     return 1;
   }
@@ -2295,8 +2352,8 @@
     OPENSSL_PUT_ERROR(SSL, SSL_R_SSL3_EXT_INVALID_SERVERNAME);
     return 0;
   }
-  ssl->hostname.reset(OPENSSL_strdup(name));
-  if (ssl->hostname == nullptr) {
+  ssl_impl->hostname.reset(OPENSSL_strdup(name));
+  if (ssl_impl->hostname == nullptr) {
     return 0;
   }
   return 1;
@@ -2363,10 +2420,11 @@
 
 void SSL_get0_next_proto_negotiated(const SSL *ssl, const uint8_t **out_data,
                                     unsigned *out_len) {
+  const auto *ssl_impl = FromOpaque(ssl);
   // NPN protocols have one-byte lengths, so they must fit in `unsigned`.
-  assert(ssl->s3->next_proto_negotiated.size() <= UINT_MAX);
-  *out_data = ssl->s3->next_proto_negotiated.data();
-  *out_len = static_cast<unsigned>(ssl->s3->next_proto_negotiated.size());
+  assert(ssl_impl->s3->next_proto_negotiated.size() <= UINT_MAX);
+  *out_data = ssl_impl->s3->next_proto_negotiated.data();
+  *out_len = static_cast<unsigned>(ssl_impl->s3->next_proto_negotiated.size());
 }
 
 void SSL_CTX_set_next_protos_advertised_cb(
@@ -2402,7 +2460,8 @@
 
 int SSL_set_alpn_protos(SSL *ssl, const uint8_t *protos, size_t protos_len) {
   // Note this function's return value is backwards.
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 1;
   }
   auto span = Span(protos, protos_len);
@@ -2410,7 +2469,7 @@
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ALPN_PROTOCOL_LIST);
     return 1;
   }
-  return ssl->config->alpn_client_proto_list.CopyFrom(span) ? 0 : 1;
+  return ssl_impl->config->alpn_client_proto_list.CopyFrom(span) ? 0 : 1;
 }
 
 void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx,
@@ -2425,11 +2484,12 @@
 
 void SSL_get0_alpn_selected(const SSL *ssl, const uint8_t **out_data,
                             unsigned *out_len) {
+  const auto *ssl_impl = FromOpaque(ssl);
   Span<const uint8_t> protocol;
-  if (SSL_in_early_data(ssl) && !ssl->server) {
-    protocol = ssl->s3->hs->early_session->early_alpn;
+  if (SSL_in_early_data(ssl_impl) && !ssl_impl->server) {
+    protocol = ssl_impl->s3->hs->early_session->early_alpn;
   } else {
-    protocol = ssl->s3->alpn_selected;
+    protocol = ssl_impl->s3->alpn_selected;
   }
   // ALPN protocols have one-byte lengths, so they must fit in `unsigned`.
   assert(protocol.size() < UINT_MAX);
@@ -2444,13 +2504,14 @@
 int SSL_add_application_settings(SSL *ssl, const uint8_t *proto,
                                  size_t proto_len, const uint8_t *settings,
                                  size_t settings_len) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
   ALPSConfig config;
   if (!config.protocol.CopyFrom(Span(proto, proto_len)) ||
       !config.settings.CopyFrom(Span(settings, settings_len)) ||
-      !ssl->config->alps_configs.Push(std::move(config))) {
+      !ssl_impl->config->alps_configs.Push(std::move(config))) {
     return 0;
   }
   return 1;
@@ -2472,10 +2533,11 @@
 }
 
 void SSL_set_alps_use_new_codepoint(SSL *ssl, int use_new) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->alps_use_new_codepoint = !!use_new;
+  ssl_impl->config->alps_use_new_codepoint = !!use_new;
 }
 
 int SSL_CTX_add_cert_compression_alg(SSL_CTX *ctx, uint16_t alg_id,
@@ -2507,10 +2569,11 @@
 }
 
 void SSL_set_tls_channel_id_enabled(SSL *ssl, int enabled) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->channel_id_enabled = !!enabled;
+  ssl_impl->config->channel_id_enabled = !!enabled;
 }
 
 int SSL_enable_tls_channel_id(SSL *ssl) {
@@ -2529,7 +2592,8 @@
 }
 
 int SSL_set1_tls_channel_id(SSL *ssl, EVP_PKEY *private_key) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
   if (EVP_PKEY_get_ec_curve_nid(private_key) != NID_X9_62_prime256v1) {
@@ -2537,22 +2601,24 @@
     return 0;
   }
 
-  ssl->config->channel_id_private = UpRef(private_key);
+  ssl_impl->config->channel_id_private = UpRef(private_key);
   return 1;
 }
 
 size_t SSL_get_tls_channel_id(SSL *ssl, uint8_t *out, size_t max_out) {
-  if (!ssl->s3->channel_id_valid) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->s3->channel_id_valid) {
     return 0;
   }
-  OPENSSL_memcpy(out, ssl->s3->channel_id, (max_out < 64) ? max_out : 64);
+  OPENSSL_memcpy(out, ssl_impl->s3->channel_id, (max_out < 64) ? max_out : 64);
   return 64;
 }
 
 size_t SSL_get0_certificate_types(const SSL *ssl, const uint8_t **out_types) {
+  const auto *ssl_impl = FromOpaque(ssl);
   Span<const uint8_t> types;
-  if (!ssl->server && ssl->s3->hs != nullptr) {
-    types = ssl->s3->hs->certificate_types;
+  if (!ssl_impl->server && ssl_impl->s3->hs != nullptr) {
+    types = ssl_impl->s3->hs->certificate_types;
   }
   *out_types = types.data();
   return types.size();
@@ -2560,9 +2626,10 @@
 
 size_t SSL_get0_peer_verify_algorithms(const SSL *ssl,
                                        const uint16_t **out_sigalgs) {
+  const auto *ssl_impl = FromOpaque(ssl);
   Span<const uint16_t> sigalgs;
-  if (ssl->s3->hs != nullptr) {
-    sigalgs = ssl->s3->hs->peer_sigalgs;
+  if (ssl_impl->s3->hs != nullptr) {
+    sigalgs = ssl_impl->s3->hs->peer_sigalgs;
   }
   *out_sigalgs = sigalgs.data();
   return sigalgs.size();
@@ -2570,20 +2637,22 @@
 
 size_t SSL_get0_peer_delegation_algorithms(const SSL *ssl,
                                            const uint16_t **out_sigalgs) {
+  const auto *ssl_impl = FromOpaque(ssl);
   Span<const uint16_t> sigalgs;
-  if (ssl->s3->hs != nullptr) {
-    sigalgs = ssl->s3->hs->peer_delegated_credential_sigalgs;
+  if (ssl_impl->s3->hs != nullptr) {
+    sigalgs = ssl_impl->s3->hs->peer_delegated_credential_sigalgs;
   }
   *out_sigalgs = sigalgs.data();
   return sigalgs.size();
 }
 
 EVP_PKEY *SSL_get_privatekey(const SSL *ssl) {
-  if (!ssl->config) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     return nullptr;
   }
-  return ssl->config->cert->legacy_credential->privkey.get();
+  return ssl_impl->config->cert->legacy_credential->privkey.get();
 }
 
 EVP_PKEY *SSL_CTX_get0_privatekey(const SSL_CTX *ctx) {
@@ -2596,7 +2665,7 @@
 }
 
 int SSL_session_reused(const SSL *ssl) {
-  return ssl->s3->session_reused || SSL_in_early_data(ssl);
+  return FromOpaque(ssl)->s3->session_reused || SSL_in_early_data(ssl);
 }
 
 const COMP_METHOD *SSL_get_current_compression(SSL *ssl) { return nullptr; }
@@ -2614,54 +2683,59 @@
 }
 
 void SSL_set_quiet_shutdown(SSL *ssl, int mode) {
-  ssl->quiet_shutdown = (mode != 0);
+  FromOpaque(ssl)->quiet_shutdown = (mode != 0);
 }
 
-int SSL_get_quiet_shutdown(const SSL *ssl) { return ssl->quiet_shutdown; }
+int SSL_get_quiet_shutdown(const SSL *ssl) {
+  return FromOpaque(ssl)->quiet_shutdown;
+}
 
 void SSL_set_shutdown(SSL *ssl, int mode) {
+  auto *ssl_impl = FromOpaque(ssl);
   // It is an error to clear any bits that have already been set. (We can't try
   // to get a second close_notify or send two.)
   assert((SSL_get_shutdown(ssl) & mode) == SSL_get_shutdown(ssl));
 
   if (mode & SSL_RECEIVED_SHUTDOWN &&
-      ssl->s3->read_shutdown == ssl_shutdown_none) {
-    ssl->s3->read_shutdown = ssl_shutdown_close_notify;
+      ssl_impl->s3->read_shutdown == ssl_shutdown_none) {
+    ssl_impl->s3->read_shutdown = ssl_shutdown_close_notify;
   }
 
   if (mode & SSL_SENT_SHUTDOWN &&
-      ssl->s3->write_shutdown == ssl_shutdown_none) {
-    ssl->s3->write_shutdown = ssl_shutdown_close_notify;
+      ssl_impl->s3->write_shutdown == ssl_shutdown_none) {
+    ssl_impl->s3->write_shutdown = ssl_shutdown_close_notify;
   }
 }
 
 int SSL_get_shutdown(const SSL *ssl) {
+  const auto *ssl_impl = FromOpaque(ssl);
   int ret = 0;
-  if (ssl->s3->read_shutdown != ssl_shutdown_none) {
+  if (ssl_impl->s3->read_shutdown != ssl_shutdown_none) {
     // Historically, OpenSSL set `SSL_RECEIVED_SHUTDOWN` on both close_notify
     // and fatal alert.
     ret |= SSL_RECEIVED_SHUTDOWN;
   }
-  if (ssl->s3->write_shutdown == ssl_shutdown_close_notify) {
+  if (ssl_impl->s3->write_shutdown == ssl_shutdown_close_notify) {
     // Historically, OpenSSL set `SSL_SENT_SHUTDOWN` on only close_notify.
     ret |= SSL_SENT_SHUTDOWN;
   }
   return ret;
 }
 
-SSL_CTX *SSL_get_SSL_CTX(const SSL *ssl) { return ssl->ctx.get(); }
+SSL_CTX *SSL_get_SSL_CTX(const SSL *ssl) { return FromOpaque(ssl)->ctx.get(); }
 
 SSL_CTX *SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return nullptr;
   }
-  if (ssl->ctx.get() == ctx) {
-    return ssl->ctx.get();
+  if (ssl_impl->ctx.get() == ctx) {
+    return ssl_impl->ctx.get();
   }
 
   // One cannot change the X.509 callbacks during a connection.
   auto *ctx_impl = FromOpaque(ctx);
-  if (ssl->ctx->x509_method != ctx_impl->x509_method) {
+  if (ssl_impl->ctx->x509_method != ctx_impl->x509_method) {
     assert(0);
     return nullptr;
   }
@@ -2671,21 +2745,21 @@
     return nullptr;
   }
 
-  ssl->config->cert = std::move(new_cert);
-  ssl->ctx = UpRef(ctx_impl);
-  ssl->enable_early_data = ssl->ctx->enable_early_data;
+  ssl_impl->config->cert = std::move(new_cert);
+  ssl_impl->ctx = UpRef(ctx_impl);
+  ssl_impl->enable_early_data = ssl_impl->ctx->enable_early_data;
 
-  return ssl->ctx.get();
+  return ssl_impl->ctx.get();
 }
 
 void SSL_set_info_callback(SSL *ssl,
                            void (*cb)(const SSL *ssl, int type, int value)) {
-  ssl->info_callback = cb;
+  FromOpaque(ssl)->info_callback = cb;
 }
 
 void (*SSL_get_info_callback(const SSL *ssl))(const SSL *ssl, int type,
                                               int value) {
-  return ssl->info_callback;
+  return FromOpaque(ssl)->info_callback;
 }
 
 int SSL_state(const SSL *ssl) {
@@ -2717,10 +2791,11 @@
 }
 
 int SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method) {
-  if (ssl->method->is_dtls) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->method->is_dtls) {
     return 0;
   }
-  ssl->quic_method = quic_method;
+  ssl_impl->quic_method = quic_method;
   return 1;
 }
 
@@ -2731,11 +2806,11 @@
 }
 
 int SSL_set_ex_data(SSL *ssl, int idx, void *data) {
-  return CRYPTO_set_ex_data(&ssl->ex_data, idx, data);
+  return CRYPTO_set_ex_data(&FromOpaque(ssl)->ex_data, idx, data);
 }
 
 void *SSL_get_ex_data(const SSL *ssl, int idx) {
-  return CRYPTO_get_ex_data(&ssl->ex_data, idx);
+  return CRYPTO_get_ex_data(&FromOpaque(ssl)->ex_data, idx);
 }
 
 int SSL_CTX_get_ex_new_index(long argl, void *argp, CRYPTO_EX_unused *unused,
@@ -2754,11 +2829,12 @@
 }
 
 int SSL_want(const SSL *ssl) {
+  const auto *ssl_impl = FromOpaque(ssl);
   // Historically, OpenSSL did not track `SSL_ERROR_ZERO_RETURN` as an `rwstate`
   // value. We do, but map it back to `SSL_ERROR_NONE` to preserve the original
   // behavior.
-  return ssl->s3->rwstate == SSL_ERROR_ZERO_RETURN ? SSL_ERROR_NONE
-                                                   : ssl->s3->rwstate;
+  return ssl_impl->s3->rwstate == SSL_ERROR_ZERO_RETURN ? SSL_ERROR_NONE
+                                                        : ssl_impl->s3->rwstate;
 }
 
 void SSL_CTX_set_tmp_rsa_callback(SSL_CTX *ctx,
@@ -2806,21 +2882,24 @@
 }
 
 int SSL_use_psk_identity_hint(SSL *ssl, const char *identity_hint) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return use_psk_identity_hint(&ssl->config->psk_identity_hint, identity_hint);
+  return use_psk_identity_hint(&ssl_impl->config->psk_identity_hint,
+                               identity_hint);
 }
 
 const char *SSL_get_psk_identity_hint(const SSL *ssl) {
   if (ssl == nullptr) {
     return nullptr;
   }
-  if (ssl->config == nullptr) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->config == nullptr) {
+    assert(ssl_impl->config);
     return nullptr;
   }
-  return ssl->config->psk_identity_hint.get();
+  return ssl_impl->config->psk_identity_hint.get();
 }
 
 const char *SSL_get_psk_identity(const SSL *ssl) {
@@ -2838,10 +2917,11 @@
     SSL *ssl, unsigned (*cb)(SSL *ssl, const char *hint, char *identity,
                              unsigned max_identity_len, uint8_t *psk,
                              unsigned max_psk_len)) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->psk_client_callback = cb;
+  ssl_impl->config->psk_client_callback = cb;
 }
 
 void SSL_CTX_set_psk_client_callback(
@@ -2855,10 +2935,11 @@
                                  unsigned (*cb)(SSL *ssl, const char *identity,
                                                 uint8_t *psk,
                                                 unsigned max_psk_len)) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->psk_server_callback = cb;
+  ssl_impl->config->psk_server_callback = cb;
 }
 
 void SSL_CTX_set_psk_server_callback(
@@ -2882,11 +2963,11 @@
                           void (*cb)(int write_p, int version, int content_type,
                                      const void *buf, size_t len, SSL *ssl,
                                      void *arg)) {
-  ssl->msg_callback = cb;
+  FromOpaque(ssl)->msg_callback = cb;
 }
 
 void SSL_set_msg_callback_arg(SSL *ssl, void *arg) {
-  ssl->msg_callback_arg = arg;
+  FromOpaque(ssl)->msg_callback_arg = arg;
 }
 
 void SSL_CTX_set_keylog_callback(SSL_CTX *ctx,
@@ -2906,14 +2987,15 @@
 }
 
 int SSL_can_release_private_key(const SSL *ssl) {
-  if (ssl_can_renegotiate(ssl)) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_can_renegotiate(ssl_impl)) {
     // If the connection can renegotiate (client only), the private key may be
     // used in a future handshake.
     return 0;
   }
 
   // Otherwise, this is determined by the current handshake.
-  return !ssl->s3->hs || ssl->s3->hs->can_release_private_key;
+  return !ssl_impl->s3->hs || ssl_impl->s3->hs->can_release_private_key;
 }
 
 int SSL_is_init_finished(const SSL *ssl) { return !SSL_in_init(ssl); }
@@ -2922,24 +3004,28 @@
   // This returns false once all the handshake state has been finalized, to
   // allow callbacks and getters based on SSL_in_init to return the correct
   // values.
-  SSL_HANDSHAKE *hs = ssl->s3->hs.get();
+  const auto *ssl_impl = FromOpaque(ssl);
+  SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get();
   return hs != nullptr && !hs->handshake_finalized;
 }
 
 int SSL_in_false_start(const SSL *ssl) {
-  if (ssl->s3->hs == nullptr) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->s3->hs == nullptr) {
     return 0;
   }
-  return ssl->s3->hs->in_false_start;
+  return ssl_impl->s3->hs->in_false_start;
 }
 
 int SSL_cutthrough_complete(const SSL *ssl) { return SSL_in_false_start(ssl); }
 
-int SSL_is_server(const SSL *ssl) { return ssl->server; }
+int SSL_is_server(const SSL *ssl) { return FromOpaque(ssl)->server; }
 
-int SSL_is_dtls(const SSL *ssl) { return ssl->method->is_dtls; }
+int SSL_is_dtls(const SSL *ssl) { return FromOpaque(ssl)->method->is_dtls; }
 
-int SSL_is_quic(const SSL *ssl) { return ssl->quic_method != nullptr; }
+int SSL_is_quic(const SSL *ssl) {
+  return FromOpaque(ssl)->quic_method != nullptr;
+}
 
 void SSL_CTX_set_select_certificate_cb(
     SSL_CTX *ctx,
@@ -2957,12 +3043,13 @@
 }
 
 void SSL_set_renegotiate_mode(SSL *ssl, enum ssl_renegotiate_mode_t mode) {
-  ssl->renegotiate_mode = mode;
+  auto *ssl_impl = FromOpaque(ssl);
+  ssl_impl->renegotiate_mode = mode;
 
   // Check if `ssl_can_renegotiate` has changed and the configuration may now be
   // shed. HTTP clients may initially allow renegotiation for HTTP/1.1, and then
   // disable after the handshake once the ALPN protocol is known to be HTTP/2.
-  ssl_maybe_shed_handshake_config(ssl);
+  ssl_maybe_shed_handshake_config(ssl_impl);
 }
 
 int SSL_get_ivs(const SSL *ssl, const uint8_t **out_read_iv,
@@ -2974,9 +3061,10 @@
     return 0;
   }
 
+  const auto *ssl_impl = FromOpaque(ssl);
   size_t write_iv_len;
-  if (!ssl->s3->aead_read_ctx->GetIV(out_read_iv, out_iv_len) ||
-      !ssl->s3->aead_write_ctx->GetIV(out_write_iv, &write_iv_len) ||
+  if (!ssl_impl->s3->aead_read_ctx->GetIV(out_read_iv, out_iv_len) ||
+      !ssl_impl->s3->aead_write_ctx->GetIV(out_write_iv, &write_iv_len) ||
       *out_iv_len != write_iv_len) {
     return 0;
   }
@@ -2985,14 +3073,15 @@
 }
 
 uint64_t SSL_get_read_sequence(const SSL *ssl) {
+  const auto *ssl_impl = FromOpaque(ssl);
   if (SSL_is_dtls(ssl)) {
     // TODO(crbug.com/42290608): This API should not be implemented for DTLS or
     // QUIC. In QUIC we do not maintain a sequence number.
-    const DTLSReadEpoch *read_epoch = &ssl->d1->read_epoch;
+    const DTLSReadEpoch *read_epoch = &ssl_impl->d1->read_epoch;
     return DTLSRecordNumber(read_epoch->epoch, read_epoch->bitmap.max_seq_num())
         .combined();
   }
-  return ssl->s3->read_sequence;
+  return ssl_impl->s3->read_sequence;
 }
 
 uint64_t SSL_get_write_sequence(const SSL *ssl) {
@@ -3000,60 +3089,66 @@
   // QUIC. In QUIC we do not maintain a sequence number. In DTLS, this API isn't
   // harmful per se, but the caller already needs to use a DTLS-specific API on
   // the read side.
+  const auto *ssl_impl = FromOpaque(ssl);
   if (SSL_is_dtls(ssl)) {
-    return ssl->d1->write_epoch.next_record.combined();
+    return ssl_impl->d1->write_epoch.next_record.combined();
   }
 
-  return ssl->s3->write_sequence;
+  return ssl_impl->s3->write_sequence;
 }
 
 int SSL_is_dtls_handshake_idle(const SSL *ssl) {
   BSSL_CHECK(SSL_is_dtls(ssl));
+  const auto *ssl_impl = FromOpaque(ssl);
 
   return !SSL_in_init(ssl) &&
          // No unacknowledged messages in DTLS 1.3. In DTLS 1.2, there no ACKs
          // and we currently never clear `outgoing_messages` on the side that
          // speaks last.
-         (ssl_protocol_version(ssl) < TLS1_3_VERSION ||
-          ssl->d1->outgoing_messages.empty()) &&
+         (ssl_protocol_version(ssl_impl) < TLS1_3_VERSION ||
+          ssl_impl->d1->outgoing_messages.empty()) &&
          // No partial or out-of-order messages.
-         std::all_of(std::begin(ssl->d1->incoming_messages),
-                     std::end(ssl->d1->incoming_messages),
+         std::all_of(std::begin(ssl_impl->d1->incoming_messages),
+                     std::end(ssl_impl->d1->incoming_messages),
                      [](const auto &msg) { return msg == nullptr; }) &&
          // Not trying to send a KeyUpdate.
-         !ssl->s3->key_update_pending &&
-         ssl->d1->queued_key_update == bssl::QueuedKeyUpdate::kNone;
+         !ssl_impl->s3->key_update_pending &&
+         ssl_impl->d1->queued_key_update == bssl::QueuedKeyUpdate::kNone;
 }
 
 uint32_t SSL_get_dtls_handshake_read_seq(const SSL *ssl) {
   BSSL_CHECK(SSL_is_dtls(ssl));
-  return ssl->d1->handshake_read_overflow
+  const auto *ssl_impl = FromOpaque(ssl);
+  return ssl_impl->d1->handshake_read_overflow
              ? uint32_t{0x10000}
-             : uint32_t{ssl->d1->handshake_read_seq};
+             : uint32_t{ssl_impl->d1->handshake_read_seq};
 }
 
 uint32_t SSL_get_dtls_handshake_write_seq(const SSL *ssl) {
   BSSL_CHECK(SSL_is_dtls(ssl));
-  return ssl->d1->handshake_write_overflow
+  const auto *ssl_impl = FromOpaque(ssl);
+  return ssl_impl->d1->handshake_write_overflow
              ? uint32_t{0x10000}
-             : uint32_t{ssl->d1->handshake_write_seq};
+             : uint32_t{ssl_impl->d1->handshake_write_seq};
 }
 
 uint16_t SSL_get_dtls_read_epoch(const SSL *ssl) {
   BSSL_CHECK(SSL_is_dtls(ssl));
+  const auto *ssl_impl = FromOpaque(ssl);
   // Return the highest available epoch.
-  return ssl->d1->next_read_epoch ? ssl->d1->next_read_epoch->epoch
-                                  : ssl->d1->read_epoch.epoch;
+  return ssl_impl->d1->next_read_epoch ? ssl_impl->d1->next_read_epoch->epoch
+                                       : ssl_impl->d1->read_epoch.epoch;
 }
 
 uint16_t SSL_get_dtls_write_epoch(const SSL *ssl) {
   BSSL_CHECK(SSL_is_dtls(ssl));
-  return ssl->d1->write_epoch.epoch();
+  return FromOpaque(ssl)->d1->write_epoch.epoch();
 }
 
 uint64_t SSL_get_dtls_read_sequence(const SSL *ssl, uint16_t epoch) {
   BSSL_CHECK(SSL_is_dtls(ssl));
-  const DTLSReadEpoch *read_epoch = dtls_get_read_epoch(ssl, epoch);
+  const auto *ssl_impl = FromOpaque(ssl);
+  const DTLSReadEpoch *read_epoch = dtls_get_read_epoch(ssl_impl, epoch);
   if (read_epoch == nullptr) {
     return UINT64_MAX;
   }
@@ -3071,7 +3166,8 @@
 
 uint64_t SSL_get_dtls_write_sequence(const SSL *ssl, uint16_t epoch) {
   BSSL_CHECK(SSL_is_dtls(ssl));
-  const DTLSWriteEpoch *write_epoch = dtls_get_write_epoch(ssl, epoch);
+  const auto *ssl_impl = FromOpaque(ssl);
+  const DTLSWriteEpoch *write_epoch = dtls_get_write_epoch(ssl_impl, epoch);
   if (write_epoch == nullptr) {
     return UINT64_MAX;
   }
@@ -3080,7 +3176,7 @@
 
 template <typename EpochState>
 static int get_dtls_traffic_secret(
-    const SSL *ssl, EpochState *(*get_epoch)(const SSL *, uint16_t),
+    const SSLImpl *ssl, EpochState *(*get_epoch)(const SSLImpl *, uint16_t),
     const uint8_t **out_data, size_t *out_len, uint16_t epoch) {
   BSSL_CHECK(SSL_is_dtls(ssl));
   // This function only applies to encrypted DTLS 1.3 epochs.
@@ -3100,14 +3196,14 @@
 
 int SSL_get_dtls_read_traffic_secret(const SSL *ssl, const uint8_t **out_data,
                                      size_t *out_len, uint16_t epoch) {
-  return get_dtls_traffic_secret(ssl, dtls_get_read_epoch, out_data, out_len,
-                                 epoch);
+  return get_dtls_traffic_secret(FromOpaque(ssl), dtls_get_read_epoch, out_data,
+                                 out_len, epoch);
 }
 
 int SSL_get_dtls_write_traffic_secret(const SSL *ssl, const uint8_t **out_data,
                                       size_t *out_len, uint16_t epoch) {
-  return get_dtls_traffic_secret(ssl, dtls_get_write_epoch, out_data, out_len,
-                                 epoch);
+  return get_dtls_traffic_secret(FromOpaque(ssl), dtls_get_write_epoch,
+                                 out_data, out_len, epoch);
 }
 
 uint16_t SSL_get_peer_signature_algorithm(const SSL *ssl) {
@@ -3120,29 +3216,32 @@
 }
 
 size_t SSL_get_client_random(const SSL *ssl, uint8_t *out, size_t max_out) {
+  const auto *ssl_impl = FromOpaque(ssl);
   if (max_out == 0) {
-    return sizeof(ssl->s3->client_random);
+    return sizeof(ssl_impl->s3->client_random);
   }
-  if (max_out > sizeof(ssl->s3->client_random)) {
-    max_out = sizeof(ssl->s3->client_random);
+  if (max_out > sizeof(ssl_impl->s3->client_random)) {
+    max_out = sizeof(ssl_impl->s3->client_random);
   }
-  OPENSSL_memcpy(out, ssl->s3->client_random, max_out);
+  OPENSSL_memcpy(out, ssl_impl->s3->client_random, max_out);
   return max_out;
 }
 
 size_t SSL_get_server_random(const SSL *ssl, uint8_t *out, size_t max_out) {
+  const auto *ssl_impl = FromOpaque(ssl);
   if (max_out == 0) {
-    return sizeof(ssl->s3->server_random);
+    return sizeof(ssl_impl->s3->server_random);
   }
-  if (max_out > sizeof(ssl->s3->server_random)) {
-    max_out = sizeof(ssl->s3->server_random);
+  if (max_out > sizeof(ssl_impl->s3->server_random)) {
+    max_out = sizeof(ssl_impl->s3->server_random);
   }
-  OPENSSL_memcpy(out, ssl->s3->server_random, max_out);
+  OPENSSL_memcpy(out, ssl_impl->s3->server_random, max_out);
   return max_out;
 }
 
 uint16_t SSL_get_signature_algorithm_used(const SSL *ssl) {
-  SSL_HANDSHAKE *hs = ssl->s3->hs.get();
+  const auto *ssl_impl = FromOpaque(ssl);
+  SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get();
   if (hs == nullptr) {
     return 0;
   }
@@ -3150,7 +3249,8 @@
 }
 
 const SSL_CIPHER *SSL_get_pending_cipher(const SSL *ssl) {
-  SSL_HANDSHAKE *hs = ssl->s3->hs.get();
+  const auto *ssl_impl = FromOpaque(ssl);
+  SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get();
   if (hs == nullptr) {
     return nullptr;
   }
@@ -3158,10 +3258,11 @@
 }
 
 void SSL_set_retain_only_sha256_of_client_certs(SSL *ssl, int enabled) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->retain_only_sha256_of_client_certs = !!enabled;
+  ssl_impl->config->retain_only_sha256_of_client_certs = !!enabled;
 }
 
 void SSL_CTX_set_retain_only_sha256_of_client_certs(SSL_CTX *ctx, int enabled) {
@@ -3181,14 +3282,15 @@
 }
 
 void SSL_set_permute_extensions(SSL *ssl, int enabled) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->permute_extensions = !!enabled;
+  ssl_impl->config->permute_extensions = !!enabled;
 }
 
 int32_t SSL_get_ticket_age_skew(const SSL *ssl) {
-  return ssl->s3->ticket_age_skew;
+  return FromOpaque(ssl)->s3->ticket_age_skew;
 }
 
 void SSL_CTX_set_false_start_allowed_without_alpn(SSL_CTX *ctx, int allowed) {
@@ -3196,32 +3298,36 @@
 }
 
 int SSL_used_hello_retry_request(const SSL *ssl) {
-  return ssl->s3->used_hello_retry_request;
+  return FromOpaque(ssl)->s3->used_hello_retry_request;
 }
 
 void SSL_set_shed_handshake_config(SSL *ssl, int enable) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->shed_handshake_config = !!enable;
+  ssl_impl->config->shed_handshake_config = !!enable;
 }
 
 void SSL_set_jdk11_workaround(SSL *ssl, int enable) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->jdk11_workaround = !!enable;
+  ssl_impl->config->jdk11_workaround = !!enable;
 }
 
 void SSL_set_quic_use_legacy_codepoint(SSL *ssl, int use_legacy) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->quic_use_legacy_codepoint = !!use_legacy;
+  ssl_impl->config->quic_use_legacy_codepoint = !!use_legacy;
 }
 
 int SSL_clear(SSL *ssl) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;  // SSL_clear may not be used after shedding config.
   }
 
@@ -3229,8 +3335,8 @@
   // established session to be offered the next time around. wpa_supplicant
   // depends on this behavior, so emulate it.
   UniquePtr<SSL_SESSION> session;
-  if (!ssl->server && ssl->s3->established_session != nullptr) {
-    session = UpRef(ssl->s3->established_session);
+  if (!ssl_impl->server && ssl_impl->s3->established_session != nullptr) {
+    session = UpRef(ssl_impl->s3->established_session);
   }
 
   // The ssl->d1->mtu is simultaneously configuration (preserved across
@@ -3238,17 +3344,17 @@
   //
   // TODO(davidben): Avoid this.
   unsigned mtu = 0;
-  if (ssl->d1 != nullptr) {
-    mtu = ssl->d1->mtu;
+  if (ssl_impl->d1 != nullptr) {
+    mtu = ssl_impl->d1->mtu;
   }
 
-  ssl->method->ssl_free(ssl);
-  if (!ssl->method->ssl_new(ssl)) {
+  ssl_impl->method->ssl_free(ssl_impl);
+  if (!ssl_impl->method->ssl_new(ssl_impl)) {
     return 0;
   }
 
   if (SSL_is_dtls(ssl) && (SSL_get_options(ssl) & SSL_OP_NO_QUERY_MTU)) {
-    ssl->d1->mtu = mtu;
+    ssl_impl->d1->mtu = mtu;
   }
 
   if (session != nullptr) {
@@ -3307,9 +3413,10 @@
 
 SSL_SESSION *SSL_process_tls13_new_session_ticket(SSL *ssl, const uint8_t *buf,
                                                   size_t buf_len) {
-  if (SSL_in_init(ssl) ||                             //
-      ssl_protocol_version(ssl) != TLS1_3_VERSION ||  //
-      ssl->server) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (SSL_in_init(ssl_impl) ||                             //
+      ssl_protocol_version(ssl_impl) != TLS1_3_VERSION ||  //
+      ssl_impl->server) {
     // Only TLS 1.3 clients are supported.
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return nullptr;
@@ -3325,7 +3432,8 @@
     return nullptr;
   }
 
-  UniquePtr<SSL_SESSION> session = tls13_create_session_with_ticket(ssl, &body);
+  UniquePtr<SSL_SESSION> session =
+      tls13_create_session_with_ticket(ssl_impl, &body);
   if (!session) {
     // `tls13_create_session_with_ticket` puts the correct error.
     return nullptr;
@@ -3345,22 +3453,24 @@
 }
 
 int SSL_set_tlsext_status_type(SSL *ssl, int type) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  ssl->config->ocsp_stapling_enabled = type == TLSEXT_STATUSTYPE_ocsp;
+  ssl_impl->config->ocsp_stapling_enabled = type == TLSEXT_STATUSTYPE_ocsp;
   return 1;
 }
 
 int SSL_get_tlsext_status_type(const SSL *ssl) {
-  if (ssl->server) {
-    SSL_HANDSHAKE *hs = ssl->s3->hs.get();
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->server) {
+    SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get();
     return hs != nullptr && hs->ocsp_stapling_requested
                ? TLSEXT_STATUSTYPE_ocsp
                : TLSEXT_STATUSTYPE_nothing;
   }
 
-  return ssl->config != nullptr && ssl->config->ocsp_stapling_enabled
+  return ssl_impl->config != nullptr && ssl_impl->config->ocsp_stapling_enabled
              ? TLSEXT_STATUSTYPE_ocsp
              : TLSEXT_STATUSTYPE_nothing;
 }
@@ -3469,7 +3579,7 @@
       SSL_CTX_set_verify_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs));
 }
 
-static int Configure(SSL *ssl) {
+static int Configure(SSLImpl *ssl) {
   ssl->config->compliance_policy = ssl_compliance_policy_fips_202205;
 
   // See `Configure(SSL_CTX)`, above, for reasoning.
@@ -3513,7 +3623,7 @@
          SSL_CTX_set_verify_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs));
 }
 
-static int Configure(SSL *ssl) {
+static int Configure(SSLImpl *ssl) {
   ssl->config->compliance_policy = ssl_compliance_policy_wpa3_192_202304;
 
   return SSL_set_min_proto_version(ssl, TLS1_2_VERSION) &&
@@ -3533,7 +3643,7 @@
   return 1;
 }
 
-static int Configure(SSL *ssl) {
+static int Configure(SSLImpl *ssl) {
   ssl->config->compliance_policy = ssl_compliance_policy_cnsa_202407;
   return 1;
 }
@@ -3573,7 +3683,7 @@
          SSL_CTX_set_verify_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs));
 }
 
-static int Configure(SSL *ssl) {
+static int Configure(SSLImpl *ssl) {
   ssl->config->compliance_policy = ssl_compliance_policy_cnsa1_202603;
 
   return SSL_set_min_proto_version(ssl, TLS1_2_VERSION) &&
@@ -3610,7 +3720,7 @@
          SSL_CTX_set_verify_algorithm_prefs(ctx, kSigAlgs, std::size(kSigAlgs));
 }
 
-static int Configure(SSL *ssl) {
+static int Configure(SSLImpl *ssl) {
   ssl->config->compliance_policy = ssl_compliance_policy_cnsa2_202603;
 
   return SSL_set_min_proto_version(ssl, TLS1_3_VERSION) &&
@@ -3646,34 +3756,36 @@
 }
 
 int SSL_set_compliance_policy(SSL *ssl, enum ssl_compliance_policy_t policy) {
+  auto *ssl_impl = FromOpaque(ssl);
   switch (policy) {
     case ssl_compliance_policy_fips_202205:
-      return fips202205::Configure(ssl);
+      return fips202205::Configure(ssl_impl);
     case ssl_compliance_policy_wpa3_192_202304:
-      return wpa202304::Configure(ssl);
+      return wpa202304::Configure(ssl_impl);
     case ssl_compliance_policy_cnsa_202407:
-      return cnsa202407::Configure(ssl);
+      return cnsa202407::Configure(ssl_impl);
     case ssl_compliance_policy_cnsa1_202603:
-      return cnsa1_202603::Configure(ssl);
+      return cnsa1_202603::Configure(ssl_impl);
     case ssl_compliance_policy_cnsa2_202603:
-      return cnsa2_202603::Configure(ssl);
+      return cnsa2_202603::Configure(ssl_impl);
     default:
       return 0;
   }
 }
 
 enum ssl_compliance_policy_t SSL_get_compliance_policy(const SSL *ssl) {
-  return ssl->config->compliance_policy;
+  return FromOpaque(ssl)->config->compliance_policy;
 }
 
 int SSL_peer_matched_trust_anchor(const SSL *ssl) {
-  return ssl->s3->hs != nullptr && ssl->s3->hs->peer_matched_trust_anchor;
+  return FromOpaque(ssl)->s3->hs != nullptr &&
+         FromOpaque(ssl)->s3->hs->peer_matched_trust_anchor;
 }
 
 void SSL_get0_peer_available_trust_anchors(const SSL *ssl, const uint8_t **out,
                                            size_t *out_len) {
   Span<const uint8_t> ret;
-  if (SSL_HANDSHAKE *hs = ssl->s3->hs.get(); hs != nullptr) {
+  if (SSL_HANDSHAKE *hs = FromOpaque(ssl)->s3->hs.get(); hs != nullptr) {
     ret = hs->peer_available_trust_anchors;
   }
   *out = ret.data();
@@ -3692,7 +3804,7 @@
 
 int SSL_set1_available_trust_anchors(SSL *ssl, const uint8_t *ids,
                                      size_t ids_len) {
-  if (!ssl->config) {
+  if (!FromOpaque(ssl)->config) {
     return 0;
   }
   auto span = Span(ids, ids_len);
@@ -3700,7 +3812,7 @@
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_TRUST_ANCHOR_LIST);
     return 0;
   }
-  return ssl->config->cert->available_trust_anchors.CopyFrom(span);
+  return FromOpaque(ssl)->config->cert->available_trust_anchors.CopyFrom(span);
 }
 
 int SSL_CTX_set1_requested_trust_anchors(SSL_CTX *ctx, const uint8_t *ids,
@@ -3720,7 +3832,7 @@
 
 int SSL_set1_requested_trust_anchors(SSL *ssl, const uint8_t *ids,
                                      size_t ids_len) {
-  if (!ssl->config) {
+  if (!FromOpaque(ssl)->config) {
     return 0;
   }
   auto span = Span(ids, ids_len);
@@ -3732,7 +3844,7 @@
   if (!copy.CopyFrom(span)) {
     return 0;
   }
-  ssl->config->requested_trust_anchors = std::move(copy);
+  FromOpaque(ssl)->config->requested_trust_anchors = std::move(copy);
   return 1;
 }
 
@@ -3776,10 +3888,10 @@
 
 int SSL_set1_accepted_peer_cert_types(SSL *ssl, const uint8_t *values,
                                       size_t num_values) {
-  if (!ssl->config) {
+  if (!FromOpaque(ssl)->config) {
     return 0;
   }
-  return set1_cert_types(&ssl->config->accepted_peer_cert_types,
+  return set1_cert_types(&FromOpaque(ssl)->config->accepted_peer_cert_types,
                          Span(values, num_values));
 }
 
@@ -3792,10 +3904,10 @@
 
 int SSL_set1_available_client_cert_types(SSL *ssl, const uint8_t *values,
                                          size_t num_values) {
-  if (!ssl->config) {
+  if (!FromOpaque(ssl)->config) {
     return 0;
   }
-  return set1_cert_types(&ssl->config->available_client_cert_types,
+  return set1_cert_types(&FromOpaque(ssl)->config->available_client_cert_types,
                          Span(values, num_values));
 }
 
@@ -3814,21 +3926,21 @@
 }
 
 void SSL_set_server_padding_request(SSL *ssl, uint16_t num_bytes) {
-  if (!ssl->config) {
+  if (!FromOpaque(ssl)->config) {
     return;
   }
 
-  ssl->config->server_padding_request = num_bytes;
+  FromOpaque(ssl)->config->server_padding_request = num_bytes;
 }
 
 void SSL_set_server_padding_enabled(SSL *ssl, int enabled) {
-  if (!ssl->config) {
+  if (!FromOpaque(ssl)->config) {
     return;
   }
 
-  ssl->config->server_padding_enabled = enabled;
+  FromOpaque(ssl)->config->server_padding_enabled = enabled;
 }
 
 int SSL_server_sent_requested_padding(const SSL *ssl) {
-  return ssl->s3->server_sent_requested_padding;
+  return FromOpaque(ssl)->s3->server_sent_requested_padding;
 }
diff --git a/ssl/ssl_privkey.cc b/ssl/ssl_privkey.cc
index 4e43717..ae7c9bb 100644
--- a/ssl/ssl_privkey.cc
+++ b/ssl/ssl_privkey.cc
@@ -142,7 +142,7 @@
       spki.data(), spki.size(), algs, std::size(algs)));
 }
 
-bool ssl_pkey_supports_algorithm(const SSL *ssl, EVP_PKEY *pkey,
+bool ssl_pkey_supports_algorithm(const SSLImpl *ssl, EVP_PKEY *pkey,
                                  uint16_t sigalg, bool is_verify) {
   const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
   if (alg == nullptr || EVP_PKEY_id(pkey) != alg->pkey_type) {
@@ -197,7 +197,7 @@
   return true;
 }
 
-static bool setup_ctx(SSL *ssl, EVP_MD_CTX *ctx, EVP_PKEY *pkey,
+static bool setup_ctx(SSLImpl *ssl, EVP_MD_CTX *ctx, EVP_PKEY *pkey,
                       uint16_t sigalg, bool is_verify) {
   if (!ssl_pkey_supports_algorithm(ssl, pkey, sigalg, is_verify)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SIGNATURE_TYPE);
@@ -229,7 +229,7 @@
 enum ssl_private_key_result_t ssl_private_key_sign(
     SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, size_t max_out,
     uint16_t sigalg, Span<const uint8_t> in) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   const SSLCredential *const cred = hs->credential.get();
   Array<uint8_t> spki;
   if (hs->provided_hints != nullptr || hs->pending_hints != nullptr) {
@@ -304,7 +304,7 @@
   return ssl_private_key_success;
 }
 
-bool ssl_public_key_verify(SSL *ssl, Span<const uint8_t> signature,
+bool ssl_public_key_verify(SSLImpl *ssl, Span<const uint8_t> signature,
                            uint16_t sigalg, EVP_PKEY *pkey,
                            Span<const uint8_t> in) {
   ScopedEVP_MD_CTX ctx;
@@ -325,7 +325,7 @@
                                                       size_t *out_len,
                                                       size_t max_out,
                                                       Span<const uint8_t> in) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   const SSLCredential *const cred = hs->credential.get();
   assert(!hs->can_release_private_key);
   if (cred->key_method != nullptr) {
@@ -372,7 +372,7 @@
 using namespace bssl;
 
 int SSL_use_RSAPrivateKey(SSL *ssl, RSA *rsa) {
-  if (rsa == nullptr || ssl->config == nullptr) {
+  if (rsa == nullptr || FromOpaque(ssl)->config == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER);
     return 0;
   }
@@ -398,13 +398,14 @@
 }
 
 int SSL_use_PrivateKey(SSL *ssl, EVP_PKEY *pkey) {
-  if (pkey == nullptr || ssl->config == nullptr) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (pkey == nullptr || ssl_impl->config == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER);
     return 0;
   }
 
   return SSL_CREDENTIAL_set1_private_key(
-      ssl->config->cert->legacy_credential.get(), pkey);
+      ssl_impl->config->cert->legacy_credential.get(), pkey);
 }
 
 int SSL_use_PrivateKey_ASN1(int type, SSL *ssl, const uint8_t *der,
@@ -479,11 +480,12 @@
 
 void SSL_set_private_key_method(SSL *ssl,
                                 const SSL_PRIVATE_KEY_METHOD *key_method) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return;
   }
   BSSL_CHECK(SSL_CREDENTIAL_set_private_key_method(
-      ssl->config->cert->legacy_credential.get(), key_method));
+      ssl_impl->config->cert->legacy_credential.get(), key_method));
 }
 
 void SSL_CTX_set_private_key_method(SSL_CTX *ctx,
@@ -659,11 +661,12 @@
 
 int SSL_set_signing_algorithm_prefs(SSL *ssl, const uint16_t *prefs,
                                     size_t num_prefs) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
   return SSL_CREDENTIAL_set1_signing_algorithm_prefs(
-      ssl->config->cert->legacy_credential.get(), prefs, num_prefs);
+      ssl_impl->config->cert->legacy_credential.get(), prefs, num_prefs);
 }
 
 static constexpr struct {
@@ -739,7 +742,8 @@
 }
 
 int SSL_set1_sigalgs(SSL *ssl, const int *values, size_t num_values) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
@@ -749,8 +753,10 @@
     return 0;
   }
 
-  if (!SSL_set_signing_algorithm_prefs(ssl, sigalgs.data(), sigalgs.size()) ||
-      !SSL_set_verify_algorithm_prefs(ssl, sigalgs.data(), sigalgs.size())) {
+  if (!SSL_set_signing_algorithm_prefs(ssl_impl, sigalgs.data(),
+                                       sigalgs.size()) ||
+      !SSL_set_verify_algorithm_prefs(ssl_impl, sigalgs.data(),
+                                      sigalgs.size())) {
     return 0;
   }
 
@@ -927,7 +933,8 @@
 }
 
 int SSL_set1_sigalgs_list(SSL *ssl, const char *str) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
@@ -937,8 +944,10 @@
     return 0;
   }
 
-  if (!SSL_set_signing_algorithm_prefs(ssl, sigalgs.data(), sigalgs.size()) ||
-      !SSL_set_verify_algorithm_prefs(ssl, sigalgs.data(), sigalgs.size())) {
+  if (!SSL_set_signing_algorithm_prefs(ssl_impl, sigalgs.data(),
+                                       sigalgs.size()) ||
+      !SSL_set_verify_algorithm_prefs(ssl_impl, sigalgs.data(),
+                                      sigalgs.size())) {
     return 0;
   }
 
@@ -953,10 +962,12 @@
 
 int SSL_set_verify_algorithm_prefs(SSL *ssl, const uint16_t *prefs,
                                    size_t num_prefs) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
 
-  return set_sigalg_prefs(&ssl->config->verify_sigalgs, Span(prefs, num_prefs));
+  return set_sigalg_prefs(&ssl_impl->config->verify_sigalgs,
+                          Span(prefs, num_prefs));
 }
diff --git a/ssl/ssl_session.cc b/ssl/ssl_session.cc
index 6c94441..4be8b58 100644
--- a/ssl/ssl_session.cc
+++ b/ssl/ssl_session.cc
@@ -157,7 +157,7 @@
   return new_session;
 }
 
-void ssl_session_rebase_time(SSL *ssl, SSL_SESSION *session) {
+void ssl_session_rebase_time(SSLImpl *ssl, SSL_SESSION *session) {
   OPENSSL_timeval now = ssl_ctx_get_current_time(ssl->ctx.get());
 
   // To avoid overflows and underflows, if we've gone back in time, update the
@@ -185,7 +185,7 @@
   }
 }
 
-void ssl_session_renew_timeout(SSL *ssl, SSL_SESSION *session,
+void ssl_session_renew_timeout(SSLImpl *ssl, SSL_SESSION *session,
                                uint32_t timeout) {
   // Rebase the timestamp relative to the current time so `timeout` is measured
   // correctly.
@@ -219,7 +219,7 @@
 }
 
 bool ssl_get_new_session(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl->mode & SSL_MODE_NO_SESSION_CREATION) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_SESSION_MAY_NOT_BE_CREATED);
     return false;
@@ -401,7 +401,7 @@
 static int ssl_encrypt_ticket_with_method(SSL_HANDSHAKE *hs, CBB *out,
                                           const uint8_t *session_buf,
                                           size_t session_len) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   const SSL_TICKET_AEAD_METHOD *method = ssl->session_ctx->ticket_aead_method;
   const size_t max_overhead = method->max_overhead(ssl);
   const size_t max_out = session_len + max_overhead;
@@ -469,7 +469,7 @@
          Span(session->sid_ctx) == hs->config->cert->sid_ctx;
 }
 
-bool ssl_session_is_time_valid(const SSL *ssl, const SSL_SESSION *session) {
+bool ssl_session_is_time_valid(const SSLImpl *ssl, const SSL_SESSION *session) {
   if (session == nullptr) {
     return false;
   }
@@ -486,7 +486,7 @@
 
 bool ssl_session_is_resumable(const SSL_HANDSHAKE *hs,
                               const SSL_SESSION *session) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   return ssl_session_is_context_valid(hs, session) &&
          // The session must have been created by the same type of end point as
          // we're now using it with.
@@ -518,7 +518,7 @@
 static enum ssl_hs_wait_t ssl_lookup_session(
     SSL_HANDSHAKE *hs, UniquePtr<SSL_SESSION> *out_session,
     Span<const uint8_t> session_id) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   out_session->reset();
 
   if (session_id.empty() || session_id.size() > SSL_MAX_SSL_SESSION_ID_LENGTH) {
@@ -660,7 +660,7 @@
   return found;
 }
 
-void ssl_set_session(SSL *ssl, SSL_SESSION *session) {
+void ssl_set_session(SSLImpl *ssl, SSL_SESSION *session) {
   if (ssl->session.get() == session) {
     return;
   }
@@ -767,7 +767,7 @@
   return true;
 }
 
-void ssl_update_cache(SSL *ssl) {
+void ssl_update_cache(SSLImpl *ssl) {
   SSLContext *ctx = ssl->session_ctx.get();
   SSL_SESSION *session = ssl->s3->established_session.get();
   int mode = SSL_is_server(ssl) ? SSL_SESS_CACHE_SERVER : SSL_SESS_CACHE_CLIENT;
@@ -1055,6 +1055,7 @@
 }
 
 SSL_SESSION *SSL_get_session(const SSL *ssl) {
+  const auto *ssl_impl = FromOpaque(ssl);
   // Once the initially handshake completes, we return the most recently
   // established session. In particular, if there is a pending renegotiation, we
   // do not return information about it until it completes.
@@ -1062,14 +1063,14 @@
   // Code in the handshake must either use `hs->new_session` (if updating a
   // partial session) or `ssl_handshake_session` (if trying to query properties
   // consistently across TLS 1.2 resumption and other handshakes).
-  if (ssl->s3->established_session != nullptr) {
-    return ssl->s3->established_session.get();
+  if (ssl_impl->s3->established_session != nullptr) {
+    return ssl_impl->s3->established_session.get();
   }
 
   // Otherwise, we must be in the initial handshake.
-  SSL_HANDSHAKE *hs = ssl->s3->hs.get();
+  SSL_HANDSHAKE *hs = ssl_impl->s3->hs.get();
   assert(hs != nullptr);
-  assert(!ssl->s3->initial_handshake_complete);
+  assert(!ssl_impl->s3->initial_handshake_complete);
 
   // Return the 0-RTT session, if in the 0-RTT state. While the handshake has
   // not actually completed, the public accessors all report properties as if
@@ -1117,14 +1118,15 @@
 }
 
 int SSL_set_session(SSL *ssl, SSL_SESSION *session) {
+  auto *ssl_impl = FromOpaque(ssl);
   // SSL_set_session may only be called before the handshake has started.
-  if (ssl->s3->initial_handshake_complete ||  //
-      ssl->s3->hs == nullptr ||               //
-      ssl->s3->hs->state != 0) {
+  if (ssl_impl->s3->initial_handshake_complete ||  //
+      ssl_impl->s3->hs == nullptr ||               //
+      ssl_impl->s3->hs->state != 0) {
     abort();
   }
 
-  ssl_set_session(ssl, session);
+  ssl_set_session(ssl_impl, session);
   return 1;
 }
 
@@ -1228,7 +1230,7 @@
 }
 
 void SSL_set_resumption_across_names_enabled(SSL *ssl, int enabled) {
-  ssl->resumption_across_names_enabled = !!enabled;
+  FromOpaque(ssl)->resumption_across_names_enabled = !!enabled;
 }
 
 void SSL_CTX_set_info_callback(SSL_CTX *ctx, void (*cb)(const SSL *ssl,
diff --git a/ssl/ssl_stat.cc b/ssl/ssl_stat.cc
index 6201df9..84d26cc 100644
--- a/ssl/ssl_stat.cc
+++ b/ssl/ssl_stat.cc
@@ -20,13 +20,16 @@
 #include "internal.h"
 
 
+using namespace bssl;
+
 const char *SSL_state_string_long(const SSL *ssl) {
-  if (ssl->s3->hs == nullptr) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (ssl_impl->s3->hs == nullptr) {
     return "SSL negotiation finished successfully";
   }
 
-  return ssl->server ? ssl_server_handshake_state(ssl->s3->hs.get())
-                     : ssl_client_handshake_state(ssl->s3->hs.get());
+  return ssl_impl->server ? ssl_server_handshake_state(ssl_impl->s3->hs.get())
+                          : ssl_client_handshake_state(ssl_impl->s3->hs.get());
 }
 
 const char *SSL_state_string(const SSL *ssl) { return "!!!!!!"; }
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index 9c8101c..f9ba6a9 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -704,12 +704,13 @@
 
     bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
     ASSERT_TRUE(ssl);
-    EXPECT_THAT(ssl->config->supported_group_list,
+    EXPECT_THAT(FromOpaque(ssl.get())->config->supported_group_list,
                 Not(ElementsAreArray(kDefaults)));
 
     // Setting an empty list restores the defaults.
     ASSERT_TRUE(SSL_set1_group_ids(ssl.get(), nullptr, 0));
-    EXPECT_THAT(ssl->config->supported_group_list, ElementsAreArray(kDefaults));
+    EXPECT_THAT(FromOpaque(ssl.get())->config->supported_group_list,
+                ElementsAreArray(kDefaults));
     ASSERT_TRUE(SSL_CTX_set1_group_ids(ctx.get(), nullptr, 0));
     EXPECT_THAT(FromOpaque(ctx.get())->supported_group_list,
                 ElementsAreArray(kDefaults));
@@ -732,12 +733,13 @@
 
     bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
     ASSERT_TRUE(ssl);
-    EXPECT_THAT(ssl->config->supported_group_list,
+    EXPECT_THAT(FromOpaque(ssl.get())->config->supported_group_list,
                 Not(ElementsAreArray(kDefaults)));
 
     // Setting an empty list restores the defaults.
     ASSERT_TRUE(SSL_set1_groups(ssl.get(), nullptr, 0));
-    EXPECT_THAT(ssl->config->supported_group_list, ElementsAreArray(kDefaults));
+    EXPECT_THAT(FromOpaque(ssl.get())->config->supported_group_list,
+                ElementsAreArray(kDefaults));
     ASSERT_TRUE(SSL_CTX_set1_groups(ctx.get(), nullptr, 0));
     EXPECT_THAT(FromOpaque(ctx.get())->supported_group_list,
                 ElementsAreArray(kDefaults));
@@ -832,7 +834,8 @@
     ASSERT_TRUE(ctx);
     bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
     ASSERT_TRUE(ssl);
-    ASSERT_FALSE(ssl->config->client_key_share_selections.has_value());
+    ASSERT_FALSE(
+        FromOpaque(ssl.get())->config->client_key_share_selections.has_value());
 
     ASSERT_TRUE(SSL_set1_group_ids(ssl.get(), t.supported_groups.data(),
                                    t.supported_groups.size()));
@@ -840,9 +843,11 @@
                                          t.key_shares.size()),
               t.expected_success);
     if (t.expected_success) {
-      ASSERT_TRUE(ssl->config->client_key_share_selections.has_value());
-      EXPECT_THAT(ssl->config->client_key_share_selections.value(),
-                  ElementsAreArray(t.key_shares));
+      ASSERT_TRUE(FromOpaque(ssl.get())
+                      ->config->client_key_share_selections.has_value());
+      EXPECT_THAT(
+          FromOpaque(ssl.get())->config->client_key_share_selections.value(),
+          ElementsAreArray(t.key_shares));
     }
   }
 }
@@ -855,7 +860,8 @@
   ASSERT_TRUE(ctx);
   bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
   ASSERT_TRUE(ssl);
-  ASSERT_FALSE(ssl->config->client_key_share_selections.has_value());
+  ASSERT_FALSE(
+      FromOpaque(ssl.get())->config->client_key_share_selections.has_value());
 
   // An initial groups list and key shares that are compatible.
   const uint16_t kGroups1[] = {SSL_GROUP_X25519_MLKEM768, SSL_GROUP_X25519};
@@ -863,22 +869,27 @@
   ASSERT_TRUE(SSL_set1_group_ids(ssl.get(), kGroups1, std::size(kGroups1)));
   ASSERT_TRUE(
       SSL_set1_client_key_shares(ssl.get(), kKeyShares, std::size(kKeyShares)));
-  ASSERT_TRUE(ssl->config->client_key_share_selections.has_value());
-  EXPECT_EQ(ssl->config->client_key_share_selections->size(), 2u);
+  ASSERT_TRUE(
+      FromOpaque(ssl.get())->config->client_key_share_selections.has_value());
+  EXPECT_EQ(FromOpaque(ssl.get())->config->client_key_share_selections->size(),
+            2u);
 
   // A new groups list that is still compatible with the previously set key
   // shares.
   const uint16_t kGroups2[] = {SSL_GROUP_MLKEM1024, SSL_GROUP_X25519_MLKEM768,
                                SSL_GROUP_X25519};
   ASSERT_TRUE(SSL_set1_group_ids(ssl.get(), kGroups2, std::size(kGroups2)));
-  ASSERT_TRUE(ssl->config->client_key_share_selections.has_value());
-  EXPECT_EQ(ssl->config->client_key_share_selections->size(), 2u);
+  ASSERT_TRUE(
+      FromOpaque(ssl.get())->config->client_key_share_selections.has_value());
+  EXPECT_EQ(FromOpaque(ssl.get())->config->client_key_share_selections->size(),
+            2u);
 
   // A new groups list that is no longer compatible with the previously set key
   // shares.
   const uint16_t kGroups3[] = {SSL_GROUP_MLKEM1024, SSL_GROUP_X25519};
   ASSERT_TRUE(SSL_set1_group_ids(ssl.get(), kGroups3, std::size(kGroups3)));
-  EXPECT_FALSE(ssl->config->client_key_share_selections.has_value());
+  EXPECT_FALSE(
+      FromOpaque(ssl.get())->config->client_key_share_selections.has_value());
 }
 
 TEST(SSLTest, ServerSupportedGroupsHint) {
@@ -957,7 +968,7 @@
     ASSERT_TRUE(SSL_connect(ssl.get()));
 
     std::vector<uint16_t> key_shares;
-    for (const auto &key_share : ssl->s3->hs->key_shares) {
+    for (const auto &key_share : FromOpaque(ssl.get())->s3->hs->key_shares) {
       key_shares.push_back(key_share->GroupID());
     }
     EXPECT_THAT(key_shares, ElementsAreArray(t.expected_key_shares));
@@ -976,19 +987,22 @@
   const uint16_t kKeyShares[] = {SSL_GROUP_SECP256R1};
   ASSERT_TRUE(
       SSL_set1_client_key_shares(ssl.get(), kKeyShares, std::size(kKeyShares)));
-  ASSERT_TRUE(ssl->config->client_key_share_selections.has_value());
-  EXPECT_THAT(ssl->config->client_key_share_selections.value(),
-              ElementsAreArray(kKeyShares));
+  ASSERT_TRUE(
+      FromOpaque(ssl.get())->config->client_key_share_selections.has_value());
+  EXPECT_THAT(
+      FromOpaque(ssl.get())->config->client_key_share_selections.value(),
+      ElementsAreArray(kKeyShares));
   const uint16_t kServerHint[] = {SSL_GROUP_X25519};
   ASSERT_TRUE(SSL_set1_server_supported_groups_hint(ssl.get(), kServerHint,
                                                     std::size(kServerHint)));
-  EXPECT_THAT(ssl->config->server_supported_groups_hint,
+  EXPECT_THAT(FromOpaque(ssl.get())->config->server_supported_groups_hint,
               ElementsAreArray(kServerHint));
 
   // The group predicted based on the server hint should win.
   ASSERT_TRUE(SSL_connect(ssl.get()));
-  ASSERT_EQ(ssl->s3->hs->key_shares.size(), 1u);
-  EXPECT_EQ(kServerHint[0], ssl->s3->hs->key_shares[0]->GroupID());
+  ASSERT_EQ(FromOpaque(ssl.get())->s3->hs->key_shares.size(), 1u);
+  EXPECT_EQ(kServerHint[0],
+            FromOpaque(ssl.get())->s3->hs->key_shares[0]->GroupID());
 }
 
 TEST(SSLTest, ServerHintOverridesEmptyClientKeyShareSelections) {
@@ -1001,17 +1015,19 @@
   ASSERT_TRUE(SSL_set1_group_ids(ssl.get(), kGroups, std::size(kGroups)));
 
   ASSERT_TRUE(SSL_set1_client_key_shares(ssl.get(), nullptr, 0));
-  EXPECT_TRUE(ssl->config->client_key_share_selections->empty());
+  EXPECT_TRUE(
+      FromOpaque(ssl.get())->config->client_key_share_selections->empty());
   const uint16_t kServerHint[] = {SSL_GROUP_X25519};
   ASSERT_TRUE(SSL_set1_server_supported_groups_hint(ssl.get(), kServerHint,
                                                     std::size(kServerHint)));
-  EXPECT_THAT(ssl->config->server_supported_groups_hint,
+  EXPECT_THAT(FromOpaque(ssl.get())->config->server_supported_groups_hint,
               ElementsAreArray(kServerHint));
 
   // The group predicted based on the server hint should win.
   ASSERT_TRUE(SSL_connect(ssl.get()));
-  ASSERT_EQ(ssl->s3->hs->key_shares.size(), 1u);
-  EXPECT_EQ(kServerHint[0], ssl->s3->hs->key_shares[0]->GroupID());
+  ASSERT_EQ(FromOpaque(ssl.get())->s3->hs->key_shares.size(), 1u);
+  EXPECT_EQ(kServerHint[0],
+            FromOpaque(ssl.get())->s3->hs->key_shares[0]->GroupID());
 }
 
 // kOpenSSLSession is a serialized SSL_SESSION.
@@ -2341,7 +2357,7 @@
   // Should set the default groups, and corresponding default (zero) flags.
   EXPECT_TRUE(
       SSL_set1_group_ids_with_flags(server.get(), nullptr, kBogusFlags, 0));
-  EXPECT_THAT(server->config->supported_group_list,
+  EXPECT_THAT(FromOpaque(server.get())->config->supported_group_list,
               ElementsAreArray(kDefaultGroups));
 
   // Set up and run the handshake to show that the bogus "equal preference with
@@ -7192,12 +7208,13 @@
   };
 
   // The default list of groups is used before applying the handoff.
-  EXPECT_THAT(server->config->supported_group_list,
+  EXPECT_THAT(FromOpaque(server.get())->config->supported_group_list,
               ElementsAreArray({SSL_GROUP_X25519, SSL_GROUP_SECP256R1,
                                 SSL_GROUP_SECP384R1}));
   ASSERT_TRUE(SSL_apply_handoff(server.get(), handoff));
-  EXPECT_EQ(1u, server->config->supported_group_list.size());
-  EXPECT_EQ(SSL_GROUP_SECP256R1, server->config->supported_group_list[0]);
+  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) {
diff --git a/ssl/ssl_versions.cc b/ssl/ssl_versions.cc
index b5d8f6b..f8d9a9c 100644
--- a/ssl/ssl_versions.cc
+++ b/ssl/ssl_versions.cc
@@ -243,7 +243,7 @@
   return true;
 }
 
-static uint16_t ssl_version(const SSL *ssl) {
+static uint16_t ssl_version(const SSLImpl *ssl) {
   // In early data, we report the predicted version. Note it is possible that we
   // have a predicted version and a *different* true version. This means 0-RTT
   // has been rejected, but until the reject has reported to the application and
@@ -260,12 +260,12 @@
   return SSL_is_dtls(ssl) ? DTLS1_2_VERSION : TLS1_2_VERSION;
 }
 
-bool ssl_has_final_version(const SSL *ssl) {
+bool ssl_has_final_version(const SSLImpl *ssl) {
   return ssl->s3->version != 0 &&
          (ssl->s3->hs == nullptr || !ssl->s3->hs->is_early_version);
 }
 
-uint16_t ssl_protocol_version(const SSL *ssl) {
+uint16_t ssl_protocol_version(const SSLImpl *ssl) {
   assert(ssl->s3->version != 0);
   uint16_t version;
   if (!ssl_protocol_version_from_wire(&version, ssl->s3->version)) {
@@ -278,7 +278,7 @@
 }
 
 bool ssl_supports_version(const SSL_HANDSHAKE *hs, uint16_t version) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   uint16_t protocol_version;
   if (!ssl_method_supports_version(ssl->method, version) ||
       !ssl_protocol_version_from_wire(&protocol_version, version) ||
@@ -369,41 +369,47 @@
 }
 
 int SSL_set_min_proto_version(SSL *ssl, uint16_t version) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return set_min_version(ssl->method, &ssl->config->conf_min_version, version);
+  return set_min_version(ssl_impl->method, &ssl_impl->config->conf_min_version,
+                         version);
 }
 
 int SSL_set_max_proto_version(SSL *ssl, uint16_t version) {
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return set_max_version(ssl->method, &ssl->config->conf_max_version, version);
+  return set_max_version(ssl_impl->method, &ssl_impl->config->conf_max_version,
+                         version);
 }
 
 uint16_t SSL_get_min_proto_version(const SSL *ssl) {
-  if (!ssl->config) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     return 0;
   }
-  return ssl->config->conf_min_version;
+  return ssl_impl->config->conf_min_version;
 }
 
 uint16_t SSL_get_max_proto_version(const SSL *ssl) {
-  if (!ssl->config) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     return 0;
   }
-  return ssl->config->conf_max_version;
+  return ssl_impl->config->conf_max_version;
 }
 
 int SSL_version(const SSL *ssl) {
-  return wire_version_to_api(ssl_version(ssl));
+  return wire_version_to_api(ssl_version(FromOpaque(ssl)));
 }
 
 const char *SSL_get_version(const SSL *ssl) {
-  return ssl_version_to_string(ssl_version(ssl));
+  return ssl_version_to_string(ssl_version(FromOpaque(ssl)));
 }
 
 size_t SSL_get_all_version_names(const char **out, size_t max_out) {
diff --git a/ssl/ssl_x509.cc b/ssl/ssl_x509.cc
index baa7037..31b401f 100644
--- a/ssl/ssl_x509.cc
+++ b/ssl/ssl_x509.cc
@@ -36,7 +36,7 @@
 // check_ssl_x509_method asserts that `ssl` has the X509-based method
 // installed. Calling an X509-based method on an `ssl` with a different method
 // will likely misbehave and possibly crash or leak memory.
-static void check_ssl_x509_method(const SSL *ssl) {
+static void check_ssl_x509_method(const SSLImpl *ssl) {
   assert(ssl == nullptr || ssl->ctx->x509_method == &ssl_crypto_x509_method);
 }
 
@@ -211,7 +211,7 @@
     return false;
   }
 
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLContext *ssl_ctx = ssl->ctx.get();
   X509_STORE *verify_store = ssl_ctx->cert_store;
   if (hs->config->cert->verify_store != nullptr) {
@@ -296,7 +296,7 @@
 static bool ssl_crypto_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) {
   // Only build a chain if the feature isn't disabled, the legacy credential
   // exists but has no intermediates configured.
-  SSL *ssl = hs->ssl;
+  SSLImpl *ssl = hs->ssl;
   SSLCredential *cred = hs->config->cert->legacy_credential.get();
   if ((ssl->mode & SSL_MODE_NO_AUTO_CHAIN) || !cred->IsComplete() ||
       sk_CRYPTO_BUFFER_num(cred->chain.get()) != 1) {
@@ -374,11 +374,12 @@
 using namespace bssl;
 
 X509 *SSL_get_peer_certificate(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  if (ssl == nullptr) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (ssl_impl == nullptr) {
     return nullptr;
   }
-  SSL_SESSION *session = SSL_get_session(ssl);
+  SSL_SESSION *session = SSL_get_session(ssl_impl);
   if (session == nullptr || session->x509_peer == nullptr) {
     return nullptr;
   }
@@ -387,23 +388,26 @@
 }
 
 STACK_OF(X509) *SSL_get_peer_cert_chain(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  if (ssl == nullptr) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (ssl_impl == nullptr) {
     return nullptr;
   }
-  SSL_SESSION *session = SSL_get_session(ssl);
+  SSL_SESSION *session = SSL_get_session(ssl_impl);
   if (session == nullptr) {
     return nullptr;
   }
 
   // OpenSSL historically didn't include the leaf certificate in the returned
   // certificate chain, but only for servers.
-  return ssl->server ? session->x509_chain_without_leaf : session->x509_chain;
+  return ssl_impl->server ? session->x509_chain_without_leaf
+                          : session->x509_chain;
 }
 
 STACK_OF(X509) *SSL_get_peer_full_cert_chain(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  SSL_SESSION *session = SSL_get_session(ssl);
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  SSL_SESSION *session = SSL_get_session(ssl_impl);
   if (session == nullptr) {
     return nullptr;
   }
@@ -418,11 +422,12 @@
 }
 
 int SSL_set_purpose(SSL *ssl, int purpose) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return X509_VERIFY_PARAM_set_purpose(ssl->config->param, purpose);
+  return X509_VERIFY_PARAM_set_purpose(ssl_impl->config->param, purpose);
 }
 
 int SSL_CTX_set_trust(SSL_CTX *ctx, int trust) {
@@ -432,11 +437,12 @@
 }
 
 int SSL_set_trust(SSL *ssl, int trust) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return X509_VERIFY_PARAM_set_trust(ssl->config->param, trust);
+  return X509_VERIFY_PARAM_set_trust(ssl_impl->config->param, trust);
 }
 
 int SSL_CTX_set1_param(SSL_CTX *ctx, const X509_VERIFY_PARAM *param) {
@@ -446,11 +452,12 @@
 }
 
 int SSL_set1_param(SSL *ssl, const X509_VERIFY_PARAM *param) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return X509_VERIFY_PARAM_set1(ssl->config->param, param);
+  return X509_VERIFY_PARAM_set1(ssl_impl->config->param, param);
 }
 
 X509_VERIFY_PARAM *SSL_CTX_get0_param(SSL_CTX *ctx) {
@@ -460,30 +467,33 @@
 }
 
 X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
-    assert(ssl->config);
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     return nullptr;
   }
-  return ssl->config->param;
+  return ssl_impl->config->param;
 }
 
 int SSL_get_verify_depth(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     return 0;
   }
-  return X509_VERIFY_PARAM_get_depth(ssl->config->param);
+  return X509_VERIFY_PARAM_get_depth(ssl_impl->config->param);
 }
 
 int (*SSL_get_verify_callback(const SSL *ssl))(int, X509_STORE_CTX *) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     return nullptr;
   }
-  return ssl->config->verify_callback;
+  return ssl_impl->config->verify_callback;
 }
 
 int SSL_CTX_get_verify_mode(const SSL_CTX *ctx) {
@@ -507,22 +517,24 @@
 
 void SSL_set_verify(SSL *ssl, int mode,
                     int (*callback)(int ok, X509_STORE_CTX *store_ctx)) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->config->verify_mode = mode;
+  ssl_impl->config->verify_mode = mode;
   if (callback != nullptr) {
-    ssl->config->verify_callback = callback;
+    ssl_impl->config->verify_callback = callback;
   }
 }
 
 void SSL_set_verify_depth(SSL *ssl, int depth) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return;
   }
-  X509_VERIFY_PARAM_set_depth(ssl->config->param, depth);
+  X509_VERIFY_PARAM_set_depth(ssl_impl->config->param, depth);
 }
 
 void SSL_CTX_set_cert_verify_callback(
@@ -561,8 +573,9 @@
 }
 
 long SSL_get_verify_result(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  SSL_SESSION *session = SSL_get_session(ssl);
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  SSL_SESSION *session = SSL_get_session(ssl_impl);
   if (session == nullptr) {
     return X509_V_ERR_INVALID_CALL;
   }
@@ -597,11 +610,12 @@
 }
 
 int SSL_use_certificate(SSL *ssl, X509 *x) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return ssl_use_certificate(ssl->config->cert.get(), x);
+  return ssl_use_certificate(ssl_impl->config->cert.get(), x);
 }
 
 int SSL_CTX_use_certificate(SSL_CTX *ctx, X509 *x) {
@@ -639,12 +653,13 @@
 }
 
 X509 *SSL_get_certificate(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     return nullptr;
   }
-  return ssl_cert_get0_leaf(ssl->config->cert.get());
+  return ssl_cert_get0_leaf(ssl_impl->config->cert.get());
 }
 
 X509 *SSL_CTX_get0_certificate(const SSL_CTX *ctx) {
@@ -694,11 +709,12 @@
 }
 
 int SSL_set0_chain(SSL *ssl, STACK_OF(X509) *chain) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  if (!ssl_cert_set1_chain(ssl->config->cert.get(), chain)) {
+  if (!ssl_cert_set1_chain(ssl_impl->config->cert.get(), chain)) {
     return 0;
   }
   sk_X509_pop_free(chain, X509_free);
@@ -706,11 +722,12 @@
 }
 
 int SSL_set1_chain(SSL *ssl, STACK_OF(X509) *chain) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return ssl_cert_set1_chain(ssl->config->cert.get(), chain);
+  return ssl_cert_set1_chain(ssl_impl->config->cert.get(), chain);
 }
 
 int SSL_CTX_add0_chain_cert(SSL_CTX *ctx, X509 *x509) {
@@ -732,19 +749,21 @@
 }
 
 int SSL_add0_chain_cert(SSL *ssl, X509 *x509) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return ssl_cert_add0_chain_cert(ssl->config->cert.get(), x509);
+  return ssl_cert_add0_chain_cert(ssl_impl->config->cert.get(), x509);
 }
 
 int SSL_add1_chain_cert(SSL *ssl, X509 *x509) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return ssl_cert_add1_chain_cert(ssl->config->cert.get(), x509);
+  return ssl_cert_add1_chain_cert(ssl_impl->config->cert.get(), x509);
 }
 
 int SSL_CTX_clear_chain_certs(SSL_CTX *ctx) {
@@ -760,8 +779,9 @@
 }
 
 int SSL_clear_chain_certs(SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  return SSL_set0_chain(ssl, nullptr);
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  return SSL_set0_chain(ssl_impl, nullptr);
 }
 
 // ssl_cert_cache_chain_certs fills in `cert->x509_chain` from elements 1.. of
@@ -812,17 +832,18 @@
 }
 
 int SSL_get0_chain_certs(const SSL *ssl, STACK_OF(X509) **out_chain) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     return 0;
   }
-  if (!ssl_cert_cache_chain_certs(ssl->config->cert.get())) {
+  if (!ssl_cert_cache_chain_certs(ssl_impl->config->cert.get())) {
     *out_chain = nullptr;
     return 0;
   }
 
-  *out_chain = ssl->config->cert->x509_chain;
+  *out_chain = ssl_impl->config->cert->x509_chain;
   return 1;
 }
 
@@ -886,12 +907,15 @@
 }
 
 void SSL_set_client_CA_list(SSL *ssl, STACK_OF(X509_NAME) *name_list) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return;
   }
-  ssl->ctx->x509_method->ssl_flush_cached_client_CA(ssl->config.get());
-  set_client_CA_list(&ssl->config->client_CA, name_list, ssl->ctx->pool.get());
+  ssl_impl->ctx->x509_method->ssl_flush_cached_client_CA(
+      ssl_impl->config.get());
+  set_client_CA_list(&ssl_impl->config->client_CA, name_list,
+                     ssl_impl->ctx->pool.get());
   sk_X509_NAME_pop_free(name_list, X509_NAME_free);
 }
 
@@ -934,9 +958,10 @@
 }
 
 STACK_OF(X509_NAME) *SSL_get_client_CA_list(const SSL *ssl) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
-    assert(ssl->config);
+  const auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
+    assert(ssl_impl->config);
     return nullptr;
   }
   // For historical reasons, this function is used both to query configuration
@@ -944,21 +969,21 @@
   // `ssl` is a client or server is not known until explicitly configured with
   // `SSL_set_connect_state`. If `do_handshake` is NULL, `ssl` is in an
   // indeterminate mode and `ssl->server` is unset.
-  if (ssl->do_handshake != nullptr && !ssl->server) {
-    if (ssl->s3->hs != nullptr) {
-      return buffer_names_to_x509(ssl->s3->hs->ca_names.get(),
-                                  &ssl->s3->hs->cached_x509_ca_names);
+  if (ssl_impl->do_handshake != nullptr && !ssl_impl->server) {
+    if (ssl_impl->s3->hs != nullptr) {
+      return buffer_names_to_x509(ssl_impl->s3->hs->ca_names.get(),
+                                  &ssl_impl->s3->hs->cached_x509_ca_names);
     }
 
     return nullptr;
   }
 
-  if (ssl->config->client_CA != nullptr) {
+  if (ssl_impl->config->client_CA != nullptr) {
     return buffer_names_to_x509(
-        ssl->config->client_CA.get(),
-        (STACK_OF(X509_NAME) **)&ssl->config->cached_x509_client_CA);
+        ssl_impl->config->client_CA.get(),
+        (STACK_OF(X509_NAME) **)&ssl_impl->config->cached_x509_client_CA);
   }
-  return SSL_CTX_get_client_CA_list(ssl->ctx.get());
+  return SSL_CTX_get_client_CA_list(ssl_impl->ctx.get());
 }
 
 STACK_OF(X509_NAME) *SSL_CTX_get_client_CA_list(const SSL_CTX *ctx) {
@@ -1011,15 +1036,17 @@
 }
 
 int SSL_add_client_CA(SSL *ssl, X509 *x509) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  if (!add_client_CA(&ssl->config->client_CA, x509, ssl->ctx->pool.get())) {
+  if (!add_client_CA(&ssl_impl->config->client_CA, x509,
+                     ssl_impl->ctx->pool.get())) {
     return 0;
   }
 
-  ssl_crypto_x509_ssl_flush_cached_client_CA(ssl->config.get());
+  ssl_crypto_x509_ssl_flush_cached_client_CA(ssl_impl->config.get());
   return 1;
 }
 
@@ -1035,17 +1062,18 @@
 }
 
 static int do_client_cert_cb(SSL *ssl, void *arg) {
+  auto *ssl_impl = FromOpaque(ssl);
   // Should only be called during handshake, but check to be sure.
-  BSSL_CHECK(ssl->config);
+  BSSL_CHECK(ssl_impl->config);
 
-  if (ssl->config->cert->legacy_credential->IsComplete() ||
-      ssl->ctx->client_cert_cb == nullptr) {
+  if (ssl_impl->config->cert->legacy_credential->IsComplete() ||
+      ssl_impl->ctx->client_cert_cb == nullptr) {
     return 1;
   }
 
   X509 *x509 = nullptr;
   EVP_PKEY *pkey = nullptr;
-  int ret = ssl->ctx->client_cert_cb(ssl, &x509, &pkey);
+  int ret = ssl_impl->ctx->client_cert_cb(ssl_impl, &x509, &pkey);
   if (ret < 0) {
     return -1;
   }
@@ -1053,8 +1081,8 @@
   UniquePtr<EVP_PKEY> free_pkey(pkey);
 
   if (ret != 0) {
-    if (!SSL_use_certificate(ssl, x509) ||  //
-        !SSL_use_PrivateKey(ssl, pkey)) {
+    if (!SSL_use_certificate(ssl_impl, x509) ||  //
+        !SSL_use_PrivateKey(ssl_impl, pkey)) {
       return 0;
     }
   }
@@ -1105,36 +1133,40 @@
 }
 
 int SSL_set0_verify_cert_store(SSL *ssl, X509_STORE *store) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return set_cert_store(&ssl->config->cert->verify_store, store, 0);
+  return set_cert_store(&ssl_impl->config->cert->verify_store, store, 0);
 }
 
 int SSL_set1_verify_cert_store(SSL *ssl, X509_STORE *store) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return set_cert_store(&ssl->config->cert->verify_store, store, 1);
+  return set_cert_store(&ssl_impl->config->cert->verify_store, store, 1);
 }
 
 int SSL_set1_host(SSL *ssl, const char *hostname) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return 0;
   }
-  return X509_VERIFY_PARAM_set1_host(ssl->config->param, hostname,
+  return X509_VERIFY_PARAM_set1_host(ssl_impl->config->param, hostname,
                                      strlen(hostname));
 }
 
 void SSL_set_hostflags(SSL *ssl, unsigned flags) {
-  check_ssl_x509_method(ssl);
-  if (!ssl->config) {
+  auto *ssl_impl = FromOpaque(ssl);
+  check_ssl_x509_method(ssl_impl);
+  if (!ssl_impl->config) {
     return;
   }
-  X509_VERIFY_PARAM_set_hostflags(ssl->config->param, flags);
+  X509_VERIFY_PARAM_set_hostflags(ssl_impl->config->param, flags);
 }
 
 int SSL_alert_from_verify_result(long result) {
diff --git a/ssl/t1_enc.cc b/ssl/t1_enc.cc
index 7c691dc..44504a8 100644
--- a/ssl/t1_enc.cc
+++ b/ssl/t1_enc.cc
@@ -47,7 +47,8 @@
                               seed2.data(), seed2.size());
 }
 
-static bool get_key_block_lengths(const SSL *ssl, size_t *out_mac_secret_len,
+static bool get_key_block_lengths(const SSLImpl *ssl,
+                                  size_t *out_mac_secret_len,
                                   size_t *out_key_len, size_t *out_iv_len,
                                   const SSL_CIPHER *cipher) {
   const EVP_AEAD *aead = nullptr;
@@ -72,7 +73,7 @@
   return true;
 }
 
-static bool generate_key_block(const SSL *ssl, Span<uint8_t> out,
+static bool generate_key_block(const SSLImpl *ssl, Span<uint8_t> out,
                                const SSL_SESSION *session) {
   const EVP_MD *digest = ssl_session_get_digest(session);
   // Note this function assumes that `session`'s key material corresponds to
@@ -81,7 +82,7 @@
                   ssl->s3->server_random, ssl->s3->client_random);
 }
 
-bool tls1_configure_aead(SSL *ssl, evp_aead_direction_t direction,
+bool tls1_configure_aead(SSLImpl *ssl, evp_aead_direction_t direction,
                          Array<uint8_t> *key_block_cache,
                          const SSL_SESSION *session,
                          Span<const uint8_t> iv_override) {
@@ -149,7 +150,7 @@
                                  Span<const uint8_t> premaster) {
   BSSL_CHECK(out.size() == SSL3_MASTER_SECRET_SIZE);
 
-  const SSL *ssl = hs->ssl;
+  const SSLImpl *ssl = hs->ssl;
   if (hs->extended_master_secret) {
     uint8_t digests[EVP_MAX_MD_SIZE];
     size_t digests_len;
@@ -173,14 +174,16 @@
 using namespace bssl;
 
 size_t SSL_get_key_block_len(const SSL *ssl) {
+  const auto *ssl_impl = FromOpaque(ssl);
   // See `SSL_generate_key_block`.
-  if (SSL_in_init(ssl) || ssl_protocol_version(ssl) > TLS1_2_VERSION) {
+  if (SSL_in_init(ssl_impl) ||
+      ssl_protocol_version(ssl_impl) > TLS1_2_VERSION) {
     return 0;
   }
 
   size_t mac_secret_len, key_len, fixed_iv_len;
-  if (!get_key_block_lengths(ssl, &mac_secret_len, &key_len, &fixed_iv_len,
-                             SSL_get_current_cipher(ssl))) {
+  if (!get_key_block_lengths(ssl_impl, &mac_secret_len, &key_len, &fixed_iv_len,
+                             SSL_get_current_cipher(ssl_impl))) {
     ERR_clear_error();
     return 0;
   }
@@ -189,27 +192,32 @@
 }
 
 int SSL_generate_key_block(const SSL *ssl, uint8_t *out, size_t out_len) {
+  const auto *ssl_impl = FromOpaque(ssl);
   // Which cipher state to use is ambiguous during a handshake. In particular,
   // there are points where read and write states are from different epochs.
   // During a handshake, before ChangeCipherSpec, the encryption states may not
   // match |ssl->s3->client_random| and |ssl->s3->server_random|.
-  if (SSL_in_init(ssl) || ssl_protocol_version(ssl) > TLS1_2_VERSION) {
+  if (SSL_in_init(ssl_impl) ||
+      ssl_protocol_version(ssl_impl) > TLS1_2_VERSION) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
   }
 
-  return generate_key_block(ssl, Span(out, out_len), SSL_get_session(ssl));
+  return generate_key_block(ssl_impl, Span(out, out_len),
+                            SSL_get_session(ssl_impl));
 }
 
 int SSL_export_keying_material(const SSL *ssl, uint8_t *out, size_t out_len,
                                const char *label, size_t label_len,
                                const uint8_t *context, size_t context_len,
                                int use_context) {
+  const auto *ssl_impl = FromOpaque(ssl);
   auto out_span = Span(out, out_len);
   std::string_view label_sv(label, label_len);
   // In TLS 1.3, the exporter may be used whenever the secret has been derived.
-  if (ssl->s3->version != 0 && ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
-    if (ssl->s3->exporter_secret.empty()) {
+  if (ssl_impl->s3->version != 0 &&
+      ssl_protocol_version(ssl_impl) >= TLS1_3_VERSION) {
+    if (ssl_impl->s3->exporter_secret.empty()) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_NOT_COMPLETE);
       return 0;
     }
@@ -217,13 +225,14 @@
       context = nullptr;
       context_len = 0;
     }
-    return tls13_export_keying_material(ssl, out_span, ssl->s3->exporter_secret,
-                                        label_sv, Span(context, context_len));
+    return tls13_export_keying_material(ssl_impl, out_span,
+                                        ssl_impl->s3->exporter_secret, label_sv,
+                                        Span(context, context_len));
   }
 
   // Exporters may be used in False Start, where the handshake has progressed
   // enough. Otherwise, they may not be used during a handshake.
-  if (SSL_in_init(ssl) && !SSL_in_false_start(ssl)) {
+  if (SSL_in_init(ssl_impl) && !SSL_in_false_start(ssl_impl)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_NOT_COMPLETE);
     return 0;
   }
@@ -241,8 +250,8 @@
     return 0;
   }
 
-  OPENSSL_memcpy(seed.data(), ssl->s3->client_random, SSL3_RANDOM_SIZE);
-  OPENSSL_memcpy(seed.data() + SSL3_RANDOM_SIZE, ssl->s3->server_random,
+  OPENSSL_memcpy(seed.data(), ssl_impl->s3->client_random, SSL3_RANDOM_SIZE);
+  OPENSSL_memcpy(seed.data() + SSL3_RANDOM_SIZE, ssl_impl->s3->server_random,
                  SSL3_RANDOM_SIZE);
   if (use_context) {
     seed[2 * SSL3_RANDOM_SIZE] = static_cast<uint8_t>(context_len >> 8);
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
index 62a2a70..68c5ea2 100644
--- a/ssl/tls13_both.cc
+++ b/ssl/tls13_both.cc
@@ -104,7 +104,7 @@
 
 bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg,
                                bool allow_anonymous) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   CBS body = msg.body;
   bssl::UniquePtr<CRYPTO_BUFFER> decompressed;
 
@@ -364,7 +364,7 @@
 
 bool tls13_process_certificate_verify(SSL_HANDSHAKE *hs,
                                       const SSLMessage &msg) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (hs->peer_pubkey == nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return false;
@@ -408,7 +408,7 @@
 
 bool tls13_process_finished(SSL_HANDSHAKE *hs, const SSLMessage &msg,
                             bool use_saved_value) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   uint8_t verify_data_buf[EVP_MAX_MD_SIZE];
   Span<const uint8_t> verify_data;
   if (use_saved_value) {
@@ -437,7 +437,7 @@
 }
 
 bool tls13_add_certificate(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   const SSLCredential *cred = hs->credential.get();
 
   ScopedCBB cbb;
@@ -612,7 +612,7 @@
 }
 
 enum ssl_private_key_result_t tls13_add_certificate_verify(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   assert(hs->signature_algorithm != 0);
   ScopedCBB cbb;
   CBB body;
@@ -656,7 +656,7 @@
 }
 
 bool tls13_add_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   size_t verify_data_len;
   uint8_t verify_data[EVP_MAX_MD_SIZE];
 
@@ -677,7 +677,7 @@
   return true;
 }
 
-bool tls13_add_key_update(SSL *ssl, int request_type) {
+bool tls13_add_key_update(SSLImpl *ssl, int request_type) {
   if (ssl->s3->key_update_pending) {
     return true;
   }
@@ -713,7 +713,7 @@
   return true;
 }
 
-static bool tls13_receive_key_update(SSL *ssl, const SSLMessage &msg) {
+static bool tls13_receive_key_update(SSLImpl *ssl, const SSLMessage &msg) {
   CBS body = msg.body;
   uint8_t key_update_request;
   if (!CBS_get_u8(&body, &key_update_request) ||              //
@@ -738,7 +738,7 @@
   return true;
 }
 
-bool tls13_post_handshake(SSL *ssl, const SSLMessage &msg) {
+bool tls13_post_handshake(SSLImpl *ssl, const SSLMessage &msg) {
   if (msg.type == SSL3_MT_NEW_SESSION_TICKET && !ssl->server) {
     return tls13_process_new_session_ticket(ssl, msg);
   }
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc
index 36bb436..f134ebb 100644
--- a/ssl/tls13_client.cc
+++ b/ssl/tls13_client.cc
@@ -57,7 +57,7 @@
 // end_of_early_data closes the early data stream for `hs` and switches the
 // encryption level to `level`. It returns true on success and false on error.
 static bool close_early_data(SSL_HANDSHAKE *hs, ssl_encryption_level_t level) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   assert(hs->in_early_data);
 
   // Note `can_early_write` may already be false if `SSL_write` exceeded the
@@ -182,7 +182,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_hello_retry_request(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   assert(ssl->s3->version != 0);
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
@@ -383,7 +383,7 @@
 
 static bool check_session(const SSL_HANDSHAKE *hs, uint8_t *out_alert,
                           const SSL_SESSION *session) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   if (session->ssl_version != ssl->s3->version) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_VERSION_NOT_RETURNED);
     *out_alert = SSL_AD_ILLEGAL_PARAMETER;
@@ -407,7 +407,7 @@
 
 static bool check_imported_psk(const SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                const SSLImportedPSK &imported) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   const EVP_MD *md =
       ssl_get_handshake_digest(ssl_protocol_version(ssl), hs->new_cipher);
   if (imported.md != md || imported.protocol != ssl->s3->version) {
@@ -419,7 +419,7 @@
 }
 
 static bool using_certificate(const SSL_HANDSHAKE *hs) {
-  const SSL *const ssl = hs->ssl;
+  const SSLImpl *const ssl = hs->ssl;
   // Resumption is not a certificate-based handshake.
   if (ssl->s3->session_reused) {
     return false;
@@ -432,7 +432,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -663,7 +663,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_encrypted_extensions(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -744,7 +744,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_certificate_request(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // CertificateRequest may only be sent in certificate-based handshakes.
   if (!using_certificate(hs)) {
     if (ssl->s3->session_reused && ssl->ctx->reverify_on_resume &&
@@ -816,7 +816,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_server_certificate(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -838,7 +838,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_server_certificate_verify(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -879,7 +879,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_server_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -907,7 +907,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_end_of_early_data(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (ssl->s3->early_data_accepted) {
     // DTLS and QUIC omit the EndOfEarlyData message. See RFC 9001, section 8.3,
@@ -933,7 +933,7 @@
 
 static enum ssl_hs_wait_t do_send_client_encrypted_extensions(
     SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // For now, only one extension uses client EncryptedExtensions. This function
   // may be generalized if others use it in the future.
   if (hs->new_session->has_application_settings &&
@@ -984,7 +984,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_client_certificate(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   // The peer didn't request a certificate.
   if (!hs->cert_request) {
@@ -1077,7 +1077,7 @@
 }
 
 static enum ssl_hs_wait_t do_complete_second_flight(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   hs->can_release_private_key = true;
 
   // Send a Channel ID assertion if necessary.
@@ -1215,7 +1215,7 @@
   return "TLS 1.3 client unknown";
 }
 
-bool tls13_process_new_session_ticket(SSL *ssl, const SSLMessage &msg) {
+bool tls13_process_new_session_ticket(SSLImpl *ssl, const SSLMessage &msg) {
   if (ssl->s3->write_shutdown != ssl_shutdown_none) {
     // Ignore tickets on shutdown. Callers tend to indiscriminately call
     // `SSL_shutdown` before destroying an `SSL`, at which point calling the new
@@ -1239,7 +1239,8 @@
   return true;
 }
 
-UniquePtr<SSL_SESSION> tls13_create_session_with_ticket(SSL *ssl, CBS *body) {
+UniquePtr<SSL_SESSION> tls13_create_session_with_ticket(SSLImpl *ssl,
+                                                        CBS *body) {
   UniquePtr<SSL_SESSION> session = SSL_SESSION_dup(
       ssl->s3->established_session.get(), SSL_SESSION_INCLUDE_NONAUTH);
   if (!session) {
diff --git a/ssl/tls13_enc.cc b/ssl/tls13_enc.cc
index e0ae282..84c0249 100644
--- a/ssl/tls13_enc.cc
+++ b/ssl/tls13_enc.cc
@@ -177,7 +177,7 @@
   return derive_secret_with_transcript(hs, out, hs->transcript, label);
 }
 
-bool tls13_set_traffic_key(SSL *ssl, enum ssl_encryption_level_t level,
+bool tls13_set_traffic_key(SSLImpl *ssl, enum ssl_encryption_level_t level,
                            enum evp_aead_direction_t direction,
                            const SSL_SESSION *session,
                            Span<const uint8_t> traffic_secret) {
@@ -347,7 +347,7 @@
 static const char kTLS13LabelServerApplicationTraffic[] = "s ap traffic";
 
 bool tls13_derive_early_secret(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // When offering ECH on the client, early data is associated with
   // ClientHelloInner, not ClientHelloOuter.
   const SSLTranscript &transcript = (!ssl->server && hs->selected_ech_config)
@@ -363,7 +363,7 @@
 }
 
 bool tls13_derive_handshake_secrets(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (!derive_secret(hs, &hs->client_handshake_secret,
                      kTLS13LabelClientHandshakeTraffic) ||
       !ssl_log_secret(ssl, "CLIENT_HANDSHAKE_TRAFFIC_SECRET",
@@ -379,7 +379,7 @@
 }
 
 bool tls13_derive_application_secrets(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (!derive_secret(hs, &hs->client_traffic_secret_0,
                      kTLS13LabelClientApplicationTraffic) ||
       !ssl_log_secret(ssl, "CLIENT_TRAFFIC_SECRET_0",
@@ -398,7 +398,8 @@
 
 static const char kTLS13LabelApplicationTraffic[] = "traffic upd";
 
-bool tls13_rotate_traffic_key(SSL *ssl, enum evp_aead_direction_t direction) {
+bool tls13_rotate_traffic_key(SSLImpl *ssl,
+                              enum evp_aead_direction_t direction) {
   InplaceVector<uint8_t, SSL_MAX_MD_SIZE> secret(
       direction == evp_aead_open ? ssl->s3->read_traffic_secret
                                  : ssl->s3->write_traffic_secret);
@@ -469,7 +470,7 @@
 
 static const char kTLS13LabelExportKeying[] = "exporter";
 
-bool tls13_export_keying_material(const SSL *ssl, Span<uint8_t> out,
+bool tls13_export_keying_material(const SSLImpl *ssl, Span<uint8_t> out,
                                   Span<const uint8_t> secret,
                                   std::string_view label,
                                   Span<const uint8_t> context) {
@@ -687,7 +688,7 @@
          CBS_len(&cbs) == 0;
 }
 
-size_t ssl_ech_confirmation_signal_hello_offset(const SSL *ssl) {
+size_t ssl_ech_confirmation_signal_hello_offset(const SSLImpl *ssl) {
   static_assert(ECH_CONFIRMATION_SIGNAL_LEN < SSL3_RANDOM_SIZE,
                 "the confirmation signal is a suffix of the random");
   const size_t header_len =
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index d09aa53..94de72d 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -70,7 +70,7 @@
 
 static bool resolve_ecdhe_secret(SSL_HANDSHAKE *hs,
                                  const SSL_CLIENT_HELLO *client_hello) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   const uint16_t group_id = hs->new_session->group_id;
 
   bool found_key_share;
@@ -137,7 +137,7 @@
 }
 
 static const SSL_CIPHER *choose_tls13_cipher(
-    const SSL *ssl, const SSL_CLIENT_HELLO *client_hello) {
+    const SSLImpl *ssl, const SSL_CLIENT_HELLO *client_hello) {
   CBS cipher_suites;
   CBS_init(&cipher_suites, client_hello->cipher_suites,
            client_hello->cipher_suites_len);
@@ -152,7 +152,7 @@
 }
 
 static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (  // If the client doesn't accept resumption with PSK_DHE_KE, don't send a
         // session ticket.
       !hs->accept_psk_mode ||
@@ -325,7 +325,7 @@
 static bool check_psk_credential(SSL_HANDSHAKE *hs, const SSLCredential *cred,
                                  const std::optional<SSLOfferedPSKs> &psks) {
   assert(cred->type == SSLCredentialType::kPreSharedKey);
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (!psks) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_EXTENSION);
     return false;
@@ -357,7 +357,7 @@
   // the common handshake logic. Resolve the remaining non-resumption
   // parameters. First, parse out another copy of the ClientHello and important
   // extensions.
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   SSL_CLIENT_HELLO client_hello;
   if (!hs->GetClientHello(&msg, &client_hello)) {
@@ -482,7 +482,7 @@
     SSL_HANDSHAKE *hs, uint8_t *out_alert, UniquePtr<SSL_SESSION> *out_session,
     int32_t *out_ticket_age_skew, bool *out_offered_ticket,
     const SSLMessage &msg, const SSL_CLIENT_HELLO *client_hello) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   *out_session = nullptr;
 
   CBS pre_shared_key;
@@ -595,7 +595,7 @@
 }
 
 static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   SSL_CLIENT_HELLO client_hello;
   if (!hs->GetClientHello(&msg, &client_hello)) {
@@ -825,7 +825,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_hello_retry_request(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (hs->pending_hints != nullptr) {
     return ssl_hs_hints_ready;
   }
@@ -887,7 +887,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_second_client_hello(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -998,7 +998,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   Span<uint8_t> random(ssl->s3->server_random);
 
@@ -1147,7 +1147,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_server_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (hs->pending_hints != nullptr) {
     return ssl_hs_hints_ready;
   }
@@ -1169,7 +1169,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_half_rtt_ticket(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
 
   if (ssl->s3->early_data_accepted) {
     // If accepting 0-RTT, we send tickets half-RTT. This gets the tickets on
@@ -1218,14 +1218,14 @@
   return ssl_hs_flush;
 }
 
-static bool uses_end_of_early_data(const SSL *ssl) {
+static bool uses_end_of_early_data(const SSLImpl *ssl) {
   // DTLS and QUIC omit the EndOfEarlyData message. See RFC 9001, section 8.3,
   // and RFC 9147, section 5.6.
   return !SSL_is_quic(ssl) && !SSL_is_dtls(ssl);
 }
 
 static enum ssl_hs_wait_t do_read_second_client_flight(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (ssl->s3->early_data_accepted) {
     if (!tls13_set_traffic_key(ssl, ssl_encryption_early_data, evp_aead_open,
                                hs->new_session.get(),
@@ -1255,7 +1255,7 @@
 }
 
 static enum ssl_hs_wait_t do_process_end_of_early_data(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // In protocols that use EndOfEarlyData, we must consume the extra message and
   // switch to client_handshake_secret after the early return.
   if (uses_end_of_early_data(ssl)) {
@@ -1288,7 +1288,7 @@
 
 static enum ssl_hs_wait_t do_read_client_encrypted_extensions(
     SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   // For now, only one extension uses client EncryptedExtensions. This function
   // may be generalized if others use it in the future.
   if (hs->new_session->has_application_settings &&
@@ -1344,7 +1344,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_client_certificate(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (!hs->cert_request) {
     if (!ssl->s3->session_reused) {
       // OpenSSL returns X509_V_OK when no certificates are requested. This is
@@ -1378,7 +1378,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_client_certificate_verify(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (!ssl_session_has_peer_cred(hs->new_session.get())) {
     // Skip this state.
     hs->tls13_state = state13_read_channel_id;
@@ -1412,7 +1412,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_channel_id(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   if (!hs->channel_id_negotiated) {
     hs->tls13_state = state13_read_client_finished;
     return ssl_hs_ok;
@@ -1434,7 +1434,7 @@
 }
 
 static enum ssl_hs_wait_t do_read_client_finished(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -1479,7 +1479,7 @@
 }
 
 static enum ssl_hs_wait_t do_send_new_session_ticket(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+  SSLImpl *const ssl = hs->ssl;
   bool sent_tickets;
   if (!add_new_session_tickets(hs, &sent_tickets)) {
     return ssl_hs_error;
diff --git a/ssl/tls_method.cc b/ssl/tls_method.cc
index 0e5d918..a2e4dcf 100644
--- a/ssl/tls_method.cc
+++ b/ssl/tls_method.cc
@@ -25,7 +25,7 @@
 
 BSSL_NAMESPACE_BEGIN
 
-static void tls_on_handshake_complete(SSL *ssl) {
+static void tls_on_handshake_complete(SSLImpl *ssl) {
   // The handshake should have released its final message.
   assert(!ssl->s3->has_message);
 
@@ -40,7 +40,7 @@
   }
 }
 
-static bool tls_set_read_state(SSL *ssl, ssl_encryption_level_t level,
+static bool tls_set_read_state(SSLImpl *ssl, ssl_encryption_level_t level,
                                UniquePtr<SSLAEADContext> aead_ctx,
                                Span<const uint8_t> traffic_secret) {
   // Cipher changes are forbidden if the current epoch has leftover data.
@@ -72,7 +72,7 @@
   return true;
 }
 
-static bool tls_set_write_state(SSL *ssl, ssl_encryption_level_t level,
+static bool tls_set_write_state(SSLImpl *ssl, ssl_encryption_level_t level,
                                 UniquePtr<SSLAEADContext> aead_ctx,
                                 Span<const uint8_t> traffic_secret) {
   if (!tls_flush_pending_hs_data(ssl)) {
@@ -101,12 +101,12 @@
   return true;
 }
 
-static void tls_finish_flight(SSL *ssl) {
+static void tls_finish_flight(SSLImpl *ssl) {
   // We don't track whether a flight is complete in TLS and instead always flush
   // every queued message in `tls_flush`, whether the flight is complete or not.
 }
 
-static void tls_schedule_ack(SSL *ssl) {
+static void tls_schedule_ack(SSLImpl *ssl) {
   // TLS does not use ACKs.
 }
 
diff --git a/ssl/tls_record.cc b/ssl/tls_record.cc
index 90c79f7..f77d84b 100644
--- a/ssl/tls_record.cc
+++ b/ssl/tls_record.cc
@@ -46,7 +46,7 @@
 
 // ssl_needs_record_splitting returns one if `ssl`'s current outgoing cipher
 // state needs record-splitting and zero otherwise.
-bool ssl_needs_record_splitting(const SSL *ssl) {
+bool ssl_needs_record_splitting(const SSLImpl *ssl) {
   return !CRYPTO_fuzzer_mode_enabled() &&
          !ssl->s3->aead_write_ctx->is_null_cipher() &&
          ssl_protocol_version(ssl) < TLS1_1_VERSION &&
@@ -54,12 +54,12 @@
          SSL_CIPHER_is_block_cipher(ssl->s3->aead_write_ctx->cipher());
 }
 
-size_t ssl_record_prefix_len(const SSL *ssl) {
+size_t ssl_record_prefix_len(const SSLImpl *ssl) {
   assert(!SSL_is_dtls(ssl));
   return SSL3_RT_HEADER_LENGTH + ssl->s3->aead_read_ctx->ExplicitNonceLen();
 }
 
-static ssl_open_record_t skip_early_data(SSL *ssl, uint8_t *out_alert,
+static ssl_open_record_t skip_early_data(SSLImpl *ssl, uint8_t *out_alert,
                                          size_t consumed) {
   ssl->s3->early_data_skipped += consumed;
   if (ssl->s3->early_data_skipped < consumed) {
@@ -75,7 +75,7 @@
   return ssl_open_record_discard;
 }
 
-static uint16_t tls_record_version(const SSL *ssl) {
+static uint16_t tls_record_version(const SSLImpl *ssl) {
   if (ssl->s3->version == 0) {
     // Before the version is determined, outgoing records use TLS 1.0 for
     // historical compatibility requirements.
@@ -88,7 +88,7 @@
                                                      : ssl->s3->version;
 }
 
-ssl_open_record_t tls_open_record(SSL *ssl, uint8_t *out_type,
+ssl_open_record_t tls_open_record(SSLImpl *ssl, uint8_t *out_type,
                                   Span<uint8_t> *out, size_t *out_consumed,
                                   uint8_t *out_alert, Span<uint8_t> in) {
   *out_consumed = 0;
@@ -263,7 +263,7 @@
   return ssl_open_record_success;
 }
 
-static bool do_seal_record(SSL *ssl, uint8_t *out_prefix, uint8_t *out,
+static bool do_seal_record(SSLImpl *ssl, uint8_t *out_prefix, uint8_t *out,
                            uint8_t *out_suffix, uint8_t type, const uint8_t *in,
                            const size_t in_len) {
   SSLAEADContext *aead = ssl->s3->aead_write_ctx.get();
@@ -316,7 +316,7 @@
   return true;
 }
 
-static size_t tls_seal_scatter_prefix_len(const SSL *ssl, uint8_t type,
+static size_t tls_seal_scatter_prefix_len(const SSLImpl *ssl, uint8_t type,
                                           size_t in_len) {
   size_t ret = SSL3_RT_HEADER_LENGTH;
   if (type == SSL3_RT_APPLICATION_DATA && in_len > 1 &&
@@ -333,8 +333,9 @@
   return ret;
 }
 
-static bool tls_seal_scatter_suffix_len(const SSL *ssl, size_t *out_suffix_len,
-                                        uint8_t type, size_t in_len) {
+static bool tls_seal_scatter_suffix_len(const SSLImpl *ssl,
+                                        size_t *out_suffix_len, uint8_t type,
+                                        size_t in_len) {
   size_t extra_in_len = 0;
   if (!ssl->s3->aead_write_ctx->is_null_cipher() &&
       ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
@@ -361,9 +362,10 @@
 // returns one on success and zero on error. If enabled,
 // `tls_seal_scatter_record` implements TLS 1.0 CBC 1/n-1 record splitting and
 // may write two records concatenated.
-static bool tls_seal_scatter_record(SSL *ssl, uint8_t *out_prefix, uint8_t *out,
-                                    uint8_t *out_suffix, uint8_t type,
-                                    const uint8_t *in, size_t in_len) {
+static bool tls_seal_scatter_record(SSLImpl *ssl, uint8_t *out_prefix,
+                                    uint8_t *out, uint8_t *out_suffix,
+                                    uint8_t type, const uint8_t *in,
+                                    size_t in_len) {
   if (type == SSL3_RT_APPLICATION_DATA && in_len > 1 &&
       ssl_needs_record_splitting(ssl)) {
     assert(ssl->s3->aead_write_ctx->ExplicitNonceLen() == 0);
@@ -406,7 +408,7 @@
   return do_seal_record(ssl, out_prefix, out, out_suffix, type, in, in_len);
 }
 
-bool tls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len,
+bool tls_seal_record(SSLImpl *ssl, uint8_t *out, size_t *out_len,
                      size_t max_out_len, uint8_t type, const uint8_t *in,
                      size_t in_len) {
   if (buffers_alias(in, in_len, out, max_out_len)) {
@@ -440,7 +442,7 @@
   return true;
 }
 
-enum ssl_open_record_t ssl_process_alert(SSL *ssl, uint8_t *out_alert,
+enum ssl_open_record_t ssl_process_alert(SSLImpl *ssl, uint8_t *out_alert,
                                          Span<const uint8_t> in) {
   // Alerts records may not contain fragmented or multiple alerts.
   if (in.size() != 2) {
@@ -502,19 +504,20 @@
 using namespace bssl;
 
 size_t SSL_max_seal_overhead(const SSL *ssl) {
-  if (SSL_is_dtls(ssl)) {
+  const auto *ssl_impl = FromOpaque(ssl);
+  if (SSL_is_dtls(ssl_impl)) {
     // TODO(crbug.com/381113363): Use the 0-RTT epoch if writing 0-RTT.
-    return dtls_max_seal_overhead(ssl, ssl->d1->write_epoch.epoch());
+    return dtls_max_seal_overhead(ssl_impl, ssl_impl->d1->write_epoch.epoch());
   }
 
   size_t ret = SSL3_RT_HEADER_LENGTH;
-  ret += ssl->s3->aead_write_ctx->MaxOverhead();
+  ret += ssl_impl->s3->aead_write_ctx->MaxOverhead();
   // TLS 1.3 needs an extra byte for the encrypted record type.
-  if (!ssl->s3->aead_write_ctx->is_null_cipher() &&
-      ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
+  if (!ssl_impl->s3->aead_write_ctx->is_null_cipher() &&
+      ssl_protocol_version(ssl_impl) >= TLS1_3_VERSION) {
     ret += 1;
   }
-  if (ssl_needs_record_splitting(ssl)) {
+  if (ssl_needs_record_splitting(ssl_impl)) {
     ret *= 2;
   }
   return ret;