Fix ext_pre_shared_key_clienthello_length calculation.

If we're dropping the PSK extension due to an HRR with mismatched hash
(looking back at that, we could easily have avoided that nuisance...
I've filed [0] on rfc8446bis), we don't predict the length correctly.
The consequence is we don't pad the second ClientHello to the desired
range. Fix this and add an assert.

[0] https://github.com/tlswg/tls13-spec/issues/1227

Change-Id: I47d880b6bdafa95840f7513b6b7718851f22554d
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/47998
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index da89afb..33621a3 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -1937,10 +1937,27 @@
 //
 // https://tools.ietf.org/html/rfc8446#section-4.2.11
 
-static size_t ext_pre_shared_key_clienthello_length(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
+static bool should_offer_psk(const SSL_HANDSHAKE *hs) {
+  const SSL *const ssl = hs->ssl;
   if (hs->max_version < TLS1_3_VERSION || ssl->session == nullptr ||
       ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION) {
+    return false;
+  }
+
+  // Per RFC 8446 section 4.1.4, skip offering the session if the selected
+  // cipher in HelloRetryRequest does not match. This avoids performing the
+  // transcript hash transformation for multiple hashes.
+  if (ssl->s3->used_hello_retry_request &&
+      ssl->session->cipher->algorithm_prf != hs->new_cipher->algorithm_prf) {
+    return false;
+  }
+
+  return true;
+}
+
+static size_t ext_pre_shared_key_clienthello_length(const SSL_HANDSHAKE *hs) {
+  const SSL *const ssl = hs->ssl;
+  if (!should_offer_psk(hs)) {
     return 0;
   }
 
@@ -1953,16 +1970,7 @@
                                                bool *out_needs_binder) {
   const SSL *const ssl = hs->ssl;
   *out_needs_binder = false;
-  if (hs->max_version < TLS1_3_VERSION || ssl->session == nullptr ||
-      ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION) {
-    return true;
-  }
-
-  // Per RFC 8446 section 4.1.4, skip offering the session if the selected
-  // cipher in HelloRetryRequest does not match. This avoids performing the
-  // transcript hash transformation for multiple hashes.
-  if (ssl->s3->used_hello_retry_request &&
-      ssl->session->cipher->algorithm_prf != hs->new_cipher->algorithm_prf) {
+  if (!should_offer_psk(hs)) {
     return true;
   }
 
@@ -3309,8 +3317,8 @@
     last_was_empty = false;
   }
 
+  size_t psk_extension_len = ext_pre_shared_key_clienthello_length(hs);
   if (!SSL_is_dtls(ssl) && !ssl->quic_method) {
-    size_t psk_extension_len = ext_pre_shared_key_clienthello_length(hs);
     header_len +=
         SSL3_HM_HEADER_LENGTH + 2 + CBB_len(&extensions) + psk_extension_len;
     size_t padding_len = 0;
@@ -3353,11 +3361,14 @@
   }
 
   // The PSK extension must be last, including after the padding.
+  const size_t len_before = CBB_len(&extensions);
   if (!ext_pre_shared_key_add_clienthello(hs, &extensions,
                                           out_needs_psk_binder)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return false;
   }
+  assert(psk_extension_len == CBB_len(&extensions) - len_before);
+  (void)len_before;  // |assert| is omitted in release builds.
 
   // Discard empty extensions blocks.
   if (CBB_len(&extensions) == 0) {