Remove redudant version information from SSLAEADContext

After https://boringssl-review.googlesource.com/c/boringssl/+/71533, it
is never the case that we have two version's worth of the record layer
alive at once. This means we stop needing to store a version number in
the epoch state, or worry about which version we check if in theory the
connection and each epoch are in different states. (I'm not sure how
DTLS 1.3 ACKs will possibly work in such a model.)

Simply this by taking versions out of the epochs and instead having
everything check the connection's version. For that to work, we need to
relax the invariants on the version slightly. When we enter 0-RTT, we
set the version to the 0-RTT version. The final version will then either
match or trigger a 0-RTT reject before changing the version. Having the
version change is unfortunate but it reflects what's going on.

As a result, some checks added for DTLS 1.3 will now behave correctly
with DTLS 1.3 0-RTT. This also simplifies things slightly along the way
to cleaning up the epoch state to implement DTLS 1.3 ACKs.

There are two checks that arguably did not want to pay attention to the
0-RTT version, but the difference seems unimportant. But, for
completeness:

1. If we offer 0-RTT, the logic to ignore dummy ChangeCipherSpec records
   deep in the record layer will kick in before the ServerHello, instead
   of after. This is arguably supported by RFC 8446, which says any time
   after the first ClientHello suffices:

   > An implementation may receive an unencrypted record of type
   > change_cipher_spec consisting of the single byte value 0x01 at any
   > time after the first ClientHello message has been sent or received
   > and before the peer's Finished message has been received and MUST
   > simply drop it without further processing.

   Though that guidance is pretty bizarre because we haven't learned the
   version yet before ServerHello. Anyway, I don't think this has any
   real practical impact and isn't worth fussing over.

2. If we offer 0-RTT, the logic to treat a "warning" decode_error alert
   as fatal will kick in before the ServerHello, instead of after. If
   the server deployed 0-RTT, then rolled back to TLS 1.2 (against the
   guidance in Appendix D.3 but possible), AND if it sent a warning
   alert before the TLS 1.2 ServerHello, we would fail the connection
   with a different error and not trigger the application's retry.

   This also does not seem worth fussing over.

Bug: 42290594
Change-Id: I8d97e219888c75274c3c640b2da5c769c6b9ad36
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/71534
Commit-Queue: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/ssl/d1_lib.cc b/ssl/d1_lib.cc
index 4caadad..4aa71ac 100644
--- a/ssl/d1_lib.cc
+++ b/ssl/d1_lib.cc
@@ -100,8 +100,7 @@
     tls_free(ssl);
     return false;
   }
-  d1->initial_epoch_state->aead_write_ctx =
-      SSLAEADContext::CreateNullCipher(true);
+  d1->initial_epoch_state->aead_write_ctx = SSLAEADContext::CreateNullCipher();
   if (!d1->initial_epoch_state->aead_write_ctx) {
     tls_free(ssl);
     return false;
diff --git a/ssl/dtls_record.cc b/ssl/dtls_record.cc
index ba06acf..479ecd4 100644
--- a/ssl/dtls_record.cc
+++ b/ssl/dtls_record.cc
@@ -159,6 +159,18 @@
   }
 }
 
+static uint16_t dtls_record_version(const SSL *ssl) {
+  if (ssl->s3->version == 0) {
+    // Before the version is determined, outgoing records use dTLS 1.0 for
+    // historical compatibility requirements.
+    return DTLS1_VERSION;
+  }
+  // DTLS 1.3 freezes the record version at DTLS 1.2. Previous ones use the
+  // version itself.
+  return ssl_protocol_version(ssl) >= TLS1_3_VERSION ? DTLS1_2_VERSION
+                                                     : ssl->s3->version;
+}
+
 // reconstruct_epoch finds the largest epoch that ends with the epoch bits from
 // |wire_epoch| that is less than or equal to |current_epoch|, to match the
 // epoch reconstruction algorithm described in RFC 9147 section 4.2.2.
