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.recoverablekeystore;
18 
19 import android.app.KeyguardManager;
20 import android.content.Context;
21 import android.os.RemoteException;
22 import android.os.UserHandle;
23 import android.security.GateKeeper;
24 import android.security.keystore.AndroidKeyStoreSecretKey;
25 import android.security.keystore.KeyPermanentlyInvalidatedException;
26 import android.security.keystore.KeyProperties;
27 import android.security.keystore.KeyProtection;
28 import android.service.gatekeeper.IGateKeeperService;
29 import android.util.Log;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
33 
34 import java.io.IOException;
35 import java.security.InvalidAlgorithmParameterException;
36 import java.security.InvalidKeyException;
37 import java.security.KeyStore;
38 import java.security.KeyStoreException;
39 import java.security.NoSuchAlgorithmException;
40 import java.security.UnrecoverableKeyException;
41 import java.security.cert.CertificateException;
42 import java.util.Locale;
43 
44 import javax.crypto.Cipher;
45 import javax.crypto.KeyGenerator;
46 import javax.crypto.NoSuchPaddingException;
47 import javax.crypto.SecretKey;
48 import javax.crypto.spec.GCMParameterSpec;
49 
50 /**
51  * Manages creating and checking the validity of the platform key.
52  *
53  * <p>The platform key is used to wrap the material of recoverable keys before persisting them to
54  * disk. It is also used to decrypt the same keys on a screen unlock, before re-wrapping them with
55  * a recovery key and syncing them with remote storage.
56  *
57  * <p>Each platform key has two entries in AndroidKeyStore:
58  *
59  * <ul>
60  *     <li>Encrypt entry - this entry enables the root user to at any time encrypt.
61  *     <li>Decrypt entry - this entry enables the root user to decrypt only after recent user
62  *       authentication, i.e., within 15 seconds after a screen unlock.
63  * </ul>
64  *
65  * <p>Both entries are enabled only for AES/GCM/NoPadding Cipher algorithm.
66  *
67  * @hide
68  */
69 public class PlatformKeyManager {
70     private static final String TAG = "PlatformKeyManager";
71 
72     private static final String KEY_ALGORITHM = "AES";
73     private static final int KEY_SIZE_BITS = 256;
74     private static final String KEY_ALIAS_PREFIX =
75             "com.android.server.locksettings.recoverablekeystore/platform/";
76     private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt";
77     private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt";
78     private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
79     private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
80     private static final int GCM_TAG_LENGTH_BITS = 128;
81     // Only used for checking if a key is usable
82     private static final byte[] GCM_INSECURE_NONCE_BYTES = new byte[12];
83 
84     private final Context mContext;
85     private final KeyStoreProxy mKeyStore;
86     private final RecoverableKeyStoreDb mDatabase;
87 
88     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
89 
90     /**
91      * A new instance operating on behalf of {@code userId}, storing its prefs in the location
92      * defined by {@code context}.
93      *
94      * @param context This should be the context of the RecoverableKeyStoreLoader service.
95      * @throws KeyStoreException if failed to initialize AndroidKeyStore.
96      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
97      * @throws SecurityException if the caller does not have permission to write to /data/system.
98      *
99      * @hide
100      */
getInstance(Context context, RecoverableKeyStoreDb database)101     public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database)
102             throws KeyStoreException, NoSuchAlgorithmException {
103         return new PlatformKeyManager(
104                 context.getApplicationContext(),
105                 new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
106                 database);
107     }
108 
109     @VisibleForTesting
PlatformKeyManager( Context context, KeyStoreProxy keyStore, RecoverableKeyStoreDb database)110     PlatformKeyManager(
111             Context context,
112             KeyStoreProxy keyStore,
113             RecoverableKeyStoreDb database) {
114         mKeyStore = keyStore;
115         mContext = context;
116         mDatabase = database;
117     }
118 
119     /**
120      * Returns the current generation ID of the platform key. This increments whenever a platform
121      * key has to be replaced. (e.g., because the user has removed and then re-added their lock
122      * screen). Returns -1 if no key has been generated yet.
123      *
124      * @param userId The ID of the user to whose lock screen the platform key must be bound.
125      *
126      * @hide
127      */
getGenerationId(int userId)128     public int getGenerationId(int userId) {
129         return mDatabase.getPlatformKeyGenerationId(userId);
130     }
131 
132     /**
133      * Returns {@code true} if the platform key is available. A platform key won't be available if
134      * the user has not set up a lock screen.
135      *
136      * @param userId The ID of the user to whose lock screen the platform key must be bound.
137      *
138      * @hide
139      */
isAvailable(int userId)140     public boolean isAvailable(int userId) {
141         return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
142     }
143 
144     /**
145      * Removes the platform key from Android KeyStore.
146      * It is triggered when user disables lock screen.
147      *
148      * @param userId The ID of the user to whose lock screen the platform key must be bound.
149      * @param generationId Generation id.
150      *
151      * @hide
152      */
invalidatePlatformKey(int userId, int generationId)153     public void invalidatePlatformKey(int userId, int generationId) {
154         if (generationId != -1) {
155             try {
156                 mKeyStore.deleteEntry(getEncryptAlias(userId, generationId));
157                 mKeyStore.deleteEntry(getDecryptAlias(userId, generationId));
158             } catch (KeyStoreException e) {
159                 // Ignore failed attempt to delete key.
160             }
161         }
162     }
163 
164     /**
165      * Generates a new key and increments the generation ID. Should be invoked if the platform key
166      * is corrupted and needs to be rotated.
167      * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
168      *
169      * @param userId The ID of the user to whose lock screen the platform key must be bound.
170      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
171      * @throws KeyStoreException if there is an error in AndroidKeyStore.
172      * @throws InsecureUserException if the user does not have a lock screen set.
173      * @throws IOException if there was an issue with local database update.
174      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
175      *
176      * @hide
177      */
178     @VisibleForTesting
regenerate(int userId)179     void regenerate(int userId)
180             throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException,
181                     RemoteException {
182         if (!isAvailable(userId)) {
183             throw new InsecureUserException(String.format(
184                     Locale.US, "%d does not have a lock screen set.", userId));
185         }
186 
187         int generationId = getGenerationId(userId);
188         int nextId;
189         if (generationId == -1) {
190             nextId = 1;
191         } else {
192             invalidatePlatformKey(userId, generationId);
193             nextId = generationId + 1;
194         }
195         generateAndLoadKey(userId, nextId);
196     }
197 
198     /**
199      * Returns the platform key used for encryption.
200      * Tries to regenerate key one time if it is permanently invalid.
201      *
202      * @param userId The ID of the user to whose lock screen the platform key must be bound.
203      * @throws KeyStoreException if there was an AndroidKeyStore error.
204      * @throws UnrecoverableKeyException if the key could not be recovered.
205      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
206      * @throws InsecureUserException if the user does not have a lock screen set.
207      * @throws IOException if there was an issue with local database update.
208      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
209      *
210      * @hide
211      */
getEncryptKey(int userId)212     public PlatformEncryptionKey getEncryptKey(int userId)
213             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
214                     InsecureUserException, IOException, RemoteException {
215         init(userId);
216         try {
217             // Try to see if the decryption key is still accessible before using the encryption key.
218             // The auth-bound decryption will be unrecoverable if the screen lock is disabled.
219             getDecryptKeyInternal(userId);
220             return getEncryptKeyInternal(userId);
221         } catch (UnrecoverableKeyException e) {
222             Log.i(TAG, String.format(Locale.US,
223                     "Regenerating permanently invalid Platform key for user %d.",
224                     userId));
225             regenerate(userId);
226             return getEncryptKeyInternal(userId);
227         }
228     }
229 
230     /**
231      * Returns the platform key used for encryption.
232      *
233      * @param userId The ID of the user to whose lock screen the platform key must be bound.
234      * @throws KeyStoreException if there was an AndroidKeyStore error.
235      * @throws UnrecoverableKeyException if the key could not be recovered.
236      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
237      * @throws InsecureUserException if the user does not have a lock screen set.
238      *
239      * @hide
240      */
getEncryptKeyInternal(int userId)241     private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException,
242            UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
243         int generationId = getGenerationId(userId);
244         String alias = getEncryptAlias(userId, generationId);
245         if (!isKeyLoaded(userId, generationId)) {
246             throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias);
247         }
248         AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
249                 alias, /*password=*/ null);
250         return new PlatformEncryptionKey(generationId, key);
251     }
252 
253     /**
254      * Returns the platform key used for decryption. Only works after a recent screen unlock.
255      * Tries to regenerate key one time if it is permanently invalid.
256      *
257      * @param userId The ID of the user to whose lock screen the platform key must be bound.
258      * @throws KeyStoreException if there was an AndroidKeyStore error.
259      * @throws UnrecoverableKeyException if the key could not be recovered.
260      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
261      * @throws InsecureUserException if the user does not have a lock screen set.
262      * @throws IOException if there was an issue with local database update.
263      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
264      *
265      * @hide
266      */
getDecryptKey(int userId)267     public PlatformDecryptionKey getDecryptKey(int userId)
268             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
269                     InsecureUserException, IOException, RemoteException {
270         init(userId);
271         try {
272             PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
273             ensureDecryptionKeyIsValid(userId, decryptionKey);
274             return decryptionKey;
275         } catch (UnrecoverableKeyException e) {
276             Log.i(TAG, String.format(Locale.US,
277                     "Regenerating permanently invalid Platform key for user %d.",
278                     userId));
279             regenerate(userId);
280             return getDecryptKeyInternal(userId);
281         }
282     }
283 
284     /**
285      * Returns the platform key used for decryption. Only works after a recent screen unlock.
286      *
287      * @param userId The ID of the user to whose lock screen the platform key must be bound.
288      * @throws KeyStoreException if there was an AndroidKeyStore error.
289      * @throws UnrecoverableKeyException if the key could not be recovered.
290      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
291      * @throws InsecureUserException if the user does not have a lock screen set.
292      *
293      * @hide
294      */
getDecryptKeyInternal(int userId)295     private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException,
296            UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
297         int generationId = getGenerationId(userId);
298         String alias = getDecryptAlias(userId, generationId);
299         if (!isKeyLoaded(userId, generationId)) {
300             throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias);
301         }
302         AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
303                 alias, /*password=*/ null);
304         return new PlatformDecryptionKey(generationId, key);
305     }
306 
307     /**
308      * Tries to use the decryption key to make sure it is not permanently invalidated. The exception
309      * {@code KeyPermanentlyInvalidatedException} is thrown only when the key is in use.
310      *
311      * <p>Note that we ignore all other InvalidKeyException exceptions, because such an exception
312      * may be thrown for auth-bound keys if there's no recent unlock event.
313      */
ensureDecryptionKeyIsValid(int userId, PlatformDecryptionKey decryptionKey)314     private void ensureDecryptionKeyIsValid(int userId, PlatformDecryptionKey decryptionKey)
315             throws UnrecoverableKeyException {
316         try {
317             Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM).init(Cipher.UNWRAP_MODE,
318                     decryptionKey.getKey(),
319                     new GCMParameterSpec(GCM_TAG_LENGTH_BITS, GCM_INSECURE_NONCE_BYTES));
320         } catch (KeyPermanentlyInvalidatedException e) {
321             Log.e(TAG, String.format(Locale.US, "The platform key for user %d became invalid.",
322                     userId));
323             throw new UnrecoverableKeyException(e.getMessage());
324         } catch (NoSuchAlgorithmException | InvalidKeyException
325                 | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
326             // Ignore all other exceptions
327         }
328     }
329 
330     /**
331      * Initializes the class. If there is no current platform key, and the user has a lock screen
332      * set, will create the platform key and set the generation ID.
333      *
334      * @param userId The ID of the user to whose lock screen the platform key must be bound.
335      * @throws KeyStoreException if there was an error in AndroidKeyStore.
336      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
337      * @throws IOException if there was an issue with local database update.
338      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
339      *
340      * @hide
341      */
init(int userId)342     void init(int userId)
343             throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException,
344                     RemoteException {
345         if (!isAvailable(userId)) {
346             throw new InsecureUserException(String.format(
347                     Locale.US, "%d does not have a lock screen set.", userId));
348         }
349 
350         int generationId = getGenerationId(userId);
351         if (isKeyLoaded(userId, generationId)) {
352             Log.i(TAG, String.format(
353                     Locale.US, "Platform key generation %d exists already.", generationId));
354             return;
355         }
356         if (generationId == -1) {
357             Log.i(TAG, "Generating initial platform key generation ID.");
358             generationId = 1;
359         } else {
360             Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
361                     + "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
362             // Have to generate a fresh key, so bump the generation id
363             generationId++;
364         }
365 
366         generateAndLoadKey(userId, generationId);
367     }
368 
369     /**
370      * Returns the alias of the encryption key with the specific {@code generationId} in the
371      * AndroidKeyStore.
372      *
373      * <p>These IDs look as follows:
374      * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/encrypt}
375      *
376      * @param userId The ID of the user to whose lock screen the platform key must be bound.
377      * @param generationId The generation ID.
378      * @return The alias.
379      */
getEncryptAlias(int userId, int generationId)380     private String getEncryptAlias(int userId, int generationId) {
381         return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
382     }
383 
384     /**
385      * Returns the alias of the decryption key with the specific {@code generationId} in the
386      * AndroidKeyStore.
387      *
388      * <p>These IDs look as follows:
389      * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt}
390      *
391      * @param userId The ID of the user to whose lock screen the platform key must be bound.
392      * @param generationId The generation ID.
393      * @return The alias.
394      */
getDecryptAlias(int userId, int generationId)395     private String getDecryptAlias(int userId, int generationId) {
396         return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
397     }
398 
399     /**
400      * Sets the current generation ID to {@code generationId}.
401      * @throws IOException if there was an issue with local database update.
402      */
setGenerationId(int userId, int generationId)403     private void setGenerationId(int userId, int generationId) throws IOException {
404         mDatabase.setPlatformKeyGenerationId(userId, generationId);
405     }
406 
407     /**
408      * Returns {@code true} if a key has been loaded with the given {@code generationId} into
409      * AndroidKeyStore.
410      *
411      * @throws KeyStoreException if there was an error checking AndroidKeyStore.
412      */
isKeyLoaded(int userId, int generationId)413     private boolean isKeyLoaded(int userId, int generationId) throws KeyStoreException {
414         return mKeyStore.containsAlias(getEncryptAlias(userId, generationId))
415                 && mKeyStore.containsAlias(getDecryptAlias(userId, generationId));
416     }
417 
418     @VisibleForTesting
getGateKeeperService()419     IGateKeeperService getGateKeeperService() {
420         return GateKeeper.getService();
421     }
422 
423     /**
424      * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given
425      * {@code generationId} determining its aliases.
426      *
427      * @throws NoSuchAlgorithmException if AES is unavailable. This should never happen, as it is
428      *     available since API version 1.
429      * @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore.
430      * @throws IOException if there was an issue with local database update.
431      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
432      */
generateAndLoadKey(int userId, int generationId)433     private void generateAndLoadKey(int userId, int generationId)
434             throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException {
435         String encryptAlias = getEncryptAlias(userId, generationId);
436         String decryptAlias = getDecryptAlias(userId, generationId);
437         // SecretKey implementation doesn't provide reliable way to destroy the secret
438         // so it may live in memory for some time.
439         SecretKey secretKey = generateAesKey();
440 
441         KeyProtection.Builder decryptionKeyProtection =
442                 new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
443                     .setUserAuthenticationRequired(true)
444                     .setUserAuthenticationValidityDurationSeconds(
445                             USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
446                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
447                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
448         if (userId != UserHandle.USER_SYSTEM) {
449             // Bind decryption key to secondary profile lock screen secret.
450             long secureUserId = getGateKeeperService().getSecureUserId(userId);
451             // TODO(b/124095438): Propagate this failure instead of silently failing.
452             if (secureUserId == GateKeeper.INVALID_SECURE_USER_ID) {
453                 Log.e(TAG, "No SID available for user " + userId);
454                 return;
455             }
456             decryptionKeyProtection
457                     .setBoundToSpecificSecureUserId(secureUserId)
458                     // Ignore caller uid which always belongs to the primary profile.
459                     .setCriticalToDeviceEncryption(true);
460         }
461         // Store decryption key first since it is more likely to fail.
462         mKeyStore.setEntry(
463                 decryptAlias,
464                 new KeyStore.SecretKeyEntry(secretKey),
465                 decryptionKeyProtection.build());
466         mKeyStore.setEntry(
467                 encryptAlias,
468                 new KeyStore.SecretKeyEntry(secretKey),
469                 new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
470                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
471                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
472                     .build());
473 
474         setGenerationId(userId, generationId);
475     }
476 
477     /**
478      * Generates a new 256-bit AES key, in software.
479      *
480      * @return The software-generated AES key.
481      * @throws NoSuchAlgorithmException if AES key generation is not available. This should never
482      *     happen, as AES has been supported since API level 1.
483      */
generateAesKey()484     private static SecretKey generateAesKey() throws NoSuchAlgorithmException {
485         KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
486         keyGenerator.init(KEY_SIZE_BITS);
487         return keyGenerator.generateKey();
488     }
489 
490     /**
491      * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked
492      * {@link KeyStore#load(KeyStore.LoadStoreParameter)}.
493      *
494      * @throws KeyStoreException if there was a problem getting or initializing the key store.
495      */
getAndLoadAndroidKeyStore()496     private static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException {
497         KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
498         try {
499             keyStore.load(/*param=*/ null);
500         } catch (CertificateException | IOException | NoSuchAlgorithmException e) {
501             // Should never happen.
502             throw new KeyStoreException("Unable to load keystore.", e);
503         }
504         return keyStore;
505     }
506 
507 }
508