1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.security.keystore.recovery;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.RequiresPermission;
22 import android.annotation.SystemApi;
23 import android.app.KeyguardManager;
24 import android.app.PendingIntent;
25 import android.content.Context;
26 import android.os.RemoteException;
27 import android.os.ServiceManager;
28 import android.os.ServiceSpecificException;
29 import android.security.KeyStore2;
30 import android.security.keystore.KeyPermanentlyInvalidatedException;
31 import android.security.keystore2.AndroidKeyStoreProvider;
32 import android.system.keystore2.Domain;
33 import android.system.keystore2.KeyDescriptor;
34 
35 import com.android.internal.widget.ILockSettings;
36 
37 import java.security.Key;
38 import java.security.UnrecoverableKeyException;
39 import java.security.cert.CertPath;
40 import java.security.cert.CertificateException;
41 import java.security.cert.X509Certificate;
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Map;
45 
46 /**
47  * Backs up cryptographic keys to remote secure hardware, encrypted with the user's lock screen.
48  *
49  * <p>A system app with the {@code android.permission.RECOVER_KEYSTORE} permission may generate or
50  * import recoverable keys using this class. To generate a key, the app must call
51  * {@link #generateKey(String)} with the desired alias for the key. This returns an AndroidKeyStore
52  * reference to a 256-bit {@link javax.crypto.SecretKey}, which can be used for AES/GCM/NoPadding.
53  * In order to get the same key again at a later time, the app can call {@link #getKey(String)} with
54  * the same alias. If a key is generated in this way the key's raw material is never directly
55  * exposed to the calling app. The system app may also import key material using
56  * {@link #importKey(String, byte[])}. The app may only generate and import keys for its own
57  * {@code uid}.
58  *
59  * <p>The same system app must also register a Recovery Agent to manage syncing recoverable keys to
60  * remote secure hardware. The Recovery Agent is a service that registers itself with the controller
61  * as follows:
62  *
63  * <ul>
64  *     <li>Invokes {@link #initRecoveryService(String, byte[], byte[])}
65  *     <ul>
66  *         <li>The first argument is the alias of the root certificate used to verify trusted
67  *         hardware modules. Each trusted hardware module must have a public key signed with this
68  *         root of trust. Roots of trust must be shipped with the framework. The app can list all
69  *         valid roots of trust by calling {@link #getRootCertificates()}.
70  *         <li>The second argument is the UTF-8 bytes of the XML listing file. It lists the X509
71  *         certificates containing the public keys of all available remote trusted hardware modules.
72  *         Each of the X509 certificates can be validated against the chosen root of trust.
73  *         <li>The third argument is the UTF-8 bytes of the XML signing file. The file contains a
74  *         signature of the XML listing file. The signature can be validated against the chosen root
75  *         of trust.
76  *     </ul>
77  *     <p>This will cause the controller to choose a random public key from the list. From then
78  *     on the controller will attempt to sync the key chain with the trusted hardware module to whom
79  *     that key belongs.
80  *     <li>Invokes {@link #setServerParams(byte[])} with a byte string that identifies the device
81  *     to a remote server. This server may act as the front-end to the trusted hardware modules. It
82  *     is up to the Recovery Agent to decide how best to identify devices, but this could be, e.g.,
83  *     based on the <a href="https://developers.google.com/instance-id/">Instance ID</a> of the
84  *     system app.
85  *     <li>Invokes {@link #setRecoverySecretTypes(int[])} with a list of types of secret used to
86  *     secure the recoverable key chain. For now only
87  *     {@link KeyChainProtectionParams#TYPE_LOCKSCREEN} is supported.
88  *     <li>Invokes {@link #setSnapshotCreatedPendingIntent(PendingIntent)} with a
89  *     {@link PendingIntent} that is to be invoked whenever a new snapshot is created. Although the
90  *     controller can create snapshots without the Recovery Agent registering this intent, it is a
91  *     good idea to register the intent so that the Recovery Agent is able to sync this snapshot to
92  *     the trusted hardware module as soon as it is available.
93  * </ul>
94  *
95  * <p>The trusted hardware module's public key MUST be generated on secure hardware with protections
96  * equivalent to those described in the
97  * <a href="https://developer.android.com/preview/features/security/ckv-whitepaper.html">Google
98  * Cloud Key Vault Service whitepaper</a>. The trusted hardware module itself must protect the key
99  * chain from brute-forcing using the methods also described in the whitepaper: i.e., it should
100  * limit the number of allowed attempts to enter the lock screen. If the number of attempts is
101  * exceeded the key material must no longer be recoverable.
102  *
103  * <p>A recoverable key chain snapshot is considered pending if any of the following conditions
104  * are met:
105  *
106  * <ul>
107  *     <li>The system app mutates the key chain. i.e., generates, imports, or removes a key.
108  *     <li>The user changes their lock screen.
109  * </ul>
110  *
111  * <p>Whenever the user unlocks their device, if a snapshot is pending, the Recovery Controller
112  * generates a new snapshot. It follows these steps to do so:
113  *
114  * <ul>
115  *     <li>Generates a 256-bit AES key using {@link java.security.SecureRandom}. This is the
116  *     Recovery Key.
117  *     <li>Wraps the key material of all keys in the recoverable key chain with the Recovery Key.
118  *     <li>Encrypts the Recovery Key with both the public key of the trusted hardware module and a
119  *     symmetric key derived from the user's lock screen.
120  * </ul>
121  *
122  * <p>The controller then writes this snapshot to disk, and uses the {@link PendingIntent} that was
123  * set by the Recovery Agent during initialization to inform it that a new snapshot is available.
124  * The snapshot only contains keys for that Recovery Agent's {@code uid} - i.e., keys the agent's
125  * app itself generated. If multiple Recovery Agents exist on the device, each will be notified of
126  * their new snapshots, and each snapshots' keys will be only those belonging to the same
127  * {@code uid}.
128  *
129  * <p>The Recovery Agent retrieves its most recent snapshot by calling
130  * {@link #getKeyChainSnapshot()}. It syncs the snapshot to the remote server. The snapshot contains
131  * the public key used for encryption, which the server uses to forward the encrypted recovery key
132  * to the correct trusted hardware module. The snapshot also contains the server params, which are
133  * used to identify this device to the server.
134  *
135  * <p>The client uses the server params to identify a device whose key chain it wishes to restore.
136  * This may be on a different device to the device that originally synced the key chain. The client
137  * sends the server params identifying the previous device to the server. The server returns the
138  * X509 certificate identifying the trusted hardware module in which the encrypted Recovery Key is
139  * stored. It also returns some vault parameters identifying that particular Recovery Key to the
140  * trusted hardware module. And it also returns a vault challenge, which is used as part of the
141  * vault opening protocol to ensure the recovery claim is fresh. See the whitepaper for more
142  * details.
143  *
144  * <p>The key chain is recovered via a {@link RecoverySession}. A Recovery Agent creates one by
145  * invoking {@link #createRecoverySession()}. It then invokes
146  * {@link RecoverySession#start(String, CertPath, byte[], byte[], List)} with these arguments:
147  *
148  * <ul>
149  *     <li>The alias of the root of trust used to verify the trusted hardware module.
150  *     <li>The X509 certificate of the trusted hardware module.
151  *     <li>The vault parameters used to identify the Recovery Key to the trusted hardware module.
152  *     <li>The vault challenge, as issued by the trusted hardware module.
153  *     <li>A list of secrets, corresponding to the secrets used to protect the key chain. At the
154  *     moment this is a single {@link KeyChainProtectionParams} containing the lock screen of the
155  *     device whose key chain is to be recovered.
156  * </ul>
157  *
158  * <p>This method returns a byte array containing the Recovery Claim, which can be issued to the
159  * remote trusted hardware module. It is encrypted with the trusted hardware module's public key
160  * (which has itself been certified with the root of trust). It also contains an ephemeral symmetric
161  * key generated for this recovery session, which the remote trusted hardware module uses to encrypt
162  * its responses. This is the Session Key.
163  *
164  * <p>If the lock screen provided is correct, the remote trusted hardware module decrypts one of the
165  * layers of lock-screen encryption from the Recovery Key. It then returns this key, encrypted with
166  * the Session Key to the Recovery Agent. As the Recovery Agent does not know the Session Key, it
167  * must then invoke {@link RecoverySession#recoverKeyChainSnapshot(byte[], List)} with the encrypted
168  * Recovery Key and the list of wrapped application keys. The controller then decrypts the layer of
169  * encryption provided by the Session Key, and uses the lock screen to decrypt the final layer of
170  * encryption. It then uses the Recovery Key to decrypt all of the wrapped application keys, and
171  * imports them into its own KeyStore. The Recovery Agent's app may then access these keys by
172  * calling {@link #getKey(String)}. Only this app's {@code uid} may access the keys that have been
173  * recovered.
174  *
175  * @hide
176  */
177 @SystemApi
178 public class RecoveryController {
179     private static final String TAG = "RecoveryController";
180 
181     /** Key has been successfully synced. */
182     public static final int RECOVERY_STATUS_SYNCED = 0;
183     /** Waiting for recovery agent to sync the key. */
184     public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
185     /** Key cannot be synced. */
186     public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
187 
188     /**
189      * Failed because no snapshot is yet pending to be synced for the user.
190      *
191      * @hide
192      */
193     public static final int ERROR_NO_SNAPSHOT_PENDING = 21;
194 
195     /**
196      * Failed due to an error internal to the recovery service. This is unexpected and indicates
197      * either a problem with the logic in the service, or a problem with a dependency of the
198      * service (such as AndroidKeyStore).
199      *
200      * @hide
201      */
202     public static final int ERROR_SERVICE_INTERNAL_ERROR = 22;
203 
204     /**
205      * Failed because the user does not have a lock screen set.
206      *
207      * @hide
208      */
209     public static final int ERROR_INSECURE_USER = 23;
210 
211     /**
212      * Error thrown when attempting to use a recovery session that has since been closed.
213      *
214      * @hide
215      */
216     public static final int ERROR_SESSION_EXPIRED = 24;
217 
218     /**
219      * Failed because the format of the provided certificate is incorrect, e.g., cannot be decoded
220      * properly or misses necessary fields.
221      *
222      * <p>Note that this is different from {@link #ERROR_INVALID_CERTIFICATE}, which implies the
223      * certificate has a correct format but cannot be validated.
224      *
225      * @hide
226      */
227     public static final int ERROR_BAD_CERTIFICATE_FORMAT = 25;
228 
229     /**
230      * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong,
231      * the data has become corrupted, the data has been tampered with, etc.
232      *
233      * @hide
234      */
235     public static final int ERROR_DECRYPTION_FAILED = 26;
236 
237     /**
238      * Error thrown if the format of a given key is invalid. This might be because the key has a
239      * wrong length, invalid content, etc.
240      *
241      * @hide
242      */
243     public static final int ERROR_INVALID_KEY_FORMAT = 27;
244 
245     /**
246      * Failed because the provided certificate cannot be validated, e.g., is expired or has invalid
247      * signatures.
248      *
249      * <p>Note that this is different from {@link #ERROR_BAD_CERTIFICATE_FORMAT}, which denotes
250      * incorrect certificate formats, e.g., due to wrong encoding or structure.
251      *
252      * @hide
253      */
254     public static final int ERROR_INVALID_CERTIFICATE = 28;
255 
256 
257     /**
258      * Failed because the provided certificate contained serial version which is lower that the
259      * version device is already initialized with. It is not possible to downgrade serial version of
260      * the provided certificate.
261      *
262      * @hide
263      */
264     public static final int ERROR_DOWNGRADE_CERTIFICATE = 29;
265 
266     /**
267      * Requested key is not available in AndroidKeyStore.
268      *
269      * @hide
270      */
271     public static final int ERROR_KEY_NOT_FOUND = 30;
272 
273     private final ILockSettings mBinder;
274 
RecoveryController(ILockSettings binder)275     private RecoveryController(ILockSettings binder) {
276         mBinder = binder;
277     }
278 
279     /**
280      * Internal method used by {@code RecoverySession}.
281      *
282      * @hide
283      */
getBinder()284     ILockSettings getBinder() {
285         return mBinder;
286     }
287 
288     /**
289      * Gets a new instance of the class.
290      */
291     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
getInstance(@onNull Context context)292     @NonNull public static RecoveryController getInstance(@NonNull Context context) {
293         // lockSettings may be null.
294         ILockSettings lockSettings =
295                 ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
296         return new RecoveryController(lockSettings);
297     }
298 
299     /**
300      * Checks whether the recoverable key store is currently available.
301      *
302      * <p>If it returns true, the device must currently be using a screen lock that is supported for
303      * use with the recoverable key store, i.e. AOSP PIN, pattern or password.
304      */
305     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
isRecoverableKeyStoreEnabled(@onNull Context context)306     public static boolean isRecoverableKeyStoreEnabled(@NonNull Context context) {
307         KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
308         return keyguardManager != null && keyguardManager.isDeviceSecure();
309     }
310 
311     /**
312      * Initializes the recovery service for the calling application. The detailed steps should be:
313      * <ol>
314      *     <li>Parse {@code signatureFile} to get relevant information.
315      *     <li>Validate the signer's X509 certificate, contained in {@code signatureFile}, against
316      *         the root certificate pre-installed in the OS and chosen by {@code
317      *         rootCertificateAlias}.
318      *     <li>Verify the public-key signature, contained in {@code signatureFile}, and verify it
319      *         against the entire {@code certificateFile}.
320      *     <li>Parse {@code certificateFile} to get relevant information.
321      *     <li>Check the serial number, contained in {@code certificateFile}, and skip the following
322      *         steps if the serial number is not larger than the one previously stored.
323      *     <li>Randomly choose a X509 certificate from the endpoint X509 certificates, contained in
324      *         {@code certificateFile}, and validate it against the root certificate pre-installed
325      *         in the OS and chosen by {@code rootCertificateAlias}.
326      *     <li>Store the chosen X509 certificate and the serial in local database for later use.
327      * </ol>
328      *
329      * @param rootCertificateAlias the alias of a root certificate pre-installed in the OS
330      * @param certificateFile the binary content of the XML file containing a list of recovery
331      *     service X509 certificates, and other metadata including the serial number
332      * @param signatureFile the binary content of the XML file containing the public-key signature
333      *     of the entire certificate file, and a signer's X509 certificate
334      * @throws CertificateException if the given certificate files cannot be parsed or validated
335      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
336      *     service.
337      */
338     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
initRecoveryService( @onNull String rootCertificateAlias, @NonNull byte[] certificateFile, @NonNull byte[] signatureFile)339     public void initRecoveryService(
340             @NonNull String rootCertificateAlias, @NonNull byte[] certificateFile,
341             @NonNull byte[] signatureFile)
342             throws CertificateException, InternalRecoveryServiceException {
343         try {
344             mBinder.initRecoveryServiceWithSigFile(
345                     rootCertificateAlias, certificateFile, signatureFile);
346         } catch (RemoteException e) {
347             throw e.rethrowFromSystemServer();
348         } catch (ServiceSpecificException e) {
349             if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT
350                     || e.errorCode == ERROR_INVALID_CERTIFICATE) {
351                 throw new CertificateException("Invalid certificate for recovery service", e);
352             }
353             if (e.errorCode == ERROR_DOWNGRADE_CERTIFICATE) {
354                 throw new CertificateException(
355                         "Downgrading certificate serial version isn't supported.", e);
356             }
357             throw wrapUnexpectedServiceSpecificException(e);
358         }
359     }
360 
361     /**
362      * Returns data necessary to store all recoverable keys. Key material is
363      * encrypted with user secret and recovery public key.
364      *
365      * @return Data necessary to recover keystore or {@code null} if snapshot is not available.
366      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
367      *     service.
368      */
369     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
getKeyChainSnapshot()370     public @Nullable KeyChainSnapshot getKeyChainSnapshot()
371             throws InternalRecoveryServiceException {
372         try {
373             return mBinder.getKeyChainSnapshot();
374         } catch (RemoteException e) {
375             throw e.rethrowFromSystemServer();
376         } catch (ServiceSpecificException e) {
377             if (e.errorCode == ERROR_NO_SNAPSHOT_PENDING) {
378                 return null;
379             }
380             throw wrapUnexpectedServiceSpecificException(e);
381         }
382     }
383 
384     /**
385      * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
386      * #getKeyChainSnapshot} can be used to get the snapshot. Note that every recovery agent can
387      * have at most one registered listener at any time.
388      *
389      * @param intent triggered when new snapshot is available. Unregisters listener if the value is
390      *     {@code null}.
391      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
392      *     service.
393      */
394     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
setSnapshotCreatedPendingIntent(@ullable PendingIntent intent)395     public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
396             throws InternalRecoveryServiceException {
397         try {
398             mBinder.setSnapshotCreatedPendingIntent(intent);
399         } catch (RemoteException e) {
400             throw e.rethrowFromSystemServer();
401         } catch (ServiceSpecificException e) {
402             throw wrapUnexpectedServiceSpecificException(e);
403         }
404     }
405 
406     /**
407      * Server parameters used to generate new recovery key blobs. This value will be included in
408      * {@code KeyChainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included
409      * in vaultParams {@link RecoverySession#start(CertPath, byte[], byte[], List)}.
410      *
411      * @param serverParams included in recovery key blob.
412      * @see #getKeyChainSnapshot
413      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
414      *     service.
415      */
416     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
setServerParams(@onNull byte[] serverParams)417     public void setServerParams(@NonNull byte[] serverParams)
418             throws InternalRecoveryServiceException {
419         try {
420             mBinder.setServerParams(serverParams);
421         } catch (RemoteException e) {
422             throw e.rethrowFromSystemServer();
423         } catch (ServiceSpecificException e) {
424             throw wrapUnexpectedServiceSpecificException(e);
425         }
426     }
427 
428     /**
429      * Returns a list of aliases of keys belonging to the application.
430      */
431     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
getAliases()432     public @NonNull List<String> getAliases() throws InternalRecoveryServiceException {
433         try {
434             Map<String, Integer> allStatuses = mBinder.getRecoveryStatus();
435             return new ArrayList<>(allStatuses.keySet());
436         } catch (RemoteException e) {
437             throw e.rethrowFromSystemServer();
438         } catch (ServiceSpecificException e) {
439             throw wrapUnexpectedServiceSpecificException(e);
440         }
441     }
442 
443     /**
444      * Sets the recovery status for given key. It is used to notify the keystore that the key was
445      * successfully stored on the server or that there was an error. An application can check this
446      * value using {@link #getRecoveryStatus(String, String)}.
447      *
448      * @param alias The alias of the key whose status to set.
449      * @param status The status of the key. One of {@link #RECOVERY_STATUS_SYNCED},
450      *     {@link #RECOVERY_STATUS_SYNC_IN_PROGRESS} or {@link #RECOVERY_STATUS_PERMANENT_FAILURE}.
451      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
452      *     service.
453      */
454     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
setRecoveryStatus(@onNull String alias, int status)455     public void setRecoveryStatus(@NonNull String alias, int status)
456             throws InternalRecoveryServiceException {
457         try {
458             mBinder.setRecoveryStatus(alias, status);
459         } catch (RemoteException e) {
460             throw e.rethrowFromSystemServer();
461         } catch (ServiceSpecificException e) {
462             throw wrapUnexpectedServiceSpecificException(e);
463         }
464     }
465 
466     /**
467      * Returns the recovery status for the key with the given {@code alias}.
468      *
469      * <ul>
470      *   <li>{@link #RECOVERY_STATUS_SYNCED}
471      *   <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
472      *   <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
473      * </ul>
474      *
475      * @see #setRecoveryStatus(String, int)
476      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
477      *     service.
478      */
479     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
getRecoveryStatus(@onNull String alias)480     public int getRecoveryStatus(@NonNull String alias) throws InternalRecoveryServiceException {
481         try {
482             Map<String, Integer> allStatuses = mBinder.getRecoveryStatus();
483             Integer status = allStatuses.get(alias);
484             if (status == null) {
485                 return RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE;
486             } else {
487                 return status;
488             }
489         } catch (RemoteException e) {
490             throw e.rethrowFromSystemServer();
491         } catch (ServiceSpecificException e) {
492             throw wrapUnexpectedServiceSpecificException(e);
493         }
494     }
495 
496     /**
497      * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
498      * is necessary to recover data.
499      *
500      * @param secretTypes {@link KeyChainProtectionParams#TYPE_LOCKSCREEN}
501      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
502      *     service.
503      */
504     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
setRecoverySecretTypes( @onNull @eyChainProtectionParams.UserSecretType int[] secretTypes)505     public void setRecoverySecretTypes(
506             @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
507             throws InternalRecoveryServiceException {
508         try {
509             mBinder.setRecoverySecretTypes(secretTypes);
510         } catch (RemoteException e) {
511             throw e.rethrowFromSystemServer();
512         } catch (ServiceSpecificException e) {
513             throw wrapUnexpectedServiceSpecificException(e);
514         }
515     }
516 
517     /**
518      * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
519      * necessary to generate KeyChainSnapshot.
520      *
521      * @return list of recovery secret types
522      * @see KeyChainSnapshot
523      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
524      *     service.
525      */
526     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
getRecoverySecretTypes()527     public @NonNull @KeyChainProtectionParams.UserSecretType int[] getRecoverySecretTypes()
528             throws InternalRecoveryServiceException {
529         try {
530             return mBinder.getRecoverySecretTypes();
531         } catch (RemoteException e) {
532             throw e.rethrowFromSystemServer();
533         } catch (ServiceSpecificException e) {
534             throw wrapUnexpectedServiceSpecificException(e);
535         }
536     }
537 
538     /**
539      * Generates a recoverable key with the given {@code alias}.
540      *
541      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
542      *     service.
543      * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
544      *     screen is required to generate recoverable keys.
545      *
546      * @deprecated Use the method {@link #generateKey(String, byte[])} instead.
547      */
548     @Deprecated
549     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
generateKey(@onNull String alias)550     public @NonNull Key generateKey(@NonNull String alias) throws InternalRecoveryServiceException,
551             LockScreenRequiredException {
552         try {
553             String grantAlias = mBinder.generateKey(alias);
554             if (grantAlias == null) {
555                 throw new InternalRecoveryServiceException("null grant alias");
556             }
557             return getKeyFromGrant(grantAlias);
558         } catch (RemoteException e) {
559             throw e.rethrowFromSystemServer();
560         } catch (KeyPermanentlyInvalidatedException | UnrecoverableKeyException e) {
561             throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
562         } catch (ServiceSpecificException e) {
563             if (e.errorCode == ERROR_INSECURE_USER) {
564                 throw new LockScreenRequiredException(e.getMessage());
565             }
566             throw wrapUnexpectedServiceSpecificException(e);
567         }
568     }
569 
570     /**
571      * Generates a recoverable key with the given {@code alias} and {@code metadata}.
572      *
573      * <p>The metadata should contain any data that needs to be cryptographically bound to the
574      * generated key, but does not need to be encrypted by the key. For example, the metadata can
575      * be a byte string describing the algorithms and non-secret parameters to be used with the
576      * key. The supplied metadata can later be obtained via
577      * {@link WrappedApplicationKey#getMetadata()}.
578      *
579      * <p>During the key recovery process, the same metadata has to be supplied via
580      * {@link WrappedApplicationKey.Builder#setMetadata(byte[])}; otherwise, the recovery process
581      * will fail due to the checking of the cryptographic binding. This can help prevent
582      * potential attacks that try to swap key materials on the backup server and trick the
583      * application to use keys with different algorithms or parameters.
584      *
585      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
586      *     service.
587      * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
588      *     screen is required to generate recoverable keys.
589      */
590     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
generateKey(@onNull String alias, @Nullable byte[] metadata)591     public @NonNull Key generateKey(@NonNull String alias, @Nullable byte[] metadata)
592             throws InternalRecoveryServiceException, LockScreenRequiredException {
593         try {
594             String grantAlias = mBinder.generateKeyWithMetadata(alias, metadata);
595             if (grantAlias == null) {
596                 throw new InternalRecoveryServiceException("null grant alias");
597             }
598             return getKeyFromGrant(grantAlias);
599         } catch (RemoteException e) {
600             throw e.rethrowFromSystemServer();
601         } catch (KeyPermanentlyInvalidatedException | UnrecoverableKeyException  e) {
602             throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
603         } catch (ServiceSpecificException e) {
604             if (e.errorCode == ERROR_INSECURE_USER) {
605                 throw new LockScreenRequiredException(e.getMessage());
606             }
607             throw wrapUnexpectedServiceSpecificException(e);
608         }
609     }
610 
611     /**
612      * Imports a 256-bit recoverable AES key with the given {@code alias} and the raw bytes {@code
613      * keyBytes}.
614      *
615      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
616      *     service.
617      * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
618      *     screen is required to generate recoverable keys.
619      *
620      * @deprecated Use the method {@link #importKey(String, byte[], byte[])} instead.
621      */
622     @Deprecated
623     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
importKey(@onNull String alias, @NonNull byte[] keyBytes)624     public @NonNull Key importKey(@NonNull String alias, @NonNull byte[] keyBytes)
625             throws InternalRecoveryServiceException, LockScreenRequiredException {
626         try {
627             String grantAlias = mBinder.importKey(alias, keyBytes);
628             if (grantAlias == null) {
629                 throw new InternalRecoveryServiceException("Null grant alias");
630             }
631             return getKeyFromGrant(grantAlias);
632         } catch (RemoteException e) {
633             throw e.rethrowFromSystemServer();
634         } catch (KeyPermanentlyInvalidatedException | UnrecoverableKeyException e) {
635             throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
636         } catch (ServiceSpecificException e) {
637             if (e.errorCode == ERROR_INSECURE_USER) {
638                 throw new LockScreenRequiredException(e.getMessage());
639             }
640             throw wrapUnexpectedServiceSpecificException(e);
641         }
642     }
643 
644     /**
645      * Imports a recoverable 256-bit AES key with the given {@code alias}, the raw bytes {@code
646      * keyBytes}, and the {@code metadata}.
647      *
648      * <p>The metadata should contain any data that needs to be cryptographically bound to the
649      * imported key, but does not need to be encrypted by the key. For example, the metadata can
650      * be a byte string describing the algorithms and non-secret parameters to be used with the
651      * key. The supplied metadata can later be obtained via
652      * {@link WrappedApplicationKey#getMetadata()}.
653      *
654      * <p>During the key recovery process, the same metadata has to be supplied via
655      * {@link WrappedApplicationKey.Builder#setMetadata(byte[])}; otherwise, the recovery process
656      * will fail due to the checking of the cryptographic binding. This can help prevent
657      * potential attacks that try to swap key materials on the backup server and trick the
658      * application to use keys with different algorithms or parameters.
659      *
660      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
661      *     service.
662      * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
663      *     screen is required to generate recoverable keys.
664      */
665     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
importKey(@onNull String alias, @NonNull byte[] keyBytes, @Nullable byte[] metadata)666     public @NonNull Key importKey(@NonNull String alias, @NonNull byte[] keyBytes,
667             @Nullable byte[] metadata)
668             throws InternalRecoveryServiceException, LockScreenRequiredException {
669         try {
670             String grantAlias = mBinder.importKeyWithMetadata(alias, keyBytes, metadata);
671             if (grantAlias == null) {
672                 throw new InternalRecoveryServiceException("Null grant alias");
673             }
674             return getKeyFromGrant(grantAlias);
675         } catch (RemoteException e) {
676             throw e.rethrowFromSystemServer();
677         } catch (KeyPermanentlyInvalidatedException | UnrecoverableKeyException e) {
678             throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
679         } catch (ServiceSpecificException e) {
680             if (e.errorCode == ERROR_INSECURE_USER) {
681                 throw new LockScreenRequiredException(e.getMessage());
682             }
683             throw wrapUnexpectedServiceSpecificException(e);
684         }
685     }
686 
687     /**
688      * Gets a key called {@code alias} from the recoverable key store.
689      *
690      * @param alias The key alias.
691      * @return The key.
692      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
693      *     service.
694      * @throws UnrecoverableKeyException if key is permanently invalidated or not found.
695      */
696     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
getKey(@onNull String alias)697     public @Nullable Key getKey(@NonNull String alias)
698             throws InternalRecoveryServiceException, UnrecoverableKeyException {
699         try {
700             String grantAlias = mBinder.getKey(alias);
701             if (grantAlias == null || "".equals(grantAlias)) {
702                 return null;
703             }
704             return getKeyFromGrant(grantAlias);
705         } catch (RemoteException e) {
706             throw e.rethrowFromSystemServer();
707         } catch (KeyPermanentlyInvalidatedException | UnrecoverableKeyException e) {
708             throw new UnrecoverableKeyException(e.getMessage());
709         } catch (ServiceSpecificException e) {
710             if (e.errorCode == ERROR_KEY_NOT_FOUND) {
711                 throw new UnrecoverableKeyException(e.getMessage());
712             }
713             throw wrapUnexpectedServiceSpecificException(e);
714         }
715     }
716 
717     /**
718      * Returns the key with the given {@code grantAlias}.
719      */
getKeyFromGrant(@onNull String grantAlias)720     @NonNull Key getKeyFromGrant(@NonNull String grantAlias)
721             throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
722         return AndroidKeyStoreProvider
723                 .loadAndroidKeyStoreSecretKeyFromKeystore(
724                         KeyStore2.getInstance(),
725                         getGrantDescriptor(grantAlias));
726     }
727 
728     private static final String APPLICATION_KEY_GRANT_PREFIX = "recoverable_key:";
729 
getGrantDescriptor(String grantAlias)730     private static @Nullable KeyDescriptor getGrantDescriptor(String grantAlias) {
731         KeyDescriptor result = new KeyDescriptor();
732         result.domain = Domain.GRANT;
733         result.blob = null;
734         result.alias = null;
735         try {
736             result.nspace = Long.parseUnsignedLong(
737                     grantAlias.substring(APPLICATION_KEY_GRANT_PREFIX.length()), 16);
738         } catch (NumberFormatException e) {
739             return null;
740         }
741         return result;
742     }
743 
744     /**
745      * Removes a key called {@code alias} from the recoverable key store.
746      *
747      * @param alias The key alias.
748      * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
749      *     service.
750      */
751     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
removeKey(@onNull String alias)752     public void removeKey(@NonNull String alias) throws InternalRecoveryServiceException {
753         try {
754             mBinder.removeKey(alias);
755         } catch (RemoteException e) {
756             throw e.rethrowFromSystemServer();
757         } catch (ServiceSpecificException e) {
758             throw wrapUnexpectedServiceSpecificException(e);
759         }
760     }
761 
762     /**
763      * Returns a new {@link RecoverySession}.
764      *
765      * <p>A recovery session is required to restore keys from a remote store.
766      */
767     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
createRecoverySession()768     public @NonNull RecoverySession createRecoverySession() {
769         return RecoverySession.newInstance(this);
770     }
771 
772     @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
getRootCertificates()773     public @NonNull Map<String, X509Certificate> getRootCertificates() {
774         return TrustedRootCertificates.getRootCertificates();
775     }
776 
wrapUnexpectedServiceSpecificException( ServiceSpecificException e)777     InternalRecoveryServiceException wrapUnexpectedServiceSpecificException(
778             ServiceSpecificException e) {
779         if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) {
780             return new InternalRecoveryServiceException(e.getMessage(), e);
781         }
782 
783         // Should never happen. If it does, it's a bug, and we need to update how the method that
784         // called this throws its exceptions.
785         return new InternalRecoveryServiceException("Unexpected error code for method: "
786                 + e.errorCode, e);
787     }
788 }
789