1 /*
   2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @bug 7142509
  27  * @summary Cipher.doFinal(ByteBuffer,ByteBuffer) fails to
  28  *     process when in.remaining() == 0
  29  * @key randomness
  30  */
  31 
  32 import java.nio.ByteBuffer;
  33 import java.security.SecureRandom;
  34 import java.util.Arrays;
  35 import java.util.Random;
  36 
  37 import javax.crypto.Cipher;
  38 import javax.crypto.SecretKey;
  39 import javax.crypto.spec.SecretKeySpec;
  40 
  41 /*
  42  * Simple test case to show that Cipher.doFinal(ByteBuffer, ByteBuffer) fails to
  43  * process the data internally buffered inBB the cipher when input.remaining()
  44  * == 0 and at least one buffer is a direct buffer.
  45  */
  46 public class DirectBBRemaining {
  47 
  48     private static Random random = new SecureRandom();
  49     private static int testSizes = 40;
  50     private static int outputFrequency = 5;
  51 
  52     public static void main(String args[]) throws Exception {
  53         boolean failedOnce = false;
  54         Exception failedReason = null;
  55 
  56         byte[] keyBytes = new byte[8];
  57         random.nextBytes(keyBytes);
  58         SecretKey key = new SecretKeySpec(keyBytes, "DES");
  59 
  60         Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding", "SunJCE");
  61         cipher.init(Cipher.ENCRYPT_MODE, key);
  62 
  63         /*
  64          * Iterate through various sizes to make sure that the code does empty
  65          * blocks, single partial blocks, 1 full block, full + partial blocks,
  66          * multiple full blocks, etc. 5 blocks (using DES) is probably overkill
  67          * but will feel more confident the fix isn't breaking anything.
  68          */
  69         System.out.println("Output test results for every "
  70                 + outputFrequency + " tests...");
  71 
  72         for (int size = 0; size <= testSizes; size++) {
  73             boolean output = (size % outputFrequency) == 0;
  74             if (output) {
  75                 System.out.print("\nTesting buffer size: " + size + ":");
  76             }
  77 
  78             int outSize = cipher.getOutputSize(size);
  79 
  80             try {
  81                 encrypt(cipher, size,
  82                         ByteBuffer.allocate(size),
  83                         ByteBuffer.allocate(outSize),
  84                         ByteBuffer.allocateDirect(size),
  85                         ByteBuffer.allocateDirect(outSize),
  86                         output);
  87             } catch (Exception e) {
  88                 System.out.print("\n    Failed with size " + size);
  89                 failedOnce = true;
  90                 failedReason = e;
  91 
  92                 // If we got an exception, let's be safe for future
  93                 // testing and reset the cipher to a known good state.
  94                 cipher.init(Cipher.ENCRYPT_MODE, key);
  95             }
  96         }
  97         if (failedOnce) {
  98             throw failedReason;
  99         }
 100         System.out.println("\nTest Passed...");
 101     }
 102 
 103     private enum TestVariant {
 104 
 105         HEAP_HEAP, HEAP_DIRECT, DIRECT_HEAP, DIRECT_DIRECT
 106     };
 107 
 108     private static void encrypt(Cipher cipher, int size,
 109             ByteBuffer heapIn, ByteBuffer heapOut,
 110             ByteBuffer directIn, ByteBuffer directOut,
 111             boolean output) throws Exception {
 112 
 113         ByteBuffer inBB = null;
 114         ByteBuffer outBB = null;
 115 
 116         // Set up data and encrypt to known/expected values.
 117         byte[] testdata = new byte[size];
 118         random.nextBytes(testdata);
 119         byte[] expected = cipher.doFinal(testdata);
 120 
 121         for (TestVariant tv : TestVariant.values()) {
 122             if (output) {
 123                 System.out.print(" " + tv);
 124             }
 125 
 126             switch (tv) {
 127             case HEAP_HEAP:
 128                 inBB = heapIn;
 129                 outBB = heapOut;
 130                 break;
 131             case HEAP_DIRECT:
 132                 inBB = heapIn;
 133                 outBB = directOut;
 134                 break;
 135             case DIRECT_HEAP:
 136                 inBB = directIn;
 137                 outBB = heapOut;
 138                 break;
 139             case DIRECT_DIRECT:
 140                 inBB = directIn;
 141                 outBB = directOut;
 142                 break;
 143             }
 144 
 145             inBB.clear();
 146             outBB.clear();
 147 
 148             inBB.put(testdata);
 149             inBB.flip();
 150 
 151             // Process all data in one shot, but don't call doFinal() yet.
 152             // May store up to n-1 bytes (w/block size n) internally.
 153             cipher.update(inBB, outBB);
 154             if (inBB.hasRemaining()) {
 155                 throw new Exception("buffer not empty");
 156             }
 157 
 158             // finish encryption and process all data buffered
 159             cipher.doFinal(inBB, outBB);
 160             outBB.flip();
 161 
 162             // validate output size
 163             if (outBB.remaining() != expected.length) {
 164                 throw new Exception(
 165                         "incomplete encryption output, expected "
 166                         + expected.length + " bytes but was only "
 167                         + outBB.remaining() + " bytes");
 168             }
 169 
 170             // validate output data
 171             byte[] encrypted = new byte[outBB.remaining()];
 172             outBB.get(encrypted);
 173             if (!Arrays.equals(expected, encrypted)) {
 174                 throw new Exception("bad encryption output");
 175             }
 176 
 177             if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
 178                 throw new Exception("Internal buffers still held data!");
 179             }
 180         }
 181     }
 182 }