• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * @license
3  * Copyright 2016 Google Inc. All rights reserved.
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.security.wycheproof;
18 
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.security.NoSuchAlgorithmException;
22 import java.security.SecureRandom;
23 import java.security.spec.AlgorithmParameterSpec;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import javax.crypto.Cipher;
27 import javax.crypto.CipherOutputStream;
28 import javax.crypto.spec.GCMParameterSpec;
29 import javax.crypto.spec.SecretKeySpec;
30 import junit.framework.TestCase;
31 
32 /** CipherOutputStream tests */
33 public class CipherOutputStreamTest extends TestCase {
34   static final SecureRandom rand = new SecureRandom();
35 
randomBytes(int size)36   static byte[] randomBytes(int size) {
37     byte[] bytes = new byte[size];
38     rand.nextBytes(bytes);
39     return bytes;
40   }
41 
randomKey(String algorithm, int keySizeInBytes)42   static SecretKeySpec randomKey(String algorithm, int keySizeInBytes) {
43     return new SecretKeySpec(randomBytes(keySizeInBytes), "AES");
44   }
45 
randomParameters( String algorithm, int ivSizeInBytes, int tagSizeInBytes)46   static AlgorithmParameterSpec randomParameters(
47       String algorithm, int ivSizeInBytes, int tagSizeInBytes) {
48     if ("AES/GCM/NoPadding".equals(algorithm) || "AES/EAX/NoPadding".equals(algorithm)) {
49       return new GCMParameterSpec(8 * tagSizeInBytes, randomBytes(ivSizeInBytes));
50     }
51     return null;
52   }
53 
54   /** Test vectors */
55   @SuppressWarnings("InsecureCryptoUsage")
56   public static class TestVector {
57     public String algorithm;
58     public SecretKeySpec key;
59     public AlgorithmParameterSpec params;
60     public byte[] pt;
61     public byte[] aad;
62     public byte[] ct;
63 
TestVector( String algorithm, int keySize, int ivSize, int tagSize, int ptSize, int aadSize)64     public TestVector(
65         String algorithm, int keySize, int ivSize, int tagSize, int ptSize, int aadSize)
66         throws Exception {
67       this.algorithm = algorithm;
68       this.key = randomKey(algorithm, keySize);
69       this.params = randomParameters(algorithm, ivSize, tagSize);
70       this.pt = randomBytes(ptSize);
71       this.aad = randomBytes(aadSize);
72       Cipher cipher = Cipher.getInstance(algorithm);
73       cipher.init(Cipher.ENCRYPT_MODE, this.key, this.params);
74       cipher.updateAAD(aad);
75       this.ct = cipher.doFinal(pt);
76     }
77   }
78 
getTestVectors( String algorithm, int[] keySizes, int[] ivSizes, int[] tagSizes, int[] ptSizes, int[] aadSizes)79   Iterable<TestVector> getTestVectors(
80       String algorithm,
81       int[] keySizes,
82       int[] ivSizes,
83       int[] tagSizes,
84       int[] ptSizes,
85       int[] aadSizes)
86       throws Exception {
87     ArrayList<TestVector> result = new ArrayList<TestVector>();
88     for (int keySize : keySizes) {
89       for (int ivSize : ivSizes) {
90         for (int tagSize : tagSizes) {
91           for (int ptSize : ptSizes) {
92             for (int aadSize : aadSizes) {
93               result.add(new TestVector(algorithm, keySize, ivSize, tagSize, ptSize, aadSize));
94             }
95           }
96         }
97       }
98     }
99     return result;
100   }
101 
102   @SuppressWarnings("InsecureCryptoUsage")
testEncrypt(Iterable<TestVector> tests)103   public void testEncrypt(Iterable<TestVector> tests) throws Exception {
104     for (TestVector t : tests) {
105       Cipher cipher = Cipher.getInstance(t.algorithm);
106       cipher.init(Cipher.ENCRYPT_MODE, t.key, t.params);
107       cipher.updateAAD(t.aad);
108       ByteArrayOutputStream os = new ByteArrayOutputStream();
109       CipherOutputStream cos = new CipherOutputStream(os, cipher);
110       cos.write(t.pt);
111       cos.close();
112       assertEquals(TestUtil.bytesToHex(t.ct), TestUtil.bytesToHex(os.toByteArray()));
113     }
114   }
115 
116   @SuppressWarnings("InsecureCryptoUsage")
testDecrypt(Iterable<TestVector> tests)117   public void testDecrypt(Iterable<TestVector> tests) throws Exception {
118     for (TestVector t : tests) {
119       Cipher cipher = Cipher.getInstance(t.algorithm);
120       cipher.init(Cipher.DECRYPT_MODE, t.key, t.params);
121       cipher.updateAAD(t.aad);
122       ByteArrayOutputStream os = new ByteArrayOutputStream();
123       CipherOutputStream cos = new CipherOutputStream(os, cipher);
124       cos.write(t.ct);
125       cos.close();
126       assertEquals(TestUtil.bytesToHex(t.pt), TestUtil.bytesToHex(os.toByteArray()));
127     }
128   }
129 
130   @SuppressWarnings("InsecureCryptoUsage")
testCorruptDecrypt(Iterable<TestVector> tests)131   public void testCorruptDecrypt(Iterable<TestVector> tests) throws Exception {
132     for (TestVector t : tests) {
133       Cipher cipher = Cipher.getInstance(t.algorithm);
134       cipher.init(Cipher.DECRYPT_MODE, t.key, t.params);
135       cipher.updateAAD(t.aad);
136       byte[] ct = Arrays.copyOf(t.ct, t.ct.length);
137       ct[ct.length - 1] ^= (byte) 1;
138       ByteArrayOutputStream os = new ByteArrayOutputStream();
139       CipherOutputStream cos = new CipherOutputStream(os, cipher);
140       cos.write(ct);
141       try {
142         // cos.close() should call cipher.doFinal().
143         cos.close();
144         byte[] decrypted = os.toByteArray();
145         // Unfortunately Oracle thinks that returning an empty array is valid behaviour.
146         // We accept empty results here, but flag them in the next test, so that we can distinguish
147         // between beheviour considered acceptable by Oracle and more serious flaws.
148         if (decrypted.length > 0) {
149           fail(
150               "this should fail; decrypted:"
151                   + TestUtil.bytesToHex(decrypted)
152                   + " pt: "
153                   + TestUtil.bytesToHex(t.pt));
154         }
155       } catch (IOException ex) {
156         // expected
157       }
158     }
159   }
160 
161   @SuppressWarnings("InsecureCryptoUsage")
testCorruptDecryptEmpty(Iterable<TestVector> tests)162   public void testCorruptDecryptEmpty(Iterable<TestVector> tests) throws Exception {
163     for (TestVector t : tests) {
164       Cipher cipher = Cipher.getInstance(t.algorithm);
165       cipher.init(Cipher.DECRYPT_MODE, t.key, t.params);
166       cipher.updateAAD(t.aad);
167       byte[] ct = Arrays.copyOf(t.ct, t.ct.length);
168       ct[ct.length - 1] ^= (byte) 1;
169       ByteArrayOutputStream os = new ByteArrayOutputStream();
170       CipherOutputStream cos = new CipherOutputStream(os, cipher);
171       cos.write(ct);
172       try {
173         // cos.close() should call cipher.doFinal().
174         cos.close();
175         byte[] decrypted = os.toByteArray();
176         fail(
177             "this should fail; decrypted:"
178                 + TestUtil.bytesToHex(decrypted)
179                 + " pt: "
180                 + TestUtil.bytesToHex(t.pt));
181       } catch (IOException ex) {
182         // expected
183       }
184     }
185   }
186 
testAesGcm()187   public void testAesGcm() throws Exception {
188     final int[] keySizes = {16, 32};
189     final int[] ivSizes = {12};
190     final int[] tagSizes = {12, 16};
191     final int[] ptSizes = {8, 16, 65, 8100};
192     final int[] aadSizes = {0, 8, 24};
193     Iterable<TestVector> v =
194         getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
195     testEncrypt(v);
196     testDecrypt(v);
197     testCorruptDecrypt(v);
198   }
199 
200   /**
201    * Unfortunately Oracle thinks that returning an empty array is valid behaviour for corrupt
202    * ciphertexts. Because of this we test empty plaintext separately to distinguish behaviour
203    * considered acceptable by Oracle from other behaviour.
204    */
testEmptyPlaintext()205   public void testEmptyPlaintext() throws Exception {
206     final int[] keySizes = {16, 32};
207     final int[] ivSizes = {12};
208     final int[] tagSizes = {12, 16};
209     final int[] ptSizes = {0};
210     final int[] aadSizes = {0, 8, 24};
211     Iterable<TestVector> v =
212         getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
213     testEncrypt(v);
214     testDecrypt(v);
215     testCorruptDecryptEmpty(v);
216   }
217 
218   /** Tests CipherOutputStream with AES-EAX if AES-EAS is supported by the provider. */
219   @SuppressWarnings("InsecureCryptoUsage")
testAesEax()220   public void testAesEax() throws Exception {
221     final String algorithm = "AES/EAX/NoPadding";
222     final int[] keySizes = {16, 32};
223     final int[] ivSizes = {12, 16};
224     final int[] tagSizes = {12, 16};
225     final int[] ptSizes = {8, 16, 65, 8100};
226     final int[] aadSizes = {0, 8, 24};
227     try {
228       Cipher.getInstance(algorithm);
229     } catch (NoSuchAlgorithmException ex) {
230       System.out.println("Skipping testAesEax");
231       return;
232     }
233     Iterable<TestVector> v =
234         getTestVectors(algorithm, keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
235     testEncrypt(v);
236     testDecrypt(v);
237     testCorruptDecrypt(v);
238   }
239 }
240