@@ -283,7 +295,7 @@
     // version negotiation failure alerts.
     version_ok = (*out_version >> 8) == DTLS1_VERSION_MAJOR;
   } else {
-    version_ok = *out_version == aead->RecordVersion();
+    version_ok = *out_version == dtls_record_version(ssl);
   }
 
   if (!version_ok) {
@@ -295,7 +307,7 @@
 
   // Discard the packet if we're expecting an encrypted DTLS 1.3 record but we
   // get the old record header format.
-  if (!aead->is_null_cipher() && aead->ProtocolVersion() >= TLS1_3_VERSION) {
+  if (!aead->is_null_cipher() && ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return false;
   }
   return true;
@@ -334,7 +346,7 @@
   // used for encrypted records with DTLS 1.3. Plaintext records or DTLS 1.2
   // records use the old record header format.
   if ((type & 0xe0) == 0x20 && !aead->is_null_cipher() &&
-      aead->ProtocolVersion() >= TLS1_3_VERSION) {
+      ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     valid_record_header = parse_dtls13_record_header(
         ssl, &cbs, in, type, &body, &sequence, &epoch, &record_header_len);
   } else {
@@ -379,7 +391,7 @@
 
   // DTLS 1.3 hides the record type inside the encrypted data.
   bool has_padding =
-      !aead->is_null_cipher() && aead->ProtocolVersion() >= TLS1_3_VERSION;
+      !aead->is_null_cipher() && ssl_protocol_version(ssl) >= TLS1_3_VERSION;
   // Check the plaintext length.
   size_t plaintext_limit = SSL3_RT_MAX_PLAIN_LENGTH + (has_padding ? 1 : 0);
   if (out->size() > plaintext_limit) {
@@ -492,7 +504,7 @@
     return false;
   }
 
-  uint16_t record_version = ssl->s3->aead_write_ctx->RecordVersion();
+  uint16_t record_version = dtls_record_version(ssl);
   uint64_t seq_with_epoch = (uint64_t{epoch} << 48) | *seq;
 
   bool dtls13_header = use_dtls13_record_header(ssl, epoch);
diff --git a/ssl/handoff.cc b/ssl/handoff.cc
index 438f899..ec950d0 100644
--- a/ssl/handoff.cc
+++ b/ssl/handoff.cc
@@ -689,7 +689,6 @@
   hs->wait = ssl_hs_flush;
   hs->extended_master_secret = extended_master_secret;
   hs->ticket_expected = ticket_expected;
-  s3->aead_write_ctx->SetVersionIfNullCipher(ssl->s3->version);
   hs->cert_request = cert_request;
 
   if (type != handback_after_handshake &&
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index 82c4df9..2f74ff5 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -575,10 +575,12 @@
     return ssl_hs_ok;
   }
 
-  // Stash the early data session. This must happen before
-  // |do_early_reverify_server_certificate|, so early connection properties are
-  // available to the callback.
+  // Stash the early data session and activate the early version. This must
+  // happen before |do_early_reverify_server_certificate|, so early connection
+  // properties are available to the callback. Note the early version may be
+  // overwritten later by the final version.
   hs->early_session = UpRef(ssl->session);
+  ssl->s3->version = hs->early_session->ssl_version;
   hs->state = state_early_reverify_server_certificate;
   return ssl_hs_ok;
 }
@@ -604,8 +606,6 @@
     }
   }
 
-  ssl->s3->aead_write_ctx->SetVersionIfNullCipher(
-      hs->early_session->ssl_version);
   if (!ssl->method->add_change_cipher_spec(ssl)) {
     return ssl_hs_error;
   }
@@ -723,12 +723,13 @@
     return ssl_hs_error;
   }
 
-  assert((ssl->s3->version != 0) == ssl->s3->initial_handshake_complete);
-  if (ssl->s3->version == 0) {
+  if (!ssl->s3->initial_handshake_complete) {
+    // |ssl->s3->version| may be set due to 0-RTT. If it was to a different
+    // value, the check below will fire.
+    assert(ssl->s3->version == 0 ||
+           (hs->early_data_offered &&
+            ssl->s3->version == hs->early_session->ssl_version));
     ssl->s3->version = server_version;
-    // At this point, the connection's version is known and ssl->s3->version is
-    // fixed. Begin enforcing the record-layer version.
-    ssl->s3->aead_write_ctx->SetVersionIfNullCipher(ssl->s3->version);
   } else if (server_version != ssl->s3->version) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_PROTOCOL_VERSION);
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index 4b68ebc..7821ce0 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -245,10 +245,6 @@
     return false;
   }
 
