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