RecoveryController.java revision 1e6a9dcecb92b4a9a8d3c60372821ba7cd830873
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} or {@link
415     *     KeyChainProtectionParams#TYPE_CUSTOM_PASSWORD}
416     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
417     *     service.
418     */
419    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
420    public void setRecoverySecretTypes(
421            @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
422            throws InternalRecoveryServiceException {
423        try {
424            mBinder.setRecoverySecretTypes(secretTypes);
425        } catch (RemoteException e) {
426            throw e.rethrowFromSystemServer();
427        } catch (ServiceSpecificException e) {
428            throw wrapUnexpectedServiceSpecificException(e);
429        }
430    }
431
432    /**
433     * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
434     * necessary to generate KeyChainSnapshot.
435     *
436     * @return list of recovery secret types
437     * @see KeyChainSnapshot
438     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
439     *     service.
440     */
441    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
442    public @NonNull @KeyChainProtectionParams.UserSecretType int[] getRecoverySecretTypes()
443            throws InternalRecoveryServiceException {
444        try {
445            return mBinder.getRecoverySecretTypes();
446        } catch (RemoteException e) {
447            throw e.rethrowFromSystemServer();
448        } catch (ServiceSpecificException e) {
449            throw wrapUnexpectedServiceSpecificException(e);
450        }
451    }
452
453    /**
454     * Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
455     * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
456     * called.
457     *
458     * @return list of recovery secret types
459     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
460     *     service.
461     */
462    @NonNull
463    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
464    public @KeyChainProtectionParams.UserSecretType int[] getPendingRecoverySecretTypes()
465            throws InternalRecoveryServiceException {
466        try {
467            return mBinder.getPendingRecoverySecretTypes();
468        } catch (RemoteException e) {
469            throw e.rethrowFromSystemServer();
470        } catch (ServiceSpecificException e) {
471            throw wrapUnexpectedServiceSpecificException(e);
472        }
473    }
474
475    /**
476     * Method notifies KeyStore that a user-generated secret is available. This method generates a
477     * symmetric session key which a trusted remote device can use to return a recovery key. Caller
478     * should use {@link KeyChainProtectionParams#clearSecret} to override the secret value in
479     * memory.
480     *
481     * @param recoverySecret user generated secret together with parameters necessary to regenerate
482     *     it on a new device.
483     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
484     *     service.
485     */
486    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
487    public void recoverySecretAvailable(@NonNull KeyChainProtectionParams recoverySecret)
488            throws InternalRecoveryServiceException {
489        try {
490            mBinder.recoverySecretAvailable(recoverySecret);
491        } catch (RemoteException e) {
492            throw e.rethrowFromSystemServer();
493        } catch (ServiceSpecificException e) {
494            throw wrapUnexpectedServiceSpecificException(e);
495        }
496    }
497
498    /**
499     * Deprecated.
500     * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable
501     * key store. Returns the raw material of the key.
502     *
503     * @param alias The key alias.
504     * @param account The account associated with the key
505     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
506     *     service.
507     * @throws LockScreenRequiredException if the user has not set a lock screen. This is required
508     *     to generate recoverable keys, as the snapshots are encrypted using a key derived from the
509     *     lock screen.
510     */
511    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
512    public byte[] generateAndStoreKey(@NonNull String alias, byte[] account)
513            throws InternalRecoveryServiceException, LockScreenRequiredException {
514        try {
515            return mBinder.generateAndStoreKey(alias);
516        } catch (RemoteException e) {
517            throw e.rethrowFromSystemServer();
518        } catch (ServiceSpecificException e) {
519            if (e.errorCode == ERROR_INSECURE_USER) {
520                throw new LockScreenRequiredException(e.getMessage());
521            }
522            throw wrapUnexpectedServiceSpecificException(e);
523        }
524    }
525
526    /**
527     * @deprecated Use {@link #generateKey(String)}.
528     */
529    @Deprecated
530    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
531    public Key generateKey(@NonNull String alias, byte[] account)
532            throws InternalRecoveryServiceException, LockScreenRequiredException {
533        return generateKey(alias);
534    }
535
536    /**
537     * Generates a recoverable key with the given {@code alias}.
538     *
539     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
540     *     service.
541     * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
542     *     screen is required to generate recoverable keys.
543     */
544    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
545    public Key generateKey(@NonNull String alias) throws InternalRecoveryServiceException,
546            LockScreenRequiredException {
547        try {
548            String grantAlias = mBinder.generateKey(alias);
549            if (grantAlias == null) {
550                throw new InternalRecoveryServiceException("null grant alias");
551            }
552            return getKeyFromGrant(grantAlias);
553        } catch (RemoteException e) {
554            throw e.rethrowFromSystemServer();
555        } catch (UnrecoverableKeyException e) {
556            throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
557        } catch (ServiceSpecificException e) {
558            if (e.errorCode == ERROR_INSECURE_USER) {
559                throw new LockScreenRequiredException(e.getMessage());
560            }
561            throw wrapUnexpectedServiceSpecificException(e);
562        }
563    }
564
565    /**
566     * Imports a 256-bit recoverable AES key with the given {@code alias} and the raw bytes {@code
567     * keyBytes}.
568     *
569     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
570     *     service.
571     * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
572     *     screen is required to generate recoverable keys.
573     *
574     */
575    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
576    public Key importKey(@NonNull String alias, byte[] keyBytes)
577            throws InternalRecoveryServiceException, LockScreenRequiredException {
578        try {
579            String grantAlias = mBinder.importKey(alias, keyBytes);
580            if (grantAlias == null) {
581                throw new InternalRecoveryServiceException("Null grant alias");
582            }
583            return getKeyFromGrant(grantAlias);
584        } catch (RemoteException e) {
585            throw e.rethrowFromSystemServer();
586        } catch (UnrecoverableKeyException e) {
587            throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
588        } catch (ServiceSpecificException e) {
589            if (e.errorCode == ERROR_INSECURE_USER) {
590                throw new LockScreenRequiredException(e.getMessage());
591            }
592            throw wrapUnexpectedServiceSpecificException(e);
593        }
594    }
595
596    /**
597     * Gets a key called {@code alias} from the recoverable key store.
598     *
599     * @param alias The key alias.
600     * @return The key.
601     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
602     *     service.
603     * @throws UnrecoverableKeyException if key is permanently invalidated or not found.
604     */
605    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
606    public @Nullable Key getKey(@NonNull String alias)
607            throws InternalRecoveryServiceException, UnrecoverableKeyException {
608        try {
609            String grantAlias = mBinder.getKey(alias);
610            if (grantAlias == null) {
611                return null;
612            }
613            return getKeyFromGrant(grantAlias);
614        } catch (RemoteException e) {
615            throw e.rethrowFromSystemServer();
616        } catch (ServiceSpecificException e) {
617            throw wrapUnexpectedServiceSpecificException(e);
618        }
619    }
620
621    /**
622     * Returns the key with the given {@code grantAlias}.
623     */
624    Key getKeyFromGrant(String grantAlias) throws UnrecoverableKeyException {
625        return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
626                mKeyStore,
627                grantAlias,
628                KeyStore.UID_SELF);
629    }
630
631    /**
632     * Removes a key called {@code alias} from the recoverable key store.
633     *
634     * @param alias The key alias.
635     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
636     *     service.
637     */
638    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
639    public void removeKey(@NonNull String alias) throws InternalRecoveryServiceException {
640        try {
641            mBinder.removeKey(alias);
642        } catch (RemoteException e) {
643            throw e.rethrowFromSystemServer();
644        } catch (ServiceSpecificException e) {
645            throw wrapUnexpectedServiceSpecificException(e);
646        }
647    }
648
649    /**
650     * Returns a new {@link RecoverySession}.
651     *
652     * <p>A recovery session is required to restore keys from a remote store.
653     */
654    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
655    public @NonNull RecoverySession createRecoverySession() {
656        return RecoverySession.newInstance(this);
657    }
658
659    @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
660    public Map<String, X509Certificate> getRootCertificates() {
661        return TrustedRootCertificates.getRootCertificates();
662    }
663
664    InternalRecoveryServiceException wrapUnexpectedServiceSpecificException(
665            ServiceSpecificException e) {
666        if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) {
667            return new InternalRecoveryServiceException(e.getMessage());
668        }
669
670        // Should never happen. If it does, it's a bug, and we need to update how the method that
671        // called this throws its exceptions.
672        return new InternalRecoveryServiceException("Unexpected error code for method: "
673                + e.errorCode, e);
674    }
675}
676