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