/* * Copyright (c) 2007, 2015, 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. */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; /* * @test * @bug 8048596 * @summary Test CICO AEAD read/write/skip operations */ public class ReadWriteSkip { static enum BufferType { BYTE_ARRAY_BUFFERING, INT_BYTE_BUFFERING } static final int KEY_LENGTHS[] = {128, 192, 256}; static final int TXT_LENGTHS[] = {800, 0}; static final int AAD_LENGTHS[] = {0, 100}; static final int BLOCK = 50; static final int SAVE = 45; static final int DISCARD = BLOCK - SAVE; static final String PROVIDER = "SunJCE"; static final String AES = "AES"; static final String GCM = "GCM"; static final String PADDING = "NoPadding"; static final String TRANSFORM = AES + "/" + GCM + "/" + PADDING; final SecretKey key; final byte[] plaintext; final byte[] AAD; final int textLength;; final int keyLength; Cipher encryptCipher; Cipher decryptCipher; CipherInputStream ciInput; public static void main(String[] args) throws Exception { boolean success = true; for (int keyLength : KEY_LENGTHS) { if (keyLength > Cipher.getMaxAllowedKeyLength(TRANSFORM)) { // skip this if this key length is larger than what's // configured in the jce jurisdiction policy files continue; } for (int textLength : TXT_LENGTHS) { for (int AADLength : AAD_LENGTHS) { System.out.println("Key length = " + keyLength + ", text length = " + textLength + ", AAD length = " + AADLength); try { run(keyLength, textLength, AADLength); System.out.println("Test case passed"); } catch (Exception e) { System.out.println("Test case failed: " + e); success = false; } } } } if (!success) { throw new RuntimeException("At least one test case failed"); } System.out.println("Test passed"); } ReadWriteSkip(int keyLength, int textLength, int AADLength) throws Exception { this.keyLength = keyLength; this.textLength = textLength; // init AAD this.AAD = Helper.generateBytes(AADLength); // init a secret Key KeyGenerator kg = KeyGenerator.getInstance(AES, PROVIDER); kg.init(this.keyLength); this.key = kg.generateKey(); this.plaintext = Helper.generateBytes(textLength); } final void doTest(BufferType type) throws Exception { // init ciphers encryptCipher = createCipher(Cipher.ENCRYPT_MODE); decryptCipher = createCipher(Cipher.DECRYPT_MODE); // init cipher input stream ciInput = new CipherInputStream(new ByteArrayInputStream(plaintext), encryptCipher); runTest(type); } void runTest(BufferType type) throws Exception {} private Cipher createCipher(int mode) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(TRANSFORM, PROVIDER); if (mode == Cipher.ENCRYPT_MODE) { cipher.init(Cipher.ENCRYPT_MODE, key); } else { if (encryptCipher != null) { cipher.init(Cipher.DECRYPT_MODE, key, encryptCipher.getParameters()); } else { throw new RuntimeException("Can't create a cipher"); } } cipher.updateAAD(AAD); return cipher; } /* * Run test cases */ static void run(int keyLength, int textLength, int AADLength) throws Exception { new ReadWriteTest(keyLength, textLength, AADLength) .doTest(BufferType.BYTE_ARRAY_BUFFERING); new ReadWriteTest(keyLength, textLength, AADLength) .doTest(BufferType.INT_BYTE_BUFFERING); new SkipTest(keyLength, textLength, AADLength) .doTest(BufferType.BYTE_ARRAY_BUFFERING); new SkipTest(keyLength, textLength, AADLength) .doTest(BufferType.INT_BYTE_BUFFERING); } static void check(byte[] first, byte[] second) { if (!Arrays.equals(first, second)) { throw new RuntimeException("Arrays are not equal"); } } /* * CICO AEAD read/write functional test. * * Check if encrypt/decrypt operations work correctly. * * Test scenario: * - initializes plain text * - for given AEAD algorithm instantiates encrypt and decrypt Ciphers * - instantiates CipherInputStream with the encrypt Cipher * - instantiates CipherOutputStream with the decrypt Cipher * - performs reading from the CipherInputStream (encryption data) * and writing to the CipherOutputStream (decryption). As a result, * output of the CipherOutputStream should be equal * with original plain text * - check if the original plain text is equal to output * of the CipherOutputStream * - if it is equal the test passes, otherwise it fails */ static class ReadWriteTest extends ReadWriteSkip { public ReadWriteTest(int keyLength, int textLength, int AADLength) throws Exception { super(keyLength, textLength, AADLength); } @Override public void runTest(BufferType bufType) throws IOException, GeneralSecurityException { ByteArrayOutputStream baOutput = new ByteArrayOutputStream(); try (CipherOutputStream ciOutput = new CipherOutputStream(baOutput, decryptCipher)) { if (bufType == BufferType.BYTE_ARRAY_BUFFERING) { doByteTest(ciOutput); } else { doIntTest(ciOutput); } } check(plaintext, baOutput.toByteArray()); } /* * Implements byte array buffering type test case */ public void doByteTest(CipherOutputStream out) throws IOException { byte[] buffer = Helper.generateBytes(textLength + 1); int len = ciInput.read(buffer); while (len != -1) { out.write(buffer, 0, len); len = ciInput.read(buffer); } } /* * Implements integer buffering type test case */ public void doIntTest(CipherOutputStream out) throws IOException { int buffer = ciInput.read(); while (buffer != -1) { out.write(buffer); buffer = ciInput.read(); } } } /* * CICO AEAD SKIP functional test. * * Checks if the encrypt/decrypt operations work correctly * when skip() method is used. * * Test scenario: * - initializes a plain text * - initializes ciphers * - initializes cipher streams * - split plain text to TEXT_SIZE/BLOCK blocks * - read from CipherInputStream2 one block at time * - the last DISCARD = BLOCK - SAVE bytes are skipping for each block * - therefore, plain text data go through CipherInputStream1 (encrypting) * and CipherInputStream2 (decrypting) * - as a result, output should equal to the original text * except DISCART byte for each block * - check result buffers */ static class SkipTest extends ReadWriteSkip { private final int numberOfBlocks; private final byte[] outputText; public SkipTest(int keyLength, int textLength, int AADLength) throws Exception { super(keyLength, textLength, AADLength); numberOfBlocks = this.textLength / BLOCK; outputText = new byte[numberOfBlocks * SAVE]; } private void doByteTest(int blockNum, CipherInputStream cis) throws IOException { int index = blockNum * SAVE; int len = cis.read(outputText, index, SAVE); index += len; int read = 0; while (len != SAVE && read != -1) { read = cis.read(outputText, index, SAVE - len); len += read; index += read; } } private void doIntTest(int blockNum, CipherInputStream cis) throws IOException { int i = blockNum * SAVE; for (int j = 0; j < SAVE && i < outputText.length; j++, i++) { int b = cis.read(); if (b != -1) { outputText[i] = (byte) b; } } } @Override public void runTest(BufferType type) throws IOException, NoSuchAlgorithmException { try (CipherInputStream cis = new CipherInputStream(ciInput, decryptCipher)) { for (int i = 0; i < numberOfBlocks; i++) { if (type == BufferType.BYTE_ARRAY_BUFFERING) { doByteTest(i, cis); } else { doIntTest(i, cis); } if (cis.available() >= DISCARD) { cis.skip(DISCARD); } else { for (int k = 0; k < DISCARD; k++) { cis.read(); } } } } byte[] expectedText = new byte[numberOfBlocks * SAVE]; for (int m = 0; m < numberOfBlocks; m++) { for (int n = 0; n < SAVE; n++) { expectedText[m * SAVE + n] = plaintext[m * BLOCK + n]; } } check(expectedText, outputText); } } }