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