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