6946830: javax.crypto.Cipher.doFinal behavior differs depending on platform
authorvaleriep
Wed, 28 Sep 2016 03:18:01 +0000
changeset 41206 591cd107586c
parent 41205 2867bb7f5b53
child 41207 813a335bcb0c
6946830: javax.crypto.Cipher.doFinal behavior differs depending on platform Summary: Updated OracleUcrypto and SunPKCS11 providers with SunJCE provider behavior Reviewed-by: xuelei
jdk/src/jdk.crypto.pkcs11/share/classes/sun/security/pkcs11/P11Cipher.java
jdk/src/jdk.crypto.ucrypto/solaris/classes/com/oracle/security/ucrypto/NativeCipherWithJavaPadding.java
jdk/test/javax/crypto/Cipher/EmptyFinalBuffer.java
--- a/jdk/src/jdk.crypto.pkcs11/share/classes/sun/security/pkcs11/P11Cipher.java	Wed Sep 28 03:10:37 2016 +0000
+++ b/jdk/src/jdk.crypto.pkcs11/share/classes/sun/security/pkcs11/P11Cipher.java	Wed Sep 28 03:18:01 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -344,7 +344,7 @@
     private void implInit(int opmode, Key key, byte[] iv,
             SecureRandom random)
             throws InvalidKeyException, InvalidAlgorithmParameterException {
-        cancelOperation();
+        reset(true);
         if (fixedKeySize != -1 && key.getEncoded().length != fixedKeySize) {
             throw new InvalidKeyException("Key size is invalid");
         }
@@ -404,23 +404,26 @@
         if (initialized == false) {
             return;
         }
-        initialized = false;
+
         if ((session == null) || (token.explicitCancel == false)) {
             return;
         }
-        // cancel operation by finishing it
-        int bufLen = doFinalLength(0);
-        byte[] buffer = new byte[bufLen];
         try {
-            if (encrypt) {
-                token.p11.C_EncryptFinal(session.id(), 0, buffer, 0, bufLen);
+            if (session.hasObjects() == false) {
+                session = token.killSession(session);
+                return;
             } else {
-                token.p11.C_DecryptFinal(session.id(), 0, buffer, 0, bufLen);
+                // cancel operation by finishing it
+                int bufLen = doFinalLength(0);
+                byte[] buffer = new byte[bufLen];
+                if (encrypt) {
+                    token.p11.C_EncryptFinal(session.id(), 0, buffer, 0, bufLen);
+                } else {
+                    token.p11.C_DecryptFinal(session.id(), 0, buffer, 0, bufLen);
+                }
             }
         } catch (PKCS11Exception e) {
             throw new ProviderException("Cancel failed", e);
-        } finally {
-            reset();
         }
     }
 
@@ -483,7 +486,9 @@
     }
 
     // reset the states to the pre-initialized values
-    private void reset() {
+    private void reset(boolean doCancel) {
+        if (doCancel) cancelOperation();
+
         initialized = false;
         bytesBuffered = 0;
         padBufferLen = 0;
@@ -610,7 +615,7 @@
                 throw (ShortBufferException)
                         (new ShortBufferException().initCause(e));
             }
-            reset();
+            reset(false);
             throw new ProviderException("update() failed", e);
         }
     }
@@ -728,7 +733,7 @@
                 throw (ShortBufferException)
                         (new ShortBufferException().initCause(e));
             }
-            reset();
+            reset(false);
             throw new ProviderException("update() failed", e);
         }
     }
@@ -740,6 +745,7 @@
         if (outLen < requiredOutLen) {
             throw new ShortBufferException();
         }
+        boolean doCancel = true;
         try {
             ensureInitialized();
             int k = 0;
@@ -753,7 +759,12 @@
                 }
                 k += token.p11.C_EncryptFinal(session.id(),
                         0, out, (outOfs + k), (outLen - k));
+                doCancel = false;
             } else {
+                // Special handling to match SunJCE provider behavior
+                if (bytesBuffered == 0 && padBufferLen == 0) {
+                    return 0;
+                }
                 if (paddingObj != null) {
                     if (padBufferLen != 0) {
                         k = token.p11.C_DecryptUpdate(session.id(), 0,
@@ -762,20 +773,24 @@
                     }
                     k += token.p11.C_DecryptFinal(session.id(), 0, padBuffer, k,
                             padBuffer.length - k);
+                    doCancel = false;
+
                     int actualPadLen = paddingObj.unpad(padBuffer, k);
                     k -= actualPadLen;
                     System.arraycopy(padBuffer, 0, out, outOfs, k);
                 } else {
                     k = token.p11.C_DecryptFinal(session.id(), 0, out, outOfs,
                             outLen);
+                    doCancel = false;
                 }
             }
             return k;
         } catch (PKCS11Exception e) {
+            doCancel = false;
             handleException(e);
             throw new ProviderException("doFinal() failed", e);
         } finally {
-            reset();
+            reset(doCancel);
         }
     }
 
