Add API functions for OpenSSL ciphers with sized output.

These take an argument |out_size| for the actually allocated size of the
output buffer, and will fail if the size is insufficient to store the
encrypted/decrypted data.

Bug: 42290361
Change-Id: Id662e4274f01e0ea95ce5a944943bd4258fe8f06
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/83007
Reviewed-by: David Benjamin <davidben@google.com>
Auto-Submit: Rudolf Polzer <rpolzer@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/cipher/cipher_test.cc b/crypto/cipher/cipher_test.cc
index e21a52d..a2c3dbd 100644
--- a/crypto/cipher/cipher_test.cc
+++ b/crypto/cipher/cipher_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include <limits.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -116,6 +117,27 @@
   abort();
 }
 
+enum class API {
+  // kCipher tests the EVP_Cipher() API. Arcane!
+  kCipher,
+  // kUnsized tests the EVP_CipherUpdate_ex() API. No size checking!
+  kUnsized,
+  // kSized tests the EVP_CipherUpdate_Ex2() API. With size checking!
+  kSized,
+};
+
+static const char *APIToString(API api) {
+  switch (api) {
+    case API::kCipher:
+      return "Cipher";
+    case API::kUnsized:
+      return "Unsized";
+    case API::kSized:
+      return "Sized";
+  }
+  abort();
+}
+
 // MaybeCopyCipherContext, if |copy| is true, replaces |*ctx| with a, hopefully
 // equivalent, copy of it.
 static bool MaybeCopyCipherContext(bool copy,
@@ -132,13 +154,15 @@
 }
 
 static void TestCipherAPI(const EVP_CIPHER *cipher, Operation op, bool padding,
-                          bool copy, bool in_place, bool use_evp_cipher,
-                          size_t chunk_size, bssl::Span<const uint8_t> key,
+                          bool copy, bool in_place, API api, size_t chunk_size,
+                          bssl::Span<const uint8_t> key,
                           bssl::Span<const uint8_t> iv,
                           bssl::Span<const uint8_t> plaintext,
                           bssl::Span<const uint8_t> ciphertext,
                           bssl::Span<const uint8_t> aad,
                           bssl::Span<const uint8_t> tag) {
+  SCOPED_TRACE(APIToString(api));
+
   bool encrypt = op == Operation::kEncrypt;
   bool is_custom_cipher =
       EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_CUSTOM_CIPHER;
@@ -193,18 +217,27 @@
   while (!aad.empty()) {
     size_t todo =
         chunk_size == 0 ? aad.size() : std::min(aad.size(), chunk_size);
-    if (use_evp_cipher) {
-      // AEADs always use the "custom cipher" return value convention. Passing a
-      // null output pointer triggers the AAD logic.
-      ASSERT_TRUE(is_custom_cipher);
-      ASSERT_EQ(static_cast<int>(todo),
-                EVP_Cipher(ctx.get(), nullptr, aad.data(), todo));
-    } else {
-      int len;
-      ASSERT_TRUE(EVP_CipherUpdate(ctx.get(), nullptr, &len, aad.data(), todo));
-      // Although it doesn't output anything, |EVP_CipherUpdate| should claim to
-      // output the input length.
-      EXPECT_EQ(len, static_cast<int>(todo));
+    switch (api) {
+      case API::kCipher:
+        // AEADs always use the "custom cipher" return value convention. Passing
+        // a null output pointer triggers the AAD logic.
+        ASSERT_TRUE(is_custom_cipher);
+        ASSERT_EQ(static_cast<int>(todo),
+                  EVP_Cipher(ctx.get(), nullptr, aad.data(), todo));
+        break;
+      case API::kUnsized: {
+        int len;
+        ASSERT_TRUE(
+            EVP_CipherUpdate(ctx.get(), nullptr, &len, aad.data(), todo));
+        // Although it doesn't output anything, |EVP_CipherUpdate| should claim
+        // to output the input length.
+        EXPECT_EQ(len, static_cast<int>(todo));
+        break;
+      }
+      case API::kSized: {
+        ASSERT_TRUE(EVP_CipherUpdateAAD(ctx.get(), aad.data(), todo));
+        break;
+      }
     }
     aad = aad.subspan(todo);
   }
@@ -229,46 +262,78 @@
     size_t todo = chunk_size == 0 ? in.size() : std::min(in.size(), chunk_size);
     EXPECT_LE(todo, static_cast<size_t>(INT_MAX));
     ASSERT_TRUE(MaybeCopyCipherContext(copy, &ctx));
-    if (use_evp_cipher) {
-      // |EVP_Cipher| sometimes returns the number of bytes written, or -1 on
-      // error, and sometimes 1 or 0, implicitly writing |in_len| bytes.
-      if (is_custom_cipher) {
-        len = EVP_Cipher(ctx.get(), result.data() + total, in.data(), todo);
-      } else {
-        ASSERT_EQ(
-            1, EVP_Cipher(ctx.get(), result.data() + total, in.data(), todo));
-        len = static_cast<int>(todo);
+    switch (api) {
+      case API::kCipher:
+        // |EVP_Cipher| sometimes returns the number of bytes written, or -1 on
+        // error, and sometimes 1 or 0, implicitly writing |in_len| bytes.
+        if (is_custom_cipher) {
+          len = EVP_Cipher(ctx.get(), result.data() + total, in.data(), todo);
+        } else {
+          ASSERT_EQ(
+              1, EVP_Cipher(ctx.get(), result.data() + total, in.data(), todo));
+          len = static_cast<int>(todo);
+        }
+        break;
+      case API::kUnsized:
+        ASSERT_TRUE(EVP_CipherUpdate(ctx.get(), result.data() + total, &len,
+                                     in.data(), static_cast<int>(todo)));
+        break;
+      case API::kSized: {
+        size_t len_sz;
+        ASSERT_TRUE(EVP_CipherUpdate_ex(ctx.get(), result.data() + total,
+                                        &len_sz, result.size() - total,
+                                        in.data(), static_cast<int>(todo)));
+        len = static_cast<int>(len_sz);
+        break;
       }
-    } else {
-      ASSERT_TRUE(EVP_CipherUpdate(ctx.get(), result.data() + total, &len,
-                                   in.data(), static_cast<int>(todo)));
     }
     ASSERT_GE(len, 0);
     total += static_cast<size_t>(len);
     in = in.subspan(todo);
   }
   if (op == Operation::kInvalidDecrypt) {
-    if (use_evp_cipher) {
-      // Only the "custom cipher" return value convention can report failures.
-      // Passing all nulls should act like |EVP_CipherFinal_ex|.
-      ASSERT_TRUE(is_custom_cipher);
-      EXPECT_EQ(-1, EVP_Cipher(ctx.get(), nullptr, nullptr, 0));
-    } else {
-      // Invalid padding and invalid tags all appear as a failed
-      // |EVP_CipherFinal_ex|.
-      EXPECT_FALSE(EVP_CipherFinal_ex(ctx.get(), result.data() + total, &len));
+    switch (api) {
+      case API::kCipher:
+        // Only the "custom cipher" return value convention can report failures.
+        // Passing all nulls should act like |EVP_CipherFinal_ex|.
+        ASSERT_TRUE(is_custom_cipher);
+        EXPECT_EQ(-1, EVP_Cipher(ctx.get(), nullptr, nullptr, 0));
+        break;
+      case API::kSized:
+        // Invalid padding and invalid tags all appear as a failed
+        // |EVP_CipherFinal_ex|.
+        EXPECT_FALSE(
+            EVP_CipherFinal_ex(ctx.get(), result.data() + total, &len));
+        break;
+      case API::kUnsized: {
+        size_t len_sz;
+        EXPECT_FALSE(EVP_CipherFinal_ex2(ctx.get(), result.data() + total,
+                                         &len_sz, result.size() - total));
+        len = static_cast<int>(len_sz);
+        break;
+      }
     }
   } else {
-    if (use_evp_cipher) {
-      if (is_custom_cipher) {
-        // Only the "custom cipher" convention has an |EVP_CipherFinal_ex|
-        // equivalent.
-        len = EVP_Cipher(ctx.get(), nullptr, nullptr, 0);
-      } else {
-        len = 0;
+    switch (api) {
+      case API::kCipher:
+        if (is_custom_cipher) {
+          // Only the "custom cipher" convention has an |EVP_CipherFinal_ex|
+          // equivalent.
+          len = EVP_Cipher(ctx.get(), nullptr, nullptr, 0);
+        } else {
+          len = 0;
+        }
+        break;
+      case API::kUnsized:
+        ASSERT_TRUE(EVP_CipherFinal_ex(ctx.get(), result.data() + total, &len));
+        break;
+      case API::kSized: {
+        size_t len_sz;
+        ASSERT_TRUE(EVP_CipherFinal_ex2(ctx.get(), result.data() + total,
+                                        &len_sz, result.size() - total));
+        len = static_cast<int>(len_sz);
+        break;
       }
-    } else {
-      ASSERT_TRUE(EVP_CipherFinal_ex(ctx.get(), result.data() + total, &len));
     }
     ASSERT_GE(len, 0);
     total += static_cast<size_t>(len);
@@ -374,6 +439,164 @@
   }
 }
 
+enum class SizedAPIFailCase { kNever, kUpdate1, kUpdate2, kFinal };
+
+static const char *SizedAPIFailCaseToString(SizedAPIFailCase fail_case) {
+  switch (fail_case) {
+    case SizedAPIFailCase::kNever:
+      return "Never";
+    case SizedAPIFailCase::kUpdate1:
+      return "Update1";
+    case SizedAPIFailCase::kUpdate2:
+      return "Update2";
+    case SizedAPIFailCase::kFinal:
+      return "Final";
+  }
+  abort();
+}
+
+static size_t RoundDown(size_t n, size_t block_size) {
+  return n / block_size * block_size;
+}
+
+static void TestSizedAPIRangeChecks(const EVP_CIPHER *cipher, Operation op,
+                                    bool padding, bssl::Span<const uint8_t> key,
+                                    bssl::Span<const uint8_t> iv,
+                                    bssl::Span<const uint8_t> plaintext,
+                                    bssl::Span<const uint8_t> ciphertext,
+                                    bssl::Span<const uint8_t> aad,
+                                    bssl::Span<const uint8_t> tag) {
+  bool encrypt = op == Operation::kEncrypt;
+  bssl::Span<const uint8_t> in = encrypt ? plaintext : ciphertext;
+  bssl::Span<const uint8_t> expected = encrypt ? ciphertext : plaintext;
+  bool is_aead = EVP_CIPHER_mode(cipher) == EVP_CIPH_GCM_MODE;
+
+  // Some |EVP_CIPHER|s take a variable-length key, and need to first be
+  // configured with the key length, which requires configuring the cipher.
+  bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
+  ASSERT_TRUE(ctx);
+  ASSERT_TRUE(EVP_CipherInit_ex(ctx.get(), cipher, /*engine=*/nullptr,
+                                /*key=*/nullptr, /*iv=*/nullptr,
+                                encrypt ? 1 : 0));
+  ASSERT_TRUE(EVP_CIPHER_CTX_set_key_length(ctx.get(), key.size()));
+  if (!padding) {
+    ASSERT_TRUE(EVP_CIPHER_CTX_set_padding(ctx.get(), 0));
+  }
+
+  // Configure the key.
+  ASSERT_TRUE(EVP_CipherInit_ex(ctx.get(), /*cipher=*/nullptr,
+                                /*engine=*/nullptr, key.data(),
+                                /*iv=*/nullptr,
+                                /*enc=*/-1));
+
+  // Configure the IV to run the actual operation. Callers that wish to use
+  // a key for multiple, potentially concurrent, operations will likely copy
+  // at this point. The |EVP_CIPHER_CTX| API uses the same type to represent
+  // a pre-computed key schedule and a streaming operation.
+  if (is_aead) {
+    ASSERT_LE(iv.size(), size_t{INT_MAX});
+    ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_IVLEN,
+                                    static_cast<int>(iv.size()), nullptr));
+    ASSERT_EQ(EVP_CIPHER_CTX_iv_length(ctx.get()), iv.size());
+  } else {
+    ASSERT_EQ(iv.size(), EVP_CIPHER_CTX_iv_length(ctx.get()));
+  }
+  ASSERT_TRUE(EVP_CipherInit_ex(ctx.get(), /*cipher=*/nullptr,
+                                /*engine=*/nullptr,
+                                /*key=*/nullptr, iv.data(), /*enc=*/-1));
+
+  if (is_aead) {
+    if (!encrypt) {
+      ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG,
+                                      tag.size(),
+                                      const_cast<uint8_t *>(tag.data())));
+    }
+    ASSERT_TRUE(EVP_CipherUpdateAAD(ctx.get(), aad.data(), aad.size()));
+  }
+
+  size_t block_size = EVP_CIPHER_block_size(cipher);
+  for (size_t len_to_split = 0; len_to_split <= in.size(); ++len_to_split) {
+    SCOPED_TRACE(len_to_split);
+    for (SizedAPIFailCase fail_case :
+         {SizedAPIFailCase::kNever, SizedAPIFailCase::kUpdate1,
+          SizedAPIFailCase::kUpdate2, SizedAPIFailCase::kFinal}) {
+      SCOPED_TRACE(SizedAPIFailCaseToString(fail_case));
+
+      bssl::UniquePtr<EVP_CIPHER_CTX> thisctx(EVP_CIPHER_CTX_new());
+      ASSERT_TRUE(thisctx);
+      ASSERT_TRUE(EVP_CIPHER_CTX_copy(thisctx.get(), ctx.get()));
+
+      size_t update_size = RoundDown(
+          (padding && !encrypt)
+              // When decrypting, the final block will not be output right away.
+              ? std::max(in.size(), size_t{1}) - 1
+              : in.size(),
+          block_size);
+      bssl::Span<const uint8_t> in1 = in.first(len_to_split);
+      size_t update1_size = RoundDown(
+          (padding && !encrypt)
+              // When decrypting, the final block will not be output right away.
+              ? std::max(in1.size(), size_t{1}) - 1
+              : in1.size(),
+          block_size);
+      size_t update2_size = update_size - update1_size;
+      bssl::Span<const uint8_t> in2 = in.subspan(len_to_split);
+      size_t final_size = expected.size() - update1_size - update2_size;
+
+      // Now, finally, the cipher is ready to be abused.
+      size_t len;
+      if (fail_case == SizedAPIFailCase::kUpdate1 && update1_size > 0) {
+        std::vector<uint8_t> buf(update1_size - 1);
+        EXPECT_FALSE(EVP_CipherUpdate_ex(thisctx.get(), buf.data(), &len,
+                                         buf.size(), in1.data(), in1.size()));
+        EXPECT_EQ(len, size_t{0});
+        continue;
+      } else {
+        std::vector<uint8_t> buf(update1_size);
+        EXPECT_TRUE(EVP_CipherUpdate_ex(thisctx.get(), buf.data(), &len,
+                                        buf.size(), in1.data(), in1.size()));
+        EXPECT_EQ(len, update1_size);
+        EXPECT_EQ(Bytes(expected.first(update1_size)), Bytes(buf));
+      }
+      if (fail_case == SizedAPIFailCase::kUpdate2 && update2_size > 0) {
+        std::vector<uint8_t> buf(update2_size - 1);
+        EXPECT_FALSE(EVP_CipherUpdate_ex(thisctx.get(), buf.data(), &len,
+                                         buf.size(), in2.data(), in2.size()));
+        EXPECT_EQ(len, size_t{0});
+        continue;
+      } else {
+        std::vector<uint8_t> buf(update2_size);
+        EXPECT_TRUE(EVP_CipherUpdate_ex(thisctx.get(), buf.data(), &len,
+                                        buf.size(), in2.data(), in2.size()));
+        EXPECT_EQ(len, update2_size);
+        EXPECT_EQ(Bytes(expected.subspan(update1_size, update2_size)),
+                  Bytes(buf));
+      }
+      if (fail_case == SizedAPIFailCase::kFinal && final_size > 0) {
+        std::vector<uint8_t> buf(final_size - 1);
+        EXPECT_FALSE(
+            EVP_CipherFinal_ex2(thisctx.get(), buf.data(), &len, buf.size()));
+        EXPECT_EQ(len, size_t{0});
+        continue;
+      } else {
+        std::vector<uint8_t> buf(final_size);
+        EXPECT_TRUE(
+            EVP_CipherFinal_ex2(thisctx.get(), buf.data(), &len, buf.size()));
+        EXPECT_EQ(len, final_size);
+        EXPECT_EQ(Bytes(expected.subspan(update1_size + update2_size)),
+                  Bytes(buf));
+      }
+      if (encrypt && is_aead) {
+        uint8_t rtag[16];
+        ASSERT_LE(tag.size(), sizeof(rtag));
+        ASSERT_TRUE(EVP_CIPHER_CTX_ctrl(thisctx.get(), EVP_CTRL_AEAD_GET_TAG,
+                                        tag.size(), rtag));
+        EXPECT_EQ(Bytes(tag), Bytes(rtag, tag.size()));
+      }
+    }
+  }
+}
+
 static void TestCipher(const EVP_CIPHER *cipher, Operation input_op,
                        bool padding, bssl::Span<const uint8_t> key,
                        bssl::Span<const uint8_t> iv,
@@ -403,13 +626,13 @@
         SCOPED_TRACE(in_place);
         for (bool copy : {false, true}) {
           SCOPED_TRACE(copy);
-          TestCipherAPI(cipher, op, padding, copy, in_place,
-                        /*use_evp_cipher=*/false, chunk_size, key, iv,
-                        plaintext, ciphertext, aad, tag);
+          TestCipherAPI(cipher, op, padding, copy, in_place, API::kSized,
+                        chunk_size, key, iv, plaintext, ciphertext, aad, tag);
+          TestCipherAPI(cipher, op, padding, copy, in_place, API::kUnsized,
+                        chunk_size, key, iv, plaintext, ciphertext, aad, tag);
           if (!padding && chunk_size % block_size == 0) {
-            TestCipherAPI(cipher, op, padding, copy, in_place,
-                          /*use_evp_cipher=*/true, chunk_size, key, iv,
-                          plaintext, ciphertext, aad, tag);
+            TestCipherAPI(cipher, op, padding, copy, in_place, API::kCipher,
+                          chunk_size, key, iv, plaintext, ciphertext, aad, tag);
           }
         }
         if (!padding) {
@@ -418,12 +641,17 @@
         }
       }
     }
