Make the bssl::SealRecord out_suffix arg fixed length.

Similarly, add EVP_AEAD_CTX_tag_len which computes the exact tag length
for required by EVP_AEAD_CTX_seal_scatter.

Change-Id: I069b0ad16fab314fd42f6048a3c1dc45e8376f7f
Reviewed-on: https://boringssl-review.googlesource.com/18324
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/cipher_extra/e_aesctrhmac.c b/crypto/cipher_extra/e_aesctrhmac.c
index 2982d0d..3034b8f 100644
--- a/crypto/cipher_extra/e_aesctrhmac.c
+++ b/crypto/cipher_extra/e_aesctrhmac.c
@@ -254,6 +254,7 @@
     aead_aes_ctr_hmac_sha256_seal_scatter,
     aead_aes_ctr_hmac_sha256_open_gather,
     NULL /* get_iv */,
+    NULL /* tag_len */,
 };
 
 static const EVP_AEAD aead_aes_256_ctr_hmac_sha256 = {
@@ -270,6 +271,7 @@
     aead_aes_ctr_hmac_sha256_seal_scatter,
     aead_aes_ctr_hmac_sha256_open_gather,
     NULL /* get_iv */,
+    NULL /* tag_len */,
 };
 
 const EVP_AEAD *EVP_aead_aes_128_ctr_hmac_sha256(void) {
diff --git a/crypto/cipher_extra/e_aesgcmsiv.c b/crypto/cipher_extra/e_aesgcmsiv.c
index 2dd1267..6adcf17 100644
--- a/crypto/cipher_extra/e_aesgcmsiv.c
+++ b/crypto/cipher_extra/e_aesgcmsiv.c
@@ -520,6 +520,7 @@
     aead_aes_gcm_siv_asm_seal_scatter,
     NULL /* open_gather */,
     NULL /* get_iv */,
+    NULL /* tag_len */,
 };
 
 static const EVP_AEAD aead_aes_256_gcm_siv_asm = {
@@ -536,6 +537,7 @@
     aead_aes_gcm_siv_asm_seal_scatter,
     NULL /* open_gather */,
     NULL /* get_iv */,
+    NULL /* tag_len */,
 };
 
 #endif  /* X86_64 && !NO_ASM */
@@ -804,6 +806,7 @@
     aead_aes_gcm_siv_seal_scatter,
     aead_aes_gcm_siv_open_gather,
     NULL /* get_iv */,
+    NULL /* tag_len */,
 };
 
 static const EVP_AEAD aead_aes_256_gcm_siv = {
@@ -820,6 +823,7 @@
     aead_aes_gcm_siv_seal_scatter,
     aead_aes_gcm_siv_open_gather,
     NULL /* get_iv */,
+    NULL /* tag_len */,
 };
 
 #if defined(OPENSSL_X86_64) && !defined(OPENSSL_NO_ASM)
diff --git a/crypto/cipher_extra/e_chacha20poly1305.c b/crypto/cipher_extra/e_chacha20poly1305.c
index 515b60f..162e14b 100644
--- a/crypto/cipher_extra/e_chacha20poly1305.c
+++ b/crypto/cipher_extra/e_chacha20poly1305.c
@@ -294,6 +294,7 @@
     aead_chacha20_poly1305_seal_scatter,
     aead_chacha20_poly1305_open_gather,
     NULL, /* get_iv */
+    NULL, /* tag_len */
 };
 
 const EVP_AEAD *EVP_aead_chacha20_poly1305(void) {
diff --git a/crypto/cipher_extra/e_ssl3.c b/crypto/cipher_extra/e_ssl3.c
index f2eb357..dc43713 100644
--- a/crypto/cipher_extra/e_ssl3.c
+++ b/crypto/cipher_extra/e_ssl3.c
@@ -123,13 +123,33 @@
   return 1;
 }
 
+static size_t aead_ssl3_tag_len(const EVP_AEAD_CTX *ctx, const size_t in_len,
+                                const size_t extra_in_len) {
+  assert(extra_in_len == 0);
+  const AEAD_SSL3_CTX *ssl3_ctx = (AEAD_SSL3_CTX*)ctx->aead_state;
+
+  const size_t digest_len = EVP_MD_CTX_size(&ssl3_ctx->md_ctx);
+  if (EVP_CIPHER_CTX_mode(&ssl3_ctx->cipher_ctx) != EVP_CIPH_CBC_MODE) {
+    // The NULL cipher.
+    return digest_len;
+  }
+
+  const size_t block_size = EVP_CIPHER_CTX_block_size(&ssl3_ctx->cipher_ctx);
+  /* An overflow of |in_len + digest_len| doesn't affect the result mod
+   * |block_size|, provided that |block_size| is a smaller power of two. */
+  assert(block_size != 0 && (block_size & (block_size - 1)) == 0);
+  const size_t pad_len = block_size - ((in_len + digest_len) % block_size);
+  return digest_len + pad_len;
+}
+
 static int aead_ssl3_seal_scatter(const EVP_AEAD_CTX *ctx, uint8_t *out,
                                   uint8_t *out_tag, size_t *out_tag_len,
-                                  size_t max_out_tag_len, const uint8_t *nonce,
-                                  size_t nonce_len, const uint8_t *in,
-                                  size_t in_len, const uint8_t *extra_in,
-                                  size_t extra_in_len, const uint8_t *ad,
-                                  size_t ad_len) {
+                                  const size_t max_out_tag_len,
+                                  const uint8_t *nonce, const size_t nonce_len,
+                                  const uint8_t *in, const size_t in_len,
+                                  const uint8_t *extra_in,
+                                  const size_t extra_in_len, const uint8_t *ad,
+                                  const size_t ad_len) {
   AEAD_SSL3_CTX *ssl3_ctx = (AEAD_SSL3_CTX *)ctx->aead_state;
 
   if (!ssl3_ctx->cipher_ctx.encrypt) {
@@ -144,8 +164,7 @@
     return 0;
   }
 
-  const size_t max_overhead = EVP_AEAD_max_overhead(ctx->aead);
-  if (max_out_tag_len < max_overhead) {
+  if (max_out_tag_len < aead_ssl3_tag_len(ctx, in_len, extra_in_len)) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
     return 0;
   }
@@ -222,7 +241,7 @@
     return 0;
   }
   tag_len += len;
