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 com.android.server;
18
19import android.security.keystore.KeyProperties;
20import android.security.keystore.KeyProtection;
21
22import java.io.ByteArrayOutputStream;
23import java.io.IOException;
24import java.security.InvalidAlgorithmParameterException;
25import java.security.InvalidKeyException;
26import java.security.KeyStore;
27import java.security.KeyStoreException;
28import java.security.MessageDigest;
29import java.security.NoSuchAlgorithmException;
30import java.security.SecureRandom;
31import java.security.UnrecoverableKeyException;
32import java.security.cert.CertificateException;
33import java.util.Arrays;
34
35import javax.crypto.BadPaddingException;
36import javax.crypto.Cipher;
37import javax.crypto.IllegalBlockSizeException;
38import javax.crypto.KeyGenerator;
39import javax.crypto.NoSuchPaddingException;
40import javax.crypto.SecretKey;
41import javax.crypto.spec.GCMParameterSpec;
42import javax.crypto.spec.SecretKeySpec;
43
44public class SyntheticPasswordCrypto {
45    private static final int PROFILE_KEY_IV_SIZE = 12;
46    private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
47    private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
48    // Time between the user credential is verified with GK and the decryption of synthetic password
49    // under the auth-bound key. This should always happen one after the other, but give it 15
50    // seconds just to be sure.
51    private static final int USER_AUTHENTICATION_VALIDITY = 15;
52
53    private static byte[] decrypt(SecretKey key, byte[] blob)
54            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
55            InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
56        if (blob == null) {
57            return null;
58        }
59        byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
60        byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
61        Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
62                + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
63        cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
64        return cipher.doFinal(ciphertext);
65    }
66
67    private static byte[] encrypt(SecretKey key, byte[] blob)
68            throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
69            InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
70        if (blob == null) {
71            return null;
72        }
73        Cipher cipher = Cipher.getInstance(
74                KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
75                        + KeyProperties.ENCRYPTION_PADDING_NONE);
76        cipher.init(Cipher.ENCRYPT_MODE, key);
77        byte[] ciphertext = cipher.doFinal(blob);
78        byte[] iv = cipher.getIV();
79        if (iv.length != PROFILE_KEY_IV_SIZE) {
80            throw new RuntimeException("Invalid iv length: " + iv.length);
81        }
82        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
83        outputStream.write(iv);
84        outputStream.write(ciphertext);
85        return outputStream.toByteArray();
86    }
87
88    public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
89        byte[] keyHash = personalisedHash(personalisation, keyBytes);
90        SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
91                KeyProperties.KEY_ALGORITHM_AES);
92        try {
93            return encrypt(key, message);
94        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
95                | IllegalBlockSizeException | BadPaddingException | IOException e) {
96            e.printStackTrace();
97            return null;
98        }
99    }
100
101    public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
102        byte[] keyHash = personalisedHash(personalisation, keyBytes);
103        SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
104                KeyProperties.KEY_ALGORITHM_AES);
105        try {
106            return decrypt(key, ciphertext);
107        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
108                | IllegalBlockSizeException | BadPaddingException
109                | InvalidAlgorithmParameterException e) {
110            e.printStackTrace();
111            return null;
112        }
113    }
114
115    public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
116        try {
117            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
118            keyStore.load(null);
119
120            SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
121            byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
122            return decrypt(decryptionKey, intermediate);
123        } catch (CertificateException | IOException | BadPaddingException
124                | IllegalBlockSizeException
125                | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
126                | InvalidKeyException | UnrecoverableKeyException
127                | InvalidAlgorithmParameterException e) {
128            e.printStackTrace();
129            throw new RuntimeException("Failed to decrypt blob", e);
130        }
131    }
132
133    public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
134        try {
135            KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
136            keyGenerator.init(new SecureRandom());
137            SecretKey secretKey = keyGenerator.generateKey();
138            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
139            keyStore.load(null);
140            KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
141                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
142                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
143                    .setCriticalToDeviceEncryption(true);
144            if (sid != 0) {
145                builder.setUserAuthenticationRequired(true)
146                        .setBoundToSpecificSecureUserId(sid)
147                        .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
148            }
149
150            keyStore.setEntry(keyAlias,
151                    new KeyStore.SecretKeyEntry(secretKey),
152                    builder.build());
153            byte[] intermediate = encrypt(secretKey, data);
154            return encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
155
156        } catch (CertificateException | IOException | BadPaddingException
157                | IllegalBlockSizeException
158                | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
159                | InvalidKeyException e) {
160            e.printStackTrace();
161            throw new RuntimeException("Failed to encrypt blob", e);
162        }
163    }
164
165    public static void destroyBlobKey(String keyAlias) {
166        KeyStore keyStore;
167        try {
168            keyStore = KeyStore.getInstance("AndroidKeyStore");
169            keyStore.load(null);
170            keyStore.deleteEntry(keyAlias);
171        } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
172                | IOException e) {
173            e.printStackTrace();
174        }
175    }
176
177    protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
178        try {
179            final int PADDING_LENGTH = 128;
180            MessageDigest digest = MessageDigest.getInstance("SHA-512");
181            if (personalisation.length > PADDING_LENGTH) {
182                throw new RuntimeException("Personalisation too long");
183            }
184            // Personalize the hash
185            // Pad it to the block size of the hash function
186            personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
187            digest.update(personalisation);
188            for (byte[] data : message) {
189                digest.update(data);
190            }
191            return digest.digest();
192        } catch (NoSuchAlgorithmException e) {
193            throw new RuntimeException("NoSuchAlgorithmException for SHA-512", e);
194        }
195    }
196}
197