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.text.TextUtils;
26 import android.util.Slog;
27 
28 import com.android.internal.util.ArrayUtils;
29 
30 import java.io.IOException;
31 import java.security.InvalidAlgorithmParameterException;
32 import java.security.InvalidKeyException;
33 import java.security.KeyStore;
34 import java.security.KeyStoreException;
35 import java.security.MessageDigest;
36 import java.security.NoSuchAlgorithmException;
37 import java.security.SecureRandom;
38 import java.security.UnrecoverableKeyException;
39 import java.security.cert.CertificateException;
40 import java.security.spec.InvalidParameterSpecException;
41 import java.util.Arrays;
42 
43 import javax.crypto.BadPaddingException;
44 import javax.crypto.Cipher;
45 import javax.crypto.IllegalBlockSizeException;
46 import javax.crypto.KeyGenerator;
47 import javax.crypto.NoSuchPaddingException;
48 import javax.crypto.SecretKey;
49 import javax.crypto.spec.GCMParameterSpec;
50 import javax.crypto.spec.SecretKeySpec;
51 
52 class SyntheticPasswordCrypto {
53     private static final String TAG = "SyntheticPasswordCrypto";
54     private static final int AES_GCM_KEY_SIZE = 32; // AES-256-GCM
55     private static final int AES_GCM_IV_SIZE = 12;
56     private static final int AES_GCM_TAG_SIZE = 16;
57     private static final byte[] PROTECTOR_SECRET_PERSONALIZATION = "application-id".getBytes();
58     // Time between the user credential is verified with GK and the decryption of synthetic password
59     // under the auth-bound key. This should always happen one after the other, but give it 15
60     // seconds just to be sure.
61     private static final int USER_AUTHENTICATION_VALIDITY = 15;
62 
decrypt(SecretKey key, byte[] blob)63     private static byte[] decrypt(SecretKey key, byte[] blob)
64             throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
65             InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
66         if (blob == null) {
67             return null;
68         }
69         byte[] iv = Arrays.copyOfRange(blob, 0, AES_GCM_IV_SIZE);
70         byte[] ciphertext = Arrays.copyOfRange(blob, AES_GCM_IV_SIZE, blob.length);
71         Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
72                 + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
73         cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(AES_GCM_TAG_SIZE * 8, iv));
74         return cipher.doFinal(ciphertext);
75     }
76 
encrypt(SecretKey key, byte[] blob)77     private static byte[] encrypt(SecretKey key, byte[] blob)
78             throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
79             InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
80             InvalidParameterSpecException {
81         if (blob == null) {
82             return null;
83         }
84         Cipher cipher = Cipher.getInstance(
85                 KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
86                         + KeyProperties.ENCRYPTION_PADDING_NONE);
87         cipher.init(Cipher.ENCRYPT_MODE, key);
88         byte[] ciphertext = cipher.doFinal(blob);
89         byte[] iv = cipher.getIV();
90         if (iv.length != AES_GCM_IV_SIZE) {
91             throw new IllegalArgumentException("Invalid iv length: " + iv.length + " bytes");
92         }
93         final GCMParameterSpec spec = cipher.getParameters().getParameterSpec(
94                 GCMParameterSpec.class);
95         if (spec.getTLen() != AES_GCM_TAG_SIZE * 8) {
96             throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen() + " bits");
97         }
98         return ArrayUtils.concat(iv, ciphertext);
99     }
100 
encrypt(byte[] keyBytes, byte[] personalization, byte[] message)101     public static byte[] encrypt(byte[] keyBytes, byte[] personalization, byte[] message) {
102         byte[] keyHash = personalizedHash(personalization, keyBytes);
103         SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_GCM_KEY_SIZE),
104                 KeyProperties.KEY_ALGORITHM_AES);
105         try {
106             return encrypt(key, message);
107         } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
108                 | IllegalBlockSizeException | BadPaddingException | IOException
109                 | InvalidParameterSpecException e) {
110             Slog.e(TAG, "Failed to encrypt", e);
111             return null;
112         }
113     }
114 
decrypt(byte[] keyBytes, byte[] personalization, byte[] ciphertext)115     public static byte[] decrypt(byte[] keyBytes, byte[] personalization, byte[] ciphertext) {
116         byte[] keyHash = personalizedHash(personalization, keyBytes);
117         SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_GCM_KEY_SIZE),
118                 KeyProperties.KEY_ALGORITHM_AES);
119         try {
120             return decrypt(key, ciphertext);
121         } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
122                 | IllegalBlockSizeException | BadPaddingException
123                 | InvalidAlgorithmParameterException e) {
124             Slog.e(TAG, "Failed to decrypt", e);
125             return null;
126         }
127     }
128 
129     /**
130      * Decrypts a legacy SP blob which did the Keystore and software encryption layers in the wrong
131      * order.
132      */
decryptBlobV1(String protectorKeyAlias, byte[] blob, byte[] protectorSecret)133     public static byte[] decryptBlobV1(String protectorKeyAlias, byte[] blob,
134             byte[] protectorSecret) {
135         try {
136             KeyStore keyStore = getKeyStore();
137             SecretKey protectorKey = (SecretKey) keyStore.getKey(protectorKeyAlias, null);
138             if (protectorKey == null) {
139                 throw new IllegalStateException("SP protector key is missing: "
140                         + protectorKeyAlias);
141             }
142             byte[] intermediate = decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, blob);
143             return decrypt(protectorKey, intermediate);
144         } catch (Exception e) {
145             Slog.e(TAG, "Failed to decrypt V1 blob", e);
146             throw new IllegalStateException("Failed to decrypt blob", e);
147         }
148     }
149 
androidKeystoreProviderName()150     static String androidKeystoreProviderName() {
151         return "AndroidKeyStore";
152     }
153 
keyNamespace()154     static int keyNamespace() {
155         return KeyProperties.NAMESPACE_LOCKSETTINGS;
156     }
157 
getKeyStore()158     private static KeyStore getKeyStore()
159             throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
160         KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName());
161         keyStore.load(new AndroidKeyStoreLoadStoreParameter(keyNamespace()));
162         return keyStore;
163     }
164 
165     /**
166      * Decrypts an SP blob that was created by {@link #createBlob}.
167      */
decryptBlob(String protectorKeyAlias, byte[] blob, byte[] protectorSecret)168     public static byte[] decryptBlob(String protectorKeyAlias, byte[] blob,
169             byte[] protectorSecret) {
170         try {
171             final KeyStore keyStore = getKeyStore();
172 
173             SecretKey protectorKey = (SecretKey) keyStore.getKey(protectorKeyAlias, null);
174             if (protectorKey == null) {
175                 throw new IllegalStateException("SP protector key is missing: "
176                         + protectorKeyAlias);
177             }
178             byte[] intermediate = decrypt(protectorKey, blob);
179             return decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, intermediate);
180         } catch (CertificateException | IOException | BadPaddingException
181                 | IllegalBlockSizeException
182                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
183                 | InvalidKeyException | UnrecoverableKeyException
184                 | InvalidAlgorithmParameterException e) {
185             Slog.e(TAG, "Failed to decrypt blob", e);
186             throw new IllegalStateException("Failed to decrypt blob", e);
187         }
188     }
189 
190     /**
191      * Creates a new SP blob by encrypting the given data.  Two encryption layers are applied: an
192      * inner layer using a hash of protectorSecret as the key, and an outer layer using the
193      * protector key, which is a Keystore key that is optionally bound to a SID.  This method
194      * creates the protector key and stores it under protectorKeyAlias.
195      *
196      * The reason we use a layer of software encryption, instead of using protectorSecret as the
197      * applicationId of the Keystore key, is to work around buggy KeyMint implementations that don't
198      * cryptographically bind the applicationId to the key.  The Keystore layer has to be the outer
199      * layer, so that LSKF verification is ratelimited by Gatekeeper when Weaver is unavailable.
200      */
createBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret, long sid)201     public static byte[] createBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret,
202             long sid) {
203         try {
204             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
205             keyGenerator.init(AES_GCM_KEY_SIZE * 8, new SecureRandom());
206             SecretKey protectorKey = keyGenerator.generateKey();
207             final KeyStore keyStore = getKeyStore();
208             KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
209                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
210                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
211                     .setCriticalToDeviceEncryption(true);
212             if (sid != 0) {
213                 builder.setUserAuthenticationRequired(true)
214                         .setBoundToSpecificSecureUserId(sid)
215                         .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
216             }
217             final KeyProtection protNonRollbackResistant = builder.build();
218             builder.setRollbackResistant(true);
219             final KeyProtection protRollbackResistant = builder.build();
220             final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(protectorKey);
221             try {
222                 keyStore.setEntry(protectorKeyAlias, entry, protRollbackResistant);
223                 Slog.i(TAG, "Using rollback-resistant key");
224             } catch (KeyStoreException e) {
225                 Slog.w(TAG, "Rollback-resistant keys unavailable.  Falling back to "
226                         + "non-rollback-resistant key");
227                 keyStore.setEntry(protectorKeyAlias, entry, protNonRollbackResistant);
228             }
229 
230             byte[] intermediate = encrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, data);
231             return encrypt(protectorKey, intermediate);
232         } catch (CertificateException | IOException | BadPaddingException
233                 | IllegalBlockSizeException
234                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
235                 | InvalidKeyException
236                 | InvalidParameterSpecException e) {
237             Slog.e(TAG, "Failed to create blob", e);
238             throw new IllegalStateException("Failed to encrypt blob", e);
239         }
240     }
241 
destroyProtectorKey(String keyAlias)242     public static void destroyProtectorKey(String keyAlias) {
243         KeyStore keyStore;
244         try {
245             keyStore = getKeyStore();
246             keyStore.deleteEntry(keyAlias);
247             Slog.i(TAG, "Deleted SP protector key " + keyAlias);
248         } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
249                 | IOException e) {
250             Slog.e(TAG, "Failed to delete SP protector key " + keyAlias, e);
251         }
252     }
253 
personalizedHash(byte[] personalization, byte[]... message)254     protected static byte[] personalizedHash(byte[] personalization, byte[]... message) {
255         try {
256             final int PADDING_LENGTH = 128;
257             MessageDigest digest = MessageDigest.getInstance("SHA-512");
258             if (personalization.length > PADDING_LENGTH) {
259                 throw new IllegalArgumentException("Personalization too long");
260             }
261             // Personalize the hash
262             // Pad it to the block size of the hash function
263             personalization = Arrays.copyOf(personalization, PADDING_LENGTH);
264             digest.update(personalization);
265             for (byte[] data : message) {
266                 digest.update(data);
267             }
268             return digest.digest();
269         } catch (NoSuchAlgorithmException e) {
270             throw new IllegalStateException("NoSuchAlgorithmException for SHA-512", e);
271         }
272     }
273 
migrateLockSettingsKey(String alias)274     static boolean migrateLockSettingsKey(String alias) {
275         final KeyDescriptor legacyKey = new KeyDescriptor();
276         legacyKey.domain = Domain.APP;
277         legacyKey.nspace = KeyProperties.NAMESPACE_APPLICATION;
278         legacyKey.alias = alias;
279 
280         final KeyDescriptor newKey = new KeyDescriptor();
281         newKey.domain = Domain.SELINUX;
282         newKey.nspace = SyntheticPasswordCrypto.keyNamespace();
283         newKey.alias = alias;
284         Slog.i(TAG, "Migrating key " + alias);
285         int err = AndroidKeyStoreMaintenance.migrateKeyNamespace(legacyKey, newKey);
286         if (err == 0) {
287             return true;
288         } else if (err == AndroidKeyStoreMaintenance.KEY_NOT_FOUND) {
289             Slog.i(TAG, "Key does not exist");
290             // Treat this as a success so we don't migrate again.
291             return true;
292         } else if (err == AndroidKeyStoreMaintenance.INVALID_ARGUMENT) {
293             Slog.i(TAG, "Key already exists");
294             // Treat this as a success so we don't migrate again.
295             return true;
296         } else {
297             Slog.e(TAG, TextUtils.formatSimple("Failed to migrate key: %d", err));
298             return false;
299         }
300     }
301 }
302