-  assert(tag_len <= max_overhead);
+  assert(tag_len == aead_ssl3_tag_len(ctx, in_len, extra_in_len));
 
   *out_tag_len = tag_len;
   return 1;
@@ -372,6 +391,7 @@
     aead_ssl3_seal_scatter,
     NULL, /* open_gather */
     aead_ssl3_get_iv,
+    aead_ssl3_tag_len,
 };
 
 static const EVP_AEAD aead_aes_256_cbc_sha1_ssl3 = {
@@ -388,6 +408,7 @@
     aead_ssl3_seal_scatter,
     NULL, /* open_gather */
     aead_ssl3_get_iv,
+    aead_ssl3_tag_len,
 };
 
 static const EVP_AEAD aead_des_ede3_cbc_sha1_ssl3 = {
@@ -404,6 +425,7 @@
     aead_ssl3_seal_scatter,
     NULL, /* open_gather */
     aead_ssl3_get_iv,
+    aead_ssl3_tag_len,
 };
 
 static const EVP_AEAD aead_null_sha1_ssl3 = {
@@ -420,6 +442,7 @@
     aead_ssl3_seal_scatter,
     NULL, /* open_gather */
     NULL, /* get_iv */
+    aead_ssl3_tag_len,
 };
 
 const EVP_AEAD *EVP_aead_aes_128_cbc_sha1_ssl3(void) {
diff --git a/crypto/cipher_extra/e_tls.c b/crypto/cipher_extra/e_tls.c
index 14d5377..ca206ab 100644
--- a/crypto/cipher_extra/e_tls.c
+++ b/crypto/cipher_extra/e_tls.c
@@ -99,13 +99,33 @@
   return 1;
 }
 
