/* * 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 * @key randomness */ 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!"); } } } }