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