+static size_t aead_tls_tag_len(const EVP_AEAD_CTX *ctx, const size_t in_len,
+                               const size_t extra_in_len) {
+  assert(extra_in_len == 0);
+  AEAD_TLS_CTX *tls_ctx = (AEAD_TLS_CTX *)ctx->aead_state;
+
+  const size_t hmac_len = HMAC_size(&tls_ctx->hmac_ctx);
+  if (EVP_CIPHER_CTX_mode(&tls_ctx->cipher_ctx) != EVP_CIPH_CBC_MODE) {
+    // The NULL cipher.
+    return hmac_len;
+  }
+
+  const size_t block_size = EVP_CIPHER_CTX_block_size(&tls_ctx->cipher_ctx);
+  /* An overflow of |in_len + hmac_len| doesn't affect the result mod
+   * |block_size|, provided that |block_size| is a smaller power of two. */
+  assert(block_size != 0 && (block_size & (block_size - 1)) == 0);
+  const size_t pad_len = block_size - (in_len + hmac_len) % block_size;
+  return hmac_len + pad_len;
+}
+
 static int aead_tls_seal_scatter(const EVP_AEAD_CTX *ctx, uint8_t *out,
                                  uint8_t *out_tag, size_t *out_tag_len,
-                                 size_t max_out_tag_len, const uint8_t *nonce,
-                                 size_t nonce_len, const uint8_t *in,
-                                 size_t in_len, const uint8_t *extra_in,
-                                 size_t extra_in_len, const uint8_t *ad,
-                                 size_t ad_len) {
+                                 const size_t max_out_tag_len,
+                                 const uint8_t *nonce, const size_t nonce_len,
+                                 const uint8_t *in, const size_t in_len,
+                                 const uint8_t *extra_in,
+                                 const size_t extra_in_len, const uint8_t *ad,
+                                 const size_t ad_len) {
   AEAD_TLS_CTX *tls_ctx = (AEAD_TLS_CTX *)ctx->aead_state;
 
   if (!tls_ctx->cipher_ctx.encrypt) {
@@ -120,8 +140,7 @@
     return 0;
   }
 
-  const size_t max_overhead = EVP_AEAD_max_overhead(ctx->aead);
-  if (max_out_tag_len < max_overhead) {
+  if (max_out_tag_len < aead_tls_tag_len(ctx, in_len, extra_in_len)) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
     return 0;
   }
@@ -173,7 +192,8 @@
    * block from encrypting the input and split the result between |out| and
    * |out_tag|. Then feed the rest. */
 
-  size_t early_mac_len = (block_size - (in_len % block_size)) % block_size;
+  const size_t early_mac_len =
+      (block_size - (in_len % block_size) % block_size);
   if (early_mac_len != 0) {
     assert(len + block_size - early_mac_len == in_len);
     uint8_t buf[EVP_MAX_BLOCK_LENGTH];
@@ -212,8 +232,8 @@
   if (!EVP_EncryptFinal_ex(&tls_ctx->cipher_ctx, out_tag + tag_len, &len)) {
     return 0;
   }
-  tag_len += len;
-  assert(tag_len <= max_overhead);
+  assert(len == 0); /* Padding is explicit. */
+  assert(tag_len == aead_tls_tag_len(ctx, in_len, extra_in_len));
 
   *out_tag_len = tag_len;
   return 1;
@@ -467,6 +487,7 @@
     aead_tls_seal_scatter,
     NULL, /* open_gather */
     NULL, /* get_iv */
+    aead_tls_tag_len,
 };
 
 static const EVP_AEAD aead_aes_128_cbc_sha1_tls_implicit_iv = {
@@ -483,6 +504,7 @@
     aead_tls_seal_scatter,
     NULL,            /* open_gather */
     aead_tls_get_iv, /* get_iv */
+    aead_tls_tag_len,
 };
 
 static const EVP_AEAD aead_aes_128_cbc_sha256_tls = {
@@ -499,6 +521,7 @@
     aead_tls_seal_scatter,
     NULL, /* open_gather */
     NULL, /* get_iv */
+    aead_tls_tag_len,
 };
 
 static const EVP_AEAD aead_aes_256_cbc_sha1_tls = {
@@ -515,6 +538,7 @@
     aead_tls_seal_scatter,
     NULL, /* open_gather */
     NULL, /* get_iv */
+    aead_tls_tag_len,
 };
 
 static const EVP_AEAD aead_aes_256_cbc_sha1_tls_implicit_iv = {
@@ -531,6 +555,7 @@
     aead_tls_seal_scatter,
     NULL,            /* open_gather */
     aead_tls_get_iv, /* get_iv */
+    aead_tls_tag_len,
 };
 
 static const EVP_AEAD aead_aes_256_cbc_sha256_tls = {
@@ -547,6 +572,7 @@
     aead_tls_seal_scatter,
     NULL, /* open_gather */
     NULL, /* get_iv */
+    aead_tls_tag_len,
 };
 
 static const EVP_AEAD aead_aes_256_cbc_sha384_tls = {
@@ -563,6 +589,7 @@
     aead_tls_seal_scatter,
     NULL, /* open_gather */
     NULL, /* get_iv */
+    aead_tls_tag_len,
 };
 
 static const EVP_AEAD aead_des_ede3_cbc_sha1_tls = {
@@ -579,6 +606,7 @@
     aead_tls_seal_scatter,
     NULL, /* open_gather */
     NULL, /* get_iv */
+    aead_tls_tag_len,
 };
 
 static const EVP_AEAD aead_des_ede3_cbc_sha1_tls_implicit_iv = {
@@ -595,6 +623,7 @@
     aead_tls_seal_scatter,
     NULL,            /* open_gather */
     aead_tls_get_iv, /* get_iv */
+    aead_tls_tag_len,
 };
 
 static const EVP_AEAD aead_null_sha1_tls = {
@@ -611,6 +640,7 @@
     aead_tls_seal_scatter,
     NULL, /* open_gather */
     NULL, /* get_iv */
+    aead_tls_tag_len,
 };
 
 const EVP_AEAD *EVP_aead_aes_128_cbc_sha1_tls(void) {
diff --git a/crypto/fipsmodule/cipher/aead.c b/crypto/fipsmodule/cipher/aead.c
index 79139e6..ed30209 100644
--- a/crypto/fipsmodule/cipher/aead.c
+++ b/crypto/fipsmodule/cipher/aead.c
@@ -264,3 +264,21 @@
 
   return ctx->aead->get_iv(ctx, out_iv, out_len);
 }
+
+int EVP_AEAD_CTX_tag_len(const EVP_AEAD_CTX *ctx, size_t *out_tag_len,
+                         const size_t in_len, const size_t extra_in_len) {
+  assert(ctx->aead->seal_scatter_supports_extra_in || !extra_in_len);
+
+  if (ctx->aead->tag_len) {
+    *out_tag_len = ctx->aead->tag_len(ctx, in_len, extra_in_len);
+    return 1;
+  }
+
+  if (extra_in_len + ctx->tag_len < extra_in_len) {
+    OPENSSL_PUT_ERROR(CIPHER, ERR_R_OVERFLOW);
+    *out_tag_len = 0;
+    return 0;
+  }
+  *out_tag_len = extra_in_len + ctx->tag_len;
+  return 1;
+}
diff --git a/crypto/fipsmodule/cipher/e_aes.c b/crypto/fipsmodule/cipher/e_aes.c
index 7c7521b..2c6fc41 100644
--- a/crypto/fipsmodule/cipher/e_aes.c
+++ b/crypto/fipsmodule/cipher/e_aes.c
@@ -1217,7 +1217,7 @@
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE);
     return 0;
   }
-  if (max_out_tag_len < ctx->tag_len + extra_in_len) {
+  if (max_out_tag_len < extra_in_len + ctx->tag_len) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
     return 0;
   }
@@ -1226,11 +1226,6 @@
     return 0;
   }
 
-  if (max_out_tag_len < ctx->tag_len) {
-    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
-    return 0;
-  }
-
   const AES_KEY *key = &gcm_ctx->ks.ks;
 
   OPENSSL_memcpy(&gcm, &gcm_ctx->gcm, sizeof(gcm));
diff --git a/crypto/fipsmodule/cipher/internal.h b/crypto/fipsmodule/cipher/internal.h
index ea59723..02335e0 100644
--- a/crypto/fipsmodule/cipher/internal.h
+++ b/crypto/fipsmodule/cipher/internal.h
@@ -107,6 +107,9 @@
 
   int (*get_iv)(const EVP_AEAD_CTX *ctx, const uint8_t **out_iv,
                 size_t *out_len);
