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