+    if (op != Operation::kInvalidDecrypt) {
+      TestSizedAPIRangeChecks(cipher, op, padding, key, iv, plaintext,
+                              ciphertext, aad, tag);
+    }
   }
 }
 
 static void CipherFileTest(FileTest *t) {
   std::string cipher_str;
   ASSERT_TRUE(t->GetAttribute(&cipher_str, "Cipher"));
+  SCOPED_TRACE(cipher_str);
   const EVP_CIPHER *cipher = GetCipher(cipher_str);
   ASSERT_TRUE(cipher);
 
@@ -501,6 +729,8 @@
 
                   std::string key_size;
                   ASSERT_TRUE(t->GetInstruction(&key_size, "keySize"));
+                  SCOPED_TRACE(std::string{"aes_"} + key_size +
+                               std::string{"_cbc"});
                   const EVP_CIPHER *cipher;
                   switch (atoi(key_size.c_str())) {
                     case 128:
diff --git a/crypto/cipher/e_des.cc b/crypto/cipher/e_des.cc
index cf85371..cf3180c 100644
--- a/crypto/cipher/e_des.cc
+++ b/crypto/cipher/e_des.cc
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <stddef.h>
+
 #include <openssl/cipher.h>
 #include <openssl/des.h>
 #include <openssl/nid.h>
@@ -35,10 +37,10 @@
   return 1;
 }
 
-static int des_cbc_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                          size_t in_len) {
+static int des_cbc_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                 const uint8_t *in, size_t len) {
   EVP_DES_KEY *dat = (EVP_DES_KEY *)ctx->cipher_data;
-  DES_ncbc_encrypt_ex(in, out, in_len, &dat->ks.ks, ctx->iv, ctx->encrypt);
+  DES_ncbc_encrypt_ex(in, out, len, &dat->ks.ks, ctx->iv, ctx->encrypt);
   return 1;
 }
 
@@ -50,22 +52,24 @@
     /*ctx_size=*/sizeof(EVP_DES_KEY),
     /*flags=*/EVP_CIPH_CBC_MODE,
     /*init=*/des_init_key,
-    /*cipher=*/des_cbc_cipher,
+    /*cipher_update=*/des_cbc_cipher_update,
+    /*cipher_final=*/nullptr,
+    /*update_aad=*/nullptr,
     /*cleanup=*/nullptr,
     /*ctrl=*/nullptr,
 };
 
 const EVP_CIPHER *EVP_des_cbc(void) { return &evp_des_cbc; }
 