+
+  size_t (*tag_len)(const EVP_AEAD_CTX *ctx, size_t in_Len,
+                    size_t extra_in_len);
 };
 
 /* aes_ctr_set_key initialises |*aes_key| using |key_bytes| bytes from |key|,
diff --git a/include/openssl/aead.h b/include/openssl/aead.h
index f0a67d8..dd2e418 100644
--- a/include/openssl/aead.h
+++ b/include/openssl/aead.h
@@ -389,6 +389,15 @@
 OPENSSL_EXPORT int EVP_AEAD_CTX_get_iv(const EVP_AEAD_CTX *ctx,
                                        const uint8_t **out_iv, size_t *out_len);
 
+/* EVP_AEAD_CTX_tag_len computes the exact byte length of the tag written by
+ * |EVP_AEAD_CTX_seal_scatter| and writes it to |*out_tag_len|. It returns one
+ * on success or zero on error. |in_len| and |extra_in_len| must equal the
+ * arguments of the same names passed to |EVP_AEAD_CTX_seal_scatter|. */
+OPENSSL_EXPORT int EVP_AEAD_CTX_tag_len(const EVP_AEAD_CTX *ctx,
+                                        size_t *out_tag_len,
+                                        const size_t in_len,
+                                        const size_t extra_in_len);
+
 
 #if defined(__cplusplus)
 }  /* extern C */
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 31af857..6bbf15d 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -4620,8 +4620,16 @@
                                            uint8_t *out_alert,
                                            Span<uint8_t> in);
 
-OPENSSL_EXPORT size_t SealRecordPrefixLen(SSL *ssl, size_t plaintext_len);
-OPENSSL_EXPORT size_t SealRecordMaxSuffixLen(SSL *ssl);
+OPENSSL_EXPORT size_t SealRecordPrefixLen(const SSL *ssl, size_t plaintext_len);
+
+/* SealRecordSuffixLen returns the length of the suffix written by |SealRecord|.
+ *
+ * |plaintext_len| must be equal to the size of the plaintext passed to
+ * |SealRecord|.
+ *
+ * |plaintext_len| must not exceed |SSL3_RT_MAX_PLAINTEXT_LENGTH|. The returned
+ * suffix length will not exceed |SSL3_RT_MAX_ENCRYPTED_OVERHEAD|. */
+OPENSSL_EXPORT size_t SealRecordSuffixLen(const SSL *ssl, size_t plaintext_len);
 
 /*  *** EXPERIMENTAL -- DO NOT USE ***
  *
@@ -4630,9 +4638,9 @@
  * returns true on success or false if an error occurred.
  *
  * The length of |out_prefix| must equal |SealRecordPrefixLen|. The length of
- * |out| must equal the length of |in|. The length of |out_suffix| must equal
- * |MaxSealRecordSuffixLen|. |*out_suffix_len| is set to the actual number of
- * bytes written to |out_suffix|.
+ * |out| must equal the length of |in|, which must not exceed
+ * |SSL3_RT_MAX_PLAINTEXT_LENGTH|. The length of |out_suffix| must equal
+ * |SealRecordSuffixLen|.
  *
  * If enabled, |SealRecord| may perform TLS 1.0 CBC 1/n-1 record splitting.
  * |SealRecordPrefixLen| accounts for the required overhead if that is the case.
@@ -4641,7 +4649,7 @@
  * |out_prefix| and |out_suffix| may not alias anything. */
 OPENSSL_EXPORT bool SealRecord(SSL *ssl, Span<uint8_t> out_prefix,
                                Span<uint8_t> out, Span<uint8_t> out_suffix,
-                               size_t *out_suffix_len, Span<const uint8_t> in);
+                               Span<const uint8_t> in);
 
 }  // namespace bssl
 
diff --git a/ssl/internal.h b/ssl/internal.h
index bd1d112..cb39a93 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -498,10 +498,12 @@
   /* MaxOverhead returns the maximum overhead of calling |Seal|. */
   size_t MaxOverhead() const;
 
-  /* MaxSuffixLen returns the maximum suffix length written by |SealScatter|.
-   * |extra_in_len| should equal the argument of the same name passed to
-   * |SealScatter|. */
-  size_t MaxSuffixLen(size_t extra_in_len) const;
+  /* SuffixLen calculates the suffix length written by |SealScatter| and writes
+   * it to |*out_suffix_len|. It returns true on success and false on error.
+   * |in_len| and |extra_in_len| should equal the argument of the same names
+   * passed to |SealScatter|. */
+  bool SuffixLen(size_t *out_suffix_len, size_t in_len,
+                 size_t extra_in_len) const;
 
   /* Open authenticates and decrypts |in_len| bytes from |in| in-place. On
    * success, it sets |*out| to the plaintext in |in| and returns true.
@@ -523,19 +525,17 @@
    * success and zero on error.
    *
    * On successful return, exactly |ExplicitNonceLen| bytes are written to
-   * |out_prefix|, |in_len| bytes to |out|, and up to |MaxSuffixLen| bytes to
-   * |out_suffix|. |*out_suffix_len| is set to the actual number of bytes
-   * written to |out_suffix|.
+   * |out_prefix|, |in_len| bytes to |out|, and |SuffixLen| bytes to
+   * |out_suffix|.
    *
    * |extra_in| may point to an additional plaintext buffer. If present,
    * |extra_in_len| additional bytes are encrypted and authenticated, and the
-   * ciphertext is written to the beginning of |out_suffix|.  |MaxSuffixLen|
-   * may be used to size |out_suffix| accordingly.
+   * ciphertext is written to the beginning of |out_suffix|. |SuffixLen| should
+   * be used to size |out_suffix| accordingly.
    *
    * If |in| and |out| alias then |out| must be == |in|. Other arguments may not
    * alias anything. */
   bool SealScatter(uint8_t *out_prefix, uint8_t *out, uint8_t *out_suffix,
-                   size_t *out_suffix_len, size_t max_out_suffix_len,
                    uint8_t type, uint16_t wire_version, const uint8_t seqnum[8],
                    const uint8_t *in, size_t in_len, const uint8_t *extra_in,
                    size_t extra_in_len);
