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.security.NoSuchAlgorithmException;
25import java.security.NoSuchProviderException;
26import java.util.Arrays;
27import javax.crypto.SecretKey;
28import javax.crypto.Cipher;
29import javax.crypto.KeyGenerator;
30import javax.crypto.spec.GCMParameterSpec;
31
32/*
33 * @test
34 * @bug 8048596
35 * @summary Check if GCMParameterSpec works as expected
36 */
37public class GCMParameterSpecTest {
38
39    private static final int[] IV_LENGTHS = { 96, 8, 1024 };
40    private static final int[] KEY_LENGTHS = { 128, 192, 256 };
41    private static final int[] DATA_LENGTHS = { 0, 128, 1024 };
42    private static final int[] AAD_LENGTHS = { 0, 128, 1024 };
43    private static final int[] TAG_LENGTHS = { 128, 120, 112, 104, 96 };
44    private static final int[] OFFSETS = { 0, 2, 5, 99 };
45    private static final String TRANSFORMATION = "AES/GCM/NoPadding";
46    private static final String TEMPLATE = "Test:\n  tag = %d\n"
47            + "  IV length = %d\n  data length = %d\n"
48            + "  AAD length = %d\n  offset = %d\n  keylength = %d\n";
49
50    private final byte[] IV;
51    private final byte[] IVO;
52    private final byte[] data;
53    private final byte[] AAD;
54    private final SecretKey key;
55    private final int tagLength;
56    private final int IVlength;
57    private final int offset;
58
59    /**
60     * Initialize IV, IV with offset, plain text, AAD and SecretKey
61     *
62     * @param keyLength length of a secret key
63     * @param tagLength tag length
64     * @param IVlength IV length
65     * @param offset offset in a buffer for IV
66     * @param textLength plain text length
67     * @param AADLength AAD length
68     */
69    public GCMParameterSpecTest(int keyLength, int tagLength, int IVlength,
70            int offset, int textLength, int AADLength)
71            throws NoSuchAlgorithmException, NoSuchProviderException {
72        this.tagLength = tagLength; // save tag length
73        this.IVlength = IVlength; // save IV length
74        this.offset = offset; // save IV offset
75
76        // prepare IV
77        IV = Helper.generateBytes(IVlength);
78
79        // prepare IV with offset
80        IVO = new byte[this.IVlength + this.offset];
81        System.arraycopy(IV, 0, IVO, offset, this.IVlength);
82
83        // prepare data
84        data = Helper.generateBytes(textLength);
85
86        // prepare AAD
87        AAD = Helper.generateBytes(AADLength);
88
89        // init a secret key
90        KeyGenerator kg = KeyGenerator.getInstance("AES", "SunJCE");
91        kg.init(keyLength);
92        key = kg.generateKey();
93    }
94
95    /*
96     * Run the test for each key length, tag length, IV length, plain text
97     * length, AAD length and offset.
98     */
99    public static void main(String[] args) throws Exception {
100        boolean success = true;
101        for (int k : KEY_LENGTHS) {
102            if (k > Cipher.getMaxAllowedKeyLength(TRANSFORMATION)) {
103                // skip this if this key length is larger than what's
104                // allowed in the jce jurisdiction policy files
105                continue;
106            }
107            for (int t : TAG_LENGTHS) {
108                for (int n : IV_LENGTHS) {
109                    for (int p : DATA_LENGTHS) {
110                        for (int a : AAD_LENGTHS) {
111                            for (int o : OFFSETS) {
112                                System.out.printf(TEMPLATE, t, n, p, a, o, k);
113                                success &= new GCMParameterSpecTest(
114                                        k, t, n, o, p, a).doTest();
115                            }
116                        }
117                    }
118                }
119            }
120        }
121
122        if (!success) {
123            throw new RuntimeException("At least one test case failed");
124        }
125    }
126
127    /*
128     * Run the test:
129     *   - check if result of encryption of plain text is the same
130     *     when parameters constructed with different GCMParameterSpec
131     *     constructors are used
132     *   - check if GCMParameterSpec.getTLen() is equal to actual tag length
133     *   - check if ciphertext has the same length as plaintext
134     */
135    private boolean doTest() throws Exception {
136        GCMParameterSpec spec1 = new GCMParameterSpec(tagLength, IV);
137        GCMParameterSpec spec2 = new GCMParameterSpec(tagLength, IVO, offset,
138                IVlength);
139        byte[] cipherText1 = getCipherTextBySpec(spec1);
140        if (cipherText1 == null) {
141            return false;
142        }
143        byte[] cipherText2 = getCipherTextBySpec(spec2);
144        if (cipherText2 == null) {
145            return false;
146        }
147        if (!Arrays.equals(cipherText1, cipherText2)) {
148            System.out.println("Cipher texts are different");
149            return false;
150        }
151        if (spec1.getTLen() != spec2.getTLen()) {
152            System.out.println("Tag lengths are not equal");
153            return false;
154        }
155        byte[] recoveredText1 = recoverCipherText(cipherText1, spec2);
156        if (recoveredText1 == null) {
157            return false;
158        }
159        byte[] recoveredText2 = recoverCipherText(cipherText2, spec1);
160        if (recoveredText2 == null) {
161            return false;
162        }
163        if (!Arrays.equals(recoveredText1, recoveredText2)) {
164            System.out.println("Recovered texts are different");
165            return false;
166        }
167        if (!Arrays.equals(recoveredText1, data)) {
168            System.out.println("Recovered and original texts are not equal");
169            return false;
170        }
171
172        return true;
173    }
174
175    /*
176     * Encrypt a plain text, and check if GCMParameterSpec.getIV()
177     * is equal to Cipher.getIV()
178     */
179    private byte[] getCipherTextBySpec(GCMParameterSpec spec) throws Exception {
180        // init a cipher
181        Cipher cipher = createCipher(Cipher.ENCRYPT_MODE, spec);
182        cipher.updateAAD(AAD);
183        byte[] cipherText = cipher.doFinal(data);
184
185        // check IVs
186        if (!Arrays.equals(cipher.getIV(), spec.getIV())) {
187            System.out.println("IV in parameters is incorrect");
188            return null;
189        }
190        if (spec.getTLen() != (cipherText.length - data.length) * 8) {
191            System.out.println("Tag length is incorrect");
192            return null;
193        }
194        return cipherText;
195    }
196
197    private byte[] recoverCipherText(byte[] cipherText, GCMParameterSpec spec)
198            throws Exception {
199        // init a cipher
200        Cipher cipher = createCipher(Cipher.DECRYPT_MODE, spec);
201
202        // check IVs
203        if (!Arrays.equals(cipher.getIV(), spec.getIV())) {
204            System.out.println("IV in parameters is incorrect");
205            return null;
206        }
207
208        cipher.updateAAD(AAD);
209        return cipher.doFinal(cipherText);
210    }
211
212    private Cipher createCipher(int mode, GCMParameterSpec spec)
213            throws Exception {
214        Cipher cipher = Cipher.getInstance(TRANSFORMATION, "SunJCE");
215        cipher.init(mode, key, spec);
216        return cipher;
217    }
218}
219