OSDN Git Service

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