-static int des_ecb_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                          size_t in_len) {
-  if (in_len < ctx->cipher->block_size) {
+static int des_ecb_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                 const uint8_t *in, size_t len) {
+  if (len < ctx->cipher->block_size) {
     return 1;
   }
-  in_len -= ctx->cipher->block_size;
+  len -= ctx->cipher->block_size;
 
   EVP_DES_KEY *dat = (EVP_DES_KEY *)ctx->cipher_data;
-  for (size_t i = 0; i <= in_len; i += ctx->cipher->block_size) {
+  for (size_t i = 0; i <= len; i += ctx->cipher->block_size) {
     DES_ecb_encrypt_ex(in + i, out + i, &dat->ks.ks, ctx->encrypt);
   }
   return 1;
@@ -79,7 +83,9 @@
     /*ctx_size=*/sizeof(EVP_DES_KEY),
     /*flags=*/EVP_CIPH_ECB_MODE,
     /*init=*/des_init_key,
-    /*cipher=*/des_ecb_cipher,
+    /*cipher_update=*/des_ecb_cipher_update,
+    /*cipher_final=*/nullptr,
+    /*update_aad=*/nullptr,
     /*cleanup=*/nullptr,
     /*ctrl=*/nullptr,
 };
@@ -102,10 +108,10 @@
   return 1;
 }
 
-static int des_ede3_cbc_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out,
-                               const uint8_t *in, size_t in_len) {
+static int des_ede3_cbc_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                      const uint8_t *in, size_t len) {
   DES_EDE_KEY *dat = (DES_EDE_KEY *)ctx->cipher_data;
-  DES_ede3_cbc_encrypt_ex(in, out, in_len, &dat->ks.ks[0], &dat->ks.ks[1],
+  DES_ede3_cbc_encrypt_ex(in, out, len, &dat->ks.ks[0], &dat->ks.ks[1],
                           &dat->ks.ks[2], ctx->iv, ctx->encrypt);
   return 1;
 }
@@ -118,7 +124,9 @@
     /*ctx_size=*/sizeof(DES_EDE_KEY),
     /*flags=*/EVP_CIPH_CBC_MODE,
     /*init=*/des_ede3_init_key,
-    /*cipher=*/des_ede3_cbc_cipher,
+    /*cipher_update=*/des_ede3_cbc_cipher_update,
+    /*cipher_final=*/nullptr,
+    /*update_aad=*/nullptr,
     /*cleanup=*/nullptr,
     /*ctrl=*/nullptr,
 };
@@ -143,22 +151,24 @@
     /*ctx_size=*/sizeof(DES_EDE_KEY),
     /*flags=*/EVP_CIPH_CBC_MODE,
     /*init=*/des_ede_init_key,
-    /*cipher=*/des_ede3_cbc_cipher,
+    /*cipher_update=*/des_ede3_cbc_cipher_update,
+    /*cipher_final=*/nullptr,
+    /*update_aad=*/nullptr,
     /*cleanup=*/nullptr,
     /*ctrl=*/nullptr,
 };
 
 const EVP_CIPHER *EVP_des_ede_cbc(void) { return &evp_des_ede_cbc; }
 
-static int des_ede_ecb_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out,
-                              const uint8_t *in, size_t in_len) {
-  if (in_len < ctx->cipher->block_size) {
+static int des_ede_ecb_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                     const uint8_t *in, size_t len) {
+  if (len < ctx->cipher->block_size) {
     return 1;
   }
-  in_len -= ctx->cipher->block_size;
+  len -= ctx->cipher->block_size;
 
   DES_EDE_KEY *dat = (DES_EDE_KEY *)ctx->cipher_data;
-  for (size_t i = 0; i <= in_len; i += ctx->cipher->block_size) {
+  for (size_t i = 0; i <= len; i += ctx->cipher->block_size) {
     DES_ecb3_encrypt_ex(in + i, out + i, &dat->ks.ks[0], &dat->ks.ks[1],
                         &dat->ks.ks[2], ctx->encrypt);
   }
@@ -173,7 +183,9 @@
     /*ctx_size=*/sizeof(DES_EDE_KEY),
     /*flags=*/EVP_CIPH_ECB_MODE,
     /*init=*/des_ede_init_key,
-    /*cipher=*/des_ede_ecb_cipher,
+    /*cipher_update=*/des_ede_ecb_cipher_update,
+    /*cipher_final=*/nullptr,
+    /*update_aad=*/nullptr,
     /*cleanup=*/nullptr,
     /*ctrl=*/nullptr,
 };
@@ -188,7 +200,9 @@
     /*ctx_size=*/sizeof(DES_EDE_KEY),
     /*flags=*/EVP_CIPH_ECB_MODE,
     /*init=*/des_ede3_init_key,
-    /*cipher=*/des_ede_ecb_cipher,
+    /*cipher_update=*/des_ede_ecb_cipher_update,
+    /*cipher_final=*/nullptr,
+    /*update_aad=*/nullptr,
     /*cleanup=*/nullptr,
     /*ctrl=*/nullptr,
 };
diff --git a/crypto/cipher/e_null.cc b/crypto/cipher/e_null.cc
index 4e59448..4dfe6e4 100644
--- a/crypto/cipher/e_null.cc
+++ b/crypto/cipher/e_null.cc
@@ -27,10 +27,10 @@
   return 1;
 }
 
-static int null_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                       size_t in_len) {
+static int null_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                              const uint8_t *in, size_t len) {
   if (in != out) {
-    OPENSSL_memcpy(out, in, in_len);
+    OPENSSL_memcpy(out, in, len);
   }
   return 1;
 }
@@ -43,7 +43,9 @@
     /*ctx_size=*/0,
     /*flags=*/0,
     /*init=*/null_init_key,
-    /*cipher=*/null_cipher,
+    /*cipher_update=*/null_cipher_update,
+    /*cipher_final=*/nullptr,
+    /*update_aad=*/nullptr,
     /*cleanup=*/nullptr,
     /*ctrl=*/nullptr,
 };
diff --git a/crypto/cipher/e_rc2.cc b/crypto/cipher/e_rc2.cc
index 250351a..f52faa5 100644
--- a/crypto/cipher/e_rc2.cc
+++ b/crypto/cipher/e_rc2.cc
@@ -351,19 +351,19 @@
   return 1;
 }
 
-static int rc2_cbc_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                          size_t inl) {
+static int rc2_cbc_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                 const uint8_t *in, size_t len) {
   EVP_RC2_KEY *key = (EVP_RC2_KEY *)ctx->cipher_data;
   static const size_t kChunkSize = 0x10000;
 
-  while (inl >= kChunkSize) {
+  while (len >= kChunkSize) {
     RC2_cbc_encrypt(in, out, kChunkSize, &key->ks, ctx->iv, ctx->encrypt);
-    inl -= kChunkSize;
+    len -= kChunkSize;
     in += kChunkSize;
     out += kChunkSize;
   }
-  if (inl) {
-    RC2_cbc_encrypt(in, out, inl, &key->ks, ctx->iv, ctx->encrypt);
+  if (len) {
+    RC2_cbc_encrypt(in, out, len, &key->ks, ctx->iv, ctx->encrypt);
   }
   return 1;
 }
@@ -394,7 +394,9 @@
     /*ctx_size=*/sizeof(EVP_RC2_KEY),
     /*flags=*/EVP_CIPH_CBC_MODE | EVP_CIPH_VARIABLE_LENGTH | EVP_CIPH_CTRL_INIT,
     /*init=*/rc2_init_key,
-    /*cipher=*/rc2_cbc_cipher,
+    /*cipher_update=*/rc2_cbc_cipher_update,
+    /*cipher_final=*/nullptr,
+    /*update_aad=*/nullptr,
     /*cleanup=*/nullptr,
     /*ctrl=*/rc2_ctrl,
 };
@@ -409,7 +411,9 @@
     /*ctx_size=*/sizeof(EVP_RC2_KEY),
     /*flags=*/EVP_CIPH_CBC_MODE | EVP_CIPH_VARIABLE_LENGTH | EVP_CIPH_CTRL_INIT,
     /*init=*/rc2_init_key,
-    /*cipher=*/rc2_cbc_cipher,
+    /*cipher_update=*/rc2_cbc_cipher_update,
+    /*cipher_final=*/nullptr,
+    /*update_aad=*/nullptr,
     /*cleanup=*/nullptr,
     /*ctrl=*/rc2_ctrl,
 };
diff --git a/crypto/cipher/e_rc4.cc b/crypto/cipher/e_rc4.cc
index 2450a73..c2e6d44 100644
--- a/crypto/cipher/e_rc4.cc
+++ b/crypto/cipher/e_rc4.cc
@@ -30,11 +30,11 @@
   return 1;
 }
 
-static int rc4_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                      size_t in_len) {
+static int rc4_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                             const uint8_t *in, size_t len) {
   RC4_KEY *rc4key = (RC4_KEY *)ctx->cipher_data;
 
-  RC4(rc4key, in_len, in, out);
+  RC4(rc4key, len, in, out);
   return 1;
 }
 
@@ -46,7 +46,9 @@
     /*ctx_size=*/sizeof(RC4_KEY),
     /*flags=*/EVP_CIPH_VARIABLE_LENGTH,
     /*init=*/rc4_init_key,
-    /*cipher=*/rc4_cipher,
+    /*cipher_update=*/rc4_cipher_update,
+    /*cipher_final=*/nullptr,
+    /*update_aad=*/nullptr,
     /*cleanup=*/nullptr,
     /*ctrl=*/nullptr,
 };
diff --git a/crypto/compiler_test.cc b/crypto/compiler_test.cc
index ef29e78..042d514 100644
--- a/crypto/compiler_test.cc
+++ b/crypto/compiler_test.cc
@@ -107,6 +107,9 @@
   // size_t does not exceed uint64_t.
   static_assert(sizeof(size_t) <= 8u, "size_t must not exceed uint64_t");
 
+  // The positive maximum of |int| must fit into |size_t|.
+  static_assert(sizeof(int) <= sizeof(size_t), "int must not exceed size_t");
+
   // Require that |int| be exactly 32 bits. OpenSSL historically mixed up
   // |unsigned| and |uint32_t|, so we require it be at least 32 bits. Requiring
   // at most 32-bits is a bit more subtle. C promotes arithmetic operands to
diff --git a/crypto/fipsmodule/cipher/cipher.cc.inc b/crypto/fipsmodule/cipher/cipher.cc.inc
index bd07890..c2bbb9f 100644
--- a/crypto/fipsmodule/cipher/cipher.cc.inc
+++ b/crypto/fipsmodule/cipher/cipher.cc.inc
@@ -205,7 +205,7 @@
 
 // block_remainder returns the number of bytes to remove from |len| to get a
 // multiple of |ctx|'s block size.
