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 
17 package com.android.server.locksettings;
18 
19 import android.security.AndroidKeyStoreMaintenance;
20 import android.security.keystore.KeyProperties;
21 import android.security.keystore.KeyProtection;
22 import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
23 import android.system.keystore2.Domain;
24 import android.system.keystore2.KeyDescriptor;
25 import android.util.Slog;
26 
27 import java.io.ByteArrayOutputStream;
28 import java.io.IOException;
29 import java.security.InvalidAlgorithmParameterException;
30 import java.security.InvalidKeyException;
31 import java.security.KeyStore;
32 import java.security.KeyStoreException;
33 import java.security.MessageDigest;
34 import java.security.NoSuchAlgorithmException;
35 import java.security.SecureRandom;
36 import java.security.UnrecoverableKeyException;
37 import java.security.cert.CertificateException;
38 import java.security.spec.InvalidParameterSpecException;
39 import java.util.Arrays;
40 
41 import javax.crypto.BadPaddingException;
42 import javax.crypto.Cipher;
43 import javax.crypto.IllegalBlockSizeException;
44 import javax.crypto.KeyGenerator;
45 import javax.crypto.NoSuchPaddingException;
46 import javax.crypto.SecretKey;
47 import javax.crypto.spec.GCMParameterSpec;
48 import javax.crypto.spec.SecretKeySpec;
49 
50 public class SyntheticPasswordCrypto {
51     private static final String TAG = "SyntheticPasswordCrypto";
52     private static final int PROFILE_KEY_IV_SIZE = 12;
53     private static final int DEFAULT_TAG_LENGTH_BITS = 128;
54     private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
55     private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
56     // Time between the user credential is verified with GK and the decryption of synthetic password
57     // under the auth-bound key. This should always happen one after the other, but give it 15
58     // seconds just to be sure.
59     private static final int USER_AUTHENTICATION_VALIDITY = 15;
60 
decrypt(SecretKey key, byte[] blob)61     private static byte[] decrypt(SecretKey key, byte[] blob)
62             throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
63             InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
64         if (blob == null) {
65             return null;
66         }
67         byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
68         byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
69         Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
70                 + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
71         cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv));
72         return cipher.doFinal(ciphertext);
73     }
74 
encrypt(SecretKey key, byte[] blob)75     private static byte[] encrypt(SecretKey key, byte[] blob)
76             throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
77             InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
78             InvalidParameterSpecException {
79         if (blob == null) {
80             return null;
81         }
82         Cipher cipher = Cipher.getInstance(
83                 KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
84                         + KeyProperties.ENCRYPTION_PADDING_NONE);
85         cipher.init(Cipher.ENCRYPT_MODE, key);
86         byte[] ciphertext = cipher.doFinal(blob);
87         byte[] iv = cipher.getIV();
88         if (iv.length != PROFILE_KEY_IV_SIZE) {
89             throw new IllegalArgumentException("Invalid iv length: " + iv.length);
90         }
91         final GCMParameterSpec spec = cipher.getParameters().getParameterSpec(
92                 GCMParameterSpec.class);
93         if (spec.getTLen() != DEFAULT_TAG_LENGTH_BITS) {
94             throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen());
95         }
96         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
97         outputStream.write(iv);
98         outputStream.write(ciphertext);
99         return outputStream.toByteArray();
100     }
101 
encrypt(byte[] keyBytes, byte[] personalisation, byte[] message)102     public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
103         byte[] keyHash = personalisedHash(personalisation, keyBytes);
104         SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
105                 KeyProperties.KEY_ALGORITHM_AES);
106         try {
107             return encrypt(key, message);
108         } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
109                 | IllegalBlockSizeException | BadPaddingException | IOException
110                 | InvalidParameterSpecException e) {
111             Slog.e(TAG, "Failed to encrypt", e);
112             return null;
113         }
114     }
115 
decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext)116     public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
117         byte[] keyHash = personalisedHash(personalisation, keyBytes);
118         SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
119                 KeyProperties.KEY_ALGORITHM_AES);
120         try {
121             return decrypt(key, ciphertext);
122         } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
123                 | IllegalBlockSizeException | BadPaddingException
124                 | InvalidAlgorithmParameterException e) {
125             Slog.e(TAG, "Failed to decrypt", e);
126             return null;
127         }
128     }
129 
decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId)130     public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
131         try {
132             KeyStore keyStore = getKeyStore();
133             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
134             if (decryptionKey == null) {
135                 throw new IllegalStateException("SP key is missing: " + keyAlias);
136             }
137             byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
138             return decrypt(decryptionKey, intermediate);
139         } catch (Exception e) {
140             Slog.e(TAG, "Failed to decrypt V1 blob", e);
141             throw new IllegalStateException("Failed to decrypt blob", e);
142         }
143     }
144 
androidKeystoreProviderName()145     static String androidKeystoreProviderName() {
146         return "AndroidKeyStore";
147     }
148 
keyNamespace()149     static int keyNamespace() {
150         return KeyProperties.NAMESPACE_LOCKSETTINGS;
151     }
152 
getKeyStore()153     private static KeyStore getKeyStore()
154             throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
155         KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName());
156         keyStore.load(new AndroidKeyStoreLoadStoreParameter(keyNamespace()));
157         return keyStore;
158     }
159 
decryptBlob(String keyAlias, byte[] blob, byte[] applicationId)160     public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
161         try {
162             final KeyStore keyStore = getKeyStore();
163 
164             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
165             if (decryptionKey == null) {
166                 throw new IllegalStateException("SP key is missing: " + keyAlias);
167             }
168             byte[] intermediate = decrypt(decryptionKey, blob);
169             return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
170         } catch (CertificateException | IOException | BadPaddingException
171                 | IllegalBlockSizeException
172                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
173                 | InvalidKeyException | UnrecoverableKeyException
174                 | InvalidAlgorithmParameterException e) {
175             Slog.e(TAG, "Failed to decrypt blob", e);
176             throw new IllegalStateException("Failed to decrypt blob", e);
177         }
178     }
179 
createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid)180     public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
181         try {
182             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
183             keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
184             SecretKey secretKey = keyGenerator.generateKey();
185             final KeyStore keyStore = getKeyStore();
186             KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
187                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
188                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
189                     .setCriticalToDeviceEncryption(true);
190             if (sid != 0) {
191                 builder.setUserAuthenticationRequired(true)
192                         .setBoundToSpecificSecureUserId(sid)
193                         .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
194             }
195 
196             keyStore.setEntry(keyAlias,
197                     new KeyStore.SecretKeyEntry(secretKey),
198                     builder.build());
199             byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
200             return encrypt(secretKey, intermediate);
201         } catch (CertificateException | IOException | BadPaddingException
202                 | IllegalBlockSizeException
203                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
204                 | InvalidKeyException
205                 | InvalidParameterSpecException e) {
206             Slog.e(TAG, "Failed to create blob", e);
207             throw new IllegalStateException("Failed to encrypt blob", e);
208         }
209     }
210 
destroyBlobKey(String keyAlias)211     public static void destroyBlobKey(String keyAlias) {
212         KeyStore keyStore;
213         try {
214             keyStore = getKeyStore();
215             keyStore.deleteEntry(keyAlias);
216             Slog.i(TAG, "SP key deleted: " + keyAlias);
217         } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
218                 | IOException e) {
219             Slog.e(TAG, "Failed to destroy blob", e);
220         }
221     }
222 
personalisedHash(byte[] personalisation, byte[]... message)223     protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
224         try {
225             final int PADDING_LENGTH = 128;
226             MessageDigest digest = MessageDigest.getInstance("SHA-512");
227             if (personalisation.length > PADDING_LENGTH) {
228                 throw new IllegalArgumentException("Personalisation too long");
229             }
230             // Personalize the hash
231             // Pad it to the block size of the hash function
232             personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
233             digest.update(personalisation);
234             for (byte[] data : message) {
235                 digest.update(data);
236             }
237             return digest.digest();
238         } catch (NoSuchAlgorithmException e) {
239             throw new IllegalStateException("NoSuchAlgorithmException for SHA-512", e);
240         }
241     }
242 
migrateLockSettingsKey(String alias)243     static boolean migrateLockSettingsKey(String alias) {
244         final KeyDescriptor legacyKey = new KeyDescriptor();
245         legacyKey.domain = Domain.APP;
246         legacyKey.nspace = KeyProperties.NAMESPACE_APPLICATION;
247         legacyKey.alias = alias;
248 
249         final KeyDescriptor newKey = new KeyDescriptor();
250         newKey.domain = Domain.SELINUX;
251         newKey.nspace = SyntheticPasswordCrypto.keyNamespace();
252         newKey.alias = alias;
253         Slog.i(TAG, "Migrating key " + alias);
254         int err = AndroidKeyStoreMaintenance.migrateKeyNamespace(legacyKey, newKey);
255         if (err == 0) {
256             return true;
257         } else if (err == AndroidKeyStoreMaintenance.KEY_NOT_FOUND) {
258             Slog.i(TAG, "Key does not exist");
259             // Treat this as a success so we don't migrate again.
260             return true;
261         } else if (err == AndroidKeyStoreMaintenance.INVALID_ARGUMENT) {
262             Slog.i(TAG, "Key already exists");
263             // Treat this as a success so we don't migrate again.
264             return true;
265         } else {
266             Slog.e(TAG, String.format("Failed to migrate key: %d", err));
267             return false;
268         }
269     }
270 }
271