diff --git a/ssl/ssl_aead_ctx.cc b/ssl/ssl_aead_ctx.cc
index 3b8d1b2..53dff78 100644
--- a/ssl/ssl_aead_ctx.cc
+++ b/ssl/ssl_aead_ctx.cc
@@ -150,15 +150,21 @@
   return 0;
 }
 
-size_t SSLAEADContext::MaxSuffixLen(size_t extra_in_len) const {
-  return extra_in_len +
-         (is_null_cipher() || FUZZER_MODE
-              ? 0
-              : EVP_AEAD_max_overhead(EVP_AEAD_CTX_aead(ctx_.get())));
+bool SSLAEADContext::SuffixLen(size_t *out_suffix_len, const size_t in_len,
+                               const size_t extra_in_len) const {
+  if (is_null_cipher() || FUZZER_MODE) {
+    *out_suffix_len = extra_in_len;
+    return true;
+  }
+  return !!EVP_AEAD_CTX_tag_len(ctx_.get(), out_suffix_len, in_len,
+                                extra_in_len);
 }
 
 size_t SSLAEADContext::MaxOverhead() const {
-  return ExplicitNonceLen() + MaxSuffixLen(0);
+  return ExplicitNonceLen() +
+         (is_null_cipher() || FUZZER_MODE
+              ? 0
+              : EVP_AEAD_max_overhead(EVP_AEAD_CTX_aead(ctx_.get())));
 }
 
 size_t SSLAEADContext::GetAdditionalData(uint8_t out[13], uint8_t type,
@@ -255,18 +261,20 @@
 }
 
 bool SSLAEADContext::SealScatter(uint8_t *out_prefix, uint8_t *out,
-                                 uint8_t *out_suffix, size_t *out_suffix_len,
-                                 size_t max_out_suffix_len, uint8_t type,
+                                 uint8_t *out_suffix, uint8_t type,
                                  uint16_t wire_version, const uint8_t seqnum[8],
                                  const uint8_t *in, size_t in_len,
                                  const uint8_t *extra_in, size_t extra_in_len) {
-  if ((in != out && buffers_alias(in, in_len, out, in_len)) ||
-      buffers_alias(in, in_len, out_suffix, max_out_suffix_len)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_OUTPUT_ALIASES_INPUT);
+  const size_t prefix_len = ExplicitNonceLen();
+  size_t suffix_len;
+  if (!SuffixLen(&suffix_len, in_len, extra_in_len)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
     return false;
   }
-  if (extra_in_len > max_out_suffix_len) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
+  if ((in != out && buffers_alias(in, in_len, out, in_len)) ||
+      buffers_alias(in, in_len, out_prefix, prefix_len) ||
+      buffers_alias(in, in_len, out_suffix, suffix_len)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_OUTPUT_ALIASES_INPUT);
     return false;
   }
 
@@ -274,7 +282,6 @@
     /* Handle the initial NULL cipher. */
     OPENSSL_memmove(out, in, in_len);
     OPENSSL_memmove(out_suffix, extra_in, extra_in_len);
-    *out_suffix_len = extra_in_len;
     return true;
   }
 
@@ -327,32 +334,38 @@
     }
   }
 
-  return !!EVP_AEAD_CTX_seal_scatter(
-      ctx_.get(), out, out_suffix, out_suffix_len, max_out_suffix_len, nonce,
+  size_t written_suffix_len;
+  bool result = !!EVP_AEAD_CTX_seal_scatter(
+      ctx_.get(), out, out_suffix, &written_suffix_len, suffix_len, nonce,
       nonce_len, in, in_len, extra_in, extra_in_len, ad, ad_len);
+  assert(!result || written_suffix_len == suffix_len);
+  return result;
 }
 
 bool SSLAEADContext::Seal(uint8_t *out, size_t *out_len, size_t max_out_len,
                           uint8_t type, uint16_t wire_version,
                           const uint8_t seqnum[8], const uint8_t *in,
                           size_t in_len) {
-  size_t prefix_len = ExplicitNonceLen();
-  if (in_len + prefix_len < in_len) {
+  const size_t prefix_len = ExplicitNonceLen();
+  size_t suffix_len;
+  if (!SuffixLen(&suffix_len, in_len, 0)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
+    return false;
+  }
+  if (in_len + prefix_len < in_len ||
+      in_len + prefix_len + suffix_len < in_len + prefix_len) {
     OPENSSL_PUT_ERROR(CIPHER, SSL_R_RECORD_TOO_LARGE);
     return false;
   }
-  if (in_len + prefix_len > max_out_len) {
+  if (in_len + prefix_len + suffix_len > max_out_len) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
     return false;
   }
 
-  size_t suffix_len;
-  if (!SealScatter(out, out + prefix_len, out + prefix_len + in_len,
-                   &suffix_len, max_out_len - prefix_len - in_len, type,
+  if (!SealScatter(out, out + prefix_len, out + prefix_len + in_len, type,
                    wire_version, seqnum, in, in_len, 0, 0)) {
     return false;
   }
-  assert(suffix_len <= MaxSuffixLen(0));
   *out_len = prefix_len + in_len + suffix_len;
   return true;
 }
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index 2297964..4556fb7 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -3773,12 +3773,11 @@
   const std::vector<uint8_t> record = {1, 2, 3, 4, 5};
   std::vector<uint8_t> prefix(
       bssl::SealRecordPrefixLen(client.get(), record.size())),
-      body(record.size()), suffix(bssl::SealRecordMaxSuffixLen(client.get()));
-  size_t suffix_size;
+      body(record.size()),
+      suffix(bssl::SealRecordSuffixLen(client.get(), record.size()));
   ASSERT_TRUE(bssl::SealRecord(client.get(), bssl::MakeSpan(prefix),
                                bssl::MakeSpan(body), bssl::MakeSpan(suffix),
-                               &suffix_size, record));
-  suffix.resize(suffix_size);
+                               record));
 
   std::vector<uint8_t> sealed;
   sealed.insert(sealed.end(), prefix.begin(), prefix.end());
@@ -3819,12 +3818,10 @@
   std::vector<uint8_t> record = plaintext;
   std::vector<uint8_t> prefix(
       bssl::SealRecordPrefixLen(client.get(), record.size())),
-      suffix(bssl::SealRecordMaxSuffixLen(client.get()));
-  size_t suffix_size;
+      suffix(bssl::SealRecordSuffixLen(client.get(), record.size()));
   ASSERT_TRUE(bssl::SealRecord(client.get(), bssl::MakeSpan(prefix),
                                bssl::MakeSpan(record), bssl::MakeSpan(suffix),
-                               &suffix_size, record));
-  suffix.resize(suffix_size);
+                               record));
   record.insert(record.begin(), prefix.begin(), prefix.end());
   record.insert(record.end(), suffix.begin(), suffix.end());
 
