1 /*
   2  * Copyright (c) 2007, 2015, 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 import java.io.ByteArrayInputStream;
  25 import java.io.ByteArrayOutputStream;
  26 import java.io.IOException;
  27 import java.security.GeneralSecurityException;
  28 import java.security.NoSuchAlgorithmException;
  29 import java.util.Arrays;
  30 import javax.crypto.Cipher;
  31 import javax.crypto.CipherInputStream;
  32 import javax.crypto.CipherOutputStream;
  33 import javax.crypto.KeyGenerator;
  34 import javax.crypto.SecretKey;
  35 import jdk.testlibrary.RandomFactory;
  36 
  37 /*
  38  * @test
  39  * @bug 8048596
  40  * @key randomness
  41  * @summary Test CICO AEAD read/write/skip operations
  42  * @library /lib/testlibrary
  43  */
  44 public class ReadWriteSkip {
  45 
  46     static enum BufferType {
  47         BYTE_ARRAY_BUFFERING, INT_BYTE_BUFFERING
  48     }
  49 
  50     static final int KEY_LENGTHS[] = {128, 192, 256};
  51     static final int TXT_LENGTHS[] = {800, 0};
  52     static final int AAD_LENGTHS[] = {0, 100};
  53     static final int BLOCK = 50;
  54     static final int SAVE = 45;
  55     static final int DISCARD = BLOCK - SAVE;
  56     static final String PROVIDER = "SunJCE";
  57     static final String AES = "AES";
  58     static final String GCM = "GCM";
  59     static final String PADDING = "NoPadding";
  60     static final String TRANSFORM = AES + "/" + GCM + "/" + PADDING;
  61 
  62     final SecretKey key;
  63     final byte[] plaintext;
  64     final byte[] AAD;
  65     final int textLength;;
  66     final int keyLength;
  67     Cipher encryptCipher;
  68     Cipher decryptCipher;
  69     CipherInputStream ciInput;
  70 
  71     public static void main(String[] args) throws Exception {
  72         boolean success = true;
  73         for (int keyLength : KEY_LENGTHS) {
  74             if (keyLength > Cipher.getMaxAllowedKeyLength(TRANSFORM)) {
  75                 // skip this if this key length is larger than what's
  76                 // configured in the jce jurisdiction policy files
  77                 continue;
  78             }
  79             for (int textLength : TXT_LENGTHS) {
  80                 for (int AADLength : AAD_LENGTHS) {
  81                     System.out.println("Key length = " + keyLength
  82                             + ", text length = " + textLength
  83                             + ", AAD length = " + AADLength);
  84                     try {
  85                         run(keyLength, textLength, AADLength);
  86                         System.out.println("Test case passed");
  87                     } catch (Exception e) {
  88                         System.out.println("Test case failed: " + e);
  89                         success = false;
  90                     }
  91                 }
  92             }
  93         }
  94 
  95         if (!success) {
  96             throw new RuntimeException("At least one test case failed");
  97         }
  98 
  99         System.out.println("Test passed");
 100     }
 101 
 102     ReadWriteSkip(int keyLength, int textLength, int AADLength)
 103             throws Exception {
 104         this.keyLength = keyLength;
 105         this.textLength = textLength;
 106 
 107         // init AAD
 108         this.AAD = new byte[AADLength];
 109         RandomFactory.getRandom().nextBytes(AAD);
 110 
 111         // init a secret Key
 112         KeyGenerator kg = KeyGenerator.getInstance(AES, PROVIDER);
 113         kg.init(this.keyLength);
 114         this.key = kg.generateKey();
 115 
 116         this.plaintext = new byte[textLength];
 117         RandomFactory.getRandom().nextBytes(plaintext);
 118     }
 119 
 120     final void doTest(BufferType type) throws Exception {
 121         // init ciphers
 122         encryptCipher = createCipher(Cipher.ENCRYPT_MODE);
 123         decryptCipher = createCipher(Cipher.DECRYPT_MODE);
 124 
 125         // init cipher input stream
 126         ciInput = new CipherInputStream(new ByteArrayInputStream(plaintext),
 127                 encryptCipher);
 128 
 129         runTest(type);
 130     }
 131 
 132     void runTest(BufferType type) throws Exception {}
 133 
 134     private Cipher createCipher(int mode) throws GeneralSecurityException {
 135         Cipher cipher = Cipher.getInstance(TRANSFORM, PROVIDER);
 136         if (mode == Cipher.ENCRYPT_MODE) {
 137             cipher.init(Cipher.ENCRYPT_MODE, key);
 138         } else {
 139             if (encryptCipher != null) {
 140                 cipher.init(Cipher.DECRYPT_MODE, key,
 141                         encryptCipher.getParameters());
 142             } else {
 143                 throw new RuntimeException("Can't create a cipher");
 144             }
 145         }
 146         cipher.updateAAD(AAD);
 147         return cipher;
 148     }
 149 
 150     /*
 151      * Run test cases
 152      */
 153     static void run(int keyLength, int textLength, int AADLength)
 154             throws Exception {
 155         new ReadWriteTest(keyLength, textLength, AADLength)
 156                 .doTest(BufferType.BYTE_ARRAY_BUFFERING);
 157         new ReadWriteTest(keyLength, textLength, AADLength)
 158                 .doTest(BufferType.INT_BYTE_BUFFERING);
 159         new SkipTest(keyLength, textLength, AADLength)
 160                 .doTest(BufferType.BYTE_ARRAY_BUFFERING);
 161         new SkipTest(keyLength, textLength, AADLength)
 162                 .doTest(BufferType.INT_BYTE_BUFFERING);
 163     }
 164 
 165     static void check(byte[] first, byte[] second) {
 166         if (!Arrays.equals(first, second)) {
 167             throw new RuntimeException("Arrays are not equal");
 168         }
 169     }
 170 
 171     /*
 172      * CICO AEAD read/write functional test.
 173      *
 174      * Check if encrypt/decrypt operations work correctly.
 175      *
 176      * Test scenario:
 177      *   - initializes plain text with random generated data
 178      *   - for given AEAD algorithm instantiates encrypt and decrypt Ciphers
 179      *   - instantiates CipherInputStream with the encrypt Cipher
 180      *   - instantiates CipherOutputStream with the decrypt Cipher
 181      *   - performs reading from the CipherInputStream (encryption data)
 182      *     and writing to the CipherOutputStream (decryption). As a result,
 183      *     output of the CipherOutputStream should be equal
 184      *     with original plain text
 185      *   - check if the original plain text is equal to output
 186      *     of the CipherOutputStream
 187      *   - if it is equal the test passes, otherwise it fails
 188      */
 189     static class ReadWriteTest extends ReadWriteSkip {
 190 
 191         public ReadWriteTest(int keyLength, int textLength, int AADLength)
 192                 throws Exception {
 193             super(keyLength, textLength, AADLength);
 194         }
 195 
 196         @Override
 197         public void runTest(BufferType bufType) throws IOException,
 198                 GeneralSecurityException {
 199 
 200             ByteArrayOutputStream baOutput = new ByteArrayOutputStream();
 201             try (CipherOutputStream ciOutput = new CipherOutputStream(baOutput,
 202                     decryptCipher)) {
 203                 if (bufType == BufferType.BYTE_ARRAY_BUFFERING) {
 204                     doByteTest(ciOutput);
 205                 } else {
 206                     doIntTest(ciOutput);
 207                 }
 208             }
 209 
 210             check(plaintext, baOutput.toByteArray());
 211         }
 212 
 213         /*
 214          * Implements byte array buffering type test case
 215          */
 216         public void doByteTest(CipherOutputStream out) throws IOException {
 217             byte[] buffer = new byte[textLength + 1];
 218             int len = ciInput.read(buffer);
 219             while (len != -1) {
 220                 out.write(buffer, 0, len);
 221                 len = ciInput.read(buffer);
 222             }
 223         }
 224 
 225         /*
 226          * Implements integer buffering type test case
 227          */
 228         public void doIntTest(CipherOutputStream out) throws IOException {
 229             int buffer = ciInput.read();
 230             while (buffer != -1) {
 231                 out.write(buffer);
 232                 buffer = ciInput.read();
 233             }
 234         }
 235     }
 236 
 237     /*
 238      * CICO AEAD SKIP functional test.
 239      *
 240      * Checks if the encrypt/decrypt operations work correctly
 241      * when skip() method is used.
 242      *
 243      * Test scenario:
 244      *   - initializes a plain text with random data
 245      *   - initializes ciphers
 246      *   - initializes cipher streams
 247      *   - split plain text to TEXT_SIZE/BLOCK blocks
 248      *   - read from CipherInputStream2 one block at time
 249      *   - the last DISCARD = BLOCK - SAVE bytes are skipping for each block
 250      *   - therefore, plain text data go through CipherInputStream1 (encrypting)
 251      *     and CipherInputStream2 (decrypting)
 252      *   - as a result, output should equal to the original text
 253      *     except DISCART byte for each block
 254      *   - check result buffers
 255      */
 256     static class SkipTest extends ReadWriteSkip {
 257 
 258         private final int numberOfBlocks;
 259         private final byte[] outputText;
 260 
 261         public SkipTest(int keyLength, int textLength, int AADLength)
 262                 throws Exception {
 263             super(keyLength, textLength, AADLength);
 264             numberOfBlocks = this.textLength / BLOCK;
 265             outputText = new byte[numberOfBlocks * SAVE];
 266         }
 267 
 268         private void doByteTest(int blockNum, CipherInputStream cis)
 269                 throws IOException {
 270             int index = blockNum * SAVE;
 271             int len = cis.read(outputText, index, SAVE);
 272             index += len;
 273             int read = 0;
 274             while (len != SAVE && read != -1) {
 275                 read = cis.read(outputText, index, SAVE - len);
 276                 len += read;
 277                 index += read;
 278             }
 279         }
 280 
 281         private void doIntTest(int blockNum, CipherInputStream cis)
 282                 throws IOException {
 283             int i = blockNum * SAVE;
 284             for (int j = 0; j < SAVE && i < outputText.length; j++, i++) {
 285                 int b = cis.read();
 286                 if (b != -1) {
 287                     outputText[i] = (byte) b;
 288                 }
 289             }
 290         }
 291 
 292         @Override
 293         public void runTest(BufferType type) throws IOException,
 294                 NoSuchAlgorithmException {
 295             try (CipherInputStream cis = new CipherInputStream(ciInput,
 296                     decryptCipher)) {
 297                 for (int i = 0; i < numberOfBlocks; i++) {
 298                     if (type == BufferType.BYTE_ARRAY_BUFFERING) {
 299                         doByteTest(i, cis);
 300                     } else {
 301                         doIntTest(i, cis);
 302                     }
 303                     if (cis.available() >= DISCARD) {
 304                         cis.skip(DISCARD);
 305                     } else {
 306                         for (int k = 0; k < DISCARD; k++) {
 307                             cis.read();
 308                         }
 309                     }
 310                 }
 311             }
 312 
 313             byte[] expectedText = new byte[numberOfBlocks * SAVE];
 314             for (int m = 0; m < numberOfBlocks; m++) {
 315                 for (int n = 0; n < SAVE; n++) {
 316                     expectedText[m * SAVE + n] = plaintext[m * BLOCK + n];
 317                 }
 318             }
 319             check(expectedText, outputText);
 320         }
 321     }
 322 }