diff --git a/src/share/classes/javax/crypto/CipherSpi.java b/src/share/classes/javax/crypto/CipherSpi.java index c3442ea2d81f070273cdf6bc221d5bcf84e8b318..e563e920eb690deb21d504675336f5796b9ee160 100644 --- a/src/share/classes/javax/crypto/CipherSpi.java +++ b/src/share/classes/javax/crypto/CipherSpi.java @@ -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 @@ public abstract class CipherSpi { 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 @@ public abstract class CipherSpi { 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 @@ public abstract class CipherSpi { 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 @@ public abstract class CipherSpi { int newOut = engineGetOutputSize(chunk); outArray = new byte[newOut]; } - } + } while (inLen > 0); input.position(inLimit); return total; } diff --git a/test/javax/crypto/CipherSpi/DirectBBRemaining.java b/test/javax/crypto/CipherSpi/DirectBBRemaining.java new file mode 100644 index 0000000000000000000000000000000000000000..9cc840664c05dbbb868573b9ce98a06c0a132710 --- /dev/null +++ b/test/javax/crypto/CipherSpi/DirectBBRemaining.java @@ -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!"); + } + } + } +}