@@ -3860,12 +3857,10 @@
   std::vector<uint8_t> record = plaintext;
   std::vector<uint8_t> prefix(
       bssl::SealRecordPrefixLen(client.get(), record.size())),
-      suffix(bssl::SealRecordMaxSuffixLen(client.get()));
-  size_t suffix_size;
+      suffix(bssl::SealRecordSuffixLen(client.get(), record.size()));
   ASSERT_TRUE(bssl::SealRecord(client.get(), bssl::MakeSpan(prefix),
                                bssl::MakeSpan(record), bssl::MakeSpan(suffix),
-                               &suffix_size, record));
-  suffix.resize(suffix_size);
+                               record));
   record.insert(record.begin(), prefix.begin(), prefix.end());
   record.insert(record.end(), suffix.begin(), suffix.end());
   record.insert(record.end(), {5, 4, 3, 2, 1});
@@ -3901,8 +3896,8 @@
   std::vector<uint8_t> record = {1, 2, 3, 4, 5};
   std::vector<uint8_t> prefix(
       bssl::SealRecordPrefixLen(client.get(), record.size())),
-      suffix(bssl::SealRecordMaxSuffixLen(client.get()));
-  size_t suffix_size;
+      body(record.size()),
+      suffix(bssl::SealRecordSuffixLen(client.get(), record.size()));
 
   auto expect_err = []() {
     int err = ERR_get_error();
@@ -3912,31 +3907,31 @@
   };
   EXPECT_FALSE(bssl::SealRecord(
       client.get(), bssl::MakeSpan(prefix.data(), prefix.size() - 1),
-      bssl::MakeSpan(record), bssl::MakeSpan(suffix), &suffix_size, record));
+      bssl::MakeSpan(record), bssl::MakeSpan(suffix), record));
   expect_err();
   EXPECT_FALSE(bssl::SealRecord(
       client.get(), bssl::MakeSpan(prefix.data(), prefix.size() + 1),
-      bssl::MakeSpan(record), bssl::MakeSpan(suffix), &suffix_size, record));
+      bssl::MakeSpan(record), bssl::MakeSpan(suffix), record));
   expect_err();
 
   EXPECT_FALSE(
       bssl::SealRecord(client.get(), bssl::MakeSpan(prefix),
                        bssl::MakeSpan(record.data(), record.size() - 1),
-                       bssl::MakeSpan(suffix), &suffix_size, record));
+                       bssl::MakeSpan(suffix), record));
   expect_err();
   EXPECT_FALSE(
       bssl::SealRecord(client.get(), bssl::MakeSpan(prefix),
                        bssl::MakeSpan(record.data(), record.size() + 1),
-                       bssl::MakeSpan(suffix), &suffix_size, record));
+                       bssl::MakeSpan(suffix), record));
   expect_err();
 
   EXPECT_FALSE(bssl::SealRecord(
       client.get(), bssl::MakeSpan(prefix), bssl::MakeSpan(record),
-      bssl::MakeSpan(suffix.data(), suffix.size() - 1), &suffix_size, record));
+      bssl::MakeSpan(suffix.data(), suffix.size() - 1), record));
   expect_err();
   EXPECT_FALSE(bssl::SealRecord(
       client.get(), bssl::MakeSpan(prefix), bssl::MakeSpan(record),
-      bssl::MakeSpan(suffix.data(), suffix.size() + 1), &suffix_size, record));
+      bssl::MakeSpan(suffix.data(), suffix.size() + 1), record));
   expect_err();
 }
 
