1e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian/* 2e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * Copyright (C) 2017 The Android Open Source Project 3e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * 4e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * Licensed under the Apache License, Version 2.0 (the "License"); 5e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * you may not use this file except in compliance with the License. 6e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * You may obtain a copy of the License at 7e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * 8e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * http://www.apache.org/licenses/LICENSE-2.0 9e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * 10e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * Unless required by applicable law or agreed to in writing, software 11e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * distributed under the License is distributed on an "AS IS" BASIS, 12e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * See the License for the specific language governing permissions and 14e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian * limitations under the License. 15e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian */ 16e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian 17e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianpackage libcore.javax.crypto; 18e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian 19e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport junit.framework.TestCase; 20e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian 21e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport java.io.ByteArrayOutputStream; 22e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport java.io.IOException; 23e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport java.security.NoSuchAlgorithmException; 24e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport java.security.Provider; 25e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport java.security.Security; 26e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport java.security.spec.AlgorithmParameterSpec; 27e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport javax.crypto.AEADBadTagException; 28e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport javax.crypto.Cipher; 29e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport javax.crypto.CipherOutputStream; 30e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport javax.crypto.KeyGenerator; 31e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport javax.crypto.SecretKey; 32e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianimport javax.crypto.spec.GCMParameterSpec; 33e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian 34e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanianpublic final class CipherOutputStreamTest extends TestCase { 35e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian 36e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // From b/36636576. CipherOutputStream had a bug where it would ignore exceptions 37e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // thrown during close(). 38e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian public void testDecryptCorruptGCM() throws Exception { 39e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian for (Provider provider : Security.getProviders()) { 40e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian Cipher cipher; 41e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian try { 42e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian cipher = Cipher.getInstance("AES/GCM/NoPadding", provider); 43e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } catch (NoSuchAlgorithmException e) { 44e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian continue; 45e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } 46e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian SecretKey key; 47e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian if (provider.getName().equals("AndroidKeyStoreBCWorkaround")) { 48e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian key = getAndroidKeyStoreSecretKey(); 49e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } else { 50e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian KeyGenerator keygen = KeyGenerator.getInstance("AES"); 51e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian keygen.init(256); 52e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian key = keygen.generateKey(); 53e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } 54e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian GCMParameterSpec params = new GCMParameterSpec(128, new byte[12]); 55e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian byte[] unencrypted = new byte[200]; 56e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian 57e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // Normal providers require specifying the IV, but KeyStore prohibits it, so 58e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // we have to special-case it 59e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian if (provider.getName().equals("AndroidKeyStoreBCWorkaround")) { 60e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian cipher.init(Cipher.ENCRYPT_MODE, key); 61e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } else { 62e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian cipher.init(Cipher.ENCRYPT_MODE, key, params); 63e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } 64e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian byte[] encrypted = cipher.doFinal(unencrypted); 65e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian 66e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // Corrupt the final byte, which will corrupt the authentication tag 67e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian encrypted[encrypted.length - 1] ^= 1; 68e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian 69e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian cipher.init(Cipher.DECRYPT_MODE, key, params); 70e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian CipherOutputStream cos = new CipherOutputStream(new ByteArrayOutputStream(), cipher); 71e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian try { 72e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian cos.write(encrypted); 73e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian cos.close(); 74e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian fail("Writing a corrupted stream should throw an exception." 75e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian + " Provider: " + provider); 76e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } catch (IOException expected) { 77e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian assertTrue(expected.getCause() instanceof AEADBadTagException); 78e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } 79e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } 80e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian 81e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } 82e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian 83e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // The AndroidKeyStoreBCWorkaround provider can't use keys created by anything 84e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // but Android KeyStore, which requires using its own parameters class to create 85e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // keys. Since we're in javax, we can't link against the frameworks classes, so 86e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // we have to use reflection to make a suitable key. This will always be safe 87e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // because if we're making a key for AndroidKeyStoreBCWorkaround, the KeyStore 88e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // classes must be present. 89e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian private static SecretKey getAndroidKeyStoreSecretKey() throws Exception { 90e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian KeyGenerator keygen = KeyGenerator.getInstance("AES", "AndroidKeyStore"); 91e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian Class<?> keyParamsBuilderClass = keygen.getClass().getClassLoader().loadClass( 92e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian "android.security.keystore.KeyGenParameterSpec$Builder"); 93e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian Object keyParamsBuilder = keyParamsBuilderClass.getConstructor(String.class, Integer.TYPE) 94e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian // 3 is PURPOSE_ENCRYPT | PURPOSE_DECRYPT 95e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian .newInstance("testDecryptCorruptGCM", 3); 96e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian keyParamsBuilderClass.getMethod("setBlockModes", new Class[]{String[].class}) 97e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian .invoke(keyParamsBuilder, new Object[]{new String[]{"GCM"}}); 98e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian keyParamsBuilderClass.getMethod("setEncryptionPaddings", new Class[]{String[].class}) 99e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian .invoke(keyParamsBuilder, new Object[]{new String[]{"NoPadding"}}); 100e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian AlgorithmParameterSpec spec = (AlgorithmParameterSpec) 101e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian keyParamsBuilderClass.getMethod("build", new Class[]{}).invoke(keyParamsBuilder); 102e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian keygen.init(spec); 103e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian return keygen.generateKey(); 104e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian } 105e5a6402f50561ef98d7d1fe55e4b8db67b247e69Adam Vartanian} 106