-  // At this point, the connection's version is known and |ssl->s3->version| is
-  // fixed. Begin enforcing the record-layer version.
-  ssl->s3->aead_write_ctx->SetVersionIfNullCipher(ssl->s3->version);
-
   // Handle FALLBACK_SCSV.
   if (ssl_client_cipher_list_contains_cipher(client_hello,
                                              SSL3_CK_FALLBACK_SCSV & 0xffff) &&
diff --git a/ssl/internal.h b/ssl/internal.h
index 30e6104..4bbbeb7 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -827,7 +827,7 @@
 // encrypt an SSL connection.
 class SSLAEADContext {
  public:
-  SSLAEADContext(uint16_t version, bool is_dtls, const SSL_CIPHER *cipher);
+  explicit SSLAEADContext(const SSL_CIPHER *cipher);
   ~SSLAEADContext();
   static constexpr bool kAllowUniquePtr = true;
 
@@ -835,7 +835,7 @@
   SSLAEADContext &operator=(const SSLAEADContext &&) = delete;
 
   // CreateNullCipher creates an |SSLAEADContext| for the null cipher.
-  static UniquePtr<SSLAEADContext> CreateNullCipher(bool is_dtls);
+  static UniquePtr<SSLAEADContext> CreateNullCipher();
 
   // Create creates an |SSLAEADContext| using the supplied key material. It
   // returns nullptr on error. Only one of |Open| or |Seal| may be used with the
@@ -849,24 +849,10 @@
                                           Span<const uint8_t> fixed_iv);
 
   // CreatePlaceholderForQUIC creates a placeholder |SSLAEADContext| for the
-  // given cipher and version. The resulting object can be queried for various
-  // properties but cannot encrypt or decrypt data.
+  // given cipher. The resulting object can be queried for various properties
+  // but cannot encrypt or decrypt data.
   static UniquePtr<SSLAEADContext> CreatePlaceholderForQUIC(
-      uint16_t version, const SSL_CIPHER *cipher);
-
-  // SetVersionIfNullCipher sets the version the SSLAEADContext for the null
-  // cipher, to make version-specific determinations in the record layer prior
-  // to a cipher being selected.
-  void SetVersionIfNullCipher(uint16_t version);
-
-  // ProtocolVersion returns the protocol version associated with this
-  // SSLAEADContext. It can only be called once |version_| has been set to a
-  // valid value.
-  uint16_t ProtocolVersion() const;
-
-  // RecordVersion returns the record version that should be used with this
-  // SSLAEADContext for record construction and crypto.
-  uint16_t RecordVersion() const;
+      const SSL_CIPHER *cipher);
 
   const SSL_CIPHER *cipher() const { return cipher_; }
 
@@ -953,13 +939,9 @@
   ScopedEVP_AEAD_CTX ctx_;
   // fixed_nonce_ contains any bytes of the nonce that are fixed for all
   // records.
-  uint8_t fixed_nonce_[12];
+  uint8_t fixed_nonce_[12] = {0};
   uint8_t fixed_nonce_len_ = 0, variable_nonce_len_ = 0;
-  // version_ is the wire version that should be used with this AEAD.
-  uint16_t version_;
   UniquePtr<RecordNumberEncrypter> rn_encrypter_;
-  // is_dtls_ is whether DTLS is being used with this AEAD.
-  bool is_dtls_;
   // variable_nonce_included_in_record_ is true if the variable nonce
   // for a record is included as a prefix before the ciphertext.
   bool variable_nonce_included_in_record_ : 1;
@@ -2856,7 +2838,8 @@
   enum ssl_encryption_level_t write_level = ssl_encryption_initial;
 
   // version is the protocol version, or zero if the version has not yet been
-  // set.
+  // set. In clients offering 0-RTT, this version will initially be set to the
+  // early version, then switched to the final version.
   uint16_t version = 0;
 
   // early_data_skipped is the amount of early data that has been skipped by the
diff --git a/ssl/s3_lib.cc b/ssl/s3_lib.cc
index 472d1ab..14d9b64 100644
--- a/ssl/s3_lib.cc
+++ b/ssl/s3_lib.cc
@@ -187,8 +187,8 @@
     return false;
   }
 
