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