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 static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT; 20 import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED; 21 import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE; 22 import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER; 23 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE; 24 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT; 25 import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING; 26 import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR; 27 import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED; 28 29 import android.Manifest; 30 import android.annotation.NonNull; 31 import android.annotation.Nullable; 32 import android.app.PendingIntent; 33 import android.content.Context; 34 import android.os.Binder; 35 import android.os.RemoteException; 36 import android.os.ServiceSpecificException; 37 import android.os.UserHandle; 38 import android.security.KeyStore; 39 import android.security.keystore.recovery.KeyChainProtectionParams; 40 import android.security.keystore.recovery.KeyChainSnapshot; 41 import android.security.keystore.recovery.RecoveryCertPath; 42 import android.security.keystore.recovery.RecoveryController; 43 import android.security.keystore.recovery.WrappedApplicationKey; 44 import android.util.ArrayMap; 45 import android.util.Log; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.util.HexDump; 49 import com.android.internal.util.Preconditions; 50 import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException; 51 import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils; 52 import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException; 53 import com.android.server.locksettings.recoverablekeystore.certificate.CertXml; 54 import com.android.server.locksettings.recoverablekeystore.certificate.SigXml; 55 import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; 56 import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager; 57 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; 58 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; 59 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; 60 61 import java.io.IOException; 62 import java.security.InvalidKeyException; 63 import java.security.KeyStoreException; 64 import java.security.NoSuchAlgorithmException; 65 import java.security.PublicKey; 66 import java.security.SecureRandom; 67 import java.security.UnrecoverableKeyException; 68 import java.security.cert.CertPath; 69 import java.security.cert.CertificateEncodingException; 70 import java.security.cert.CertificateException; 71 import java.security.cert.X509Certificate; 72 import java.security.spec.InvalidKeySpecException; 73 import java.util.Arrays; 74 import java.util.HashMap; 75 import java.util.List; 76 import java.util.Locale; 77 import java.util.Map; 78 import java.util.concurrent.ExecutorService; 79 import java.util.concurrent.Executors; 80 81 import javax.crypto.AEADBadTagException; 82 83 /** 84 * Class with {@link RecoveryController} API implementation and internal methods to interact 85 * with {@code LockSettingsService}. 86 * 87 * @hide 88 */ 89 public class RecoverableKeyStoreManager { 90 private static final String TAG = "RecoverableKeyStoreMgr"; 91 92 private static RecoverableKeyStoreManager mInstance; 93 94 private final Context mContext; 95 private final RecoverableKeyStoreDb mDatabase; 96 private final RecoverySessionStorage mRecoverySessionStorage; 97 private final ExecutorService mExecutorService; 98 private final RecoverySnapshotListenersStorage mListenersStorage; 99 private final RecoverableKeyGenerator mRecoverableKeyGenerator; 100 private final RecoverySnapshotStorage mSnapshotStorage; 101 private final PlatformKeyManager mPlatformKeyManager; 102 private final ApplicationKeyStorage mApplicationKeyStorage; 103 private final TestOnlyInsecureCertificateHelper mTestCertHelper; 104 private final CleanupManager mCleanupManager; 105 106 /** 107 * Returns a new or existing instance. 108 * 109 * @hide 110 */ 111 public static synchronized RecoverableKeyStoreManager getInstance(Context context, KeyStore keystore)112 getInstance(Context context, KeyStore keystore) { 113 if (mInstance == null) { 114 RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context); 115 PlatformKeyManager platformKeyManager; 116 ApplicationKeyStorage applicationKeyStorage; 117 try { 118 platformKeyManager = PlatformKeyManager.getInstance(context, db); 119 applicationKeyStorage = ApplicationKeyStorage.getInstance(keystore); 120 } catch (NoSuchAlgorithmException e) { 121 // Impossible: all algorithms must be supported by AOSP 122 throw new RuntimeException(e); 123 } catch (KeyStoreException e) { 124 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 125 } 126 127 RecoverySnapshotStorage snapshotStorage = 128 RecoverySnapshotStorage.newInstance(); 129 CleanupManager cleanupManager = CleanupManager.getInstance( 130 context.getApplicationContext(), 131 snapshotStorage, 132 db, 133 applicationKeyStorage); 134 mInstance = new RecoverableKeyStoreManager( 135 context.getApplicationContext(), 136 db, 137 new RecoverySessionStorage(), 138 Executors.newSingleThreadExecutor(), 139 snapshotStorage, 140 new RecoverySnapshotListenersStorage(), 141 platformKeyManager, 142 applicationKeyStorage, 143 new TestOnlyInsecureCertificateHelper(), 144 cleanupManager); 145 } 146 return mInstance; 147 } 148 149 @VisibleForTesting RecoverableKeyStoreManager( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage, ExecutorService executorService, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage, PlatformKeyManager platformKeyManager, ApplicationKeyStorage applicationKeyStorage, TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper, CleanupManager cleanupManager)150 RecoverableKeyStoreManager( 151 Context context, 152 RecoverableKeyStoreDb recoverableKeyStoreDb, 153 RecoverySessionStorage recoverySessionStorage, 154 ExecutorService executorService, 155 RecoverySnapshotStorage snapshotStorage, 156 RecoverySnapshotListenersStorage listenersStorage, 157 PlatformKeyManager platformKeyManager, 158 ApplicationKeyStorage applicationKeyStorage, 159 TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper, 160 CleanupManager cleanupManager) { 161 mContext = context; 162 mDatabase = recoverableKeyStoreDb; 163 mRecoverySessionStorage = recoverySessionStorage; 164 mExecutorService = executorService; 165 mListenersStorage = listenersStorage; 166 mSnapshotStorage = snapshotStorage; 167 mPlatformKeyManager = platformKeyManager; 168 mApplicationKeyStorage = applicationKeyStorage; 169 mTestCertHelper = testOnlyInsecureCertificateHelper; 170 mCleanupManager = cleanupManager; 171 // Clears data for removed users. 172 mCleanupManager.verifyKnownUsers(); 173 try { 174 mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase); 175 } catch (NoSuchAlgorithmException e) { 176 Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e); 177 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 178 } 179 } 180 181 /** 182 * Used by {@link #initRecoveryServiceWithSigFile(String, byte[], byte[])}. 183 */ 184 @VisibleForTesting initRecoveryService( @onNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile)185 void initRecoveryService( 186 @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile) 187 throws RemoteException { 188 checkRecoverKeyStorePermission(); 189 int userId = UserHandle.getCallingUserId(); 190 int uid = Binder.getCallingUid(); 191 192 rootCertificateAlias 193 = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); 194 if (!mTestCertHelper.isValidRootCertificateAlias(rootCertificateAlias)) { 195 throw new ServiceSpecificException( 196 ERROR_INVALID_CERTIFICATE, "Invalid root certificate alias"); 197 } 198 // Always set active alias to the argument of the last call to initRecoveryService method, 199 // even if cert file is incorrect. 200 String activeRootAlias = mDatabase.getActiveRootOfTrust(userId, uid); 201 if (activeRootAlias == null) { 202 Log.d(TAG, "Root of trust for recovery agent + " + uid 203 + " is assigned for the first time to " + rootCertificateAlias); 204 } else if (!activeRootAlias.equals(rootCertificateAlias)) { 205 Log.i(TAG, "Root of trust for recovery agent " + uid + " is changed to " 206 + rootCertificateAlias + " from " + activeRootAlias); 207 } 208 long updatedRows = mDatabase.setActiveRootOfTrust(userId, uid, rootCertificateAlias); 209 if (updatedRows < 0) { 210 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, 211 "Failed to set the root of trust in the local DB."); 212 } 213 214 CertXml certXml; 215 try { 216 certXml = CertXml.parse(recoveryServiceCertFile); 217 } catch (CertParsingException e) { 218 Log.d(TAG, "Failed to parse the input as a cert file: " + HexDump.toHexString( 219 recoveryServiceCertFile)); 220 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); 221 } 222 223 // Check serial number 224 long newSerial = certXml.getSerial(); 225 Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid, rootCertificateAlias); 226 if (oldSerial != null && oldSerial >= newSerial 227 && !mTestCertHelper.isTestOnlyCertificateAlias(rootCertificateAlias)) { 228 if (oldSerial == newSerial) { 229 Log.i(TAG, "The cert file serial number is the same, so skip updating."); 230 } else { 231 Log.e(TAG, "The cert file serial number is older than the one in database."); 232 throw new ServiceSpecificException(ERROR_DOWNGRADE_CERTIFICATE, 233 "The cert file serial number is older than the one in database."); 234 } 235 return; 236 } 237 Log.i(TAG, "Updating the certificate with the new serial number " + newSerial); 238 239 // Randomly choose and validate an endpoint certificate from the list 240 CertPath certPath; 241 X509Certificate rootCert = 242 mTestCertHelper.getRootCertificate(rootCertificateAlias); 243 try { 244 Log.d(TAG, "Getting and validating a random endpoint certificate"); 245 certPath = certXml.getRandomEndpointCert(rootCert); 246 } catch (CertValidationException e) { 247 Log.e(TAG, "Invalid endpoint cert", e); 248 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); 249 } 250 251 // Save the chosen and validated certificate into database 252 try { 253 Log.d(TAG, "Saving the randomly chosen endpoint certificate to database"); 254 long updatedCertPathRows = mDatabase.setRecoveryServiceCertPath(userId, uid, 255 rootCertificateAlias, certPath); 256 if (updatedCertPathRows > 0) { 257 long updatedCertSerialRows = mDatabase.setRecoveryServiceCertSerial(userId, uid, 258 rootCertificateAlias, newSerial); 259 if (updatedCertSerialRows < 0) { 260 // Ideally CertPath and CertSerial should be updated together in single 261 // transaction, but since their mismatch doesn't create many problems 262 // extra complexity is unnecessary. 263 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, 264 "Failed to set the certificate serial number in the local DB."); 265 } 266 if (mDatabase.getSnapshotVersion(userId, uid) != null) { 267 mDatabase.setShouldCreateSnapshot(userId, uid, true); 268 Log.i(TAG, "This is a certificate change. Snapshot must be updated"); 269 } else { 270 Log.i(TAG, "This is a certificate change. Snapshot didn't exist"); 271 } 272 long updatedCounterIdRows = 273 mDatabase.setCounterId(userId, uid, new SecureRandom().nextLong()); 274 if (updatedCounterIdRows < 0) { 275 Log.e(TAG, "Failed to set the counter id in the local DB."); 276 } 277 } else if (updatedCertPathRows < 0) { 278 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, 279 "Failed to set the certificate path in the local DB."); 280 } 281 } catch (CertificateEncodingException e) { 282 Log.e(TAG, "Failed to encode CertPath", e); 283 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); 284 } 285 } 286 287 /** 288 * Initializes the recovery service with the two files {@code recoveryServiceCertFile} and 289 * {@code recoveryServiceSigFile}. 290 * 291 * @param rootCertificateAlias the alias for the root certificate that is used for validating 292 * the recovery service certificates. 293 * @param recoveryServiceCertFile the content of the XML file containing a list of certificates 294 * for the recovery service. 295 * @param recoveryServiceSigFile the content of the XML file containing the public-key signature 296 * over the entire content of {@code recoveryServiceCertFile}. 297 */ initRecoveryServiceWithSigFile( @onNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile, @NonNull byte[] recoveryServiceSigFile)298 public void initRecoveryServiceWithSigFile( 299 @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile, 300 @NonNull byte[] recoveryServiceSigFile) 301 throws RemoteException { 302 checkRecoverKeyStorePermission(); 303 rootCertificateAlias = 304 mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); 305 Preconditions.checkNotNull(recoveryServiceCertFile, "recoveryServiceCertFile is null"); 306 Preconditions.checkNotNull(recoveryServiceSigFile, "recoveryServiceSigFile is null"); 307 308 SigXml sigXml; 309 try { 310 sigXml = SigXml.parse(recoveryServiceSigFile); 311 } catch (CertParsingException e) { 312 Log.d(TAG, "Failed to parse the sig file: " + HexDump.toHexString( 313 recoveryServiceSigFile)); 314 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); 315 } 316 317 X509Certificate rootCert = 318 mTestCertHelper.getRootCertificate(rootCertificateAlias); 319 try { 320 sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile); 321 } catch (CertValidationException e) { 322 Log.d(TAG, "The signature over the cert file is invalid." 323 + " Cert: " + HexDump.toHexString(recoveryServiceCertFile) 324 + " Sig: " + HexDump.toHexString(recoveryServiceSigFile)); 325 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); 326 } 327 328 initRecoveryService(rootCertificateAlias, recoveryServiceCertFile); 329 } 330 331 /** 332 * Gets all data necessary to recover application keys on new device. 333 * 334 * @return KeyChain Snapshot. 335 * @throws ServiceSpecificException if no snapshot is pending. 336 * @hide 337 */ getKeyChainSnapshot()338 public @NonNull KeyChainSnapshot getKeyChainSnapshot() 339 throws RemoteException { 340 checkRecoverKeyStorePermission(); 341 int uid = Binder.getCallingUid(); 342 KeyChainSnapshot snapshot = mSnapshotStorage.get(uid); 343 if (snapshot == null) { 344 throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING); 345 } 346 return snapshot; 347 } 348 setSnapshotCreatedPendingIntent(@ullable PendingIntent intent)349 public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) 350 throws RemoteException { 351 checkRecoverKeyStorePermission(); 352 int uid = Binder.getCallingUid(); 353 mListenersStorage.setSnapshotListener(uid, intent); 354 } 355 356 /** 357 * Set the server params for the user's key chain. This is used to uniquely identify a key 358 * chain. Along with the counter ID, it is used to uniquely identify an instance of a vault. 359 */ setServerParams(@onNull byte[] serverParams)360 public void setServerParams(@NonNull byte[] serverParams) throws RemoteException { 361 checkRecoverKeyStorePermission(); 362 int userId = UserHandle.getCallingUserId(); 363 int uid = Binder.getCallingUid(); 364 365 byte[] currentServerParams = mDatabase.getServerParams(userId, uid); 366 367 if (Arrays.equals(serverParams, currentServerParams)) { 368 Log.v(TAG, "Not updating server params - same as old value."); 369 return; 370 } 371 372 long updatedRows = mDatabase.setServerParams(userId, uid, serverParams); 373 if (updatedRows < 0) { 374 throw new ServiceSpecificException( 375 ERROR_SERVICE_INTERNAL_ERROR, "Database failure trying to set server params."); 376 } 377 378 if (currentServerParams == null) { 379 Log.i(TAG, "Initialized server params."); 380 return; 381 } 382 383 if (mDatabase.getSnapshotVersion(userId, uid) != null) { 384 mDatabase.setShouldCreateSnapshot(userId, uid, true); 385 Log.i(TAG, "Updated server params. Snapshot must be updated"); 386 } else { 387 Log.i(TAG, "Updated server params. Snapshot didn't exist"); 388 } 389 } 390 391 /** 392 * Sets the recovery status of key with {@code alias} to {@code status}. 393 */ setRecoveryStatus(@onNull String alias, int status)394 public void setRecoveryStatus(@NonNull String alias, int status) throws RemoteException { 395 checkRecoverKeyStorePermission(); 396 Preconditions.checkNotNull(alias, "alias is null"); 397 long updatedRows = mDatabase.setRecoveryStatus(Binder.getCallingUid(), alias, status); 398 if (updatedRows < 0) { 399 throw new ServiceSpecificException( 400 ERROR_SERVICE_INTERNAL_ERROR, 401 "Failed to set the key recovery status in the local DB."); 402 } 403 } 404 405 /** 406 * Returns recovery statuses for all keys belonging to the calling uid. 407 * 408 * @return {@link Map} from key alias to recovery status. Recovery status is one of 409 * {@link RecoveryController#RECOVERY_STATUS_SYNCED}, 410 * {@link RecoveryController#RECOVERY_STATUS_SYNC_IN_PROGRESS} or 411 * {@link RecoveryController#RECOVERY_STATUS_PERMANENT_FAILURE}. 412 */ getRecoveryStatus()413 public @NonNull Map<String, Integer> getRecoveryStatus() throws RemoteException { 414 checkRecoverKeyStorePermission(); 415 return mDatabase.getStatusForAllKeys(Binder.getCallingUid()); 416 } 417 418 /** 419 * Sets recovery secrets list used by all recovery agents for given {@code userId} 420 * 421 * @hide 422 */ setRecoverySecretTypes( @onNull @eyChainProtectionParams.UserSecretType int[] secretTypes)423 public void setRecoverySecretTypes( 424 @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes) 425 throws RemoteException { 426 checkRecoverKeyStorePermission(); 427 Preconditions.checkNotNull(secretTypes, "secretTypes is null"); 428 int userId = UserHandle.getCallingUserId(); 429 int uid = Binder.getCallingUid(); 430 431 int[] currentSecretTypes = mDatabase.getRecoverySecretTypes(userId, uid); 432 if (Arrays.equals(secretTypes, currentSecretTypes)) { 433 Log.v(TAG, "Not updating secret types - same as old value."); 434 return; 435 } 436 437 long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes); 438 if (updatedRows < 0) { 439 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, 440 "Database error trying to set secret types."); 441 } 442 443 if (currentSecretTypes.length == 0) { 444 Log.i(TAG, "Initialized secret types."); 445 return; 446 } 447 448 Log.i(TAG, "Updated secret types. Snapshot pending."); 449 if (mDatabase.getSnapshotVersion(userId, uid) != null) { 450 mDatabase.setShouldCreateSnapshot(userId, uid, true); 451 Log.i(TAG, "Updated secret types. Snapshot must be updated"); 452 } else { 453 Log.i(TAG, "Updated secret types. Snapshot didn't exist"); 454 } 455 } 456 457 /** 458 * Gets secret types necessary to create Recovery Data. 459 * 460 * @return secret types 461 * @hide 462 */ getRecoverySecretTypes()463 public @NonNull int[] getRecoverySecretTypes() throws RemoteException { 464 checkRecoverKeyStorePermission(); 465 return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(), 466 Binder.getCallingUid()); 467 } 468 469 /** 470 * Initializes recovery session given the X509-encoded public key of the recovery service. 471 * 472 * @param sessionId A unique ID to identify the recovery session. 473 * @param verifierPublicKey X509-encoded public key. 474 * @param vaultParams Additional params associated with vault. 475 * @param vaultChallenge Challenge issued by vault service. 476 * @param secrets Lock-screen hashes. For now only a single secret is supported. 477 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service. 478 * @deprecated Use {@link #startRecoverySessionWithCertPath(String, String, RecoveryCertPath, 479 * byte[], byte[], List)} instead. 480 * 481 * @hide 482 */ 483 @VisibleForTesting startRecoverySession( @onNull String sessionId, @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, @NonNull List<KeyChainProtectionParams> secrets)484 @NonNull byte[] startRecoverySession( 485 @NonNull String sessionId, 486 @NonNull byte[] verifierPublicKey, 487 @NonNull byte[] vaultParams, 488 @NonNull byte[] vaultChallenge, 489 @NonNull List<KeyChainProtectionParams> secrets) 490 throws RemoteException { 491 checkRecoverKeyStorePermission(); 492 int uid = Binder.getCallingUid(); 493 494 if (secrets.size() != 1) { 495 throw new UnsupportedOperationException( 496 "Only a single KeyChainProtectionParams is supported"); 497 } 498 499 PublicKey publicKey; 500 try { 501 publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey); 502 } catch (InvalidKeySpecException e) { 503 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); 504 } 505 // The raw public key bytes contained in vaultParams must match the ones given in 506 // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned 507 // by the original recovery service. 508 if (!publicKeysMatch(publicKey, vaultParams)) { 509 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, 510 "The public keys given in verifierPublicKey and vaultParams do not match."); 511 } 512 513 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); 514 byte[] kfHash = secrets.get(0).getSecret(); 515 mRecoverySessionStorage.add( 516 uid, 517 new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams)); 518 519 Log.i(TAG, "Received VaultParams for recovery: " + HexDump.toHexString(vaultParams)); 520 try { 521 byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash); 522 return KeySyncUtils.encryptRecoveryClaim( 523 publicKey, 524 vaultParams, 525 vaultChallenge, 526 thmKfHash, 527 keyClaimant); 528 } catch (NoSuchAlgorithmException e) { 529 Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e); 530 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 531 } catch (InvalidKeyException e) { 532 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); 533 } 534 } 535 536 /** 537 * Initializes recovery session given the certificate path of the recovery service. 538 * 539 * @param sessionId A unique ID to identify the recovery session. 540 * @param verifierCertPath The certificate path of the recovery service. 541 * @param vaultParams Additional params associated with vault. 542 * @param vaultChallenge Challenge issued by vault service. 543 * @param secrets Lock-screen hashes. For now only a single secret is supported. 544 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service. 545 * 546 * @hide 547 */ startRecoverySessionWithCertPath( @onNull String sessionId, @NonNull String rootCertificateAlias, @NonNull RecoveryCertPath verifierCertPath, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, @NonNull List<KeyChainProtectionParams> secrets)548 public @NonNull byte[] startRecoverySessionWithCertPath( 549 @NonNull String sessionId, 550 @NonNull String rootCertificateAlias, 551 @NonNull RecoveryCertPath verifierCertPath, 552 @NonNull byte[] vaultParams, 553 @NonNull byte[] vaultChallenge, 554 @NonNull List<KeyChainProtectionParams> secrets) 555 throws RemoteException { 556 checkRecoverKeyStorePermission(); 557 rootCertificateAlias = 558 mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias); 559 Preconditions.checkNotNull(sessionId, "invalid session"); 560 Preconditions.checkNotNull(verifierCertPath, "verifierCertPath is null"); 561 Preconditions.checkNotNull(vaultParams, "vaultParams is null"); 562 Preconditions.checkNotNull(vaultChallenge, "vaultChallenge is null"); 563 Preconditions.checkNotNull(secrets, "secrets is null"); 564 CertPath certPath; 565 try { 566 certPath = verifierCertPath.getCertPath(); 567 } catch (CertificateException e) { 568 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage()); 569 } 570 571 try { 572 CertUtils.validateCertPath( 573 mTestCertHelper.getRootCertificate(rootCertificateAlias), certPath); 574 } catch (CertValidationException e) { 575 Log.e(TAG, "Failed to validate the given cert path", e); 576 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); 577 } 578 579 byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded(); 580 if (verifierPublicKey == null) { 581 Log.e(TAG, "Failed to encode verifierPublicKey"); 582 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, 583 "Failed to encode verifierPublicKey"); 584 } 585 586 return startRecoverySession( 587 sessionId, verifierPublicKey, vaultParams, vaultChallenge, secrets); 588 } 589 590 /** 591 * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault 592 * service. 593 * 594 * @param sessionId The session ID used to generate the claim. See 595 * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}. 596 * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault 597 * service. 598 * @param applicationKeys The encrypted key blobs returned by the remote vault service. These 599 * were wrapped with the recovery key. 600 * @throws RemoteException if an error occurred recovering the keys. 601 */ recoverKeyChainSnapshot( @onNull String sessionId, @NonNull byte[] encryptedRecoveryKey, @NonNull List<WrappedApplicationKey> applicationKeys)602 public @NonNull Map<String, String> recoverKeyChainSnapshot( 603 @NonNull String sessionId, 604 @NonNull byte[] encryptedRecoveryKey, 605 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException { 606 checkRecoverKeyStorePermission(); 607 int userId = UserHandle.getCallingUserId(); 608 int uid = Binder.getCallingUid(); 609 RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId); 610 if (sessionEntry == null) { 611 throw new ServiceSpecificException(ERROR_SESSION_EXPIRED, 612 String.format(Locale.US, 613 "Application uid=%d does not have pending session '%s'", 614 uid, 615 sessionId)); 616 } 617 618 try { 619 byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey); 620 Map<String, byte[]> keysByAlias = recoverApplicationKeys(recoveryKey, 621 applicationKeys); 622 return importKeyMaterials(userId, uid, keysByAlias); 623 } catch (KeyStoreException e) { 624 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 625 } finally { 626 sessionEntry.destroy(); 627 mRecoverySessionStorage.remove(uid); 628 } 629 } 630 631 /** 632 * Imports the key materials, returning a map from alias to grant alias for the calling user. 633 * 634 * @param userId The calling user ID. 635 * @param uid The calling uid. 636 * @param keysByAlias The key materials, keyed by alias. 637 * @throws KeyStoreException if an error occurs importing the key or getting the grant. 638 */ importKeyMaterials( int userId, int uid, Map<String, byte[]> keysByAlias)639 private @NonNull Map<String, String> importKeyMaterials( 640 int userId, int uid, Map<String, byte[]> keysByAlias) 641 throws KeyStoreException { 642 ArrayMap<String, String> grantAliasesByAlias = new ArrayMap<>(keysByAlias.size()); 643 for (String alias : keysByAlias.keySet()) { 644 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keysByAlias.get(alias)); 645 String grantAlias = getAlias(userId, uid, alias); 646 Log.i(TAG, String.format(Locale.US, "Import %s -> %s", alias, grantAlias)); 647 grantAliasesByAlias.put(alias, grantAlias); 648 } 649 return grantAliasesByAlias; 650 } 651 652 /** 653 * Returns an alias for the key. 654 * 655 * @param userId The user ID of the calling process. 656 * @param uid The uid of the calling process. 657 * @param alias The alias of the key. 658 * @return The alias in the calling process's keystore. 659 */ getAlias(int userId, int uid, String alias)660 private @Nullable String getAlias(int userId, int uid, String alias) { 661 return mApplicationKeyStorage.getGrantAlias(userId, uid, alias); 662 } 663 664 /** 665 * Destroys the session with the given {@code sessionId}. 666 */ closeSession(@onNull String sessionId)667 public void closeSession(@NonNull String sessionId) throws RemoteException { 668 checkRecoverKeyStorePermission(); 669 Preconditions.checkNotNull(sessionId, "invalid session"); 670 mRecoverySessionStorage.remove(Binder.getCallingUid(), sessionId); 671 } 672 removeKey(@onNull String alias)673 public void removeKey(@NonNull String alias) throws RemoteException { 674 checkRecoverKeyStorePermission(); 675 Preconditions.checkNotNull(alias, "alias is null"); 676 int uid = Binder.getCallingUid(); 677 int userId = UserHandle.getCallingUserId(); 678 679 boolean wasRemoved = mDatabase.removeKey(uid, alias); 680 if (wasRemoved) { 681 mDatabase.setShouldCreateSnapshot(userId, uid, true); 682 mApplicationKeyStorage.deleteEntry(userId, uid, alias); 683 } 684 } 685 686 /** 687 * Generates a key named {@code alias} in caller's namespace. 688 * The key is stored in system service keystore namespace. 689 * 690 * @param alias the alias provided by caller as a reference to the key. 691 * @return grant alias, which caller can use to access the key. 692 * @throws RemoteException if certain internal errors occur. 693 * 694 * @deprecated Use {@link #generateKeyWithMetadata(String, byte[])} instead. 695 */ 696 @Deprecated generateKey(@onNull String alias)697 public String generateKey(@NonNull String alias) throws RemoteException { 698 return generateKeyWithMetadata(alias, /*metadata=*/ null); 699 } 700 701 /** 702 * Generates a key named {@code alias} with the {@code metadata} in caller's namespace. 703 * The key is stored in system service keystore namespace. 704 * 705 * @param alias the alias provided by caller as a reference to the key. 706 * @param metadata the optional metadata blob that will authenticated (but unencrypted) together 707 * with the key material when the key is uploaded to cloud. 708 * @return grant alias, which caller can use to access the key. 709 * @throws RemoteException if certain internal errors occur. 710 */ generateKeyWithMetadata(@onNull String alias, @Nullable byte[] metadata)711 public String generateKeyWithMetadata(@NonNull String alias, @Nullable byte[] metadata) 712 throws RemoteException { 713 checkRecoverKeyStorePermission(); 714 Preconditions.checkNotNull(alias, "alias is null"); 715 int uid = Binder.getCallingUid(); 716 int userId = UserHandle.getCallingUserId(); 717 718 PlatformEncryptionKey encryptionKey; 719 try { 720 encryptionKey = mPlatformKeyManager.getEncryptKey(userId); 721 } catch (NoSuchAlgorithmException e) { 722 // Impossible: all algorithms must be supported by AOSP 723 throw new RuntimeException(e); 724 } catch (KeyStoreException | UnrecoverableKeyException | IOException e) { 725 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 726 } catch (InsecureUserException e) { 727 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); 728 } 729 730 try { 731 byte[] secretKey = mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, 732 uid, alias, metadata); 733 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey); 734 return getAlias(userId, uid, alias); 735 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) { 736 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 737 } 738 } 739 740 /** 741 * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service 742 * keystore namespace. 743 * 744 * @param alias the alias provided by caller as a reference to the key. 745 * @param keyBytes the raw bytes of the 256-bit AES key. 746 * @return grant alias, which caller can use to access the key. 747 * @throws RemoteException if the given key is invalid or some internal errors occur. 748 * 749 * @deprecated Use {{@link #importKeyWithMetadata(String, byte[], byte[])}} instead. 750 * 751 * @hide 752 */ 753 @Deprecated importKey(@onNull String alias, @NonNull byte[] keyBytes)754 public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes) 755 throws RemoteException { 756 return importKeyWithMetadata(alias, keyBytes, /*metadata=*/ null); 757 } 758 759 /** 760 * Imports a 256-bit AES-GCM key named {@code alias} with the given {@code metadata}. The key is 761 * stored in system service keystore namespace. 762 * 763 * @param alias the alias provided by caller as a reference to the key. 764 * @param keyBytes the raw bytes of the 256-bit AES key. 765 * @param metadata the metadata to be authenticated (but unencrypted) together with the key. 766 * @return grant alias, which caller can use to access the key. 767 * @throws RemoteException if the given key is invalid or some internal errors occur. 768 * 769 * @hide 770 */ importKeyWithMetadata(@onNull String alias, @NonNull byte[] keyBytes, @Nullable byte[] metadata)771 public @Nullable String importKeyWithMetadata(@NonNull String alias, @NonNull byte[] keyBytes, 772 @Nullable byte[] metadata) throws RemoteException { 773 checkRecoverKeyStorePermission(); 774 Preconditions.checkNotNull(alias, "alias is null"); 775 Preconditions.checkNotNull(keyBytes, "keyBytes is null"); 776 if (keyBytes.length != RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE) { 777 Log.e(TAG, "The given key for import doesn't have the required length " 778 + RecoverableKeyGenerator.KEY_SIZE_BITS); 779 throw new ServiceSpecificException(ERROR_INVALID_KEY_FORMAT, 780 "The given key does not contain " + RecoverableKeyGenerator.KEY_SIZE_BITS 781 + " bits."); 782 } 783 784 int uid = Binder.getCallingUid(); 785 int userId = UserHandle.getCallingUserId(); 786 787 PlatformEncryptionKey encryptionKey; 788 try { 789 encryptionKey = mPlatformKeyManager.getEncryptKey(userId); 790 } catch (NoSuchAlgorithmException e) { 791 // Impossible: all algorithms must be supported by AOSP 792 throw new RuntimeException(e); 793 } catch (KeyStoreException | UnrecoverableKeyException | IOException e) { 794 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 795 } catch (InsecureUserException e) { 796 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); 797 } 798 799 try { 800 // Wrap the key by the platform key and store the wrapped key locally 801 mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes, 802 metadata); 803 804 // Import the key to Android KeyStore and get grant 805 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes); 806 return getAlias(userId, uid, alias); 807 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) { 808 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 809 } 810 } 811 812 /** 813 * Gets a key named {@code alias} in caller's namespace. 814 * 815 * @return grant alias, which caller can use to access the key. 816 */ getKey(@onNull String alias)817 public @Nullable String getKey(@NonNull String alias) throws RemoteException { 818 checkRecoverKeyStorePermission(); 819 Preconditions.checkNotNull(alias, "alias is null"); 820 int uid = Binder.getCallingUid(); 821 int userId = UserHandle.getCallingUserId(); 822 return getAlias(userId, uid, alias); 823 } 824 decryptRecoveryKey( RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)825 private byte[] decryptRecoveryKey( 826 RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse) 827 throws RemoteException, ServiceSpecificException { 828 byte[] locallyEncryptedKey; 829 try { 830 locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse( 831 sessionEntry.getKeyClaimant(), 832 sessionEntry.getVaultParams(), 833 encryptedClaimResponse); 834 } catch (InvalidKeyException e) { 835 Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e); 836 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, 837 "Failed to decrypt recovery key " + e.getMessage()); 838 } catch (AEADBadTagException e) { 839 Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e); 840 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, 841 "Failed to decrypt recovery key " + e.getMessage()); 842 } catch (NoSuchAlgorithmException e) { 843 // Should never happen: all the algorithms used are required by AOSP implementations 844 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 845 } 846 847 try { 848 return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey); 849 } catch (InvalidKeyException e) { 850 Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e); 851 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, 852 "Failed to decrypt recovery key " + e.getMessage()); 853 } catch (AEADBadTagException e) { 854 Log.e(TAG, "Got AEADBadTagException during decrypting recovery key", e); 855 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, 856 "Failed to decrypt recovery key " + e.getMessage()); 857 } catch (NoSuchAlgorithmException e) { 858 // Should never happen: all the algorithms used are required by AOSP implementations 859 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 860 } 861 } 862 863 /** 864 * Uses {@code recoveryKey} to decrypt {@code applicationKeys}. 865 * 866 * @return Map from alias to raw key material. 867 * @throws RemoteException if an error occurred decrypting the keys. 868 */ recoverApplicationKeys(@onNull byte[] recoveryKey, @NonNull List<WrappedApplicationKey> applicationKeys)869 private @NonNull Map<String, byte[]> recoverApplicationKeys(@NonNull byte[] recoveryKey, 870 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException { 871 HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>(); 872 for (WrappedApplicationKey applicationKey : applicationKeys) { 873 String alias = applicationKey.getAlias(); 874 byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial(); 875 byte[] keyMetadata = applicationKey.getMetadata(); 876 877 try { 878 byte[] keyMaterial = KeySyncUtils.decryptApplicationKey(recoveryKey, 879 encryptedKeyMaterial, keyMetadata); 880 keyMaterialByAlias.put(alias, keyMaterial); 881 } catch (NoSuchAlgorithmException e) { 882 Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e); 883 throw new ServiceSpecificException( 884 ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); 885 } catch (InvalidKeyException e) { 886 Log.e(TAG, "Got InvalidKeyException during decrypting application key with alias: " 887 + alias, e); 888 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, 889 "Failed to recover key with alias '" + alias + "': " + e.getMessage()); 890 } catch (AEADBadTagException e) { 891 Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: " 892 + alias, e); 893 // Ignore the exception to continue to recover the other application keys. 894 } 895 } 896 if (!applicationKeys.isEmpty() && keyMaterialByAlias.isEmpty()) { 897 Log.e(TAG, "Failed to recover any of the application keys."); 898 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, 899 "Failed to recover any of the application keys."); 900 } 901 return keyMaterialByAlias; 902 } 903 904 /** 905 * This function can only be used inside LockSettingsService. 906 * 907 * @param storedHashType from {@code CredentialHash} 908 * @param credential - unencrypted byte array. Password length should be at most 16 symbols 909 * {@code mPasswordMaxLength} 910 * @param userId for user who just unlocked the device. 911 * @hide 912 */ lockScreenSecretAvailable( int storedHashType, @NonNull byte[] credential, int userId)913 public void lockScreenSecretAvailable( 914 int storedHashType, @NonNull byte[] credential, int userId) { 915 // So as not to block the critical path unlocking the phone, defer to another thread. 916 try { 917 mExecutorService.execute(KeySyncTask.newInstance( 918 mContext, 919 mDatabase, 920 mSnapshotStorage, 921 mListenersStorage, 922 userId, 923 storedHashType, 924 credential, 925 /*credentialUpdated=*/ false)); 926 } catch (NoSuchAlgorithmException e) { 927 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); 928 } catch (KeyStoreException e) { 929 Log.e(TAG, "Key store error encountered during recoverable key sync", e); 930 } catch (InsecureUserException e) { 931 Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e); 932 } 933 } 934 935 /** 936 * This function can only be used inside LockSettingsService. 937 * 938 * @param storedHashType from {@code CredentialHash} 939 * @param credential - unencrypted byte array 940 * @param userId for the user whose lock screen credentials were changed. 941 * @hide 942 */ lockScreenSecretChanged( int storedHashType, @Nullable byte[] credential, int userId)943 public void lockScreenSecretChanged( 944 int storedHashType, 945 @Nullable byte[] credential, 946 int userId) { 947 // So as not to block the critical path unlocking the phone, defer to another thread. 948 try { 949 mExecutorService.execute(KeySyncTask.newInstance( 950 mContext, 951 mDatabase, 952 mSnapshotStorage, 953 mListenersStorage, 954 userId, 955 storedHashType, 956 credential, 957 /*credentialUpdated=*/ true)); 958 } catch (NoSuchAlgorithmException e) { 959 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); 960 } catch (KeyStoreException e) { 961 Log.e(TAG, "Key store error encountered during recoverable key sync", e); 962 } catch (InsecureUserException e) { 963 Log.e(TAG, "InsecureUserException during lock screen secret update", e); 964 } 965 } 966 checkRecoverKeyStorePermission()967 private void checkRecoverKeyStorePermission() { 968 mContext.enforceCallingOrSelfPermission( 969 Manifest.permission.RECOVER_KEYSTORE, 970 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission."); 971 int userId = UserHandle.getCallingUserId(); 972 int uid = Binder.getCallingUid(); 973 mCleanupManager.registerRecoveryAgent(userId, uid); 974 } 975 publicKeysMatch(PublicKey publicKey, byte[] vaultParams)976 private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) { 977 byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey); 978 return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length)); 979 } 980 } 981