-  s3->aead_read_ctx = SSLAEADContext::CreateNullCipher(SSL_is_dtls(ssl));
-  s3->aead_write_ctx = SSLAEADContext::CreateNullCipher(SSL_is_dtls(ssl));
+  s3->aead_read_ctx = SSLAEADContext::CreateNullCipher();
+  s3->aead_write_ctx = SSLAEADContext::CreateNullCipher();
   s3->hs = ssl_handshake_new(ssl);
   if (!s3->aead_read_ctx || !s3->aead_write_ctx || !s3->hs) {
     return false;
diff --git a/ssl/ssl_aead_ctx.cc b/ssl/ssl_aead_ctx.cc
index 5079ca2..652a5e9 100644
--- a/ssl/ssl_aead_ctx.cc
+++ b/ssl/ssl_aead_ctx.cc
@@ -34,25 +34,20 @@
 
 BSSL_NAMESPACE_BEGIN
 
-SSLAEADContext::SSLAEADContext(uint16_t version_arg, bool is_dtls_arg,
-                               const SSL_CIPHER *cipher_arg)
+SSLAEADContext::SSLAEADContext(const SSL_CIPHER *cipher_arg)
     : cipher_(cipher_arg),
-      version_(version_arg),
-      is_dtls_(is_dtls_arg),
       variable_nonce_included_in_record_(false),
       random_variable_nonce_(false),
       xor_fixed_nonce_(false),
       omit_length_in_ad_(false),
       ad_is_header_(false) {
-  OPENSSL_memset(fixed_nonce_, 0, sizeof(fixed_nonce_));
   CreateRecordNumberEncrypter();
 }
 
 SSLAEADContext::~SSLAEADContext() {}
 
-UniquePtr<SSLAEADContext> SSLAEADContext::CreateNullCipher(bool is_dtls) {
-  return MakeUnique<SSLAEADContext>(0 /* version */, is_dtls,
-                                    nullptr /* cipher */);
+UniquePtr<SSLAEADContext> SSLAEADContext::CreateNullCipher() {
+  return MakeUnique<SSLAEADContext>(/*cipher=*/nullptr);
 }
 
 UniquePtr<SSLAEADContext> SSLAEADContext::Create(
@@ -90,14 +85,11 @@
                             enc_key.size() + mac_key.size() + fixed_iv.size());
   }
 
-  UniquePtr<SSLAEADContext> aead_ctx =
-      MakeUnique<SSLAEADContext>(version, is_dtls, cipher);
+  UniquePtr<SSLAEADContext> aead_ctx = MakeUnique<SSLAEADContext>(cipher);
   if (!aead_ctx) {
     return nullptr;
   }
 
-  assert(aead_ctx->ProtocolVersion() == protocol_version);
-
   if (!EVP_AEAD_CTX_init_with_direction(
           aead_ctx->ctx_.get(), aead, enc_key.data(), enc_key.size(),
           EVP_AEAD_DEFAULT_TAG_LENGTH, direction)) {
@@ -165,36 +157,8 @@
 }
 
 UniquePtr<SSLAEADContext> SSLAEADContext::CreatePlaceholderForQUIC(
-    uint16_t version, const SSL_CIPHER *cipher) {
-  return MakeUnique<SSLAEADContext>(version, false, cipher);
-}
-
-void SSLAEADContext::SetVersionIfNullCipher(uint16_t version) {
-  if (is_null_cipher()) {
-    version_ = version;
-  }
-}
-
-uint16_t SSLAEADContext::ProtocolVersion() const {
-  uint16_t protocol_version;
-  if(!ssl_protocol_version_from_wire(&protocol_version, version_)) {
-    assert(false);
-    return 0;
-  }
-  return protocol_version;
-}
-
-uint16_t SSLAEADContext::RecordVersion() const {
-  if (version_ == 0) {
-    assert(is_null_cipher());
-    return is_dtls_ ? DTLS1_VERSION : TLS1_VERSION;
-  }
-
-  if (ProtocolVersion() <= TLS1_2_VERSION) {
-    return version_;
-  }
-
-  return is_dtls_ ? DTLS1_2_VERSION : TLS1_2_VERSION;
+    const SSL_CIPHER *cipher) {
+  return MakeUnique<SSLAEADContext>(cipher);
 }
 
 size_t SSLAEADContext::ExplicitNonceLen() const {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 650a0b6..e598ac7 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -15440,6 +15440,10 @@
 		earlyData:     true,
 		flags: []string{
 			"-expect-version", strconv.Itoa(VersionTLS13),
+			// EMS and RI are always reported as supported when we report
+			// TLS 1.3.
+			"-expect-extended-master-secret",
+			"-expect-secure-renegotiation",
 		},
 	})
 
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc
index 766013f..fd9ab0a 100644
--- a/ssl/tls13_client.cc
+++ b/ssl/tls13_client.cc
@@ -73,20 +73,22 @@
   // write state. The two ClientHello sequence numbers must align, and handshake
   // write keys must be installed early to ACK the EncryptedExtensions.
   //