-static int block_remainder(const EVP_CIPHER_CTX *ctx, int len) {
+static size_t block_remainder(const EVP_CIPHER_CTX *ctx, size_t len) {
   // |block_size| must be a power of two.
   assert(ctx->cipher->block_size != 0);
   assert((ctx->cipher->block_size & (ctx->cipher->block_size - 1)) == 0);
@@ -214,6 +214,34 @@
 
 int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len,
                       const uint8_t *in, int in_len) {
+  *out_len = 0;
+  if (in_len < 0) {
+    OPENSSL_PUT_ERROR(CIPHER, ERR_R_OVERFLOW);
+    return 0;
+  }
+  size_t in_len_sz = static_cast<size_t>(in_len);
+  size_t out_len_sz;
+  if ((ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) && out == nullptr) {
+    if (!EVP_CipherUpdateAAD(ctx, in, in_len_sz)) {
+      return 0;
+    }
+    out_len_sz = in_len_sz;  // Even though no output was written!
+  } else {
+    // in_len_sz is < INT_MAX which is no more than half of SIZE_MAX.
+    size_t max_out_len =
+        std::min(in_len_sz + ctx->cipher->block_size - 1, size_t{INT_MAX});
+    if (!EVP_EncryptUpdate_ex(ctx, out, &out_len_sz, max_out_len, in,
+                              in_len_sz)) {
+      return 0;
+    }
+  }
+  *out_len = static_cast<int>(out_len_sz);
+  return 1;
+}
+
+int EVP_EncryptUpdate_ex(EVP_CIPHER_CTX *ctx, uint8_t *out, size_t *out_len,
+                         size_t max_out_len, const uint8_t *in, size_t in_len) {
+  *out_len = 0;
   if (ctx->poisoned) {
     OPENSSL_PUT_ERROR(CIPHER, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
     return 0;
@@ -223,205 +251,76 @@
   // callers do not continue to use the object in that case.
   ctx->poisoned = 1;
 
-  // Ciphers that use blocks may write up to |bl| extra bytes. Ensure the output
-  // does not overflow |*out_len|.
-  int bl = ctx->cipher->block_size;
-  if (bl > 1 && in_len > INT_MAX - bl) {
-    OPENSSL_PUT_ERROR(CIPHER, ERR_R_OVERFLOW);
-    return 0;
-  }
+  // Ciphers that use blocks may write up to |block_size| extra bytes. Ensure
+  // the output does not overflow |*out_len|.
+  size_t block_size = ctx->cipher->block_size;
 
-  if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
-    int ret = ctx->cipher->cipher(ctx, out, in, in_len);
-    if (ret < 0) {
-      return 0;
-    } else {
-      *out_len = ret;
-    }
+  if (in_len == 0) {
     ctx->poisoned = 0;
     return 1;
   }
 
-  if (in_len <= 0) {
-    *out_len = 0;
-    if (in_len == 0) {
-      ctx->poisoned = 0;
-      return 1;
-    }
-    return 0;
-  }
-
-  if (ctx->buf_len == 0 && block_remainder(ctx, in_len) == 0) {
-    if (ctx->cipher->cipher(ctx, out, in, in_len)) {
-      *out_len = in_len;
-      ctx->poisoned = 0;
-      return 1;
-    } else {
-      *out_len = 0;
-      return 0;
-    }
-  }
-
-  int i = ctx->buf_len;
-  assert(bl <= (int)sizeof(ctx->buf));
-  if (i != 0) {
-    if (bl - i > in_len) {
-      OPENSSL_memcpy(&ctx->buf[i], in, in_len);
+  size_t buf_len = ctx->buf_len;
+  assert(block_size <= sizeof(ctx->buf));
+  if (buf_len != 0) {
+    if (block_size - buf_len > in_len) {
+      OPENSSL_memcpy(&ctx->buf[buf_len], in, in_len);
       ctx->buf_len += in_len;
-      *out_len = 0;
       ctx->poisoned = 0;
       return 1;
     } else {
-      int j = bl - i;
-      OPENSSL_memcpy(&ctx->buf[i], in, j);
-      if (!ctx->cipher->cipher(ctx, out, ctx->buf, bl)) {
+      size_t j = block_size - buf_len;
+      OPENSSL_memcpy(&ctx->buf[buf_len], in, j);
+      if (max_out_len < block_size) {
+        OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
+        return 0;
+      }
+      if (!ctx->cipher->cipher_update(ctx, out, ctx->buf, block_size)) {
         return 0;
       }
       in_len -= j;
       in += j;
-      out += bl;
-      *out_len = bl;
+      out += block_size;
+      *out_len = block_size;
+      max_out_len -= block_size;
     }
-  } else {
-    *out_len = 0;
   }
 
-  i = block_remainder(ctx, in_len);
-  in_len -= i;
+  size_t remainder = block_remainder(ctx, in_len);
+  in_len -= remainder;
   if (in_len > 0) {
-    if (!ctx->cipher->cipher(ctx, out, in, in_len)) {
+    if (max_out_len < in_len) {
+      OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
+      *out_len = 0;
+      return 0;
+    }
+    if (!ctx->cipher->cipher_update(ctx, out, in, in_len)) {
+      *out_len = 0;
       return 0;
     }
     *out_len += in_len;
+    max_out_len -= in_len;
   }
 
-  if (i != 0) {
-    OPENSSL_memcpy(ctx->buf, &in[in_len], i);
+  if (remainder != 0) {
+    OPENSSL_memcpy(ctx->buf, &in[in_len], remainder);
   }
-  ctx->buf_len = i;
+  ctx->buf_len = remainder;
   ctx->poisoned = 0;
   return 1;
 }
 
 int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len) {
-  int n;
-  unsigned int i, b, bl;
-
-  if (ctx->poisoned) {
-    OPENSSL_PUT_ERROR(CIPHER, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
-    return 0;
-  }
-
-  if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
-    // When EVP_CIPH_FLAG_CUSTOM_CIPHER is set, the return value of |cipher| is
-    // the number of bytes written, or -1 on error. Otherwise the return value
-    // is one on success and zero on error.
-    const int num_bytes = ctx->cipher->cipher(ctx, out, nullptr, 0);
-    if (num_bytes < 0) {
-      return 0;
-    }
-    *out_len = num_bytes;
-    goto out;
-  }
-
-  b = ctx->cipher->block_size;
-  assert(b <= sizeof(ctx->buf));
-  if (b == 1) {
-    *out_len = 0;
-    goto out;
-  }
-
-  bl = ctx->buf_len;
-  if (ctx->flags & EVP_CIPH_NO_PADDING) {
-    if (bl) {
-      OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
-      return 0;
-    }
-    *out_len = 0;
-    goto out;
-  }
-
-  n = b - bl;
-  for (i = bl; i < b; i++) {
-    ctx->buf[i] = n;
-  }
-  if (!ctx->cipher->cipher(ctx, out, ctx->buf, b)) {
-    return 0;
-  }
-  *out_len = b;
-
-out:
-  EVP_Cipher_verify_service_indicator(ctx);
-  return 1;
+  size_t out_len_sz;
+  int ret =
+      EVP_EncryptFinal_ex2(ctx, out, &out_len_sz, ctx->cipher->block_size);
+  static_assert(EVP_MAX_BLOCK_LENGTH <= INT_MAX);
+  *out_len = static_cast<int>(out_len_sz);
+  return ret;
 }
 
-int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len,
-                      const uint8_t *in, int in_len) {
-  if (ctx->poisoned) {
-    OPENSSL_PUT_ERROR(CIPHER, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
-    return 0;
-  }
-
-  // Ciphers that use blocks may write up to |bl| extra bytes. Ensure the output
-  // does not overflow |*out_len|.
-  unsigned int b = ctx->cipher->block_size;
-  if (b > 1 && in_len > INT_MAX - (int)b) {
-    OPENSSL_PUT_ERROR(CIPHER, ERR_R_OVERFLOW);
-    return 0;
-  }
-
-  if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
-    int r = ctx->cipher->cipher(ctx, out, in, in_len);
-    if (r < 0) {
-      *out_len = 0;
-      return 0;
-    } else {
-      *out_len = r;
-    }
-    return 1;
-  }
-
-  if (in_len <= 0) {
-    *out_len = 0;
-    return in_len == 0;
-  }
-
-  if (ctx->flags & EVP_CIPH_NO_PADDING) {
-    return EVP_EncryptUpdate(ctx, out, out_len, in, in_len);
-  }
-
-  assert(b <= sizeof(ctx->final));
-  int fix_len = 0;
-  if (ctx->final_used) {
-    OPENSSL_memcpy(out, ctx->final, b);
-    out += b;
-    fix_len = 1;
-  }
-
-  if (!EVP_EncryptUpdate(ctx, out, out_len, in, in_len)) {
-    return 0;
-  }
-
-  // if we have 'decrypted' a multiple of block size, make sure
-  // we have a copy of this last block
-  if (b > 1 && !ctx->buf_len) {
-    *out_len -= b;
-    ctx->final_used = 1;
-    OPENSSL_memcpy(ctx->final, &out[*out_len], b);
-  } else {
-    ctx->final_used = 0;
-  }
-
-  if (fix_len) {
-    *out_len += b;
-  }
-
-  return 1;
-}
-
-int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *out_len) {
-  int i, n;
-  unsigned int b;
+int EVP_EncryptFinal_ex2(EVP_CIPHER_CTX *ctx, uint8_t *out, size_t *out_len,
+                         size_t max_out_len) {
   *out_len = 0;
 
   if (ctx->poisoned) {
@@ -429,65 +328,250 @@
     return 0;
   }
 
-  if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
-    i = ctx->cipher->cipher(ctx, out, nullptr, 0);
-    if (i < 0) {
-      return 0;
-    } else {
-      *out_len = i;
-    }
-    goto out;
-  }
-
-  b = ctx->cipher->block_size;
-  if (ctx->flags & EVP_CIPH_NO_PADDING) {
-    if (ctx->buf_len) {
-      OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
-      return 0;
-    }
-    *out_len = 0;
-    goto out;
-  }
-
-  if (b > 1) {
-    if (ctx->buf_len || !ctx->final_used) {
-      OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_WRONG_FINAL_BLOCK_LENGTH);
-      return 0;
-    }
-    assert(b <= sizeof(ctx->final));
-
-    // The following assumes that the ciphertext has been authenticated.
-    // Otherwise it provides a padding oracle.
-    n = ctx->final[b - 1];
-    if (n == 0 || n > (int)b) {
-      OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT);
-      return 0;
-    }
-
-    for (i = 0; i < n; i++) {
-      if (ctx->final[--b] != n) {
-        OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT);
+  size_t block_size = ctx->cipher->block_size;
+  assert(block_size <= sizeof(ctx->buf));
+  if (block_size == 1) {
+    if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
+      if (!ctx->cipher->cipher_final(ctx)) {
         return 0;
       }
     }
-
-    n = ctx->cipher->block_size - n;
-    for (i = 0; i < n; i++) {
-      out[i] = ctx->final[i];
-    }
-    *out_len = n;
-  } else {
-    *out_len = 0;
+    EVP_Cipher_verify_service_indicator(ctx);
+    return 1;
   }
 
-out:
+  size_t buf_len = ctx->buf_len;
+  if (ctx->flags & EVP_CIPH_NO_PADDING) {
+    if (buf_len) {
+      OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
+      return 0;
+    }
+    EVP_Cipher_verify_service_indicator(ctx);
+    return 1;
+  }
+
+  size_t padding = block_size - buf_len;
+  for (size_t i = buf_len; i < block_size; i++) {
+    ctx->buf[i] = padding;
+  }
+  if (max_out_len < block_size) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+  if (!ctx->cipher->cipher_update(ctx, out, ctx->buf, block_size)) {
+    return 0;
+  }
+  *out_len = block_size;
+  EVP_Cipher_verify_service_indicator(ctx);
+  return 1;
+}
+
+int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len,
+                      const uint8_t *in, int in_len) {
+  *out_len = 0;
+  if (in_len < 0) {
+    OPENSSL_PUT_ERROR(CIPHER, ERR_R_OVERFLOW);
+    return 0;
+  }
+  size_t in_len_sz = static_cast<size_t>(in_len);
+  size_t out_len_sz;
+  if ((ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) && out == nullptr) {
+    if (!EVP_CipherUpdateAAD(ctx, in, in_len_sz)) {
+      return 0;
+    }
+    out_len_sz = in_len_sz;
+  } else {
+    // in_len_sz is < INT_MAX which is no more than half of SIZE_MAX.
+    size_t max_out_len = std::min(
+        in_len_sz + (ctx->cipher->block_size > 1 ? ctx->cipher->block_size : 0),
+        size_t{INT_MAX});
+    if (!EVP_DecryptUpdate_ex(ctx, out, &out_len_sz, max_out_len, in,
+                              in_len_sz)) {
+      return 0;
+    }
+  }
+  *out_len = static_cast<int>(out_len_sz);
+  return 1;
+}
+
+int EVP_DecryptUpdate_ex(EVP_CIPHER_CTX *ctx, uint8_t *out, size_t *out_len,
+                         size_t max_out_len, const uint8_t *in, size_t in_len) {
+  *out_len = 0;
+  if (ctx->poisoned) {
+    OPENSSL_PUT_ERROR(CIPHER, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  // Ciphers that use blocks may write up to |block_size| extra bytes. Ensure
+  // the output does not overflow |*out_len|.
+  size_t block_size = ctx->cipher->block_size;
+
+  if (in_len == 0) {
+    return 1;
+  }
+
+  if (ctx->flags & EVP_CIPH_NO_PADDING) {
+    // Use the shared block handling logic from encryption.
+    return EVP_EncryptUpdate_ex(ctx, out, out_len, max_out_len, in, in_len);
+  }
+
+  assert(block_size <= sizeof(ctx->final));
+  bool fix_len = false;
+  if (ctx->final_used) {
+    if (max_out_len < block_size) {
+      OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
+      return 0;
+    }
+    OPENSSL_memcpy(out, ctx->final, block_size);
+    ctx->final_used = 0;
+    out += block_size;
+    max_out_len -= block_size;
+    fix_len = true;
+  }
+
+  // Use the shared block handling logic from encryption.
+  if (block_size > 1 && block_remainder(ctx, ctx->buf_len + in_len) == 0) {
+    // Decryption would end on a block boundary. In this case, although we
+    // can decrypt up to the block boundary, we cannot output the final
+    // plaintext block yet. It may be the final block, with padding to
+    // remove.
+    //
+    // Instead, output all but the final block's decryption, then decrypt the
+    // final block into ctx->final, to be processed later.
+
+    // NOTE: Not _really_ necessary, but let's try aligning the second
+    // EVP_EncryptUpdate_ex call to a block boundary to mess with the buffer
+    // less.
+    size_t head = in_len > block_size ? in_len - block_size : 0;
+    if (!EVP_EncryptUpdate_ex(ctx, out, out_len, max_out_len, in, head)) {
+      return 0;
+    }
+    size_t final_size;
+    if (!EVP_EncryptUpdate_ex(ctx, ctx->final, &final_size, sizeof(ctx->final),
+                              in + head, in_len - head)) {
+      return 0;
+    }
+    ctx->final_used = 1;
+    assert(final_size == block_size);
+    assert(ctx->buf_len == 0);
+  } else {
+    // Buffer will be non-empty.
+    if (!EVP_EncryptUpdate_ex(ctx, out, out_len, max_out_len, in, in_len)) {
+      return 0;
+    }
+    assert(block_size == 1 || ctx->buf_len != 0);
+  }
+
+  if (fix_len) {
+    *out_len += block_size;
+  }
+
+  return 1;
+}
+
+int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len) {
+  size_t out_len_sz;
+  int ret =
+      EVP_DecryptFinal_ex2(ctx, out, &out_len_sz, ctx->cipher->block_size);
+  static_assert(EVP_MAX_BLOCK_LENGTH <= INT_MAX);
+  *out_len = static_cast<int>(out_len_sz);
+  return ret;
+}
+
+int EVP_DecryptFinal_ex2(EVP_CIPHER_CTX *ctx, unsigned char *out,
+                         size_t *out_len, size_t max_out_len) {
+  *out_len = 0;
+
+  if (ctx->poisoned) {
+    OPENSSL_PUT_ERROR(CIPHER, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  size_t block_size = ctx->cipher->block_size;
+  assert(block_size <= sizeof(ctx->buf));
+  if (block_size == 1) {
+    if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
+      if (!ctx->cipher->cipher_final(ctx)) {
+        return 0;
+      }
+    }
+    EVP_Cipher_verify_service_indicator(ctx);
+    return 1;
+  }
+
+  size_t buf_len = ctx->buf_len;
+  if (ctx->flags & EVP_CIPH_NO_PADDING) {
+    if (buf_len) {
+      OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);
+      return 0;
+    }
+    EVP_Cipher_verify_service_indicator(ctx);
+    return 1;
+  }
+
+  if (buf_len || !ctx->final_used) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_WRONG_FINAL_BLOCK_LENGTH);
+    return 0;
+  }
+  assert(block_size <= sizeof(ctx->final));
+
+  // The following assumes that the ciphertext has been authenticated.
+  // Otherwise it provides a padding oracle.
+  size_t padding = ctx->final[block_size - 1];
+  if (padding == 0 || padding > block_size) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT);
+    return 0;
+  }
+
+  for (size_t i = block_size - padding; i < block_size; i++) {
+    if (ctx->final[i] != padding) {
+      OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT);
+      return 0;
+    }
+  }
+
+  size_t payload = ctx->cipher->block_size - padding;
+  if (max_out_len < payload) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+  OPENSSL_memcpy(out, ctx->final, payload);
+  *out_len = payload;
+
   EVP_Cipher_verify_service_indicator(ctx);
   return 1;
 }
 
 int EVP_Cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
                size_t in_len) {
-  const int ret = ctx->cipher->cipher(ctx, out, in, in_len);
+  const int kError =
+      (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) ? -1 : 0;
+
+  if ((ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) &&
+      in_len > size_t{INT_MAX}) {
+    // Can't represent the return value? That'd be bad.
+    OPENSSL_PUT_ERROR(CIPHER, ERR_R_OVERFLOW);
+    return kError;
+  }
+
+  size_t out_len;
+  if ((ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) && in == nullptr) {
+    if (!ctx->cipher->cipher_final(ctx)) {
+      return kError;
+    }
+    out_len = 0;
+  } else if ((ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) &&
+             out == nullptr) {
+    if (!ctx->cipher->update_aad(ctx, in, in_len)) {
+      return kError;
+    }
+    out_len = in_len;  // Yes, even though no output was written!
+  } else {
+    if (!ctx->cipher->cipher_update(ctx, out, in, in_len)) {
+      return kError;
+    }
+    out_len = in_len;
+  }
 
   // |EVP_CIPH_FLAG_CUSTOM_CIPHER| never sets the FIPS indicator via
   // |EVP_Cipher| because it's complicated whether the operation has completed
@@ -499,11 +583,15 @@
   // because whether |ret| indicates success or not depends on whether
   // |EVP_CIPH_FLAG_CUSTOM_CIPHER| is set. (This unreasonable, but matches
   // OpenSSL.)
-  if (!(ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) && ret) {
+  if (!(ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER)) {
     EVP_Cipher_verify_service_indicator(ctx);
   }
 
-  return ret;
+  // Custom ciphers return byte count; regular ciphers return boolean.
+  if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
+    return static_cast<int>(out_len);
+  }
+  return 1;
 }
 
 int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len,