@@ -788,6 +803,7 @@
             throw new ShortBufferException();
         }
 
+        boolean doCancel = true;
         try {
             ensureInitialized();
 
@@ -818,7 +834,13 @@
                 }
                 k += token.p11.C_EncryptFinal(session.id(),
                         outAddr, outArray, (outOfs + k), (outLen - k));
+                doCancel = false;
             } else {
+                // Special handling to match SunJCE provider behavior
+                if (bytesBuffered == 0 && padBufferLen == 0) {
+                    return 0;
+                }
+
                 if (paddingObj != null) {
                     if (padBufferLen != 0) {
                         k = token.p11.C_DecryptUpdate(session.id(),
@@ -828,6 +850,8 @@
                     }
                     k += token.p11.C_DecryptFinal(session.id(),
                             0, padBuffer, k, padBuffer.length - k);
+                    doCancel = false;
+
                     int actualPadLen = paddingObj.unpad(padBuffer, k);
                     k -= actualPadLen;
                     outArray = padBuffer;
@@ -835,6 +859,7 @@
                 } else {
                     k = token.p11.C_DecryptFinal(session.id(),
                             outAddr, outArray, outOfs, outLen);
+                    doCancel = false;
                 }
             }
             if ((!encrypt && paddingObj != null) ||
@@ -846,10 +871,11 @@
             }
             return k;
         } catch (PKCS11Exception e) {
+            doCancel = false;
             handleException(e);
             throw new ProviderException("doFinal() failed", e);
         } finally {
-            reset();
+            reset(doCancel);
         }
     }
 
--- a/jdk/src/jdk.crypto.ucrypto/solaris/classes/com/oracle/security/ucrypto/NativeCipherWithJavaPadding.java	Wed Sep 28 03:10:37 2016 +0000
+++ b/jdk/src/jdk.crypto.ucrypto/solaris/classes/com/oracle/security/ucrypto/NativeCipherWithJavaPadding.java	Wed Sep 28 03:18:01 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -160,8 +160,11 @@
                 ShortBufferException {
             int tbSize = (trailingBytes == null? 0:trailingBytes.position());
             int dataLen = tbSize + lastData.length;
-            // check total length
-            if ((dataLen < 1) || (dataLen % blockSize != 0)) {
+
+            // Special handling to match SunJCE provider behavior
+            if (dataLen <= 0) {
+                return 0;
+            } else if (dataLen % blockSize != 0) {
                 UcryptoProvider.debug("PKCS5Padding: unpad, buffered " + tbSize +
                                  " bytes, last block " + lastData.length + " bytes");
 
@@ -402,7 +405,6 @@
         throws ShortBufferException, IllegalBlockSizeException,
                BadPaddingException {
         int estimatedOutLen = engineGetOutputSize(inLen);
-
         if (out.length - outOfs < estimatedOutLen) {
             throw new ShortBufferException("Actual: " + (out.length - outOfs) +
                 ". Estimated Out Length: " + estimatedOutLen);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/crypto/Cipher/EmptyFinalBuffer.java	Wed Sep 28 03:18:01 2016 +0000
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 6946830
+ * @summary Test the Cipher.doFinal() with 0-length buffer
+ * @key randomness
+ */
+
+import java.util.*;
+import java.nio.*;
+
+import java.security.*;
+
+import javax.crypto.*;
+import javax.crypto.spec.*;
+
+public class EmptyFinalBuffer {
+
+    private static final String[] ALGOS = {
+        "AES/ECB/PKCS5Padding", "AES/CBC/PKCS5Padding"
+    };
+
+    public static void main(String[] args) throws Exception {
+
+        Provider[] provs = Security.getProviders();
+
+        SecretKey key = new SecretKeySpec(new byte[16], "AES");
+
+        boolean testFailed = false;
+        for (Provider p : provs) {
+            System.out.println("Testing: " + p.getName());
+            for (String algo : ALGOS) {
+                System.out.print("Algo: " + algo);
+                Cipher c;
+                try {
+                    c = Cipher.getInstance(algo, p);
+                } catch (NoSuchAlgorithmException nsae) {
+                    // skip
+                    System.out.println("=> No Support");
+                    continue;
+                }
+                c.init(Cipher.ENCRYPT_MODE, key);
+                AlgorithmParameters params = c.getParameters();
+                c.init(Cipher.DECRYPT_MODE, key, params);
+                try {
+                    byte[] out = c.doFinal(new byte[0]);
+                    System.out.println("=> Accepted w/ " +
+                        (out == null? "null" : (out.length + "-byte")) +
+                        " output");
+                } catch (Exception e) {
+                    testFailed = true;
+                    System.out.println("=> Rejected w/ Exception");
+                    e.printStackTrace();
+                }
+            }
+        }
+        if (testFailed) {
+            throw new Exception("One or more tests failed");
+        } else {
+            System.out.println("All tests passed");
+        }
+    }
+}