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.nio.ByteBuffer;
25import java.security.AlgorithmParameters;
26import java.security.Provider;
27import java.security.Security;
28import javax.crypto.SecretKey;
29import javax.crypto.Cipher;
30import javax.crypto.KeyGenerator;
31import javax.crypto.spec.GCMParameterSpec;
32
33/*
34 * @test
35 * @bug 8048596
36 * @summary Check if AEAD operations work correctly when buffers used
37 *          for storing plain text and cipher text are overlapped or the same
38 */
39public class SameBuffer {
40
41    private static final String PROVIDER = "SunJCE";
42    private static final String AES = "AES";
43    private static final String GCM = "GCM";
44    private static final String PADDING = "NoPadding";
45    private static final int OFFSET = 2;
46    private static final int OFFSETS = 4;
47    private static final int KEY_LENGTHS[] = { 128, 192, 256 };
48    private static final int TEXT_LENGTHS[] = { 0, 1024 };
49    private static final int AAD_LENGTHS[] = { 0, 1024 };
50
51    private final Provider provider;
52    private final SecretKey key;
53    private final String transformation;
54    private final int textLength;
55    private final int AADLength;
56
57    /**
58     * Constructor of the test
59     *
60     * @param provider security provider
61     * @param keyStrength key length
62     * @param textLength length of data
63     * @param AADLength AAD length
64     */
65    public SameBuffer(Provider provider, String algorithm, String mode,
66            String padding, int keyStrength, int textLength, int AADLength)
67            throws Exception {
68
69        // init a secret key
70        KeyGenerator kg = KeyGenerator.getInstance(algorithm, provider);
71        kg.init(keyStrength);
72        key = kg.generateKey();
73
74        this.transformation = algorithm + "/" + mode + "/" + padding;
75        this.provider = provider;
76        this.textLength = textLength;
77        this.AADLength = AADLength;
78    }
79
80    public static void main(String[] args) throws Exception {
81        Provider p = Security.getProvider(PROVIDER);
82        for (int keyLength : KEY_LENGTHS) {
83            for (int textLength : TEXT_LENGTHS) {
84                for (int AADLength : AAD_LENGTHS) {
85                    for (int i = 0; i < OFFSETS; i++) {
86                        // try different offsets
87                        int offset = i * OFFSET;
88                        runTest(p, AES, GCM, PADDING, keyLength, textLength,
89                                AADLength, offset);
90                    }
91                }
92            }
93        }
94    }
95
96    /*
97     * Run single test case with given parameters
98     */
99    static void runTest(Provider p, String algo, String mode,
100            String padding, int keyLength, int textLength, int AADLength,
101            int offset) throws Exception {
102        System.out.println("Testing " + keyLength + " key length; "
103                + textLength + " text lenght; " + AADLength + " AAD length; "
104                + offset + " offset");
105        if (keyLength > Cipher.getMaxAllowedKeyLength(algo)) {
106            // skip this if this key length is larger than what's
107            // configured in the jce jurisdiction policy files
108            return;
109        }
110        SameBuffer test = new SameBuffer(p, algo, mode,
111                padding, keyLength, textLength, AADLength);
112
113        /*
114         * There are four test cases:
115         *   1. AAD and text are placed in separated byte arrays
116         *   2. AAD and text are placed in the same byte array
117         *   3. AAD and text are placed in separated byte buffers
118         *   4. AAD and text are placed in the same byte buffer
119         */
120        Cipher ci = test.createCipher(Cipher.ENCRYPT_MODE, null);
121        AlgorithmParameters params = ci.getParameters();
122        test.doTestWithSeparateArrays(offset, params);
123        test.doTestWithSameArrays(offset, params);
124        test.doTestWithSeparatedBuffer(offset, params);
125        test.doTestWithSameBuffer(offset, params);
126    }
127
128    /*
129     * Run the test in case when AAD and text are placed in separated byte
130     * arrays.
131     */
132    private void doTestWithSeparateArrays(int offset,
133            AlgorithmParameters params) throws Exception {
134        // prepare buffers to test
135        Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
136        int outputLength = c.getOutputSize(textLength);
137        int outputBufSize = outputLength + offset * 2;
138
139        byte[] inputText = Helper.generateBytes(outputBufSize);
140        byte[] AAD = Helper.generateBytes(AADLength);
141
142        // do the test
143        runGCMWithSeparateArray(Cipher.ENCRYPT_MODE, AAD, inputText, offset * 2,
144                textLength, offset, params);
145        int tagLength = c.getParameters()
146                .getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
147        runGCMWithSeparateArray(Cipher.DECRYPT_MODE, AAD, inputText, offset,
148                textLength + tagLength, offset, params);
149    }
150
151    /**
152     * Run the test in case when AAD and text are placed in the same byte
153     * array.
154     */
155    private void doTestWithSameArrays(int offset, AlgorithmParameters params)
156            throws Exception {
157        // prepare buffers to test
158        Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
159        int outputLength = c.getOutputSize(textLength);
160        int outputBufSize = AADLength + outputLength + offset * 2;
161
162        byte[] AAD_and_text = Helper.generateBytes(outputBufSize);
163
164        // do the test
165        runGCMWithSameArray(Cipher.ENCRYPT_MODE, AAD_and_text, AADLength + offset,
166                textLength, params);
167        int tagLength = c.getParameters()
168                .getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
169        runGCMWithSameArray(Cipher.DECRYPT_MODE, AAD_and_text, AADLength + offset,
170                textLength + tagLength, params);
171    }
172
173    /*
174     * Run the test in case when AAD and text are placed in separated ByteBuffer
175     */
176    private void doTestWithSeparatedBuffer(int offset,
177            AlgorithmParameters params) throws Exception {
178        // prepare AAD byte buffers to test
179        byte[] AAD = Helper.generateBytes(AADLength);
180        ByteBuffer AAD_Buf = ByteBuffer.allocate(AADLength);
181        AAD_Buf.put(AAD, 0, AAD.length);
182        AAD_Buf.flip();
183
184        // prepare text byte buffer to encrypt/decrypt
185        Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
186        int outputLength = c.getOutputSize(textLength);
187        int outputBufSize = outputLength + offset;
188        byte[] inputText = Helper.generateBytes(outputBufSize);
189        ByteBuffer plainTextBB = ByteBuffer.allocateDirect(inputText.length);
190        plainTextBB.put(inputText);
191        plainTextBB.position(offset);
192        plainTextBB.limit(offset + textLength);
193
194        // do test
195        runGCMWithSeparateBuffers(Cipher.ENCRYPT_MODE, AAD_Buf, plainTextBB, offset,
196                textLength, params);
197        int tagLength = c.getParameters()
198                .getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
199        plainTextBB.position(offset);
200        plainTextBB.limit(offset + textLength + tagLength);
201        runGCMWithSeparateBuffers(Cipher.DECRYPT_MODE, AAD_Buf, plainTextBB, offset,
202                textLength + tagLength, params);
203    }
204
205    /*
206     * Run the test in case when AAD and text are placed in the same ByteBuffer
207     */
208    private void doTestWithSameBuffer(int offset, AlgorithmParameters params)
209            throws Exception {
210        // calculate output length
211        Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
212        int outputLength = c.getOutputSize(textLength);
213
214        // prepare byte buffer contained AAD and plain text
215        int bufSize = AADLength + offset + outputLength;
216        byte[] AAD_and_Text = Helper.generateBytes(bufSize);
217        ByteBuffer AAD_and_Text_Buf = ByteBuffer.allocate(bufSize);
218        AAD_and_Text_Buf.put(AAD_and_Text, 0, AAD_and_Text.length);
219
220        // do test
221        runGCMWithSameBuffer(Cipher.ENCRYPT_MODE, AAD_and_Text_Buf, offset,
222                textLength, params);
223        int tagLength = c.getParameters()
224                .getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
225        AAD_and_Text_Buf.limit(AADLength + offset + textLength + tagLength);
226        runGCMWithSameBuffer(Cipher.DECRYPT_MODE, AAD_and_Text_Buf, offset,
227                textLength + tagLength, params);
228
229    }
230
231    /*
232     * Execute GCM encryption/decryption of a text placed in a byte array.
233     * AAD is placed in the separated byte array.
234     * Data are processed twice:
235     *   - in a separately allocated buffer
236     *   - in the text buffer
237     * Check if two results are equal
238     */
239    private void runGCMWithSeparateArray(int mode, byte[] AAD, byte[] text,
240            int txtOffset, int lenght, int offset, AlgorithmParameters params)
241            throws Exception {
242        // first, generate the cipher text at an allocated buffer
243        Cipher cipher = createCipher(mode, params);
244        cipher.updateAAD(AAD);
245        byte[] outputText = cipher.doFinal(text, txtOffset, lenght);
246
247        // new cipher for encrypt operation
248        Cipher anotherCipher = createCipher(mode, params);
249        anotherCipher.updateAAD(AAD);
250
251        // next, generate cipher text again at the same buffer of plain text
252        int myoff = offset;
253        int off = anotherCipher.update(text, txtOffset, lenght, text, myoff);
254        anotherCipher.doFinal(text, myoff + off);
255
256        // check if two resutls are equal
257        if (!isEqual(text, myoff, outputText, 0, outputText.length)) {
258            throw new RuntimeException("Two results not equal, mode:" + mode);
259        }
260    }
261
262    /*
263     * Execute GCM encrption/decryption of a text. The AAD and text to process
264     * are placed in the same byte array. Data are processed twice:
265     *   - in a separetly allocated buffer
266     *   - in a buffer that shares content of the AAD_and_Text_BA
267     * Check if two results are equal
268     */
269    private void runGCMWithSameArray(int mode, byte[] array, int txtOffset,
270            int length, AlgorithmParameters params) throws Exception {
271        // first, generate cipher text at an allocated buffer
272        Cipher cipher = createCipher(mode, params);
273        cipher.updateAAD(array, 0, AADLength);
274        byte[] outputText = cipher.doFinal(array, txtOffset, length);
275
276        // new cipher for encrypt operation
277        Cipher anotherCipher = createCipher(mode, params);
278        anotherCipher.updateAAD(array, 0, AADLength);
279
280        // next, generate cipher text again at the same buffer of plain text
281        int off = anotherCipher.update(array, txtOffset, length,
282                array, txtOffset);
283        anotherCipher.doFinal(array, txtOffset + off);
284
285        // check if two results are equal or not
286        if (!isEqual(array, txtOffset, outputText, 0,
287                outputText.length)) {
288            throw new RuntimeException(
289                    "Two results are not equal, mode:" + mode);
290        }
291    }
292
293    /*
294     * Execute GCM encryption/decryption of textBB. AAD and text to process are
295     * placed in different byte buffers. Data are processed twice:
296     *  - in a separately allocated buffer
297     *  - in a buffer that shares content of the textBB
298     * Check if results are equal
299     */
300    private void runGCMWithSeparateBuffers(int mode, ByteBuffer buffer,
301            ByteBuffer textBB, int txtOffset, int dataLength,
302            AlgorithmParameters params) throws Exception {
303        // take offset into account
304        textBB.position(txtOffset);
305        textBB.mark();
306
307        // first, generate the cipher text at an allocated buffer
308        Cipher cipher = createCipher(mode, params);
309        cipher.updateAAD(buffer);
310        buffer.flip();
311        ByteBuffer outBB = ByteBuffer.allocateDirect(
312                cipher.getOutputSize(dataLength));
313
314        cipher.doFinal(textBB, outBB);// get cipher text in outBB
315        outBB.flip();
316
317        // restore positions
318        textBB.reset();
319
320        // next, generate cipher text again in a buffer that shares content
321        Cipher anotherCipher = createCipher(mode, params);
322        anotherCipher.updateAAD(buffer);
323        buffer.flip();
324        ByteBuffer buf2 = textBB.duplicate(); // buf2 shares textBuf context
325        buf2.limit(txtOffset + anotherCipher.getOutputSize(dataLength));
326        int dataProcessed2 = anotherCipher.doFinal(textBB, buf2);
327        buf2.position(txtOffset);
328        buf2.limit(txtOffset + dataProcessed2);
329
330        if (!buf2.equals(outBB)) {
331            throw new RuntimeException(
332                    "Two results are not equal, mode:" + mode);
333        }
334    }
335
336    /*
337     * Execute GCM encryption/decryption of text. AAD and a text to process are
338     * placed in the same buffer. Data is processed twice:
339     *   - in a separately allocated buffer
340     *   - in a buffer that shares content of the AAD_and_Text_BB
341     */
342    private void runGCMWithSameBuffer(int mode, ByteBuffer buffer,
343            int txtOffset, int length, AlgorithmParameters params)
344            throws Exception {
345
346        // allocate a separate buffer
347        Cipher cipher = createCipher(mode, params);
348        ByteBuffer outBB = ByteBuffer.allocateDirect(
349                cipher.getOutputSize(length));
350
351        // first, generate the cipher text at an allocated buffer
352        buffer.flip();
353        buffer.limit(AADLength);
354        cipher.updateAAD(buffer);
355        buffer.limit(AADLength + txtOffset + length);
356        buffer.position(AADLength + txtOffset);
357        cipher.doFinal(buffer, outBB);
358        outBB.flip(); // cipher text in outBB
359
360        // next, generate cipherText again in the same buffer
361        Cipher anotherCipher = createCipher(mode, params);
362        buffer.flip();
363        buffer.limit(AADLength);
364        anotherCipher.updateAAD(buffer);
365        buffer.limit(AADLength + txtOffset + length);
366        buffer.position(AADLength + txtOffset);
367
368        // share textBuf context
369        ByteBuffer buf2 = buffer.duplicate();
370        buf2.limit(AADLength + txtOffset + anotherCipher.getOutputSize(length));
371        int dataProcessed2 = anotherCipher.doFinal(buffer, buf2);
372        buf2.position(AADLength + txtOffset);
373        buf2.limit(AADLength + txtOffset + dataProcessed2);
374
375        if (!buf2.equals(outBB)) {
376            throw new RuntimeException(
377                    "Two results are not equal, mode:" + mode);
378        }
379    }
380
381    private boolean isEqual(byte[] A, int offsetA, byte[] B, int offsetB,
382            int bytesToCompare) {
383        System.out.println("offsetA: " + offsetA + " offsetB: " + offsetA
384                + " bytesToCompare: " + bytesToCompare);
385        for (int i = 0; i < bytesToCompare; i++) {
386            int setA = i + offsetA;
387            int setB = i + offsetB;
388            if (setA > A.length - 1 || setB > B.length - 1
389                    || A[setA] != B[setB]) {
390                return false;
391            }
392        }
393
394        return true;
395    }
396
397    /*
398     * Creates a Cipher object for testing: for encryption it creates new Cipher
399     * based on previously saved parameters (it is prohibited to use the same
400     * Cipher twice for encription during GCM mode), or returns initiated
401     * existing Cipher.
402     */
403    private Cipher createCipher(int mode, AlgorithmParameters params)
404            throws Exception {
405        Cipher cipher = Cipher.getInstance(transformation, provider);
406        if (Cipher.ENCRYPT_MODE == mode) {
407            // initiate it with the saved parameters
408            if (params != null) {
409                cipher.init(Cipher.ENCRYPT_MODE, key, params);
410            } else {
411                // intiate the cipher and save parameters
412                cipher.init(Cipher.ENCRYPT_MODE, key);
413            }
414        } else if (cipher != null) {
415            cipher.init(Cipher.DECRYPT_MODE, key, params);
416        } else {
417            throw new RuntimeException("Can't create cipher");
418        }
419
420        return cipher;
421    }
422
423}
424