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