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 */ getInstance(Context context, RecoverableKeyStoreDb database)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 PlatformKeyManager( Context context, KeyStoreProxy keyStore, RecoverableKeyStoreDb database)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 */ getGenerationId(int userId)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 */ isAvailable(int userId)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 */ invalidatePlatformKey(int userId, int generationId)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 regenerate(int userId)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 */ getEncryptKey(int userId)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 */ getEncryptKeyInternal(int userId)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 */ getDecryptKey(int userId)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 */ getDecryptKeyInternal(int userId)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 */ ensureDecryptionKeyIsValid(int userId, PlatformDecryptionKey decryptionKey)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 */ init(int userId)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 */ getEncryptAlias(int userId, int generationId)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 */ getDecryptAlias(int userId, int generationId)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 */ setGenerationId(int userId, int generationId)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 */ isKeyLoaded(int userId, int generationId)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 getGateKeeperService()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 */ generateAndLoadKey(int userId, int generationId)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 */ generateAesKey()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 */ getAndLoadAndroidKeyStore()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 } 501