-  // We do not currently implement DTLS 1.3 and, in QUIC, the caller handles
-  // 0-RTT data, so we can skip installing 0-RTT keys and act as if there is one
-  // write level. If we implement DTLS 1.3, we'll need to model this better.
+  // TODO(crbug.com/42290594): We do not currently implement DTLS 1.3 and, in
+  // QUIC, the caller handles 0-RTT data, so we can skip installing 0-RTT keys
+  // and act as if there is one write level. Now that we're implementing
+  // DTLS 1.3, switch the abstraction to the DTLS/QUIC model where handshake
+  // keys write keys are installed immediately, but the TLS record layer
+  // internally waits to activate that epoch until the 0-RTT channel is closed.
   if (ssl->quic_method == nullptr) {
     if (level == ssl_encryption_initial) {
       bssl::UniquePtr<SSLAEADContext> null_ctx =
-          SSLAEADContext::CreateNullCipher(SSL_is_dtls(ssl));
+          SSLAEADContext::CreateNullCipher();
       if (!null_ctx ||
           !ssl->method->set_write_state(ssl, ssl_encryption_initial,
                                         std::move(null_ctx),
                                         /*secret_for_quic=*/{})) {
         return false;
       }
-      ssl->s3->aead_write_ctx->SetVersionIfNullCipher(ssl->s3->version);
     } else {
       assert(level == ssl_encryption_handshake);
       if (!tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_seal,
diff --git a/ssl/tls13_enc.cc b/ssl/tls13_enc.cc
index 4a642da..19631f5 100644
--- a/ssl/tls13_enc.cc
+++ b/ssl/tls13_enc.cc
@@ -191,8 +191,7 @@
   if (ssl->quic_method != nullptr) {
     // Install a placeholder SSLAEADContext so that SSL accessors work. The
     // encryption itself will be handled by the SSL_QUIC_METHOD.
-    traffic_aead =
-        SSLAEADContext::CreatePlaceholderForQUIC(version, session->cipher);
+    traffic_aead = SSLAEADContext::CreatePlaceholderForQUIC(session->cipher);
     secret_for_quic = traffic_secret;
   } else {
     // Look up cipher suite properties.
diff --git a/ssl/tls_record.cc b/ssl/tls_record.cc
index 6e2bdd4..685b78a 100644
--- a/ssl/tls_record.cc
+++ b/ssl/tls_record.cc
@@ -143,7 +143,7 @@
 bool ssl_needs_record_splitting(const SSL *ssl) {
 #if !defined(BORINGSSL_UNSAFE_FUZZER_MODE)
   return !ssl->s3->aead_write_ctx->is_null_cipher() &&
-         ssl->s3->aead_write_ctx->ProtocolVersion() < TLS1_1_VERSION &&
+         ssl_protocol_version(ssl) < TLS1_1_VERSION &&
          (ssl->mode & SSL_MODE_CBC_RECORD_SPLITTING) != 0 &&
          SSL_CIPHER_is_block_cipher(ssl->s3->aead_write_ctx->cipher());
 #else
@@ -172,6 +172,19 @@
   return ssl_open_record_discard;
 }
 
+static uint16_t tls_record_version(const SSL *ssl) {
+  if (ssl->s3->version == 0) {
+    // Before the version is determined, outgoing records use TLS 1.0 for
+    // historical compatibility requirements.
+    return TLS1_VERSION;
+  }
+
+  // TLS 1.3 freezes the record version at TLS 1.2. Previous ones use the
+  // version itself.
+  return ssl_protocol_version(ssl) >= TLS1_3_VERSION ? TLS1_2_VERSION
+                                                     : ssl->s3->version;
+}
+
 ssl_open_record_t tls_open_record(SSL *ssl, uint8_t *out_type,
                                   Span<uint8_t> *out, size_t *out_consumed,
                                   uint8_t *out_alert, Span<uint8_t> in) {
@@ -204,7 +217,7 @@
     // version negotiation failure alerts.
     version_ok = (version >> 8) == SSL3_VERSION_MAJOR;
   } else {
-    version_ok = version == ssl->s3->aead_read_ctx->RecordVersion();
+    version_ok = version == tls_record_version(ssl);
   }
 
   if (!version_ok) {
@@ -232,12 +245,10 @@
 
   *out_consumed = in.size() - CBS_len(&cbs);
 
-  if (ssl->s3->version != 0 &&
-      ssl_protocol_version(ssl) >= TLS1_3_VERSION &&
-      SSL_in_init(ssl) &&
-      type == SSL3_RT_CHANGE_CIPHER_SPEC &&
-      ciphertext_len == 1 &&
-      CBS_data(&body)[0] == 1) {
+  // In TLS 1.3, during the handshake, skip ChangeCipherSpec records.
+  if (ssl->s3->version != 0 && ssl_protocol_version(ssl) >= TLS1_3_VERSION &&
+      SSL_in_init(ssl) && type == SSL3_RT_CHANGE_CIPHER_SPEC &&
+      Span<const uint8_t>(body) == Span<const uint8_t>({SSL3_MT_CCS})) {
     ssl->s3->empty_record_count++;
     if (ssl->s3->empty_record_count > kMaxEmptyRecords) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MANY_EMPTY_FRAGMENTS);
@@ -280,9 +291,8 @@
   ssl->s3->read_sequence++;
 
   // TLS 1.3 hides the record type inside the encrypted data.
-  bool has_padding =
-      !ssl->s3->aead_read_ctx->is_null_cipher() &&
-      ssl->s3->aead_read_ctx->ProtocolVersion() >= TLS1_3_VERSION;
+  bool has_padding = !ssl->s3->aead_read_ctx->is_null_cipher() &&
+                     ssl_protocol_version(ssl) >= TLS1_3_VERSION;
 
   // If there is padding, the plaintext limit includes the padding, but includes
   // extra room for the inner content type.
@@ -351,8 +361,7 @@
   SSLAEADContext *aead = ssl->s3->aead_write_ctx.get();
   uint8_t *extra_in = NULL;
   size_t extra_in_len = 0;
-  if (!aead->is_null_cipher() &&
-      aead->ProtocolVersion() >= TLS1_3_VERSION) {
+  if (!aead->is_null_cipher() && ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     // TLS 1.3 hides the actual record type inside the encrypted data.
     extra_in = &type;
     extra_in_len = 1;
@@ -375,8 +384,7 @@
     out_prefix[0] = type;
   }
 
-  uint16_t record_version = aead->RecordVersion();
-
+  uint16_t record_version = tls_record_version(ssl);
   out_prefix[1] = record_version >> 8;
   out_prefix[2] = record_version & 0xff;
   out_prefix[3] = ciphertext_len >> 8;
@@ -421,7 +429,7 @@
                                         uint8_t type, size_t in_len) {
   size_t extra_in_len = 0;
   if (!ssl->s3->aead_write_ctx->is_null_cipher() &&
-      ssl->s3->aead_write_ctx->ProtocolVersion() >= TLS1_3_VERSION) {
+      ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     // TLS 1.3 adds an extra byte for encrypted record type.
     extra_in_len = 1;
   }
@@ -551,8 +559,7 @@
     // without specifying how to handle it. JDK11 misuses it to signal
     // full-duplex connection close after the handshake. As a workaround, skip
     // user_canceled as in TLS 1.2. This matches NSS and OpenSSL.
-    if (ssl->s3->version != 0 &&
-        ssl_protocol_version(ssl) >= TLS1_3_VERSION &&
+    if (ssl->s3->version != 0 && ssl_protocol_version(ssl) >= TLS1_3_VERSION &&
         alert_descr != SSL_AD_USER_CANCELLED) {
       *out_alert = SSL_AD_DECODE_ERROR;
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ALERT);
@@ -593,7 +600,7 @@
   ret += ssl->s3->aead_write_ctx->MaxOverhead();
   // TLS 1.3 needs an extra byte for the encrypted record type.
   if (!ssl->s3->aead_write_ctx->is_null_cipher() &&
-      ssl->s3->aead_write_ctx->ProtocolVersion() >= TLS1_3_VERSION) {
+      ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     ret += 1;
   }
   if (ssl_needs_record_splitting(ssl)) {