diff --git a/ssl/tls_record.cc b/ssl/tls_record.cc
index 46132e1..745ddb8 100644
--- a/ssl/tls_record.cc
+++ b/ssl/tls_record.cc
@@ -345,20 +345,35 @@
 }
 
 static int do_seal_record(SSL *ssl, uint8_t *out_prefix, uint8_t *out,
-                          uint8_t *out_suffix, size_t *out_suffix_len,
-                          const size_t max_out_suffix_len, uint8_t type,
-                          const uint8_t *in, const size_t in_len) {
-  assert(in == out || !buffers_alias(in, in_len, out, in_len));
-  assert(!buffers_alias(in, in_len, out_prefix, ssl_record_prefix_len(ssl)));
-  assert(!buffers_alias(in, in_len, out_suffix, max_out_suffix_len));
-
-  /* TLS 1.3 hides the actual record type inside the encrypted data. */
+                          uint8_t *out_suffix, uint8_t type, const uint8_t *in,
+                          const size_t in_len) {
   uint8_t *extra_in = NULL;
   size_t extra_in_len = 0;
   if (!ssl->s3->aead_write_ctx->is_null_cipher() &&
       ssl->s3->aead_write_ctx->version() >= TLS1_3_VERSION) {
+    /* TLS 1.3 hides the actual record type inside the encrypted data. */
     extra_in = &type;
     extra_in_len = 1;
+  }
+
+  size_t suffix_len;
+  if (!ssl->s3->aead_write_ctx->SuffixLen(&suffix_len, in_len, extra_in_len)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
+    return 0;
+  }
+  size_t ciphertext_len =
+      ssl->s3->aead_write_ctx->ExplicitNonceLen() + suffix_len;
+  if (ciphertext_len + in_len < ciphertext_len) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
+    return 0;
+  }
+  ciphertext_len += in_len;
+
+  assert(in == out || !buffers_alias(in, in_len, out, in_len));
+  assert(!buffers_alias(in, in_len, out_prefix, ssl_record_prefix_len(ssl)));
+  assert(!buffers_alias(in, in_len, out_suffix, suffix_len));
+
+  if (extra_in_len) {
     out_prefix[0] = SSL3_RT_APPLICATION_DATA;
   } else {
     out_prefix[0] = type;
@@ -377,26 +392,17 @@
   }
   out_prefix[1] = wire_version >> 8;
   out_prefix[2] = wire_version & 0xff;
+  out_prefix[3] = ciphertext_len >> 8;
+  out_prefix[4] = ciphertext_len & 0xff;
 
-  /* Write the ciphertext, leaving two bytes for the length. */
-  if (!ssl->s3->aead_write_ctx->SealScatter(
-          out_prefix + SSL3_RT_HEADER_LENGTH, out, out_suffix, out_suffix_len,
-          max_out_suffix_len, type, wire_version, ssl->s3->write_sequence, in,
-          in_len, extra_in, extra_in_len) ||
+  if (!ssl->s3->aead_write_ctx->SealScatter(out_prefix + SSL3_RT_HEADER_LENGTH,
+                                            out, out_suffix, type, wire_version,
+                                            ssl->s3->write_sequence, in, in_len,
+                                            extra_in, extra_in_len) ||
       !ssl_record_sequence_update(ssl->s3->write_sequence, 8)) {
     return 0;
   }
 
-  /* Fill in the length. */
-  const size_t ciphertext_len =
-      ssl->s3->aead_write_ctx->ExplicitNonceLen() + in_len + *out_suffix_len;
-  if (ciphertext_len >= 1 << 15) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
-    return 0;
-  }
-  out_prefix[3] = ciphertext_len >> 8;
-  out_prefix[4] = ciphertext_len & 0xff;
-
   ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_HEADER, out_prefix,
                       SSL3_RT_HEADER_LENGTH);
   return 1;
@@ -419,28 +425,34 @@
   return ret;
 }
 
-static size_t tls_seal_scatter_max_suffix_len(const SSL *ssl) {
-  size_t 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() &&
+static bool tls_seal_scatter_suffix_len(const SSL *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->s3->aead_write_ctx->version() >= TLS1_3_VERSION) {
-    ret += 1;
+    /* TLS 1.3 adds an extra byte for encrypted record type. */
+    extra_in_len = 1;
   }
-  return ret;
+  if (type == SSL3_RT_APPLICATION_DATA &&  // clang-format off
+      in_len > 1 &&
+      ssl_needs_record_splitting(ssl)) {
+    /* With record splitting enabled, the first byte gets sealed into a separate
+     * record which is written into the prefix. */
+    in_len -= 1;
+  }
+  return ssl->s3->aead_write_ctx->SuffixLen(out_suffix_len, in_len, extra_in_len);
 }
 
 /* tls_seal_scatter_record seals a new record of type |type| and body |in| and
  * splits it between |out_prefix|, |out|, and |out_suffix|. Exactly
  * |tls_seal_scatter_prefix_len| bytes are written to |out_prefix|, |in_len|
- * bytes to |out|, and up to |tls_seal_scatter_max_suffix_len| bytes to
- * |out_suffix|. |*out_suffix_len| is set to the actual number of bytes written
- * to |out_suffix|. It returns one on success and zero on error. If enabled,
+ * bytes to |out|, and |tls_seal_scatter_suffix_len| bytes to |out_suffix|. It
+ * 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 int tls_seal_scatter_record(SSL *ssl, uint8_t *out_prefix, uint8_t *out,
-                                   uint8_t *out_suffix, size_t *out_suffix_len,
-                                   size_t max_out_suffix_len, uint8_t type,
-                                   const uint8_t *in, size_t in_len) {
+                            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);
@@ -450,17 +462,17 @@
     uint8_t *split_body = out_prefix + prefix_len;
     uint8_t *split_suffix = split_body + 1;
 
-    /* TODO(martinkr): Make AEAD code not complain if max_suffix_len is lower
-     * than |EVP_AEAD_max_overhead| but still sufficiently large. */
-    size_t split_max_suffix_len = ssl->s3->aead_write_ctx->MaxSuffixLen(0);
-    size_t split_suffix_len = 0;
-    if (!do_seal_record(ssl, out_prefix, split_body, split_suffix,
-                        &split_suffix_len, split_max_suffix_len, type, in, 1)) {
+    if (!do_seal_record(ssl, out_prefix, split_body, split_suffix, type, in,
+                        1)) {
       return 0;
     }
 
-    size_t split_record_len = prefix_len + 1 + split_suffix_len;
-
+    size_t split_record_suffix_len;
+    if (!ssl->s3->aead_write_ctx->SuffixLen(&split_record_suffix_len, 1, 0)) {
+      assert(false);
+      return 0;
+    }
+    const size_t split_record_len = prefix_len + 1 + split_record_suffix_len;
     assert(SSL3_RT_HEADER_LENGTH + ssl_cipher_get_record_split_len(
                                        ssl->s3->aead_write_ctx->cipher()) ==
            split_record_len);
@@ -468,8 +480,8 @@
     /* Write the n-1-byte fragment. The header gets split between |out_prefix|
      * (header[:-1]) and |out| (header[-1:]). */
     uint8_t tmp_prefix[SSL3_RT_HEADER_LENGTH];
-    if (!do_seal_record(ssl, tmp_prefix, out + 1, out_suffix, out_suffix_len,
-                        max_out_suffix_len, type, in + 1, in_len - 1)) {
+    if (!do_seal_record(ssl, tmp_prefix, out + 1, out_suffix, type, in + 1,
+                        in_len - 1)) {
       return 0;
     }
     assert(tls_seal_scatter_prefix_len(ssl, type, in_len) ==
@@ -480,8 +492,7 @@
     return 1;
   }
 
-  return do_seal_record(ssl, out_prefix, out, out_suffix, out_suffix_len,
-                        max_out_suffix_len, type, in, in_len);
+  return do_seal_record(ssl, out_prefix, out, out_suffix, type, in, in_len);
 }
 
 int tls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out_len,
@@ -492,12 +503,16 @@
   }
 
   const size_t prefix_len = tls_seal_scatter_prefix_len(ssl, type, in_len);
