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.security.keystore.AndroidKeyStoreSecretKey; 22 import android.security.keystore.KeyProperties; 23 import android.security.keystore.KeyProtection; 24 import android.util.Log; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; 28 29 import java.io.IOException; 30 import java.security.KeyStore; 31 import java.security.KeyStoreException; 32 import java.security.NoSuchAlgorithmException; 33 import java.security.UnrecoverableKeyException; 34 import java.security.cert.CertificateException; 35 import java.util.Locale; 36 37 import javax.crypto.KeyGenerator; 38 import javax.crypto.SecretKey; 39 import javax.security.auth.DestroyFailedException; 40 41 /** 42 * Manages creating and checking the validity of the platform key. 43 * 44 * <p>The platform key is used to wrap the material of recoverable keys before persisting them to 45 * disk. It is also used to decrypt the same keys on a screen unlock, before re-wrapping them with 46 * a recovery key and syncing them with remote storage. 47 * 48 * <p>Each platform key has two entries in AndroidKeyStore: 49 * 50 * <ul> 51 * <li>Encrypt entry - this entry enables the root user to at any time encrypt. 52 * <li>Decrypt entry - this entry enables the root user to decrypt only after recent user 53 * authentication, i.e., within 15 seconds after a screen unlock. 54 * </ul> 55 * 56 * <p>Both entries are enabled only for AES/GCM/NoPadding Cipher algorithm. 57 * 58 * @hide 59 */ 60 public class PlatformKeyManager { 61 private static final String TAG = "PlatformKeyManager"; 62 63 private static final String KEY_ALGORITHM = "AES"; 64 private static final int KEY_SIZE_BITS = 256; 65 private static final String KEY_ALIAS_PREFIX = 66 "com.android.server.locksettings.recoverablekeystore/platform/"; 67 private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt"; 68 private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt"; 69 private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15; 70 71 private final Context mContext; 72 private final KeyStoreProxy mKeyStore; 73 private final RecoverableKeyStoreDb mDatabase; 74 75 private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; 76 77 /** 78 * A new instance operating on behalf of {@code userId}, storing its prefs in the location 79 * defined by {@code context}. 80 * 81 * @param context This should be the context of the RecoverableKeyStoreLoader service. 82 * @throws KeyStoreException if failed to initialize AndroidKeyStore. 83 * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. 84 * @throws SecurityException if the caller does not have permission to write to /data/system. 85 * 86 * @hide 87 */ 88 public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database) 89 throws KeyStoreException, NoSuchAlgorithmException { 90 return new PlatformKeyManager( 91 context.getApplicationContext(), 92 new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()), 93 database); 94 } 95 96 @VisibleForTesting 97 PlatformKeyManager( 98 Context context, 99 KeyStoreProxy keyStore, 100 RecoverableKeyStoreDb database) { 101 mKeyStore = keyStore; 102 mContext = context; 103 mDatabase = database; 104 } 105 106 /** 107 * Returns the current generation ID of the platform key. This increments whenever a platform 108 * key has to be replaced. (e.g., because the user has removed and then re-added their lock 109 * screen). Returns -1 if no key has been generated yet. 110 * 111 * @param userId The ID of the user to whose lock screen the platform key must be bound. 112 * 113 * @hide 114 */ 115 public int getGenerationId(int userId) { 116 return mDatabase.getPlatformKeyGenerationId(userId); 117 } 118 119 /** 120 * Returns {@code true} if the platform key is available. A platform key won't be available if 121 * the user has not set up a lock screen. 122 * 123 * @param userId The ID of the user to whose lock screen the platform key must be bound. 124 * 125 * @hide 126 */ 127 public boolean isAvailable(int userId) { 128 return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId); 129 } 130 131 /** 132 * Removes the platform key from Android KeyStore. 133 * It is triggered when user disables lock screen. 134 * 135 * @param userId The ID of the user to whose lock screen the platform key must be bound. 136 * @param generationId Generation id. 137 * 138 * @hide 139 */ 140 public void invalidatePlatformKey(int userId, int generationId) { 141 if (generationId != -1) { 142 try { 143 mKeyStore.deleteEntry(getEncryptAlias(userId, generationId)); 144 mKeyStore.deleteEntry(getDecryptAlias(userId, generationId)); 145 } catch (KeyStoreException e) { 146 // Ignore failed attempt to delete key. 147 } 148 } 149 } 150 151 /** 152 * Generates a new key and increments the generation ID. Should be invoked if the platform key 153 * is corrupted and needs to be rotated. 154 * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}. 155 * 156 * @param userId The ID of the user to whose lock screen the platform key must be bound. 157 * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. 158 * @throws KeyStoreException if there is an error in AndroidKeyStore. 159 * @throws InsecureUserException if the user does not have a lock screen set. 160 * @throws IOException if there was an issue with local database update. 161 * 162 * @hide 163 */ 164 @VisibleForTesting 165 void regenerate(int userId) 166 throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException { 167 if (!isAvailable(userId)) { 168 throw new InsecureUserException(String.format( 169 Locale.US, "%d does not have a lock screen set.", userId)); 170 } 171 172 int generationId = getGenerationId(userId); 173 int nextId; 174 if (generationId == -1) { 175 nextId = 1; 176 } else { 177 invalidatePlatformKey(userId, generationId); 178 nextId = generationId + 1; 179 } 180 generateAndLoadKey(userId, nextId); 181 } 182 183 /** 184 * Returns the platform key used for encryption. 185 * Tries to regenerate key one time if it is permanently invalid. 186 * 187 * @param userId The ID of the user to whose lock screen the platform key must be bound. 188 * @throws KeyStoreException if there was an AndroidKeyStore error. 189 * @throws UnrecoverableKeyException if the key could not be recovered. 190 * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. 191 * @throws InsecureUserException if the user does not have a lock screen set. 192 * @throws IOException if there was an issue with local database update. 193 * 194 * @hide 195 */ 196 public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException, 197 UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException { 198 init(userId); 199 try { 200 // Try to see if the decryption key is still accessible before using the encryption key. 201 // The auth-bound decryption will be unrecoverable if the screen lock is disabled. 202 getDecryptKeyInternal(userId); 203 return getEncryptKeyInternal(userId); 204 } catch (UnrecoverableKeyException e) { 205 Log.i(TAG, String.format(Locale.US, 206 "Regenerating permanently invalid Platform key for user %d.", 207 userId)); 208 regenerate(userId); 209 return getEncryptKeyInternal(userId); 210 } 211 } 212 213 /** 214 * Returns the platform key used for encryption. 215 * 216 * @param userId The ID of the user to whose lock screen the platform key must be bound. 217 * @throws KeyStoreException if there was an AndroidKeyStore error. 218 * @throws UnrecoverableKeyException if the key could not be recovered. 219 * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. 220 * @throws InsecureUserException if the user does not have a lock screen set. 221 * 222 * @hide 223 */ 224 private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException, 225 UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { 226 int generationId = getGenerationId(userId); 227 String alias = getEncryptAlias(userId, generationId); 228 if (!isKeyLoaded(userId, generationId)) { 229 throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias); 230 } 231 AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( 232 alias, /*password=*/ null); 233 return new PlatformEncryptionKey(generationId, key); 234 } 235 236 /** 237 * Returns the platform key used for decryption. Only works after a recent screen unlock. 238 * Tries to regenerate key one time if it is permanently invalid. 239 * 240 * @param userId The ID of the user to whose lock screen the platform key must be bound. 241 * @throws KeyStoreException if there was an AndroidKeyStore error. 242 * @throws UnrecoverableKeyException if the key could not be recovered. 243 * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. 244 * @throws InsecureUserException if the user does not have a lock screen set. 245 * @throws IOException if there was an issue with local database update. 246 * 247 * @hide 248 */ 249 public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException, 250 UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException { 251 init(userId); 252 try { 253 return getDecryptKeyInternal(userId); 254 } catch (UnrecoverableKeyException e) { 255 Log.i(TAG, String.format(Locale.US, 256 "Regenerating permanently invalid Platform key for user %d.", 257 userId)); 258 regenerate(userId); 259 return getDecryptKeyInternal(userId); 260 } 261 } 262 263 /** 264 * Returns the platform key used for decryption. Only works after a recent screen unlock. 265 * 266 * @param userId The ID of the user to whose lock screen the platform key must be bound. 267 * @throws KeyStoreException if there was an AndroidKeyStore error. 268 * @throws UnrecoverableKeyException if the key could not be recovered. 269 * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. 270 * @throws InsecureUserException if the user does not have a lock screen set. 271 * 272 * @hide 273 */ 274 private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException, 275 UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { 276 int generationId = getGenerationId(userId); 277 String alias = getDecryptAlias(userId, generationId); 278 if (!isKeyLoaded(userId, generationId)) { 279 throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias); 280 } 281 AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( 282 alias, /*password=*/ null); 283 return new PlatformDecryptionKey(generationId, key); 284 } 285 286 /** 287 * Initializes the class. If there is no current platform key, and the user has a lock screen 288 * set, will create the platform key and set the generation ID. 289 * 290 * @param userId The ID of the user to whose lock screen the platform key must be bound. 291 * @throws KeyStoreException if there was an error in AndroidKeyStore. 292 * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. 293 * @throws IOException if there was an issue with local database update. 294 * 295 * @hide 296 */ 297 void init(int userId) 298 throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException { 299 if (!isAvailable(userId)) { 300 throw new InsecureUserException(String.format( 301 Locale.US, "%d does not have a lock screen set.", userId)); 302 } 303 304 int generationId = getGenerationId(userId); 305 if (isKeyLoaded(userId, generationId)) { 306 Log.i(TAG, String.format( 307 Locale.US, "Platform key generation %d exists already.", generationId)); 308 return; 309 } 310 if (generationId == -1) { 311 Log.i(TAG, "Generating initial platform key generation ID."); 312 generationId = 1; 313 } else { 314 Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no " 315 + "entry was present in AndroidKeyStore. Generating fresh key.", generationId)); 316 // Have to generate a fresh key, so bump the generation id 317 generationId++; 318 } 319 320 generateAndLoadKey(userId, generationId); 321 } 322 323 /** 324 * Returns the alias of the encryption key with the specific {@code generationId} in the 325 * AndroidKeyStore. 326 * 327 * <p>These IDs look as follows: 328 * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/encrypt} 329 * 330 * @param userId The ID of the user to whose lock screen the platform key must be bound. 331 * @param generationId The generation ID. 332 * @return The alias. 333 */ 334 private String getEncryptAlias(int userId, int generationId) { 335 return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX; 336 } 337 338 /** 339 * Returns the alias of the decryption key with the specific {@code generationId} in the 340 * AndroidKeyStore. 341 * 342 * <p>These IDs look as follows: 343 * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt} 344 * 345 * @param userId The ID of the user to whose lock screen the platform key must be bound. 346 * @param generationId The generation ID. 347 * @return The alias. 348 */ 349 private String getDecryptAlias(int userId, int generationId) { 350 return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX; 351 } 352 353 /** 354 * Sets the current generation ID to {@code generationId}. 355 * @throws IOException if there was an issue with local database update. 356 */ 357 private void setGenerationId(int userId, int generationId) throws IOException { 358 long updatedRows = mDatabase.setPlatformKeyGenerationId(userId, generationId); 359 if (updatedRows < 0) { 360 throw new IOException("Failed to set the platform key in the local DB."); 361 } 362 } 363 364 /** 365 * Returns {@code true} if a key has been loaded with the given {@code generationId} into 366 * AndroidKeyStore. 367 * 368 * @throws KeyStoreException if there was an error checking AndroidKeyStore. 369 */ 370 private boolean isKeyLoaded(int userId, int generationId) throws KeyStoreException { 371 return mKeyStore.containsAlias(getEncryptAlias(userId, generationId)) 372 && mKeyStore.containsAlias(getDecryptAlias(userId, generationId)); 373 } 374 375 /** 376 * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given 377 * {@code generationId} determining its aliases. 378 * 379 * @throws NoSuchAlgorithmException if AES is unavailable. This should never happen, as it is 380 * available since API version 1. 381 * @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore. 382 * @throws IOException if there was an issue with local database update. 383 */ 384 private void generateAndLoadKey(int userId, int generationId) 385 throws NoSuchAlgorithmException, KeyStoreException, IOException { 386 String encryptAlias = getEncryptAlias(userId, generationId); 387 String decryptAlias = getDecryptAlias(userId, generationId); 388 // SecretKey implementation doesn't provide reliable way to destroy the secret 389 // so it may live in memory for some time. 390 SecretKey secretKey = generateAesKey(); 391 392 // Store decryption key first since it is more likely to fail. 393 mKeyStore.setEntry( 394 decryptAlias, 395 new KeyStore.SecretKeyEntry(secretKey), 396 new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) 397 .setUserAuthenticationRequired(true) 398 .setUserAuthenticationValidityDurationSeconds( 399 USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS) 400 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 401 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 402 .setBoundToSpecificSecureUserId(userId) 403 .build()); 404 mKeyStore.setEntry( 405 encryptAlias, 406 new KeyStore.SecretKeyEntry(secretKey), 407 new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT) 408 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 409 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 410 .build()); 411 412 setGenerationId(userId, generationId); 413 } 414 415 /** 416 * Generates a new 256-bit AES key, in software. 417 * 418 * @return The software-generated AES key. 419 * @throws NoSuchAlgorithmException if AES key generation is not available. This should never 420 * happen, as AES has been supported since API level 1. 421 */ 422 private static SecretKey generateAesKey() throws NoSuchAlgorithmException { 423 KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); 424 keyGenerator.init(KEY_SIZE_BITS); 425 return keyGenerator.generateKey(); 426 } 427 428 /** 429 * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked 430 * {@link KeyStore#load(KeyStore.LoadStoreParameter)}. 431 * 432 * @throws KeyStoreException if there was a problem getting or initializing the key store. 433 */ 434 private static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException { 435 KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER); 436 try { 437 keyStore.load(/*param=*/ null); 438 } catch (CertificateException | IOException | NoSuchAlgorithmException e) { 439 // Should never happen. 440 throw new KeyStoreException("Unable to load keystore.", e); 441 } 442 return keyStore; 443 } 444 445 } 446