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  */
  30 
  31 import java.nio.ByteBuffer;
  32 import java.security.SecureRandom;
  33 import java.util.Arrays;
  34 import java.util.Random;
  35 
  36 import javax.crypto.Cipher;
  37 import javax.crypto.SecretKey;
  38 import javax.crypto.spec.SecretKeySpec;
  39 
  40 /*
  41  * Simple test case to show that Cipher.doFinal(ByteBuffer, ByteBuffer) fails to
  42  * process the data internally buffered inBB the cipher when input.remaining()
  43  * == 0 and at least one buffer is a direct buffer.
  44  */
  45 public class DirectBBRemaining {
  46 
  47     private static Random random = new SecureRandom();
  48     private static int testSizes = 40;
  49     private static int outputFrequency = 5;
  50 
  51     public static void main(String args[]) throws Exception {
  52         boolean failedOnce = false;
  53         Exception failedReason = null;
  54 
  55         byte[] keyBytes = new byte[8];
  56         random.nextBytes(keyBytes);
  57         SecretKey key = new SecretKeySpec(keyBytes, "DES");
  58 
  59         Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding", "SunJCE");
  60         cipher.init(Cipher.ENCRYPT_MODE, key);
  61 
  62         /*
  63          * Iterate through various sizes to make sure that the code does empty
  64          * blocks, single partial blocks, 1 full block, full + partial blocks,
  65          * multiple full blocks, etc. 5 blocks (using DES) is probably overkill
  66          * but will feel more confident the fix isn't breaking anything.
  67          */
  68         System.out.println("Output test results for every "
  69                 + outputFrequency + " tests...");
  70 
  71         for (int size = 0; size <= testSizes; size++) {
  72             boolean output = (size % outputFrequency) == 0;
  73             if (output) {
  74                 System.out.print("\nTesting buffer size: " + size + ":");
  75             }
  76 
  77             int outSize = cipher.getOutputSize(size);
  78 
  79             try {
  80                 encrypt(cipher, size,
  81                         ByteBuffer.allocate(size),
  82                         ByteBuffer.allocate(outSize),
  83                         ByteBuffer.allocateDirect(size),
  84                         ByteBuffer.allocateDirect(outSize),
  85                         output);
  86             } catch (Exception e) {
  87                 System.out.print("\n    Failed with size " + size);
  88                 failedOnce = true;
  89                 failedReason = e;
  90 
  91                 // If we got an exception, let's be safe for future
  92                 // testing and reset the cipher to a known good state.
  93                 cipher.init(Cipher.ENCRYPT_MODE, key);
  94             }
  95         }
  96         if (failedOnce) {
  97             throw failedReason;
  98         }
  99         System.out.println("\nTest Passed...");
 100     }
 101 
 102     private enum TestVariant {
 103 
 104         HEAP_HEAP, HEAP_DIRECT, DIRECT_HEAP, DIRECT_DIRECT
 105     };
 106 
 107     private static void encrypt(Cipher cipher, int size,
 108             ByteBuffer heapIn, ByteBuffer heapOut,
 109             ByteBuffer directIn, ByteBuffer directOut,
 110             boolean output) throws Exception {
 111 
 112         ByteBuffer inBB = null;
 113         ByteBuffer outBB = null;
 114 
 115         // Set up data and encrypt to known/expected values.
 116         byte[] testdata = new byte[size];
 117         random.nextBytes(testdata);
 118         byte[] expected = cipher.doFinal(testdata);
 119 
 120         for (TestVariant tv : TestVariant.values()) {
 121             if (output) {
 122                 System.out.print(" " + tv);
 123             }
 124 
 125             switch (tv) {
 126             case HEAP_HEAP:
 127                 inBB = heapIn;
 128                 outBB = heapOut;
 129                 break;
 130             case HEAP_DIRECT:
 131                 inBB = heapIn;
 132                 outBB = directOut;
 133                 break;
 134             case DIRECT_HEAP:
 135                 inBB = directIn;
 136                 outBB = heapOut;
 137                 break;
 138             case DIRECT_DIRECT:
 139                 inBB = directIn;
 140                 outBB = directOut;
 141                 break;
 142             }
 143 
 144             inBB.clear();
 145             outBB.clear();
 146 
 147             inBB.put(testdata);
 148             inBB.flip();
 149 
 150             // Process all data in one shot, but don't call doFinal() yet.
 151             // May store up to n-1 bytes (w/block size n) internally.
 152             cipher.update(inBB, outBB);
 153             if (inBB.hasRemaining()) {
 154                 throw new Exception("buffer not empty");
 155             }
 156 
 157             // finish encryption and process all data buffered
 158             cipher.doFinal(inBB, outBB);
 159             outBB.flip();
 160 
 161             // validate output size
 162             if (outBB.remaining() != expected.length) {
 163                 throw new Exception(
 164                         "incomplete encryption output, expected "
 165                         + expected.length + " bytes but was only "
 166                         + outBB.remaining() + " bytes");
 167             }
 168 
 169             // validate output data
 170             byte[] encrypted = new byte[outBB.remaining()];
 171             outBB.get(encrypted);
 172             if (!Arrays.equals(expected, encrypted)) {
 173                 throw new Exception("bad encryption output");
 174             }
 175 
 176             if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
 177                 throw new Exception("Internal buffers still held data!");
 178             }
 179         }
 180     }
 181 }