-
-  if (in_len + prefix_len < in_len) {
+  size_t suffix_len;
+  if (!tls_seal_scatter_suffix_len(ssl, &suffix_len, type, in_len)) {
+    return false;
+  }
+  if (in_len + prefix_len < in_len ||
+      prefix_len + in_len + suffix_len < prefix_len + in_len) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
     return 0;
   }
-  if (max_out_len < in_len + prefix_len) {
+  if (max_out_len < in_len + prefix_len + suffix_len) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
     return 0;
   }
@@ -505,16 +520,7 @@
   uint8_t *prefix = out;
   uint8_t *body = out + prefix_len;
   uint8_t *suffix = body + in_len;
-  size_t max_suffix_len = max_out_len - prefix_len - in_len;
-  size_t suffix_len = 0;
-
-  if (!tls_seal_scatter_record(ssl, prefix, body, suffix, &suffix_len,
-                               max_suffix_len, type, in, in_len)) {
-    return 0;
-  }
-
-  if (prefix_len + in_len + suffix_len < prefix_len + in_len) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
+  if (!tls_seal_scatter_record(ssl, prefix, body, suffix, type, in, in_len)) {
     return 0;
   }
 
@@ -630,17 +636,26 @@
   return ret;
 }
 
-size_t SealRecordPrefixLen(SSL *ssl, size_t record_len) {
+size_t SealRecordPrefixLen(const SSL *ssl, const size_t record_len) {
   return tls_seal_scatter_prefix_len(ssl, SSL3_RT_APPLICATION_DATA, record_len);
 }
 
-size_t SealRecordMaxSuffixLen(SSL *ssl) {
-  return tls_seal_scatter_max_suffix_len(ssl);
+size_t SealRecordSuffixLen(const SSL *ssl, const size_t plaintext_len) {
+  assert(plaintext_len <= SSL3_RT_MAX_PLAIN_LENGTH);
+  size_t suffix_len;
+  if (!tls_seal_scatter_suffix_len(ssl, &suffix_len, SSL3_RT_APPLICATION_DATA,
+                                   plaintext_len)) {
+    assert(false);
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+  assert(suffix_len <= SSL3_RT_MAX_ENCRYPTED_OVERHEAD);
+  return suffix_len;
 }
 
 bool SealRecord(SSL *ssl, const Span<uint8_t> out_prefix,
                 const Span<uint8_t> out, Span<uint8_t> out_suffix,
-                size_t *out_suffix_len, const Span<const uint8_t> in) {
+                const Span<const uint8_t> in) {
   // This API is a work in progress and currently only works for TLS 1.2 servers
   // and below.
   if (SSL_in_init(ssl) ||
@@ -653,13 +668,13 @@
 
   if (out_prefix.size() != SealRecordPrefixLen(ssl, in.size()) ||
       out.size() != in.size() ||
-      out_suffix.size() != SealRecordMaxSuffixLen(ssl)) {
+      out_suffix.size() != SealRecordSuffixLen(ssl, in.size())) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
     return false;
   }
-  return tls_seal_scatter_record(
-      ssl, out_prefix.data(), out.data(), out_suffix.data(), out_suffix_len,
-      out_suffix.size(), SSL3_RT_APPLICATION_DATA, in.data(), in.size());
+  return tls_seal_scatter_record(ssl, out_prefix.data(), out.data(),
+                                 out_suffix.data(), SSL3_RT_APPLICATION_DATA,
+                                 in.data(), in.size());
 }
 
 }  // namespace bssl