RecoveryController.java revision c5ab69469d53ffc5b55e91c5374da8b03dd4661c
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 17package android.security.keystore.recovery; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.annotation.RequiresPermission; 22import android.annotation.SystemApi; 23import android.app.PendingIntent; 24import android.content.Context; 25import android.content.pm.PackageManager.NameNotFoundException; 26import android.os.RemoteException; 27import android.os.ServiceManager; 28import android.os.ServiceSpecificException; 29import android.security.KeyStore; 30import android.security.keystore.AndroidKeyStoreProvider; 31 32import com.android.internal.widget.ILockSettings; 33 34import java.security.Key; 35import java.security.UnrecoverableKeyException; 36import java.security.cert.CertPath; 37import java.security.cert.CertificateException; 38import java.util.ArrayList; 39import java.util.List; 40import java.util.Map; 41 42/** 43 * An assistant for generating {@link javax.crypto.SecretKey} instances that can be recovered by 44 * other Android devices belonging to the user. The exported keychain is protected by the user's 45 * lock screen. 46 * 47 * <p>The RecoveryController must be paired with a recovery agent. The recovery agent is responsible 48 * for transporting the keychain to remote trusted hardware. This hardware must prevent brute force 49 * attempts against the user's lock screen by limiting the number of allowed guesses (to, e.g., 10). 50 * After that number of incorrect guesses, the trusted hardware no longer allows access to the 51 * key chain. 52 * 53 * <p>Only the recovery agent itself is able to create keys, so it is expected that the recovery 54 * agent is itself the system app. 55 * 56 * <p>A recovery agent requires the privileged permission 57 * {@code android.Manifest.permission#RECOVER_KEYSTORE}. 58 * 59 * @hide 60 */ 61@SystemApi 62public class RecoveryController { 63 private static final String TAG = "RecoveryController"; 64 65 /** Key has been successfully synced. */ 66 public static final int RECOVERY_STATUS_SYNCED = 0; 67 /** Waiting for recovery agent to sync the key. */ 68 public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1; 69 /** Key cannot be synced. */ 70 public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3; 71 72 /** 73 * Failed because no snapshot is yet pending to be synced for the user. 74 * 75 * @hide 76 */ 77 public static final int ERROR_NO_SNAPSHOT_PENDING = 21; 78 79 /** 80 * Failed due to an error internal to the recovery service. This is unexpected and indicates 81 * either a problem with the logic in the service, or a problem with a dependency of the 82 * service (such as AndroidKeyStore). 83 * 84 * @hide 85 */ 86 public static final int ERROR_SERVICE_INTERNAL_ERROR = 22; 87 88 /** 89 * Failed because the user does not have a lock screen set. 90 * 91 * @hide 92 */ 93 public static final int ERROR_INSECURE_USER = 23; 94 95 /** 96 * Error thrown when attempting to use a recovery session that has since been closed. 97 * 98 * @hide 99 */ 100 public static final int ERROR_SESSION_EXPIRED = 24; 101 102 /** 103 * Failed because the format of the provided certificate is incorrect, e.g., cannot be decoded 104 * properly or misses necessary fields. 105 * 106 * <p>Note that this is different from {@link #ERROR_INVALID_CERTIFICATE}, which implies the 107 * certificate has a correct format but cannot be validated. 108 * 109 * @hide 110 */ 111 public static final int ERROR_BAD_CERTIFICATE_FORMAT = 25; 112 113 /** 114 * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong, 115 * the data has become corrupted, the data has been tampered with, etc. 116 * 117 * @hide 118 */ 119 public static final int ERROR_DECRYPTION_FAILED = 26; 120 121 /** 122 * Error thrown if the format of a given key is invalid. This might be because the key has a 123 * wrong length, invalid content, etc. 124 * 125 * @hide 126 */ 127 public static final int ERROR_INVALID_KEY_FORMAT = 27; 128 129 /** 130 * Failed because the provided certificate cannot be validated, e.g., is expired or has invalid 131 * signatures. 132 * 133 * <p>Note that this is different from {@link #ERROR_BAD_CERTIFICATE_FORMAT}, which denotes 134 * incorrect certificate formats, e.g., due to wrong encoding or structure. 135 * 136 * @hide 137 */ 138 public static final int ERROR_INVALID_CERTIFICATE = 28; 139 140 private final ILockSettings mBinder; 141 private final KeyStore mKeyStore; 142 143 private RecoveryController(ILockSettings binder, KeyStore keystore) { 144 mBinder = binder; 145 mKeyStore = keystore; 146 } 147 148 /** 149 * Internal method used by {@code RecoverySession}. 150 * 151 * @hide 152 */ 153 ILockSettings getBinder() { 154 return mBinder; 155 } 156 157 /** 158 * Gets a new instance of the class. 159 */ 160 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 161 public static RecoveryController getInstance(Context context) { 162 ILockSettings lockSettings = 163 ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings")); 164 return new RecoveryController(lockSettings, KeyStore.getInstance()); 165 } 166 167 /** 168 * @deprecated Use {@link #initRecoveryService(String, byte[], byte[])} instead. 169 */ 170 @Deprecated 171 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 172 public void initRecoveryService( 173 @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList) 174 throws CertificateException, InternalRecoveryServiceException { 175 try { 176 mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList); 177 } catch (RemoteException e) { 178 throw e.rethrowFromSystemServer(); 179 } catch (ServiceSpecificException e) { 180 if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT 181 || e.errorCode == ERROR_INVALID_CERTIFICATE) { 182 throw new CertificateException(e.getMessage()); 183 } 184 throw wrapUnexpectedServiceSpecificException(e); 185 } 186 } 187 188 /** 189 * Initializes the recovery service for the calling application. The detailed steps should be: 190 * <ol> 191 * <li>Parse {@code signatureFile} to get relevant information. 192 * <li>Validate the signer's X509 certificate, contained in {@code signatureFile}, against 193 * the root certificate pre-installed in the OS and chosen by {@code 194 * rootCertificateAlias}. 195 * <li>Verify the public-key signature, contained in {@code signatureFile}, and verify it 196 * against the entire {@code certificateFile}. 197 * <li>Parse {@code certificateFile} to get relevant information. 198 * <li>Check the serial number, contained in {@code certificateFile}, and skip the following 199 * steps if the serial number is not larger than the one previously stored. 200 * <li>Randomly choose a X509 certificate from the endpoint X509 certificates, contained in 201 * {@code certificateFile}, and validate it against the root certificate pre-installed 202 * in the OS and chosen by {@code rootCertificateAlias}. 203 * <li>Store the chosen X509 certificate and the serial in local database for later use. 204 * </ol> 205 * 206 * @param rootCertificateAlias the alias of a root certificate pre-installed in the OS 207 * @param certificateFile the binary content of the XML file containing a list of recovery 208 * service X509 certificates, and other metadata including the serial number 209 * @param signatureFile the binary content of the XML file containing the public-key signature 210 * of the entire certificate file, and a signer's X509 certificate 211 * @throws CertificateException if the given certificate files cannot be parsed or validated 212 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 213 * service. 214 */ 215 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 216 public void initRecoveryService( 217 @NonNull String rootCertificateAlias, @NonNull byte[] certificateFile, 218 @NonNull byte[] signatureFile) 219 throws CertificateException, InternalRecoveryServiceException { 220 try { 221 mBinder.initRecoveryServiceWithSigFile( 222 rootCertificateAlias, certificateFile, signatureFile); 223 } catch (RemoteException e) { 224 throw e.rethrowFromSystemServer(); 225 } catch (ServiceSpecificException e) { 226 if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT 227 || e.errorCode == ERROR_INVALID_CERTIFICATE) { 228 throw new CertificateException(e.getMessage()); 229 } 230 throw wrapUnexpectedServiceSpecificException(e); 231 } 232 } 233 234 /** 235 * @deprecated Use {@link #getKeyChainSnapshot()} 236 */ 237 @Deprecated 238 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 239 public @Nullable KeyChainSnapshot getRecoveryData() throws InternalRecoveryServiceException { 240 return getKeyChainSnapshot(); 241 } 242 243 /** 244 * Returns data necessary to store all recoverable keys. Key material is 245 * encrypted with user secret and recovery public key. 246 * 247 * @return Data necessary to recover keystore or {@code null} if snapshot is not available. 248 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 249 * service. 250 */ 251 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 252 public @Nullable KeyChainSnapshot getKeyChainSnapshot() 253 throws InternalRecoveryServiceException { 254 try { 255 return mBinder.getKeyChainSnapshot(); 256 } catch (RemoteException e) { 257 throw e.rethrowFromSystemServer(); 258 } catch (ServiceSpecificException e) { 259 if (e.errorCode == ERROR_NO_SNAPSHOT_PENDING) { 260 return null; 261 } 262 throw wrapUnexpectedServiceSpecificException(e); 263 } 264 } 265 266 /** 267 * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link 268 * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at 269 * most one registered listener at any time. 270 * 271 * @param intent triggered when new snapshot is available. Unregisters listener if the value is 272 * {@code null}. 273 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 274 * service. 275 */ 276 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 277 public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent) 278 throws InternalRecoveryServiceException { 279 try { 280 mBinder.setSnapshotCreatedPendingIntent(intent); 281 } catch (RemoteException e) { 282 throw e.rethrowFromSystemServer(); 283 } catch (ServiceSpecificException e) { 284 throw wrapUnexpectedServiceSpecificException(e); 285 } 286 } 287 288 /** 289 * Server parameters used to generate new recovery key blobs. This value will be included in 290 * {@code KeyChainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included 291 * in vaultParams {@link RecoverySession#start(CertPath, byte[], byte[], List)}. 292 * 293 * @param serverParams included in recovery key blob. 294 * @see #getRecoveryData 295 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 296 * service. 297 */ 298 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 299 public void setServerParams(byte[] serverParams) throws InternalRecoveryServiceException { 300 try { 301 mBinder.setServerParams(serverParams); 302 } catch (RemoteException e) { 303 throw e.rethrowFromSystemServer(); 304 } catch (ServiceSpecificException e) { 305 throw wrapUnexpectedServiceSpecificException(e); 306 } 307 } 308 309 /** 310 * @deprecated Use {@link #getAliases()}. 311 */ 312 @Deprecated 313 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 314 public List<String> getAliases(@Nullable String packageName) 315 throws InternalRecoveryServiceException { 316 return getAliases(); 317 } 318 319 /** 320 * Returns a list of aliases of keys belonging to the application. 321 */ 322 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 323 public List<String> getAliases() throws InternalRecoveryServiceException { 324 try { 325 Map<String, Integer> allStatuses = mBinder.getRecoveryStatus(); 326 return new ArrayList<>(allStatuses.keySet()); 327 } catch (RemoteException e) { 328 throw e.rethrowFromSystemServer(); 329 } catch (ServiceSpecificException e) { 330 throw wrapUnexpectedServiceSpecificException(e); 331 } 332 } 333 334 /** 335 * @deprecated Use {@link #setRecoveryStatus(String, int)} 336 */ 337 @Deprecated 338 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 339 public void setRecoveryStatus( 340 @NonNull String packageName, String alias, int status) 341 throws NameNotFoundException, InternalRecoveryServiceException { 342 setRecoveryStatus(alias, status); 343 } 344 345 /** 346 * Sets the recovery status for given key. It is used to notify the keystore that the key was 347 * successfully stored on the server or that there was an error. An application can check this 348 * value using {@link #getRecoveryStatus(String, String)}. 349 * 350 * @param alias The alias of the key whose status to set. 351 * @param status The status of the key. One of {@link #RECOVERY_STATUS_SYNCED}, 352 * {@link #RECOVERY_STATUS_SYNC_IN_PROGRESS} or {@link #RECOVERY_STATUS_PERMANENT_FAILURE}. 353 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 354 * service. 355 */ 356 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 357 public void setRecoveryStatus(String alias, int status) 358 throws InternalRecoveryServiceException { 359 try { 360 mBinder.setRecoveryStatus(alias, status); 361 } catch (RemoteException e) { 362 throw e.rethrowFromSystemServer(); 363 } catch (ServiceSpecificException e) { 364 throw wrapUnexpectedServiceSpecificException(e); 365 } 366 } 367 368 /** 369 * @deprecated Use {@link #getRecoveryStatus(String)}. 370 */ 371 @Deprecated 372 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 373 public int getRecoveryStatus(String packageName, String alias) 374 throws InternalRecoveryServiceException { 375 return getRecoveryStatus(alias); 376 } 377 378 /** 379 * Returns the recovery status for the key with the given {@code alias}. 380 * 381 * <ul> 382 * <li>{@link #RECOVERY_STATUS_SYNCED} 383 * <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS} 384 * <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE} 385 * </ul> 386 * 387 * @see #setRecoveryStatus(String, int) 388 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 389 * service. 390 */ 391 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 392 public int getRecoveryStatus(String alias) throws InternalRecoveryServiceException { 393 try { 394 Map<String, Integer> allStatuses = mBinder.getRecoveryStatus(); 395 Integer status = allStatuses.get(alias); 396 if (status == null) { 397 return RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE; 398 } else { 399 return status; 400 } 401 } catch (RemoteException e) { 402 throw e.rethrowFromSystemServer(); 403 } catch (ServiceSpecificException e) { 404 throw wrapUnexpectedServiceSpecificException(e); 405 } 406 } 407 408 /** 409 * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them 410 * is necessary to recover data. 411 * 412 * @param secretTypes {@link KeyChainProtectionParams#TYPE_LOCKSCREEN} or {@link 413 * KeyChainProtectionParams#TYPE_CUSTOM_PASSWORD} 414 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 415 * service. 416 */ 417 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 418 public void setRecoverySecretTypes( 419 @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes) 420 throws InternalRecoveryServiceException { 421 try { 422 mBinder.setRecoverySecretTypes(secretTypes); 423 } catch (RemoteException e) { 424 throw e.rethrowFromSystemServer(); 425 } catch (ServiceSpecificException e) { 426 throw wrapUnexpectedServiceSpecificException(e); 427 } 428 } 429 430 /** 431 * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is 432 * necessary to generate KeyChainSnapshot. 433 * 434 * @return list of recovery secret types 435 * @see KeyChainSnapshot 436 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 437 * service. 438 */ 439 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 440 public @NonNull @KeyChainProtectionParams.UserSecretType int[] getRecoverySecretTypes() 441 throws InternalRecoveryServiceException { 442 try { 443 return mBinder.getRecoverySecretTypes(); 444 } catch (RemoteException e) { 445 throw e.rethrowFromSystemServer(); 446 } catch (ServiceSpecificException e) { 447 throw wrapUnexpectedServiceSpecificException(e); 448 } 449 } 450 451 /** 452 * Returns a list of recovery secret types, necessary to create a pending recovery snapshot. 453 * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be 454 * called. 455 * 456 * @return list of recovery secret types 457 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 458 * service. 459 */ 460 @NonNull 461 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 462 public @KeyChainProtectionParams.UserSecretType int[] getPendingRecoverySecretTypes() 463 throws InternalRecoveryServiceException { 464 try { 465 return mBinder.getPendingRecoverySecretTypes(); 466 } catch (RemoteException e) { 467 throw e.rethrowFromSystemServer(); 468 } catch (ServiceSpecificException e) { 469 throw wrapUnexpectedServiceSpecificException(e); 470 } 471 } 472 473 /** 474 * Method notifies KeyStore that a user-generated secret is available. This method generates a 475 * symmetric session key which a trusted remote device can use to return a recovery key. Caller 476 * should use {@link KeyChainProtectionParams#clearSecret} to override the secret value in 477 * memory. 478 * 479 * @param recoverySecret user generated secret together with parameters necessary to regenerate 480 * it on a new device. 481 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 482 * service. 483 */ 484 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 485 public void recoverySecretAvailable(@NonNull KeyChainProtectionParams recoverySecret) 486 throws InternalRecoveryServiceException { 487 try { 488 mBinder.recoverySecretAvailable(recoverySecret); 489 } catch (RemoteException e) { 490 throw e.rethrowFromSystemServer(); 491 } catch (ServiceSpecificException e) { 492 throw wrapUnexpectedServiceSpecificException(e); 493 } 494 } 495 496 /** 497 * Deprecated. 498 * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable 499 * key store. Returns the raw material of the key. 500 * 501 * @param alias The key alias. 502 * @param account The account associated with the key 503 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 504 * service. 505 * @throws LockScreenRequiredException if the user has not set a lock screen. This is required 506 * to generate recoverable keys, as the snapshots are encrypted using a key derived from the 507 * lock screen. 508 */ 509 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 510 public byte[] generateAndStoreKey(@NonNull String alias, byte[] account) 511 throws InternalRecoveryServiceException, LockScreenRequiredException { 512 try { 513 return mBinder.generateAndStoreKey(alias); 514 } catch (RemoteException e) { 515 throw e.rethrowFromSystemServer(); 516 } catch (ServiceSpecificException e) { 517 if (e.errorCode == ERROR_INSECURE_USER) { 518 throw new LockScreenRequiredException(e.getMessage()); 519 } 520 throw wrapUnexpectedServiceSpecificException(e); 521 } 522 } 523 524 /** 525 * @deprecated Use {@link #generateKey(String)}. 526 */ 527 @Deprecated 528 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 529 public Key generateKey(@NonNull String alias, byte[] account) 530 throws InternalRecoveryServiceException, LockScreenRequiredException { 531 return generateKey(alias); 532 } 533 534 /** 535 * Generates a recoverable key with the given {@code alias}. 536 * 537 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 538 * service. 539 * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock 540 * screen is required to generate recoverable keys. 541 */ 542 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 543 public Key generateKey(@NonNull String alias) throws InternalRecoveryServiceException, 544 LockScreenRequiredException { 545 try { 546 String grantAlias = mBinder.generateKey(alias); 547 if (grantAlias == null) { 548 throw new InternalRecoveryServiceException("null grant alias"); 549 } 550 return getKeyFromGrant(grantAlias); 551 } catch (RemoteException e) { 552 throw e.rethrowFromSystemServer(); 553 } catch (UnrecoverableKeyException e) { 554 throw new InternalRecoveryServiceException("Failed to get key from keystore", e); 555 } catch (ServiceSpecificException e) { 556 if (e.errorCode == ERROR_INSECURE_USER) { 557 throw new LockScreenRequiredException(e.getMessage()); 558 } 559 throw wrapUnexpectedServiceSpecificException(e); 560 } 561 } 562 563 /** 564 * Imports a 256-bit recoverable AES key with the given {@code alias} and the raw bytes {@code 565 * keyBytes}. 566 * 567 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 568 * service. 569 * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock 570 * screen is required to generate recoverable keys. 571 * 572 */ 573 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 574 public Key importKey(@NonNull String alias, byte[] keyBytes) 575 throws InternalRecoveryServiceException, LockScreenRequiredException { 576 try { 577 String grantAlias = mBinder.importKey(alias, keyBytes); 578 if (grantAlias == null) { 579 throw new InternalRecoveryServiceException("Null grant alias"); 580 } 581 return getKeyFromGrant(grantAlias); 582 } catch (RemoteException e) { 583 throw e.rethrowFromSystemServer(); 584 } catch (UnrecoverableKeyException e) { 585 throw new InternalRecoveryServiceException("Failed to get key from keystore", e); 586 } catch (ServiceSpecificException e) { 587 if (e.errorCode == ERROR_INSECURE_USER) { 588 throw new LockScreenRequiredException(e.getMessage()); 589 } 590 throw wrapUnexpectedServiceSpecificException(e); 591 } 592 } 593 594 /** 595 * Gets a key called {@code alias} from the recoverable key store. 596 * 597 * @param alias The key alias. 598 * @return The key. 599 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 600 * service. 601 * @throws UnrecoverableKeyException if key is permanently invalidated or not found. 602 */ 603 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 604 public @Nullable Key getKey(@NonNull String alias) 605 throws InternalRecoveryServiceException, UnrecoverableKeyException { 606 try { 607 String grantAlias = mBinder.getKey(alias); 608 if (grantAlias == null) { 609 return null; 610 } 611 return getKeyFromGrant(grantAlias); 612 } catch (RemoteException e) { 613 throw e.rethrowFromSystemServer(); 614 } catch (ServiceSpecificException e) { 615 throw wrapUnexpectedServiceSpecificException(e); 616 } 617 } 618 619 /** 620 * Returns the key with the given {@code grantAlias}. 621 */ 622 Key getKeyFromGrant(String grantAlias) throws UnrecoverableKeyException { 623 return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore( 624 mKeyStore, 625 grantAlias, 626 KeyStore.UID_SELF); 627 } 628 629 /** 630 * Removes a key called {@code alias} from the recoverable key store. 631 * 632 * @param alias The key alias. 633 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery 634 * service. 635 */ 636 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 637 public void removeKey(@NonNull String alias) throws InternalRecoveryServiceException { 638 try { 639 mBinder.removeKey(alias); 640 } catch (RemoteException e) { 641 throw e.rethrowFromSystemServer(); 642 } catch (ServiceSpecificException e) { 643 throw wrapUnexpectedServiceSpecificException(e); 644 } 645 } 646 647 /** 648 * Returns a new {@link RecoverySession}. 649 * 650 * <p>A recovery session is required to restore keys from a remote store. 651 */ 652 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) 653 public RecoverySession createRecoverySession() { 654 return RecoverySession.newInstance(this); 655 } 656 657 InternalRecoveryServiceException wrapUnexpectedServiceSpecificException( 658 ServiceSpecificException e) { 659 if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) { 660 return new InternalRecoveryServiceException(e.getMessage()); 661 } 662 663 // Should never happen. If it does, it's a bug, and we need to update how the method that 664 // called this throws its exceptions. 665 return new InternalRecoveryServiceException("Unexpected error code for method: " 666 + e.errorCode, e); 667 } 668} 669