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