@@ -515,6 +603,32 @@
   }
 }
 
+int EVP_CipherUpdate_ex(EVP_CIPHER_CTX *ctx, uint8_t *out, size_t *out_len,
+                        size_t max_out_len, const uint8_t *in, size_t in_len) {
+  if (ctx->encrypt) {
+    return EVP_EncryptUpdate_ex(ctx, out, out_len, max_out_len, in, in_len);
+  } else {
+    return EVP_DecryptUpdate_ex(ctx, out, out_len, max_out_len, in, in_len);
+  }
+}
+
+int EVP_CipherUpdateAAD(EVP_CIPHER_CTX *ctx, const uint8_t *in, size_t in_len) {
+  // This code is identical for encryption and decryption, so the implementation
+  // can be here.
+  if (ctx->poisoned || !(ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER)) {
+    OPENSSL_PUT_ERROR(CIPHER, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+  ctx->poisoned = 1;
+
+  if (!ctx->cipher->update_aad(ctx, in, in_len)) {
+    return 0;
+  }
+
+  ctx->poisoned = 0;
+  return 1;
+}
+
 int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len) {
   if (ctx->encrypt) {
     return EVP_EncryptFinal_ex(ctx, out, out_len);
@@ -523,6 +637,15 @@
   }
 }
 
+int EVP_CipherFinal_ex2(EVP_CIPHER_CTX *ctx, uint8_t *out, size_t *out_len,
+                        size_t max_out_len) {
+  if (ctx->encrypt) {
+    return EVP_EncryptFinal_ex2(ctx, out, out_len, max_out_len);
+  } else {
+    return EVP_DecryptFinal_ex2(ctx, out, out_len, max_out_len);
+  }
+}
+
 const EVP_CIPHER *EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *ctx) {
   return ctx->cipher;
 }
diff --git a/crypto/fipsmodule/cipher/e_aes.cc.inc b/crypto/fipsmodule/cipher/e_aes.cc.inc
index 685fa24..52ffca5 100644
--- a/crypto/fipsmodule/cipher/e_aes.cc.inc
+++ b/crypto/fipsmodule/cipher/e_aes.cc.inc
@@ -154,8 +154,8 @@
   return 1;
 }
 
-static int aes_cbc_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                          size_t len) {
+static int aes_cbc_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                 const uint8_t *in, size_t len) {
   EVP_AES_KEY *dat = (EVP_AES_KEY *)ctx->cipher_data;
 
   if (dat->stream.cbc) {
@@ -169,8 +169,8 @@
   return 1;
 }
 
-static int aes_ecb_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                          size_t len) {
+static int aes_ecb_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                 const uint8_t *in, size_t len) {
   size_t bl = ctx->cipher->block_size;
   EVP_AES_KEY *dat = (EVP_AES_KEY *)ctx->cipher_data;
 
@@ -186,16 +186,16 @@
   return 1;
 }
 
-static int aes_ctr_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                          size_t len) {
+static int aes_ctr_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                 const uint8_t *in, size_t len) {
   EVP_AES_KEY *dat = (EVP_AES_KEY *)ctx->cipher_data;
   CRYPTO_ctr128_encrypt_ctr32(in, out, len, &dat->ks.ks, ctx->iv, ctx->buf,
                               &ctx->num, dat->stream.ctr);
   return 1;
 }
 
