7142509: Cipher.doFinal(ByteBuffer,ByteBuffer) fails to process when in.remaining() == 0
authorwetmore
Fri, 10 Feb 2012 19:07:23 -0800
changeset 11837 6a7fa5f263ce
parent 11836 4b82a1c142cf
child 11838 90e9e05727dc
7142509: Cipher.doFinal(ByteBuffer,ByteBuffer) fails to process when in.remaining() == 0 Reviewed-by: valeriep
jdk/src/share/classes/javax/crypto/CipherSpi.java
jdk/test/javax/crypto/CipherSpi/DirectBBRemaining.java
--- a/jdk/src/share/classes/javax/crypto/CipherSpi.java	Thu Feb 09 22:55:28 2012 -0800
+++ b/jdk/src/share/classes/javax/crypto/CipherSpi.java	Fri Feb 10 19:07:23 2012 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2012, 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
@@ -775,7 +775,7 @@
             int outOfs = output.arrayOffset() + outPos;
             byte[] inArray = new byte[getTempArraySize(inLen)];
             int total = 0;
-            while (inLen > 0) {
+            do {
                 int chunk = Math.min(inLen, inArray.length);
                 input.get(inArray, 0, chunk);
                 int n;
@@ -787,7 +787,7 @@
                 total += n;
                 outOfs += n;
                 inLen -= chunk;
-            }
+            } while (inLen > 0);
             output.position(outPos + total);
             return total;
         } else { // output is not backed by an accessible byte[]
@@ -804,7 +804,7 @@
             int outSize = outArray.length;
             int total = 0;
             boolean resized = false;
-            while (inLen > 0) {
+            do {
                 int chunk = Math.min(inLen, outSize);
                 if ((a1 == false) && (resized == false)) {
                     input.get(inArray, 0, chunk);
@@ -834,7 +834,7 @@
                     int newOut = engineGetOutputSize(chunk);
                     outArray = new byte[newOut];
                 }
-            }
+            } while (inLen > 0);
             input.position(inLimit);
             return total;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/crypto/CipherSpi/DirectBBRemaining.java	Fri Feb 10 19:07:23 2012 -0800
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2012, 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 7142509
+ * @summary Cipher.doFinal(ByteBuffer,ByteBuffer) fails to
+ *     process when in.remaining() == 0
+ */
+
+import java.nio.ByteBuffer;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Random;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/*
+ * Simple test case to show that Cipher.doFinal(ByteBuffer, ByteBuffer) fails to
+ * process the data internally buffered inBB the cipher when input.remaining()
+ * == 0 and at least one buffer is a direct buffer.
+ */
+public class DirectBBRemaining {
+
+    private static Random random = new SecureRandom();
+    private static int testSizes = 40;
+    private static int outputFrequency = 5;
+
+    public static void main(String args[]) throws Exception {
+        boolean failedOnce = false;
+        Exception failedReason = null;
+
+        byte[] keyBytes = new byte[8];
+        random.nextBytes(keyBytes);
+        SecretKey key = new SecretKeySpec(keyBytes, "DES");
+
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding", "SunJCE");
+        cipher.init(Cipher.ENCRYPT_MODE, key);
+
+        /*
+         * Iterate through various sizes to make sure that the code does empty
+         * blocks, single partial blocks, 1 full block, full + partial blocks,
+         * multiple full blocks, etc. 5 blocks (using DES) is probably overkill
+         * but will feel more confident the fix isn't breaking anything.
+         */
+        System.out.println("Output test results for every "
+                + outputFrequency + " tests...");
+
+        for (int size = 0; size <= testSizes; size++) {
+            boolean output = (size % outputFrequency) == 0;
+            if (output) {
+                System.out.print("\nTesting buffer size: " + size + ":");
+            }
+
+            int outSize = cipher.getOutputSize(size);
+
+            try {
+                encrypt(cipher, size,
+                        ByteBuffer.allocate(size),
+                        ByteBuffer.allocate(outSize),
+                        ByteBuffer.allocateDirect(size),
+                        ByteBuffer.allocateDirect(outSize),
+                        output);
+            } catch (Exception e) {
+                System.out.print("\n    Failed with size " + size);
+                failedOnce = true;
+                failedReason = e;
+
+                // If we got an exception, let's be safe for future
+                // testing and reset the cipher to a known good state.
+                cipher.init(Cipher.ENCRYPT_MODE, key);
+            }
+        }
+        if (failedOnce) {
+            throw failedReason;
+        }
+        System.out.println("\nTest Passed...");
+    }
+
+    private enum TestVariant {
+
+        HEAP_HEAP, HEAP_DIRECT, DIRECT_HEAP, DIRECT_DIRECT
+    };
+
+    private static void encrypt(Cipher cipher, int size,
+            ByteBuffer heapIn, ByteBuffer heapOut,
+            ByteBuffer directIn, ByteBuffer directOut,
+            boolean output) throws Exception {
+
+        ByteBuffer inBB = null;
+        ByteBuffer outBB = null;
+
+        // Set up data and encrypt to known/expected values.
+        byte[] testdata = new byte[size];
+        random.nextBytes(testdata);
+        byte[] expected = cipher.doFinal(testdata);
+
+        for (TestVariant tv : TestVariant.values()) {
+            if (output) {
+                System.out.print(" " + tv);
+            }
+
+            switch (tv) {
+            case HEAP_HEAP:
+                inBB = heapIn;
+                outBB = heapOut;
+                break;
+            case HEAP_DIRECT:
+                inBB = heapIn;
+                outBB = directOut;
+                break;
+            case DIRECT_HEAP:
+                inBB = directIn;
+                outBB = heapOut;
+                break;
+            case DIRECT_DIRECT:
+                inBB = directIn;
+                outBB = directOut;
+                break;
+            }
+
+            inBB.clear();
+            outBB.clear();
+
+            inBB.put(testdata);
+            inBB.flip();
+
+            // Process all data in one shot, but don't call doFinal() yet.
+            // May store up to n-1 bytes (w/block size n) internally.
+            cipher.update(inBB, outBB);
+            if (inBB.hasRemaining()) {
+                throw new Exception("buffer not empty");
+            }
+
+            // finish encryption and process all data buffered
+            cipher.doFinal(inBB, outBB);
+            outBB.flip();
+
+            // validate output size
+            if (outBB.remaining() != expected.length) {
+                throw new Exception(
+                        "incomplete encryption output, expected "
+                        + expected.length + " bytes but was only "
+                        + outBB.remaining() + " bytes");
+            }
+
+            // validate output data
+            byte[] encrypted = new byte[outBB.remaining()];
+            outBB.get(encrypted);
+            if (!Arrays.equals(expected, encrypted)) {
+                throw new Exception("bad encryption output");
+            }
+
+            if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
+                throw new Exception("Internal buffers still held data!");
+            }
+        }
+    }
+}