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