2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.locksettings.recoverablekeystore;
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;
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
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;
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;
51 * Manages creating and checking the validity of the platform key.
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.
57 * <p>Each platform key has two entries in AndroidKeyStore:
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.
65 * <p>Both entries are enabled only for AES/GCM/NoPadding Cipher algorithm.
69 public class PlatformKeyManager {
70 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];
84 private final Context mContext;
85 private final KeyStoreProxy mKeyStore;
86 private final RecoverableKeyStoreDb mDatabase;
88 private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
91 * A new instance operating on behalf of {@code userId}, storing its prefs in the location
92 * defined by {@code context}.
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.
101 public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database)
102 throws KeyStoreException, NoSuchAlgorithmException {
103 return new PlatformKeyManager(
104 context.getApplicationContext(),
105 new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
112 KeyStoreProxy keyStore,
113 RecoverableKeyStoreDb database) {
114 mKeyStore = keyStore;
116 mDatabase = database;
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.
124 * @param userId The ID of the user to whose lock screen the platform key must be bound.
128 public int getGenerationId(int userId) {
129 return mDatabase.getPlatformKeyGenerationId(userId);
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.
136 * @param userId The ID of the user to whose lock screen the platform key must be bound.
140 public boolean isAvailable(int userId) {
141 return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
145 * Removes the platform key from Android KeyStore.
146 * It is triggered when user disables lock screen.
148 * @param userId The ID of the user to whose lock screen the platform key must be bound.
149 * @param generationId Generation id.
153 public void invalidatePlatformKey(int userId, int generationId) {
154 if (generationId != -1) {
156 mKeyStore.deleteEntry(getEncryptAlias(userId, generationId));
157 mKeyStore.deleteEntry(getDecryptAlias(userId, generationId));
158 } catch (KeyStoreException e) {
159 // Ignore failed attempt to delete key.
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}.
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}.
179 void regenerate(int userId)
180 throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException,
182 if (!isAvailable(userId)) {
183 throw new InsecureUserException(String.format(
184 Locale.US, "%d does not have a lock screen set.", userId));
187 int generationId = getGenerationId(userId);
189 if (generationId == -1) {
192 invalidatePlatformKey(userId, generationId);
193 nextId = generationId + 1;
195 generateAndLoadKey(userId, nextId);
199 * Returns the platform key used for encryption.
200 * Tries to regenerate key one time if it is permanently invalid.
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}.
212 public PlatformEncryptionKey getEncryptKey(int userId)
213 throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
214 InsecureUserException, IOException, RemoteException {
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.",
226 return getEncryptKeyInternal(userId);
231 * Returns the platform key used for encryption.
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.
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);
248 AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
249 alias, /*password=*/ null);
250 return new PlatformEncryptionKey(generationId, key);
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.
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}.
267 public PlatformDecryptionKey getDecryptKey(int userId)
268 throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
269 InsecureUserException, IOException, RemoteException {
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.",
280 return getDecryptKeyInternal(userId);
285 * Returns the platform key used for decryption. Only works after a recent screen unlock.
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.
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);
302 AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
303 alias, /*password=*/ null);
304 return new PlatformDecryptionKey(generationId, key);
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.
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.
314 private void ensureDecryptionKeyIsValid(int userId, PlatformDecryptionKey decryptionKey)
315 throws UnrecoverableKeyException {
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.",
323 throw new UnrecoverableKeyException(e.getMessage());
324 } catch (NoSuchAlgorithmException | InvalidKeyException
325 | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
326 // Ignore all other exceptions
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.
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}.
342 void init(int userId)
343 throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException,
345 if (!isAvailable(userId)) {
346 throw new InsecureUserException(String.format(
347 Locale.US, "%d does not have a lock screen set.", userId));
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));
356 if (generationId == -1) {
357 Log.i(TAG, "Generating initial platform key generation ID.");
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
366 generateAndLoadKey(userId, generationId);
370 * Returns the alias of the encryption key with the specific {@code generationId} in the
373 * <p>These IDs look as follows:
374 * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/encrypt}
376 * @param userId The ID of the user to whose lock screen the platform key must be bound.
377 * @param generationId The generation ID.
380 private String getEncryptAlias(int userId, int generationId) {
381 return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
385 * Returns the alias of the decryption key with the specific {@code generationId} in the
388 * <p>These IDs look as follows:
389 * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt}
391 * @param userId The ID of the user to whose lock screen the platform key must be bound.
392 * @param generationId The generation ID.
395 private String getDecryptAlias(int userId, int generationId) {
396 return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
400 * Sets the current generation ID to {@code generationId}.
401 * @throws IOException if there was an issue with local database update.
403 private void setGenerationId(int userId, int generationId) throws IOException {
404 mDatabase.setPlatformKeyGenerationId(userId, generationId);
408 * Returns {@code true} if a key has been loaded with the given {@code generationId} into
411 * @throws KeyStoreException if there was an error checking AndroidKeyStore.
413 private boolean isKeyLoaded(int userId, int generationId) throws KeyStoreException {
414 return mKeyStore.containsAlias(getEncryptAlias(userId, generationId))
415 && mKeyStore.containsAlias(getDecryptAlias(userId, generationId));
419 IGateKeeperService getGateKeeperService() {
420 return GateKeeper.getService();
424 * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given
425 * {@code generationId} determining its aliases.
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}.
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();
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);
456 decryptionKeyProtection
457 .setBoundToSpecificSecureUserId(secureUserId)
458 // Ignore caller uid which always belongs to the primary profile.
459 .setCriticalToDeviceEncryption(true);
461 // Store decryption key first since it is more likely to fail.
464 new KeyStore.SecretKeyEntry(secretKey),
465 decryptionKeyProtection.build());
468 new KeyStore.SecretKeyEntry(secretKey),
469 new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
470 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
471 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
474 setGenerationId(userId, generationId);
478 * Generates a new 256-bit AES key, in software.
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.
484 private static SecretKey generateAesKey() throws NoSuchAlgorithmException {
485 KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
486 keyGenerator.init(KEY_SIZE_BITS);
487 return keyGenerator.generateKey();
491 * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked
492 * {@link KeyStore#load(KeyStore.LoadStoreParameter)}.
494 * @throws KeyStoreException if there was a problem getting or initializing the key store.
496 private static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException {
497 KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
499 keyStore.load(/*param=*/ null);
500 } catch (CertificateException | IOException | NoSuchAlgorithmException e) {
501 // Should never happen.
502 throw new KeyStoreException("Unable to load keystore.", e);