-static int aes_ofb_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                          size_t len) {
+static int aes_ofb_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                 const uint8_t *in, size_t len) {
   EVP_AES_KEY *dat = (EVP_AES_KEY *)ctx->cipher_data;
 
   CRYPTO_ofb128_encrypt(in, out, len, &dat->ks.ks, ctx->iv, &ctx->num,
@@ -366,56 +366,55 @@
   }
 }
 
-static int aes_gcm_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                          size_t len) {
+static int aes_gcm_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                 const uint8_t *in, size_t len) {
   EVP_AES_GCM_CTX *gctx = reinterpret_cast<EVP_AES_GCM_CTX *>(ctx->cipher_data);
 
   // If not set up, return error
-  if (!gctx->key_set) {
-    return -1;
-  }
-  if (!gctx->iv_set) {
-    return -1;
-  }
-
-  if (len > INT_MAX) {
-    // This function signature can only express up to |INT_MAX| bytes encrypted.
-    //
-    // TODO(https://crbug.com/boringssl/494): Make the internal |EVP_CIPHER|
-    // calling convention |size_t|-clean.
-    return -1;
-  }
-
-  if (in) {
-    if (out == nullptr) {
-      if (!CRYPTO_gcm128_aad(&gctx->key, &gctx->gcm, in, len)) {
-        return -1;
-      }
-    } else if (ctx->encrypt) {
-      if (!CRYPTO_gcm128_encrypt(&gctx->key, &gctx->gcm, in, out, len)) {
-        return -1;
-      }
-    } else {
-      if (!CRYPTO_gcm128_decrypt(&gctx->key, &gctx->gcm, in, out, len)) {
-        return -1;
-      }
-    }
-    return (int)len;
-  } else {
-    if (!ctx->encrypt) {
-      if (gctx->taglen < 0 || !CRYPTO_gcm128_finish(&gctx->key, &gctx->gcm,
-                                                    ctx->buf, gctx->taglen)) {
-        return -1;
-      }
-      gctx->iv_set = 0;
-      return 0;
-    }
-    CRYPTO_gcm128_tag(&gctx->key, &gctx->gcm, ctx->buf, 16);
-    gctx->taglen = 16;
-    // Don't reuse the IV
-    gctx->iv_set = 0;
+  if (!gctx->key_set || !gctx->iv_set) {
     return 0;
   }
+
+  if (ctx->encrypt) {
+    return CRYPTO_gcm128_encrypt(&gctx->key, &gctx->gcm, in, out, len);
+  } else {
+    return CRYPTO_gcm128_decrypt(&gctx->key, &gctx->gcm, in, out, len);
+  }
+}
+
+static int aes_gcm_cipher_final(EVP_CIPHER_CTX *ctx) {
+  EVP_AES_GCM_CTX *gctx = reinterpret_cast<EVP_AES_GCM_CTX *>(ctx->cipher_data);
+
+  // If not set up, return error
+  if (!gctx->key_set || !gctx->iv_set) {
+    return 0;
+  }
+
+  if (!ctx->encrypt) {
+    if (gctx->taglen < 0 ||
+        !CRYPTO_gcm128_finish(&gctx->key, &gctx->gcm, ctx->buf, gctx->taglen)) {
+      return 0;
+    }
+    gctx->iv_set = 0;
+    return 1;
+  }
+  CRYPTO_gcm128_tag(&gctx->key, &gctx->gcm, ctx->buf, 16);
+  gctx->taglen = 16;
+  // Don't reuse the IV
+  gctx->iv_set = 0;
+  return 1;
+}
+
+static int aes_gcm_update_aad(EVP_CIPHER_CTX *ctx, const uint8_t *in,
+                              size_t len) {
+  EVP_AES_GCM_CTX *gctx = reinterpret_cast<EVP_AES_GCM_CTX *>(ctx->cipher_data);
+
+  // If not set up, return error
+  if (!gctx->key_set || !gctx->iv_set) {
+    return 0;
+  }
+
+  return CRYPTO_gcm128_aad(&gctx->key, &gctx->gcm, in, len);
 }
 
 DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_aes_128_cbc) {
@@ -428,7 +427,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_CBC_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_cbc_cipher;
+  out->cipher_update = aes_cbc_cipher_update;
 }
 
 DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_aes_128_ctr) {
@@ -441,7 +440,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_CTR_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_ctr_cipher;
+  out->cipher_update = aes_ctr_cipher_update;
 }
 
 DEFINE_LOCAL_DATA(EVP_CIPHER, aes_128_ecb_generic) {
@@ -453,7 +452,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_ECB_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_ecb_cipher;
+  out->cipher_update = aes_ecb_cipher_update;
 }
 
 DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_aes_128_ofb) {
@@ -466,7 +465,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_OFB_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_ofb_cipher;
+  out->cipher_update = aes_ofb_cipher_update;
 }
 
 DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_aes_128_gcm) {
@@ -481,7 +480,9 @@
                EVP_CIPH_FLAG_CUSTOM_CIPHER | EVP_CIPH_ALWAYS_CALL_INIT |
                EVP_CIPH_CTRL_INIT | EVP_CIPH_FLAG_AEAD_CIPHER;
   out->init = aes_gcm_init_key;
-  out->cipher = aes_gcm_cipher;
+  out->cipher_update = aes_gcm_cipher_update;
+  out->cipher_final = aes_gcm_cipher_final;
+  out->update_aad = aes_gcm_update_aad;
   out->cleanup = aes_gcm_cleanup;
   out->ctrl = aes_gcm_ctrl;
 }
@@ -496,7 +497,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_CBC_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_cbc_cipher;
+  out->cipher_update = aes_cbc_cipher_update;
 }
 
 DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_aes_192_ctr) {
@@ -509,7 +510,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_CTR_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_ctr_cipher;
+  out->cipher_update = aes_ctr_cipher_update;
 }
 
 DEFINE_LOCAL_DATA(EVP_CIPHER, aes_192_ecb_generic) {
@@ -521,7 +522,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_ECB_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_ecb_cipher;
+  out->cipher_update = aes_ecb_cipher_update;
 }
 
 DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_aes_192_ofb) {
@@ -534,7 +535,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_OFB_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_ofb_cipher;
+  out->cipher_update = aes_ofb_cipher_update;
 }
 
 DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_aes_192_gcm) {
@@ -549,7 +550,9 @@
                EVP_CIPH_FLAG_CUSTOM_CIPHER | EVP_CIPH_ALWAYS_CALL_INIT |
                EVP_CIPH_CTRL_INIT | EVP_CIPH_FLAG_AEAD_CIPHER;
   out->init = aes_gcm_init_key;
-  out->cipher = aes_gcm_cipher;
+  out->cipher_update = aes_gcm_cipher_update;
+  out->cipher_final = aes_gcm_cipher_final;
+  out->update_aad = aes_gcm_update_aad;
   out->cleanup = aes_gcm_cleanup;
   out->ctrl = aes_gcm_ctrl;
 }
@@ -564,7 +567,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_CBC_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_cbc_cipher;
+  out->cipher_update = aes_cbc_cipher_update;
 }
 
 DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_aes_256_ctr) {
@@ -577,7 +580,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_CTR_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_ctr_cipher;
+  out->cipher_update = aes_ctr_cipher_update;
 }
 
 DEFINE_LOCAL_DATA(EVP_CIPHER, aes_256_ecb_generic) {
@@ -589,7 +592,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_ECB_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_ecb_cipher;
+  out->cipher_update = aes_ecb_cipher_update;
 }
 
 DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_aes_256_ofb) {
@@ -602,7 +605,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_OFB_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_ofb_cipher;
+  out->cipher_update = aes_ofb_cipher_update;
 }
 
 DEFINE_METHOD_FUNCTION(EVP_CIPHER, EVP_aes_256_gcm) {
@@ -617,15 +620,17 @@
                EVP_CIPH_FLAG_CUSTOM_CIPHER | EVP_CIPH_ALWAYS_CALL_INIT |
                EVP_CIPH_CTRL_INIT | EVP_CIPH_FLAG_AEAD_CIPHER;
   out->init = aes_gcm_init_key;
-  out->cipher = aes_gcm_cipher;
+  out->cipher_update = aes_gcm_cipher_update;
+  out->cipher_final = aes_gcm_cipher_final;
+  out->update_aad = aes_gcm_update_aad;
   out->cleanup = aes_gcm_cleanup;
   out->ctrl = aes_gcm_ctrl;
 }
 
 #if defined(HWAES_ECB)
 
-static int aes_hw_ecb_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out,
-                             const uint8_t *in, size_t len) {
+static int aes_hw_ecb_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                    const uint8_t *in, size_t len) {
   size_t bl = ctx->cipher->block_size;
 
   if (len < bl) {
@@ -648,7 +653,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_ECB_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_hw_ecb_cipher;
+  out->cipher_update = aes_hw_ecb_cipher_update;
 }
 
 DEFINE_LOCAL_DATA(EVP_CIPHER, aes_hw_192_ecb) {
@@ -660,7 +665,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_ECB_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_hw_ecb_cipher;
+  out->cipher_update = aes_hw_ecb_cipher_update;
 }
 
 DEFINE_LOCAL_DATA(EVP_CIPHER, aes_hw_256_ecb) {
@@ -672,7 +677,7 @@
   out->ctx_size = sizeof(EVP_AES_KEY);
   out->flags = EVP_CIPH_ECB_MODE;
   out->init = aes_init_key;
-  out->cipher = aes_hw_ecb_cipher;
+  out->cipher_update = aes_hw_ecb_cipher_update;
 }
 
 #define EVP_ECB_CIPHER_FUNCTION(keybits)            \
diff --git a/crypto/fipsmodule/cipher/internal.h b/crypto/fipsmodule/cipher/internal.h
index c5373bb..794db01 100644
--- a/crypto/fipsmodule/cipher/internal.h
+++ b/crypto/fipsmodule/cipher/internal.h
@@ -95,8 +95,32 @@
   int (*init)(EVP_CIPHER_CTX *ctx, const uint8_t *key, const uint8_t *iv,
               int enc);
 
-  int (*cipher)(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                size_t inl);
+  // cipher encrypts/decrypts |in|, write output to |out|. Writes exactly |len|
+  // bytes, which must be a multiple of the |block_size|.
+  //
+  // For ciphers where encryption and decryption operations differ, |init|
+  // shall set an internal state for this.
+  //
+  // Returns 1 on success, or 0 on error.
+  int (*cipher_update)(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
+                       size_t len);
+
+  // cipher_final finalizes the cipher, performing possible final
+  // authentication checks.
+  //
+  // Only used for |EVP_CIPH_FLAG_CUSTOM_CIPHER| ciphers.
+  //
+  // Returns 1 on success, or 0 on error. When decrypting, if an error is
+  // returned, the decrypted data must not be used.
+  int (*cipher_final)(EVP_CIPHER_CTX *ctx);
+
+  // update_aad adds |in| (of length |inl|) to the authenticated data for the
+  // encryption operation.
+  //
+  // Only used for |EVP_CIPH_FLAG_CUSTOM_CIPHER| ciphers.
+  //
+  // Returns 1 on success, or 0 on error.
+  int (*update_aad)(EVP_CIPHER_CTX *ctx, const uint8_t *in, size_t inl);
 
   // cleanup, if non-NULL, releases memory associated with the context. It is
   // called if |EVP_CTRL_INIT| succeeds. Note that |init| may not have been
diff --git a/decrepit/blowfish/blowfish.cc b/decrepit/blowfish/blowfish.cc
index 1c8aa55..8cd5112 100644
--- a/decrepit/blowfish/blowfish.cc
+++ b/decrepit/blowfish/blowfish.cc
@@ -521,8 +521,8 @@
   return 1;
 }
 
