1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 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 17package libcore.javax.crypto; 18 19import junit.framework.TestCase; 20 21import java.io.ByteArrayOutputStream; 22import java.io.IOException; 23import java.security.NoSuchAlgorithmException; 24import java.security.Provider; 25import java.security.Security; 26import java.security.spec.AlgorithmParameterSpec; 27import javax.crypto.AEADBadTagException; 28import javax.crypto.Cipher; 29import javax.crypto.CipherOutputStream; 30import javax.crypto.KeyGenerator; 31import javax.crypto.SecretKey; 32import javax.crypto.spec.GCMParameterSpec; 33 34public final class CipherOutputStreamTest extends TestCase { 35 36 // From b/36636576. CipherOutputStream had a bug where it would ignore exceptions 37 // thrown during close(). 38 public void testDecryptCorruptGCM() throws Exception { 39 for (Provider provider : Security.getProviders()) { 40 Cipher cipher; 41 try { 42 cipher = Cipher.getInstance("AES/GCM/NoPadding", provider); 43 } catch (NoSuchAlgorithmException e) { 44 continue; 45 } 46 SecretKey key; 47 if (provider.getName().equals("AndroidKeyStoreBCWorkaround")) { 48 key = getAndroidKeyStoreSecretKey(); 49 } else { 50 KeyGenerator keygen = KeyGenerator.getInstance("AES"); 51 keygen.init(256); 52 key = keygen.generateKey(); 53 } 54 GCMParameterSpec params = new GCMParameterSpec(128, new byte[12]); 55 byte[] unencrypted = new byte[200]; 56 57 // Normal providers require specifying the IV, but KeyStore prohibits it, so 58 // we have to special-case it 59 if (provider.getName().equals("AndroidKeyStoreBCWorkaround")) { 60 cipher.init(Cipher.ENCRYPT_MODE, key); 61 } else { 62 cipher.init(Cipher.ENCRYPT_MODE, key, params); 63 } 64 byte[] encrypted = cipher.doFinal(unencrypted); 65 66 // Corrupt the final byte, which will corrupt the authentication tag 67 encrypted[encrypted.length - 1] ^= 1; 68 69 cipher.init(Cipher.DECRYPT_MODE, key, params); 70 CipherOutputStream cos = new CipherOutputStream(new ByteArrayOutputStream(), cipher); 71 try { 72 cos.write(encrypted); 73 cos.close(); 74 fail("Writing a corrupted stream should throw an exception." 75 + " Provider: " + provider); 76 } catch (IOException expected) { 77 assertTrue(expected.getCause() instanceof AEADBadTagException); 78 } 79 } 80 81 } 82 83 // The AndroidKeyStoreBCWorkaround provider can't use keys created by anything 84 // but Android KeyStore, which requires using its own parameters class to create 85 // keys. Since we're in javax, we can't link against the frameworks classes, so 86 // we have to use reflection to make a suitable key. This will always be safe 87 // because if we're making a key for AndroidKeyStoreBCWorkaround, the KeyStore 88 // classes must be present. 89 private static SecretKey getAndroidKeyStoreSecretKey() throws Exception { 90 KeyGenerator keygen = KeyGenerator.getInstance("AES", "AndroidKeyStore"); 91 Class<?> keyParamsBuilderClass = keygen.getClass().getClassLoader().loadClass( 92 "android.security.keystore.KeyGenParameterSpec$Builder"); 93 Object keyParamsBuilder = keyParamsBuilderClass.getConstructor(String.class, Integer.TYPE) 94 // 3 is PURPOSE_ENCRYPT | PURPOSE_DECRYPT 95 .newInstance("testDecryptCorruptGCM", 3); 96 keyParamsBuilderClass.getMethod("setBlockModes", new Class[]{String[].class}) 97 .invoke(keyParamsBuilder, new Object[]{new String[]{"GCM"}}); 98 keyParamsBuilderClass.getMethod("setEncryptionPaddings", new Class[]{String[].class}) 99 .invoke(keyParamsBuilder, new Object[]{new String[]{"NoPadding"}}); 100 AlgorithmParameterSpec spec = (AlgorithmParameterSpec) 101 keyParamsBuilderClass.getMethod("build", new Class[]{}).invoke(keyParamsBuilder); 102 keygen.init(spec); 103 return keygen.generateKey(); 104 } 105} 106