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.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; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; 33 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; 43 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; 49 50 /** 51 * Manages creating and checking the validity of the platform key. 52 * 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. 56 * 57 * <p>Each platform key has two entries in AndroidKeyStore: 58 * 59 * <ul> 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. 63 * </ul> 64 * 65 * <p>Both entries are enabled only for AES/GCM/NoPadding Cipher algorithm. 66 * 67 * @hide 68 */ 69 public class PlatformKeyManager { 70 private static final String TAG = "PlatformKeyManager"; 71 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 private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; 89 90 /** 91 * A new instance operating on behalf of {@code userId}, storing its prefs in the location 92 * defined by {@code context}. 93 * 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. 98 * 99 * @hide 100 */ getInstance(Context context, RecoverableKeyStoreDb database)101 public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database) 102 throws KeyStoreException, NoSuchAlgorithmException { 103 return new PlatformKeyManager( 104 context.getApplicationContext(), 105 new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()), 106 database); 107 } 108 109 @VisibleForTesting PlatformKeyManager( Context context, KeyStoreProxy keyStore, RecoverableKeyStoreDb database)110 PlatformKeyManager( 111 Context context, 112 KeyStoreProxy keyStore, 113 RecoverableKeyStoreDb database) { 114 mKeyStore = keyStore; 115 mContext = context; 116 mDatabase = database; 117 } 118 119 /** 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. 123 * 124 * @param userId The ID of the user to whose lock screen the platform key must be bound. 125 * 126 * @hide 127 */ getGenerationId(int userId)128 public int getGenerationId(int userId) { 129 return mDatabase.getPlatformKeyGenerationId(userId); 130 } 131 132 /** 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. 135 * 136 * @param userId The ID of the user to whose lock screen the platform key must be bound. 137 * 138 * @hide 139 */ isAvailable(int userId)140 public boolean isAvailable(int userId) { 141 return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId); 142 } 143 144 /** 145 * Removes the platform key from Android KeyStore. 146 * It is triggered when user disables lock screen. 147 * 148 * @param userId The ID of the user to whose lock screen the platform key must be bound. 149 * @param generationId Generation id. 150 * 151 * @hide 152 */ invalidatePlatformKey(int userId, int generationId)153 public void invalidatePlatformKey(int userId, int generationId) { 154 if (generationId != -1) { 155 try { 156 mKeyStore.deleteEntry(getEncryptAlias(userId, generationId)); 157 mKeyStore.deleteEntry(getDecryptAlias(userId, generationId)); 158 } catch (KeyStoreException e) { 159 // Ignore failed attempt to delete key. 160 } 161 } 162 } 163 164 /** 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}. 168 * 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}. 175 * 176 * @hide 177 */ 178 @VisibleForTesting regenerate(int userId)179 void regenerate(int userId) 180 throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException, 181 RemoteException { 182 if (!isAvailable(userId)) { 183 throw new InsecureUserException(String.format( 184 Locale.US, "%d does not have a lock screen set.", userId)); 185 } 186 187 int generationId = getGenerationId(userId); 188 int nextId; 189 if (generationId == -1) { 190 nextId = 1; 191 } else { 192 invalidatePlatformKey(userId, generationId); 193 nextId = generationId + 1; 194 } 195 generateAndLoadKey(userId, nextId); 196 } 197 198 /** 199 * Returns the platform key used for encryption. 200 * Tries to regenerate key one time if it is permanently invalid. 201 * 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}. 209 * 210 * @hide 211 */ getEncryptKey(int userId)212 public PlatformEncryptionKey getEncryptKey(int userId) 213 throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, 214 InsecureUserException, IOException, RemoteException { 215 init(userId); 216 try { 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.", 224 userId)); 225 regenerate(userId); 226 return getEncryptKeyInternal(userId); 227 } 228 } 229 230 /** 231 * Returns the platform key used for encryption. 232 * 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. 238 * 239 * @hide 240 */ getEncryptKeyInternal(int userId)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); 247 } 248 AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( 249 alias, /*password=*/ null); 250 return new PlatformEncryptionKey(generationId, key); 251 } 252 253 /** 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. 256 * 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}. 264 * 265 * @hide 266 */ getDecryptKey(int userId)267 public PlatformDecryptionKey getDecryptKey(int userId) 268 throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, 269 InsecureUserException, IOException, RemoteException { 270 init(userId); 271 try { 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.", 278 userId)); 279 regenerate(userId); 280 return getDecryptKeyInternal(userId); 281 } 282 } 283 284 /** 285 * Returns the platform key used for decryption. Only works after a recent screen unlock. 286 * 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. 292 * 293 * @hide 294 */ getDecryptKeyInternal(int userId)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); 301 } 302 AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( 303 alias, /*password=*/ null); 304 return new PlatformDecryptionKey(generationId, key); 305 } 306 307 /** 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. 310 * 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. 313 */ ensureDecryptionKeyIsValid(int userId, PlatformDecryptionKey decryptionKey)314 private void ensureDecryptionKeyIsValid(int userId, PlatformDecryptionKey decryptionKey) 315 throws UnrecoverableKeyException { 316 try { 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.", 322 userId)); 323 throw new UnrecoverableKeyException(e.getMessage()); 324 } catch (NoSuchAlgorithmException | InvalidKeyException 325 | InvalidAlgorithmParameterException | NoSuchPaddingException e) { 326 // Ignore all other exceptions 327 } 328 } 329 330 /** 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. 333 * 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}. 339 * 340 * @hide 341 */ init(int userId)342 void init(int userId) 343 throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException, 344 RemoteException { 345 if (!isAvailable(userId)) { 346 throw new InsecureUserException(String.format( 347 Locale.US, "%d does not have a lock screen set.", userId)); 348 } 349 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)); 354 return; 355 } 356 if (generationId == -1) { 357 Log.i(TAG, "Generating initial platform key generation ID."); 358 generationId = 1; 359 } else { 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 363 generationId++; 364 } 365 366 generateAndLoadKey(userId, generationId); 367 } 368 369 /** 370 * Returns the alias of the encryption 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>/encrypt} 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 */ getEncryptAlias(int userId, int generationId)380 private String getEncryptAlias(int userId, int generationId) { 381 return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX; 382 } 383 384 /** 385 * Returns the alias of the decryption key with the specific {@code generationId} in the 386 * AndroidKeyStore. 387 * 388 * <p>These IDs look as follows: 389 * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt} 390 * 391 * @param userId The ID of the user to whose lock screen the platform key must be bound. 392 * @param generationId The generation ID. 393 * @return The alias. 394 */ getDecryptAlias(int userId, int generationId)395 private String getDecryptAlias(int userId, int generationId) { 396 return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX; 397 } 398 399 /** 400 * Sets the current generation ID to {@code generationId}. 401 * @throws IOException if there was an issue with local database update. 402 */ setGenerationId(int userId, int generationId)403 private void setGenerationId(int userId, int generationId) throws IOException { 404 mDatabase.setPlatformKeyGenerationId(userId, generationId); 405 } 406 407 /** 408 * Returns {@code true} if a key has been loaded with the given {@code generationId} into 409 * AndroidKeyStore. 410 * 411 * @throws KeyStoreException if there was an error checking AndroidKeyStore. 412 */ isKeyLoaded(int userId, int generationId)413 private boolean isKeyLoaded(int userId, int generationId) throws KeyStoreException { 414 return mKeyStore.containsAlias(getEncryptAlias(userId, generationId)) 415 && mKeyStore.containsAlias(getDecryptAlias(userId, generationId)); 416 } 417 418 @VisibleForTesting getGateKeeperService()419 IGateKeeperService getGateKeeperService() { 420 return GateKeeper.getService(); 421 } 422 423 /** 424 * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given 425 * {@code generationId} determining its aliases. 426 * 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}. 432 */ generateAndLoadKey(int userId, int generationId)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(); 440 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); 454 return; 455 } 456 decryptionKeyProtection 457 .setBoundToSpecificSecureUserId(secureUserId) 458 // Ignore caller uid which always belongs to the primary profile. 459 .setCriticalToDeviceEncryption(true); 460 } 461 // Store decryption key first since it is more likely to fail. 462 mKeyStore.setEntry( 463 decryptAlias, 464 new KeyStore.SecretKeyEntry(secretKey), 465 decryptionKeyProtection.build()); 466 mKeyStore.setEntry( 467 encryptAlias, 468 new KeyStore.SecretKeyEntry(secretKey), 469 new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT) 470 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 471 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 472 .build()); 473 474 setGenerationId(userId, generationId); 475 } 476 477 /** 478 * Generates a new 256-bit AES key, in software. 479 * 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. 483 */ generateAesKey()484 private static SecretKey generateAesKey() throws NoSuchAlgorithmException { 485 KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); 486 keyGenerator.init(KEY_SIZE_BITS); 487 return keyGenerator.generateKey(); 488 } 489 490 /** 491 * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked 492 * {@link KeyStore#load(KeyStore.LoadStoreParameter)}. 493 * 494 * @throws KeyStoreException if there was a problem getting or initializing the key store. 495 */ getAndLoadAndroidKeyStore()496 private static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException { 497 KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER); 498 try { 499 keyStore.load(/*param=*/ null); 500 } catch (CertificateException | IOException | NoSuchAlgorithmException e) { 501 // Should never happen. 502 throw new KeyStoreException("Unable to load keystore.", e); 503 } 504 return keyStore; 505 } 506 507 } 508