OpenSSLCipher: adjust expected length with padding in decrypt mode

- Consider the |final| buffer when computing the expected length
- Should not expect an extra block when using padding in decrypting
mode

Bug: 19186852
Change-Id: I206442d45c4cf68363201738ba9d0b035f19c436
diff --git a/src/main/java/org/conscrypt/NativeCrypto.java b/src/main/java/org/conscrypt/NativeCrypto.java
index 4045a57..55465cb 100644
--- a/src/main/java/org/conscrypt/NativeCrypto.java
+++ b/src/main/java/org/conscrypt/NativeCrypto.java
@@ -300,6 +300,8 @@
 
     public static native int get_EVP_CIPHER_CTX_buf_len(NativeRef.EVP_CIPHER_CTX ctx);
 
+    public static native boolean get_EVP_CIPHER_CTX_final_used(NativeRef.EVP_CIPHER_CTX ctx);
+
     public static native void EVP_CIPHER_CTX_set_padding(NativeRef.EVP_CIPHER_CTX ctx,
             boolean enablePadding);
 
diff --git a/src/main/java/org/conscrypt/OpenSSLCipher.java b/src/main/java/org/conscrypt/OpenSSLCipher.java
index 591915d..47de94a 100644
--- a/src/main/java/org/conscrypt/OpenSSLCipher.java
+++ b/src/main/java/org/conscrypt/OpenSSLCipher.java
@@ -589,10 +589,19 @@
                 return inputLen;
             } else {
                 final int buffered = NativeCrypto.get_EVP_CIPHER_CTX_buf_len(cipherCtx);
+
                 if (getPadding() == Padding.NOPADDING) {
                     return buffered + inputLen;
                 } else {
-                    final int totalLen = inputLen + buffered + modeBlockSize;
+                    final boolean finalUsed = NativeCrypto.get_EVP_CIPHER_CTX_final_used(cipherCtx);
+                    // There is an additional buffer containing the possible final block.
+                    int totalLen = inputLen + buffered + (finalUsed ? modeBlockSize : 0);
+                    // Extra block for remainder bytes plus padding.
+                    // In case it's encrypting and there are no remainder bytes, add an extra block
+                    // consisting only of padding.
+                    totalLen += ((totalLen % modeBlockSize != 0) || isEncrypting())
+                            ? modeBlockSize : 0;
+                    // The minimum multiple of {@code modeBlockSize} that can hold all the bytes.
                     return totalLen - (totalLen % modeBlockSize);
                 }
             }
diff --git a/src/main/native/org_conscrypt_NativeCrypto.cpp b/src/main/native/org_conscrypt_NativeCrypto.cpp
index 07db712..6026f2c 100644
--- a/src/main/native/org_conscrypt_NativeCrypto.cpp
+++ b/src/main/native/org_conscrypt_NativeCrypto.cpp
@@ -4867,6 +4867,20 @@
     return buf_len;
 }
 
+static jboolean NativeCrypto_get_EVP_CIPHER_CTX_final_used(JNIEnv* env, jclass, jobject ctxRef) {
+    EVP_CIPHER_CTX* ctx = fromContextObject<EVP_CIPHER_CTX>(env, ctxRef);
+    JNI_TRACE("get_EVP_CIPHER_CTX_final_used(%p)", ctx);
+
+    if (ctx == NULL) {
+        JNI_TRACE("ctx=%p get_EVP_CIPHER_CTX_final_used => ctx == null", ctx);
+        return 0;
+    }
+
+    bool final_used = ctx->final_used != 0;
+    JNI_TRACE("get_EVP_CIPHER_CTX_final_used(%p) => %d", ctx, final_used);
+    return final_used;
+}
+
 static void NativeCrypto_EVP_CIPHER_CTX_set_padding(JNIEnv* env, jclass, jobject ctxRef,
                                                     jboolean enablePaddingBool) {
     EVP_CIPHER_CTX* ctx = fromContextObject<EVP_CIPHER_CTX>(env, ctxRef);
@@ -10540,6 +10554,7 @@
     NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_new, "()J"),
     NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_block_size, "(" REF_EVP_CIPHER_CTX ")I"),
     NATIVE_METHOD(NativeCrypto, get_EVP_CIPHER_CTX_buf_len, "(" REF_EVP_CIPHER_CTX ")I"),
+    NATIVE_METHOD(NativeCrypto, get_EVP_CIPHER_CTX_final_used, "(" REF_EVP_CIPHER_CTX ")Z"),
     NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_set_padding, "(" REF_EVP_CIPHER_CTX "Z)V"),
     NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_set_key_length, "(" REF_EVP_CIPHER_CTX "I)V"),
     NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_free, "(J)V"),