-static int bf_ecb_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                         size_t len) {
+static int bf_ecb_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                const uint8_t *in, size_t len) {
   BF_KEY *bf_key = reinterpret_cast<BF_KEY *>(ctx->cipher_data);
 
   while (len >= BF_BLOCK) {
@@ -536,15 +536,15 @@
   return 1;
 }
 
-static int bf_cbc_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                         size_t len) {
+static int bf_cbc_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                const uint8_t *in, size_t len) {
   BF_KEY *bf_key = reinterpret_cast<BF_KEY *>(ctx->cipher_data);
   BF_cbc_encrypt(in, out, len, bf_key, ctx->iv, ctx->encrypt);
   return 1;
 }
 
-static int bf_cfb_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                         size_t len) {
+static int bf_cfb_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                const uint8_t *in, size_t len) {
   BF_KEY *bf_key = reinterpret_cast<BF_KEY *>(ctx->cipher_data);
   int num = ctx->num;
   BF_cfb64_encrypt(in, out, len, bf_key, ctx->iv, &num, ctx->encrypt);
@@ -560,7 +560,9 @@
     /* ctx_size= */ sizeof(BF_KEY),
     /* flags= */ EVP_CIPH_ECB_MODE | EVP_CIPH_VARIABLE_LENGTH,
     /* init= */ bf_init_key,
-    /* cipher= */ bf_ecb_cipher,
+    /* cipher_update= */ bf_ecb_cipher_update,
+    /* cipher_final= */ nullptr,
+    /* update_aad= */ nullptr,
     /* cleanup= */ nullptr,
     /* ctrl= */ nullptr,
 };
@@ -573,7 +575,9 @@
     /* ctx_size= */ sizeof(BF_KEY),
     /* flags= */ EVP_CIPH_CBC_MODE | EVP_CIPH_VARIABLE_LENGTH,
     /* init= */ bf_init_key,
-    /* cipher= */ bf_cbc_cipher,
+    /* cipher_update= */ bf_cbc_cipher_update,
+    /* cipher_final= */ nullptr,
+    /* update_aad= */ nullptr,
     /* cleanup= */ nullptr,
     /* ctrl= */ nullptr,
 };
@@ -586,7 +590,9 @@
     /* ctx_size= */ sizeof(BF_KEY),
     /* flags= */ EVP_CIPH_CFB_MODE | EVP_CIPH_VARIABLE_LENGTH,
     /* init= */ bf_init_key,
-    /* cipher= */ bf_cfb_cipher,
+    /* cipher_update= */ bf_cfb_cipher_update,
+    /* cipher_final= */ nullptr,
+    /* update_aad= */ nullptr,
     /* cleanup= */ nullptr,
     /* ctrl= */ nullptr,
 };
diff --git a/decrepit/cast/cast.cc b/decrepit/cast/cast.cc
index 37e4684..1fc8057 100644
--- a/decrepit/cast/cast.cc
+++ b/decrepit/cast/cast.cc
@@ -371,8 +371,8 @@
   return 1;
 }
 
-static int cast_ecb_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                           size_t len) {
+static int cast_ecb_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                  const uint8_t *in, size_t len) {
   CAST_KEY *cast_key = reinterpret_cast<CAST_KEY *>(ctx->cipher_data);
 
   while (len >= CAST_BLOCK) {
@@ -386,8 +386,8 @@
   return 1;
 }
 
-static int cast_cbc_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                           size_t len) {
+static int cast_cbc_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                  const uint8_t *in, size_t len) {
   CAST_KEY *cast_key = reinterpret_cast<CAST_KEY *>(ctx->cipher_data);
   CAST_cbc_encrypt(in, out, len, cast_key, ctx->iv, ctx->encrypt);
   return 1;
@@ -401,7 +401,9 @@
     /* ctx_size= */ sizeof(CAST_KEY),
     /* flags= */ EVP_CIPH_ECB_MODE | EVP_CIPH_VARIABLE_LENGTH,
     /* init= */ cast_init_key,
-    /* cipher= */ cast_ecb_cipher,
+    /* cipher_update= */ cast_ecb_cipher_update,
+    /* cipher_final= */ nullptr,
+    /* update_aad= */ nullptr,
     /* cleanup= */ nullptr,
     /* ctrl= */ nullptr,
 };
@@ -414,7 +416,9 @@
     /* ctx_size= */ sizeof(CAST_KEY),
     /* flags= */ EVP_CIPH_CBC_MODE | EVP_CIPH_VARIABLE_LENGTH,
     /* init= */ cast_init_key,
-    /* cipher= */ cast_cbc_cipher,
+    /* cipher_update= */ cast_cbc_cipher_update,
+    /* cipher_final= */ nullptr,
+    /* update_aad= */ nullptr,
     /* cleanup= */ nullptr,
     /* ctrl= */ nullptr,
 };
diff --git a/decrepit/cfb/cfb.cc b/decrepit/cfb/cfb.cc
index b7fb30a..0123a5f 100644
--- a/decrepit/cfb/cfb.cc
+++ b/decrepit/cfb/cfb.cc
@@ -36,8 +36,8 @@
   return 1;
 }
 
-static int aes_cfb128_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out,
-                             const uint8_t *in, size_t len) {
+static int aes_cfb128_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                    const uint8_t *in, size_t len) {
   if (!out || !in) {
     return 0;
   }
@@ -59,7 +59,9 @@
     /* ctx_size= */ sizeof(EVP_CFB_CTX),
     /* flags= */ EVP_CIPH_CFB_MODE,
     /* init= */ aes_cfb_init_key,
-    /* cipher= */ aes_cfb128_cipher,
+    /* cipher_update= */ aes_cfb128_cipher_update,
+    /* cipher_final= */ nullptr,
+    /* update_aad= */ nullptr,
     /* cleanup= */ nullptr,
     /* ctrl= */ nullptr,
 };
@@ -72,7 +74,9 @@
     /* ctx_size= */ sizeof(EVP_CFB_CTX),
     /* flags= */ EVP_CIPH_CFB_MODE,
     /* init= */ aes_cfb_init_key,
-    /* cipher= */ aes_cfb128_cipher,
+    /* cipher_update= */ aes_cfb128_cipher_update,
+    /* cipher_final= */ nullptr,
+    /* update_aad= */ nullptr,
     /* cleanup= */ nullptr,
     /* ctrl= */ nullptr,
 };
@@ -85,7 +89,9 @@
     /* ctx_size= */ sizeof(EVP_CFB_CTX),
     /* flags= */ EVP_CIPH_CFB_MODE,
     /* init= */ aes_cfb_init_key,
-    /* cipher= */ aes_cfb128_cipher,
+    /* cipher= */ aes_cfb128_cipher_update,
+    /* cipher_final= */ nullptr,
+    /* update_aad= */ nullptr,
     /* cleanup= */ nullptr,
     /* ctrl= */ nullptr,
 };
diff --git a/decrepit/xts/xts.cc b/decrepit/xts/xts.cc
index 3b7e96c..adccf0e 100644
--- a/decrepit/xts/xts.cc
+++ b/decrepit/xts/xts.cc
@@ -159,8 +159,8 @@
   return 1;
 }
 
-static int aes_xts_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out, const uint8_t *in,
-                          size_t len) {
+static int aes_xts_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                 const uint8_t *in, size_t len) {
   EVP_AES_XTS_CTX *xctx = reinterpret_cast<EVP_AES_XTS_CTX *>(ctx->cipher_data);
   if (!xctx->xts.key1 || !xctx->xts.key2 || !out || !in ||
       len < AES_BLOCK_SIZE ||
@@ -208,7 +208,9 @@
              EVP_CIPH_ALWAYS_CALL_INIT | EVP_CIPH_CTRL_INIT |
              EVP_CIPH_CUSTOM_COPY,
     /* init= */ aes_xts_init_key,
-    /* cipher= */ aes_xts_cipher,
+    /* cipher_update= */ aes_xts_cipher_update,
+    /* cipher_final= */ nullptr,
+    /* update_aad= */ nullptr,
     /* cleanup= */ nullptr,
     /* ctrl= */ aes_xts_ctrl,
 };
diff --git a/include/openssl/cipher.h b/include/openssl/cipher.h
index a5533ca..8b47369 100644
--- a/include/openssl/cipher.h
+++ b/include/openssl/cipher.h
@@ -128,73 +128,101 @@
 
 // Cipher operations.
 
-// EVP_EncryptUpdate encrypts |in_len| bytes from |in| to |out|. The number
-// of output bytes may be up to |in_len| plus the block length minus one and
-// |out| must have sufficient space. The number of bytes actually output is
-// written to |*out_len|. It returns one on success and zero otherwise.
+// EVP_EncryptUpdate_ex encrypts |in_len| bytes from |in| and writes up to
+// |max_out| bytes of ciphertext to |out|. On success, it sets |*out_len| to
+// the number of output bytes and returns one. Otherwise, it returns zero.
 //
-// Note that the total output length across |EVP_EncryptUpdate| and
-// |EVP_EncryptFinal_ex| will never exceed the sum of |in_len|. However, in
-// ciphers whose the block size is not 1, such as CBC, individual calls to
-// |EVP_EncryptUpdate| may output more or less than |in_len| bytes. In this
-// case, the maximum output length is |in_len| plus the block size minus one.
+// If |max_out| is not large enough for the output, the function will return
+// zero. The size of output buffer needed depends on the cipher and the number
+// of bytes encrypted by |ctx| thus far.
 //
-// If |ctx| is an AEAD cipher, e.g. |EVP_aes_128_gcm|, and |out| is NULL, this
-// function instead adds |in_len| bytes from |in| to the AAD and sets |*out_len|
-// to |in_len|. The AAD must be fully specified in this way before this function
-// is used to encrypt plaintext.
-OPENSSL_EXPORT int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out,
-                                     int *out_len, const uint8_t *in,
-                                     int in_len);
+// In ciphers whose block size is not 1, such as CBC, individual calls to
+// |EVP_EncryptUpdate_ex| may output more or less than |in_len| bytes: a single
+// call to |EVP_EncryptUpdate_ex| may output at most |in_len + block_size - 1|
+// bytes. Additionally, the total output across all |EVP_EncryptUpdate_ex| and
+// |EVP_EncryptFinal_ex2| calls will be at most the total input plus one byte,
+// rounded up to a multiple of the block size.
+OPENSSL_EXPORT int EVP_EncryptUpdate_ex(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                        size_t *out_len, size_t max_out_len,
+                                        const uint8_t *in, size_t in_len);
 
