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