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