-// EVP_EncryptFinal_ex writes at most a block of ciphertext (if block length is
-// >1) to |out| and sets |*out_len| to the number of bytes written. If padding
-// is enabled (the default) then standard padding is applied to create the
-// final block. If padding is disabled (with |EVP_CIPHER_CTX_set_padding|) then
-// any partial block remaining will cause an error. The function returns one on
-// success and zero otherwise.
-OPENSSL_EXPORT int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, uint8_t *out,
-                                       int *out_len);
-
-// EVP_DecryptUpdate decrypts |in_len| bytes from |in| to |out|. The number of
-// output bytes may be up to |in_len| plus the block length (if block length is
-// >1) and |out| must have sufficient space. The number of bytes actually
-// output is written to |*out_len|. It returns one on success and zero
+// EVP_EncryptFinal_ex2 finishes an encryption operation and writes up to
+// |max_out| bytes of output to out. On success, it sets |*out_len| to the
+// number of bytes written and returns one. Otherwise, it returns zero.
+//
+// If |max_out| is not large enough for the output, the function will return
+// zero. The size of output buffer needed depends on the cipher and the number
+// of bytes encrypted.
+//
+// If the block size is 1, there will be no final output at all; otherwise, at
+// most one block of ciphertext will be written to the output.
+//
+// If padding is enabled (the default) and the block size is not 1, then
+// standard padding is applied to create the final block. If padding is
+// disabled (with |EVP_CIPHER_CTX_set_padding|) then any partial block
+// remaining will cause an error. The function returns one on success and zero
 // otherwise.
-//
-// Note that the total output length across |EVP_DecryptUpdate| and
-// |EVP_DecryptFinal_ex| will never exceed the sum of |in_len|. However, in
-// ciphers whose the block size is not 1, such as CBC, individual calls to
-// |EVP_DecryptUpdate| may output more or less than |in_len| bytes. In this
-// case, the maximum output length is |in_len| plus the block size.
-//
-// If |ctx| is an AEAD cipher, e.g. |EVP_aes_128_gcm|, and |out| is NULL, this
-// function instead adds |in_len| bytes from |in| to the AAD and sets |*out_len|
-// to |in_len|. The AAD must be fully specified in this way before this function
-// is used to decrypt ciphertext.
-OPENSSL_EXPORT int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out,
-                                     int *out_len, const uint8_t *in,
-                                     int in_len);
+OPENSSL_EXPORT int EVP_EncryptFinal_ex2(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                        size_t *out_len, size_t max_out_len);
 
-// EVP_DecryptFinal_ex writes at most a block of ciphertext (if block length is
-// >1) to |out| and sets |*out_len| to the number of bytes written. If padding
-// is enabled (the default) then padding is removed from the final block.
+// EVP_DecryptUpdate_ex decrypts |in_len| bytes from |in| and writes up to
+// |max_out| bytes of plaintext to |out|. On success, it sets |*out_len| to
+// the number of output bytes and returns one. Otherwise, it returns zero.
 //
-// WARNING: it is unsafe to call this function with unauthenticated
-// ciphertext if padding is enabled.
-OPENSSL_EXPORT int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, uint8_t *out,
-                                       int *out_len);
+// If |max_out| is not large enough for the output, the function will return
+// zero. The size of output buffer needed depends on the cipher and the number
+// of bytes decrypted by |ctx| thus far.
+//
+// In ciphers whose block size is not 1, such as CBC, individual calls to
+// |EVP_DecryptUpdate_ex| may output more or less than |in_len| bytes: a single
+// call to |EVP_DecryptUpdate_ex| may output at most |in_len + block_size|
+// bytes. Additionally, the total output across all |EVP_DecryptUpdate_ex| and
+// |EVP_DecryptFinal_ex2| calls will be at most the total input.
+//
+// WARNING: if the cipher is an AEAD cipher, decrypted data should not be
+// parsed or otherwise processed until success has been returned by
+// |EVP_EncryptFinal_ex2|.
+OPENSSL_EXPORT int EVP_DecryptUpdate_ex(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                        size_t *out_len, size_t max_out_len,
+                                        const uint8_t *in, size_t in_len);
 
-// EVP_CipherUpdate calls either |EVP_EncryptUpdate| or |EVP_DecryptUpdate|
-// depending on how |ctx| has been setup.
-OPENSSL_EXPORT int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out,
-                                    int *out_len, const uint8_t *in,
-                                    int in_len);
+// EVP_DecryptFinal_ex2 finishes a decryption operation and writes up to
+// |max_out| bytes of output to out. On success, it sets |*out_len| to the
+// number of bytes written and returns one. Otherwise, it returns zero.
+//
+// If |max_out| is not large enough for the output, the function will return
+// zero. The size of output buffer needed depends on the cipher and the number
+// of bytes decrypted.
+//
+// If the block size is 1, there will be no final output at all; otherwise, at
+// most one block of ciphertext will be written to the output.
+//
+// If padding is enabled (the default) and the block size is not 1, then
+// standard padding is removed from the final block.
+//
+// WARNING: it is unsafe to call this function after decrypting unauthenticated
+// ciphertext if padding is enabled and the block size is not 1 ("padding
+// oracle").
+OPENSSL_EXPORT int EVP_DecryptFinal_ex2(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                        size_t *out_len, size_t max_out_len);
 
-// EVP_CipherFinal_ex calls either |EVP_EncryptFinal_ex| or
-// |EVP_DecryptFinal_ex| depending on how |ctx| has been setup.
-OPENSSL_EXPORT int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, uint8_t *out,
-                                      int *out_len);
+// EVP_CipherUpdate_ex calls either |EVP_EncryptUpdate_ex| or
+// |EVP_DecryptUpdate_ex| depending on how |ctx| has been setup.
+OPENSSL_EXPORT int EVP_CipherUpdate_ex(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                       size_t *out_len, size_t max_out_len,
+                                       const uint8_t *in, size_t in_len);
+
+// EVP_CipherUpdateAAD adds |in_len| bytes from |in| to the AAD. The AAD must
+// be fully specified in this way before any plaintext or ciphertext is
+// supplied to the other functions. Please consider moving to the |EVP_AEAD|
+// APIs instead.
+OPENSSL_EXPORT int EVP_CipherUpdateAAD(EVP_CIPHER_CTX *ctx, const uint8_t *in,
+                                       size_t in_len);
+
+// EVP_CipherFinal_ex2 calls either |EVP_EncryptFinal_ex2| or
+// |EVP_DecryptFinal_ex2| depending on how |ctx| has been setup.
+OPENSSL_EXPORT int EVP_CipherFinal_ex2(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                       size_t *out_len, size_t max_out_len);
 
 
 // Cipher context accessors.
@@ -391,18 +419,81 @@
                                    const EVP_CIPHER *cipher, const uint8_t *key,
                                    const uint8_t *iv);
 
+// EVP_CipherUpdate does the same as |EVP_CipherUpdate_ex|, except that no
+// output size is given and thus no bounds checking is performed.
+//
+// Additionally, if |ctx| is an AEAD cipher, e.g. |EVP_aes_128_gcm|, and |out|
+// is NULL, this function instead behaves like |EVP_CipherUpdateAAD|.
+//
+// WARNING: This function does not check bounds on |out|, and correctly sizing
+// the output buffer is difficult. Use |EVP_CipherUpdate_ex| or
+// |EVP_CipherUpdateAAD| instead.
+OPENSSL_EXPORT int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                    int *out_len, const uint8_t *in,
+                                    int in_len);
+
+// EVP_EncryptUpdate does the same as |EVP_EncryptUpdate_ex|, except that no
+// output size is given and thus no bounds checking is performed.
+//
+// Additionally, if |ctx| is an AEAD cipher, e.g. |EVP_aes_128_gcm|, and |out|
+// is NULL, this function instead behaves like |EVP_CipherUpdateAAD|.
+//
+// WARNING: This function does not check bounds on |out|, and correctly sizing
+// the output buffer is difficult. Use |EVP_EncryptUpdate_ex| or
+// |EVP_CipherUpdateAAD| instead.
+OPENSSL_EXPORT int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                     int *out_len, const uint8_t *in,
+                                     int in_len);
+
+// EVP_DecryptUpdate does the same as |EVP_DecryptUpdate_ex|, except that no
+// output size is given and thus no bounds checking is performed.
+//
+// Additionally, if |ctx| is an AEAD cipher, e.g. |EVP_aes_128_gcm|, and |out|
+// is NULL, this function instead behaves like |EVP_CipherUpdateAAD|.
+//
+// WARNING: This function does not check bounds on out, and correctly sizing
+// the output buffer is difficult. Use |EVP_DecryptUpdate_ex| or
+// |EVP_CipherUpdateAAD| instead.
+OPENSSL_EXPORT int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                     int *out_len, const uint8_t *in,
+                                     int in_len);
+
 // EVP_CipherFinal calls |EVP_CipherFinal_ex|.
 OPENSSL_EXPORT int EVP_CipherFinal(EVP_CIPHER_CTX *ctx, uint8_t *out,
                                    int *out_len);
 
+// EVP_CipherFinal_ex does the same as |EVP_CipherFinal_ex2|, except that no
+// output size is given and thus no bounds checking is performed.
+//
+// WARNING: This function does not check bounds on out, and correctly sizing
+// the output buffer is difficult. Use |EVP_CipherFinal_ex2| instead.
+OPENSSL_EXPORT int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                      int *out_len);
+
 // EVP_EncryptFinal calls |EVP_EncryptFinal_ex|.
 OPENSSL_EXPORT int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, uint8_t *out,
                                     int *out_len);
 
+// EVP_EncryptFinal_ex does the same as |EVP_EncryptFinal_ex2|, except that no
+// output size is given and thus no bounds checking is performed.
+//
+// WARNING: This function does not check bounds on out, and correctly sizing
+// the output buffer is difficult. Use |EVP_EncryptFinal_ex2| instead.
+OPENSSL_EXPORT int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                       int *out_len);
+
 // EVP_DecryptFinal calls |EVP_DecryptFinal_ex|.
 OPENSSL_EXPORT int EVP_DecryptFinal(EVP_CIPHER_CTX *ctx, uint8_t *out,
                                     int *out_len);
 
+// EVP_DecryptFinal_ex does the same as |EVP_DecryptFinal_ex2|, except that no
+// output size is given and thus no bounds checking is performed.
+//
+// WARNING: This function does not check bounds on out, and correctly sizing
+// the output buffer is difficult. Use |EVP_DecryptFinal_ex2| instead.
+OPENSSL_EXPORT int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, uint8_t *out,
+                                       int *out_len);
+
 // EVP_Cipher historically exposed an internal implementation detail of |ctx|
 // and should not be used. Use |EVP_CipherUpdate| and |EVP_CipherFinal_ex|
 // instead.