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.ByteArrayInputStream; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.security.NoSuchAlgorithmException; 23 import java.security.SecureRandom; 24 import java.security.spec.AlgorithmParameterSpec; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import javax.crypto.Cipher; 28 import javax.crypto.CipherInputStream; 29 import javax.crypto.spec.GCMParameterSpec; 30 import javax.crypto.spec.SecretKeySpec; 31 import junit.framework.TestCase; 32 33 /** CipherInputStream tests */ 34 public class CipherInputStreamTest extends TestCase { 35 static final SecureRandom rand = new SecureRandom(); 36 randomBytes(int size)37 static byte[] randomBytes(int size) { 38 byte[] bytes = new byte[size]; 39 rand.nextBytes(bytes); 40 return bytes; 41 } 42 randomKey(String algorithm, int keySizeInBytes)43 static SecretKeySpec randomKey(String algorithm, int keySizeInBytes) { 44 return new SecretKeySpec(randomBytes(keySizeInBytes), "AES"); 45 } 46 randomParameters( String algorithm, int ivSizeInBytes, int tagSizeInBytes)47 static AlgorithmParameterSpec randomParameters( 48 String algorithm, int ivSizeInBytes, int tagSizeInBytes) { 49 if ("AES/GCM/NoPadding".equals(algorithm) || "AES/EAX/NoPadding".equals(algorithm)) { 50 return new GCMParameterSpec(8 * tagSizeInBytes, randomBytes(ivSizeInBytes)); 51 } 52 return null; 53 } 54 55 /** Test vectors */ 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 64 @SuppressWarnings("InsecureCryptoUsage") TestVector( String algorithm, int keySize, int ivSize, int tagSize, int ptSize, int aadSize)65 public TestVector( 66 String algorithm, int keySize, int ivSize, int tagSize, int ptSize, int aadSize) 67 throws Exception { 68 this.algorithm = algorithm; 69 this.key = randomKey(algorithm, keySize); 70 this.params = randomParameters(algorithm, ivSize, tagSize); 71 this.pt = randomBytes(ptSize); 72 this.aad = randomBytes(aadSize); 73 Cipher cipher = Cipher.getInstance(algorithm); 74 cipher.init(Cipher.ENCRYPT_MODE, this.key, this.params); 75 cipher.updateAAD(aad); 76 this.ct = cipher.doFinal(pt); 77 } 78 } 79 getTestVectors( String algorithm, int[] keySizes, int[] ivSizes, int[] tagSizes, int[] ptSizes, int[] aadSizes)80 Iterable<TestVector> getTestVectors( 81 String algorithm, 82 int[] keySizes, 83 int[] ivSizes, 84 int[] tagSizes, 85 int[] ptSizes, 86 int[] aadSizes) 87 throws Exception { 88 ArrayList<TestVector> result = new ArrayList<TestVector>(); 89 for (int keySize : keySizes) { 90 for (int ivSize : ivSizes) { 91 for (int tagSize : tagSizes) { 92 for (int ptSize : ptSizes) { 93 for (int aadSize : aadSizes) { 94 result.add(new TestVector(algorithm, keySize, ivSize, tagSize, ptSize, aadSize)); 95 } 96 } 97 } 98 } 99 } 100 return result; 101 } 102 103 @SuppressWarnings("InsecureCryptoUsage") testEncrypt(Iterable<TestVector> tests)104 public void testEncrypt(Iterable<TestVector> tests) throws Exception { 105 for (TestVector t : tests) { 106 Cipher cipher = Cipher.getInstance(t.algorithm); 107 cipher.init(Cipher.ENCRYPT_MODE, t.key, t.params); 108 cipher.updateAAD(t.aad); 109 InputStream is = new ByteArrayInputStream(t.pt); 110 CipherInputStream cis = new CipherInputStream(is, cipher); 111 byte[] result = new byte[t.ct.length]; 112 int totalLength = 0; 113 int length = 0; 114 do { 115 length = cis.read(result, totalLength, result.length - totalLength); 116 if (length > 0) { 117 totalLength += length; 118 } 119 } while (length >= 0 && totalLength != result.length); 120 assertEquals(-1, cis.read()); 121 assertEquals(TestUtil.bytesToHex(t.ct), TestUtil.bytesToHex(result)); 122 cis.close(); 123 } 124 } 125 126 /** JDK-8016249: CipherInputStream in decrypt mode fails on close with AEAD ciphers */ 127 @SuppressWarnings("InsecureCryptoUsage") testDecrypt(Iterable<TestVector> tests)128 public void testDecrypt(Iterable<TestVector> tests) throws Exception { 129 for (TestVector t : tests) { 130 Cipher cipher = Cipher.getInstance(t.algorithm); 131 cipher.init(Cipher.DECRYPT_MODE, t.key, t.params); 132 cipher.updateAAD(t.aad); 133 InputStream is = new ByteArrayInputStream(t.ct); 134 CipherInputStream cis = new CipherInputStream(is, cipher); 135 byte[] result = new byte[t.pt.length]; 136 int totalLength = 0; 137 int length = 0; 138 do { 139 length = cis.read(result, totalLength, result.length - totalLength); 140 if (length > 0) { 141 totalLength += length; 142 } 143 } while (length >= 0 && totalLength != result.length); 144 assertEquals(-1, cis.read()); 145 cis.close(); 146 assertEquals(TestUtil.bytesToHex(t.pt), TestUtil.bytesToHex(result)); 147 } 148 } 149 150 /** 151 * JDK-8016171 : CipherInputStream masks ciphertext tampering with AEAD ciphers in decrypt mode 152 * Further description of the bug is here: 153 * https://blog.heckel.xyz/2014/03/01/cipherinputstream-for-aead-modes-is-broken-in-jdk7-gcm/ 154 * BouncyCastle claims that this bug is fixed in version 1.51. However, the test below still fails 155 * with BouncyCastle v 1.52. A possible explanation is that BouncyCastle has its own 156 * implemenatation of CipherInputStream (org.bouncycastle.crypto.io.CipherInputStream). 157 */ 158 @SuppressWarnings("InsecureCryptoUsage") testCorruptDecrypt(Iterable<TestVector> tests)159 public void testCorruptDecrypt(Iterable<TestVector> tests) throws Exception { 160 for (TestVector t : tests) { 161 Cipher cipher = Cipher.getInstance(t.algorithm); 162 cipher.init(Cipher.DECRYPT_MODE, t.key, t.params); 163 cipher.updateAAD(t.aad); 164 byte[] ct = Arrays.copyOf(t.ct, t.ct.length); 165 ct[ct.length - 1] ^= (byte) 1; 166 InputStream is = new ByteArrayInputStream(ct); 167 CipherInputStream cis = new CipherInputStream(is, cipher); 168 try { 169 byte[] result = new byte[t.pt.length]; 170 int totalLength = 0; 171 int length = 0; 172 do { 173 length = cis.read(result, totalLength, result.length - totalLength); 174 if (length > 0) { 175 totalLength += length; 176 } 177 } while (length >= 0 && totalLength != result.length); 178 cis.close(); 179 if (result.length > 0) { 180 fail( 181 "this should fail; decrypted:" 182 + TestUtil.bytesToHex(result) 183 + " pt: " 184 + TestUtil.bytesToHex(t.pt)); 185 } 186 } catch (IOException ex) { 187 // expected 188 } 189 } 190 } 191 192 @SuppressWarnings("InsecureCryptoUsage") testCorruptDecryptEmpty(Iterable<TestVector> tests)193 public void testCorruptDecryptEmpty(Iterable<TestVector> tests) throws Exception { 194 for (TestVector t : tests) { 195 Cipher cipher = Cipher.getInstance(t.algorithm); 196 cipher.init(Cipher.DECRYPT_MODE, t.key, t.params); 197 cipher.updateAAD(t.aad); 198 byte[] ct = Arrays.copyOf(t.ct, t.ct.length); 199 ct[ct.length - 1] ^= (byte) 1; 200 InputStream is = new ByteArrayInputStream(ct); 201 CipherInputStream cis = new CipherInputStream(is, cipher); 202 try { 203 byte[] result = new byte[t.pt.length]; 204 int totalLength = 0; 205 int length = 0; 206 do { 207 length = cis.read(result, totalLength, result.length - totalLength); 208 if (length > 0) { 209 totalLength += length; 210 } 211 } while (length >= 0 && totalLength != result.length); 212 cis.close(); 213 fail("this should fail"); 214 } catch (IOException ex) { 215 // expected 216 } 217 } 218 } 219 testAesGcm()220 public void testAesGcm() throws Exception { 221 final int[] keySizes = {16, 32}; 222 final int[] ivSizes = {12}; 223 final int[] tagSizes = {12, 16}; 224 final int[] ptSizes = {0, 8, 16, 65, 8100}; 225 final int[] aadSizes = {0, 8, 24}; 226 Iterable<TestVector> v = 227 getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes); 228 testEncrypt(v); 229 testDecrypt(v); 230 } 231 testCorruptAesGcm()232 public void testCorruptAesGcm() throws Exception { 233 final int[] keySizes = {16, 32}; 234 final int[] ivSizes = {12}; 235 final int[] tagSizes = {12, 16}; 236 final int[] ptSizes = {8, 16, 65, 8100}; 237 final int[] aadSizes = {0, 8, 24}; 238 Iterable<TestVector> v = 239 getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes); 240 testCorruptDecrypt(v); 241 } 242 243 /** 244 * Unfortunately Oracle thinks that returning an empty array is valid behaviour for corrupt 245 * ciphertexts. Because of this we test empty plaintext separately to distinguish behaviour 246 * considered acceptable by Oracle from other behaviour. 247 */ testEmptyPlaintext()248 public void testEmptyPlaintext() throws Exception { 249 final int[] keySizes = {16, 32}; 250 final int[] ivSizes = {12}; 251 final int[] tagSizes = {12, 16}; 252 final int[] ptSizes = {0}; 253 final int[] aadSizes = {0, 8, 24}; 254 Iterable<TestVector> v = 255 getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes); 256 testCorruptDecryptEmpty(v); 257 } 258 259 /** Tests CipherOutputStream with AES-EAX if this algorithm is supported by the provider. */ testAesEax()260 public void testAesEax() throws Exception { 261 final String algorithm = "AES/EAX/NoPadding"; 262 final int[] keySizes = {16, 32}; 263 final int[] ivSizes = {12, 16}; 264 final int[] tagSizes = {12, 16}; 265 final int[] ptSizes = {0, 8, 16, 65, 8100}; 266 final int[] aadSizes = {0, 8, 24}; 267 try { 268 Cipher.getInstance(algorithm); 269 } catch (NoSuchAlgorithmException ex) { 270 System.out.println("Skipping testAesEax"); 271 return; 272 } 273 Iterable<TestVector> v = 274 getTestVectors(algorithm, keySizes, ivSizes, tagSizes, ptSizes, aadSizes); 275 testEncrypt(v); 276 testDecrypt(v); 277 testCorruptDecrypt(v); 278 } 279 } 280