blob: 426ca5c472b95592d74bbfb5c9a36bf9c79fb8db [file] [log] [blame]
Robert Berry81ee34b2018-01-23 11:59:59 +00001/*
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;
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080021import android.annotation.RequiresPermission;
Dmitry Dementyevf8ae5de2018-01-08 18:08:23 -080022import android.annotation.SystemApi;
Robert Berry81ee34b2018-01-23 11:59:59 +000023import android.app.PendingIntent;
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080024import android.content.Context;
Robert Berry81ee34b2018-01-23 11:59:59 +000025import android.content.pm.PackageManager.NameNotFoundException;
26import android.os.RemoteException;
27import android.os.ServiceManager;
28import android.os.ServiceSpecificException;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080029import android.security.KeyStore;
30import android.security.keystore.AndroidKeyStoreProvider;
Robert Berry81ee34b2018-01-23 11:59:59 +000031
32import com.android.internal.widget.ILockSettings;
33
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080034import java.security.Key;
35import java.security.UnrecoverableKeyException;
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080036import java.security.cert.CertificateException;
37import java.util.ArrayList;
Robert Berry81ee34b2018-01-23 11:59:59 +000038import java.util.List;
39import java.util.Map;
40
41/**
42 * An assistant for generating {@link javax.crypto.SecretKey} instances that can be recovered by
43 * other Android devices belonging to the user. The exported keychain is protected by the user's
44 * lock screen.
45 *
46 * <p>The RecoveryController must be paired with a recovery agent. The recovery agent is responsible
47 * for transporting the keychain to remote trusted hardware. This hardware must prevent brute force
48 * attempts against the user's lock screen by limiting the number of allowed guesses (to, e.g., 10).
Robert Berrybeafcb52018-02-26 19:00:29 +000049 * After that number of incorrect guesses, the trusted hardware no longer allows access to the
Robert Berry81ee34b2018-01-23 11:59:59 +000050 * key chain.
51 *
Robert Berrybeafcb52018-02-26 19:00:29 +000052 * <p>Only the recovery agent itself is able to create keys, so it is expected that the recovery
53 * agent is itself the system app.
Robert Berry81ee34b2018-01-23 11:59:59 +000054 *
55 * <p>A recovery agent requires the privileged permission
56 * {@code android.Manifest.permission#RECOVER_KEYSTORE}.
57 *
58 * @hide
59 */
Dmitry Dementyevf8ae5de2018-01-08 18:08:23 -080060@SystemApi
Robert Berry81ee34b2018-01-23 11:59:59 +000061public class RecoveryController {
62 private static final String TAG = "RecoveryController";
63
64 /** Key has been successfully synced. */
65 public static final int RECOVERY_STATUS_SYNCED = 0;
66 /** Waiting for recovery agent to sync the key. */
67 public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
Robert Berry81ee34b2018-01-23 11:59:59 +000068 /** Key cannot be synced. */
69 public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
70
71 /**
72 * Failed because no snapshot is yet pending to be synced for the user.
73 *
74 * @hide
75 */
76 public static final int ERROR_NO_SNAPSHOT_PENDING = 21;
77
78 /**
79 * Failed due to an error internal to the recovery service. This is unexpected and indicates
80 * either a problem with the logic in the service, or a problem with a dependency of the
81 * service (such as AndroidKeyStore).
82 *
83 * @hide
84 */
85 public static final int ERROR_SERVICE_INTERNAL_ERROR = 22;
86
87 /**
88 * Failed because the user does not have a lock screen set.
89 *
90 * @hide
91 */
92 public static final int ERROR_INSECURE_USER = 23;
93
94 /**
95 * Error thrown when attempting to use a recovery session that has since been closed.
96 *
97 * @hide
98 */
99 public static final int ERROR_SESSION_EXPIRED = 24;
100
101 /**
102 * Failed because the provided certificate was not a valid X509 certificate.
103 *
104 * @hide
105 */
106 public static final int ERROR_BAD_CERTIFICATE_FORMAT = 25;
107
108 /**
109 * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong,
110 * the data has become corrupted, the data has been tampered with, etc.
111 *
112 * @hide
113 */
114 public static final int ERROR_DECRYPTION_FAILED = 26;
115
Bo Zhu2c8e5382018-02-26 15:54:25 -0800116 /**
117 * Error thrown if the format of a given key is invalid. This might be because the key has a
118 * wrong length, invalid content, etc.
119 *
120 * @hide
121 */
122 public static final int ERROR_INVALID_KEY_FORMAT = 27;
123
Robert Berry81ee34b2018-01-23 11:59:59 +0000124
125 private final ILockSettings mBinder;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800126 private final KeyStore mKeyStore;
Robert Berry81ee34b2018-01-23 11:59:59 +0000127
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800128 private RecoveryController(ILockSettings binder, KeyStore keystore) {
Robert Berry81ee34b2018-01-23 11:59:59 +0000129 mBinder = binder;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800130 mKeyStore = keystore;
Robert Berry81ee34b2018-01-23 11:59:59 +0000131 }
132
133 /**
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800134 * Internal method used by {@code RecoverySession}.
135 *
136 * @hide
137 */
138 ILockSettings getBinder() {
139 return mBinder;
140 }
141
142 /**
Robert Berry81ee34b2018-01-23 11:59:59 +0000143 * Gets a new instance of the class.
144 */
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800145 public static RecoveryController getInstance(Context context) {
Robert Berry81ee34b2018-01-23 11:59:59 +0000146 ILockSettings lockSettings =
147 ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800148 return new RecoveryController(lockSettings, KeyStore.getInstance());
Robert Berry81ee34b2018-01-23 11:59:59 +0000149 }
150
151 /**
152 * Initializes key recovery service for the calling application. RecoveryController
153 * randomly chooses one of the keys from the list and keeps it to use for future key export
154 * operations. Collection of all keys in the list must be signed by the provided {@code
155 * rootCertificateAlias}, which must also be present in the list of root certificates
156 * preinstalled on the device. The random selection allows RecoveryController to select
157 * which of a set of remote recovery service devices will be used.
158 *
159 * <p>In addition, RecoveryController enforces a delay of three months between
160 * consecutive initialization attempts, to limit the ability of an attacker to often switch
161 * remote recovery devices and significantly increase number of recovery attempts.
162 *
163 * @param rootCertificateAlias alias of a root certificate preinstalled on the device
164 * @param signedPublicKeyList binary blob a list of X509 certificates and signature
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800165 * @throws CertificateException if the {@code signedPublicKeyList} is in a bad format.
Robert Berry81ee34b2018-01-23 11:59:59 +0000166 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
167 * service.
168 */
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800169 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
Robert Berry81ee34b2018-01-23 11:59:59 +0000170 public void initRecoveryService(
171 @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800172 throws CertificateException, InternalRecoveryServiceException {
Robert Berry81ee34b2018-01-23 11:59:59 +0000173 try {
174 mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList);
175 } catch (RemoteException e) {
176 throw e.rethrowFromSystemServer();
177 } catch (ServiceSpecificException e) {
178 if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800179 throw new CertificateException(e.getMessage());
Robert Berry81ee34b2018-01-23 11:59:59 +0000180 }
181 throw wrapUnexpectedServiceSpecificException(e);
182 }
183 }
184
185 /**
Robert Berry56f06b42018-02-23 13:31:32 +0000186 * @deprecated Use {@link #getKeyChainSnapshot()}
187 * @removed
Robert Berry81ee34b2018-01-23 11:59:59 +0000188 */
Robert Berry56f06b42018-02-23 13:31:32 +0000189 @Deprecated
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800190 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
Robert Berry56f06b42018-02-23 13:31:32 +0000191 public @Nullable KeyChainSnapshot getRecoveryData() throws InternalRecoveryServiceException {
192 return getKeyChainSnapshot();
Dmitry Dementyevb4fb9872018-01-26 11:49:34 -0800193 }
194
195 /**
196 * Returns data necessary to store all recoverable keys. Key material is
197 * encrypted with user secret and recovery public key.
198 *
199 * @return Data necessary to recover keystore or {@code null} if snapshot is not available.
200 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
201 * service.
202 *
203 * @hide
204 */
205 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
206 public @Nullable KeyChainSnapshot getKeyChainSnapshot()
207 throws InternalRecoveryServiceException {
208 try {
209 return mBinder.getKeyChainSnapshot();
Robert Berry81ee34b2018-01-23 11:59:59 +0000210 } catch (RemoteException e) {
211 throw e.rethrowFromSystemServer();
212 } catch (ServiceSpecificException e) {
213 if (e.errorCode == ERROR_NO_SNAPSHOT_PENDING) {
214 return null;
215 }
216 throw wrapUnexpectedServiceSpecificException(e);
217 }
218 }
219
220 /**
221 * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
222 * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
223 * most one registered listener at any time.
224 *
225 * @param intent triggered when new snapshot is available. Unregisters listener if the value is
226 * {@code null}.
227 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
228 * service.
229 */
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800230 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
Robert Berry81ee34b2018-01-23 11:59:59 +0000231 public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
232 throws InternalRecoveryServiceException {
233 try {
234 mBinder.setSnapshotCreatedPendingIntent(intent);
235 } catch (RemoteException e) {
236 throw e.rethrowFromSystemServer();
237 } catch (ServiceSpecificException e) {
238 throw wrapUnexpectedServiceSpecificException(e);
239 }
240 }
241
242 /**
Robert Berry81ee34b2018-01-23 11:59:59 +0000243 * Server parameters used to generate new recovery key blobs. This value will be included in
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800244 * {@code KeyChainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included
Robert Berry81ee34b2018-01-23 11:59:59 +0000245 * in vaultParams {@link #startRecoverySession}
246 *
247 * @param serverParams included in recovery key blob.
248 * @see #getRecoveryData
249 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
250 * service.
251 */
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800252 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
Robert Berry81ee34b2018-01-23 11:59:59 +0000253 public void setServerParams(byte[] serverParams) throws InternalRecoveryServiceException {
254 try {
255 mBinder.setServerParams(serverParams);
256 } catch (RemoteException e) {
257 throw e.rethrowFromSystemServer();
258 } catch (ServiceSpecificException e) {
259 throw wrapUnexpectedServiceSpecificException(e);
260 }
261 }
262
263 /**
Robert Berry56f06b42018-02-23 13:31:32 +0000264 * @deprecated Use {@link #getAliases()}.
265 * @removed
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800266 */
Robert Berry56f06b42018-02-23 13:31:32 +0000267 @Deprecated
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800268 public List<String> getAliases(@Nullable String packageName)
Dmitry Dementyevf8ae5de2018-01-08 18:08:23 -0800269 throws InternalRecoveryServiceException {
Robert Berry56f06b42018-02-23 13:31:32 +0000270 return getAliases();
271 }
272
273 /**
274 * Returns a list of aliases of keys belonging to the application.
275 */
276 public List<String> getAliases() throws InternalRecoveryServiceException {
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800277 try {
Robert Berry56f06b42018-02-23 13:31:32 +0000278 Map<String, Integer> allStatuses = mBinder.getRecoveryStatus();
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800279 return new ArrayList<>(allStatuses.keySet());
280 } catch (RemoteException e) {
281 throw e.rethrowFromSystemServer();
282 } catch (ServiceSpecificException e) {
283 throw wrapUnexpectedServiceSpecificException(e);
284 }
285 }
286
287 /**
Robert Berrybbe02ae2018-02-20 19:47:43 +0000288 * @deprecated Use {@link #setRecoveryStatus(String, int)}
289 * @removed
Robert Berry81ee34b2018-01-23 11:59:59 +0000290 */
Robert Berrybbe02ae2018-02-20 19:47:43 +0000291 @Deprecated
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800292 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
Robert Berry81ee34b2018-01-23 11:59:59 +0000293 public void setRecoveryStatus(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800294 @NonNull String packageName, String alias, int status)
Robert Berry81ee34b2018-01-23 11:59:59 +0000295 throws NameNotFoundException, InternalRecoveryServiceException {
Robert Berrybbe02ae2018-02-20 19:47:43 +0000296 setRecoveryStatus(alias, status);
297 }
298
299 /**
300 * Sets the recovery status for given key. It is used to notify the keystore that the key was
301 * successfully stored on the server or that there was an error. An application can check this
302 * value using {@link #getRecoveryStatus(String, String)}.
303 *
304 * @param alias The alias of the key whose status to set.
305 * @param status The status of the key. One of {@link #RECOVERY_STATUS_SYNCED},
306 * {@link #RECOVERY_STATUS_SYNC_IN_PROGRESS} or {@link #RECOVERY_STATUS_PERMANENT_FAILURE}.
307 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
308 * service.
309 */
310 @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
311 public void setRecoveryStatus(String alias, int status)
312 throws InternalRecoveryServiceException {
Robert Berry81ee34b2018-01-23 11:59:59 +0000313 try {
Robert Berrybbe02ae2018-02-20 19:47:43 +0000314 mBinder.setRecoveryStatus(alias, status);
Robert Berry81ee34b2018-01-23 11:59:59 +0000315 } catch (RemoteException e) {
316 throw e.rethrowFromSystemServer();
317 } catch (ServiceSpecificException e) {
318 throw wrapUnexpectedServiceSpecificException(e);
319 }
320 }
321
322 /**
Robert Berry56f06b42018-02-23 13:31:32 +0000323 * @deprecated Use {@link #getRecoveryStatus(String)}.
324 * @removed
325 */
326 @Deprecated
327 public int getRecoveryStatus(String packageName, String alias)
328 throws InternalRecoveryServiceException {
329 return getRecoveryStatus(alias);
330 }
331
332 /**
333 * Returns the recovery status for the key with the given {@code alias}.
Robert Berry81ee34b2018-01-23 11:59:59 +0000334 *
335 * <ul>
336 * <li>{@link #RECOVERY_STATUS_SYNCED}
337 * <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
Robert Berry81ee34b2018-01-23 11:59:59 +0000338 * <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
339 * </ul>
340 *
Robert Berry56f06b42018-02-23 13:31:32 +0000341 * @see #setRecoveryStatus(String, int)
Robert Berry81ee34b2018-01-23 11:59:59 +0000342 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
343 * service.
344 */
Robert Berry56f06b42018-02-23 13:31:32 +0000345 public int getRecoveryStatus(String alias) throws InternalRecoveryServiceException {
Robert Berry81ee34b2018-01-23 11:59:59 +0000346 try {
Robert Berry56f06b42018-02-23 13:31:32 +0000347 Map<String, Integer> allStatuses = mBinder.getRecoveryStatus();
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800348 Integer status = allStatuses.get(alias);
349 if (status == null) {
350 return RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE;
351 } else {
352 return status;
353 }
Robert Berry81ee34b2018-01-23 11:59:59 +0000354 } catch (RemoteException e) {
355 throw e.rethrowFromSystemServer();
356 } catch (ServiceSpecificException e) {
357 throw wrapUnexpectedServiceSpecificException(e);
358 }
359 }
360
361 /**
362 * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
363 * is necessary to recover data.
364 *
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800365 * @param secretTypes {@link KeyChainProtectionParams#TYPE_LOCKSCREEN} or {@link
366 * KeyChainProtectionParams#TYPE_CUSTOM_PASSWORD}
Robert Berry81ee34b2018-01-23 11:59:59 +0000367 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
368 * service.
369 */
370 public void setRecoverySecretTypes(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800371 @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
Robert Berry81ee34b2018-01-23 11:59:59 +0000372 throws InternalRecoveryServiceException {
373 try {
374 mBinder.setRecoverySecretTypes(secretTypes);
375 } catch (RemoteException e) {
376 throw e.rethrowFromSystemServer();
377 } catch (ServiceSpecificException e) {
378 throw wrapUnexpectedServiceSpecificException(e);
379 }
380 }
381
382 /**
383 * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800384 * necessary to generate KeyChainSnapshot.
Robert Berry81ee34b2018-01-23 11:59:59 +0000385 *
386 * @return list of recovery secret types
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800387 * @see KeyChainSnapshot
Robert Berry81ee34b2018-01-23 11:59:59 +0000388 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
389 * service.
390 */
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800391 public @NonNull @KeyChainProtectionParams.UserSecretType int[] getRecoverySecretTypes()
Robert Berry81ee34b2018-01-23 11:59:59 +0000392 throws InternalRecoveryServiceException {
393 try {
394 return mBinder.getRecoverySecretTypes();
395 } catch (RemoteException e) {
396 throw e.rethrowFromSystemServer();
397 } catch (ServiceSpecificException e) {
398 throw wrapUnexpectedServiceSpecificException(e);
399 }
400 }
401
402 /**
403 * Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
404 * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
405 * called.
406 *
407 * @return list of recovery secret types
408 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
409 * service.
410 */
411 @NonNull
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800412 public @KeyChainProtectionParams.UserSecretType int[] getPendingRecoverySecretTypes()
Robert Berry81ee34b2018-01-23 11:59:59 +0000413 throws InternalRecoveryServiceException {
414 try {
415 return mBinder.getPendingRecoverySecretTypes();
416 } catch (RemoteException e) {
417 throw e.rethrowFromSystemServer();
418 } catch (ServiceSpecificException e) {
419 throw wrapUnexpectedServiceSpecificException(e);
420 }
421 }
422
423 /**
424 * Method notifies KeyStore that a user-generated secret is available. This method generates a
425 * symmetric session key which a trusted remote device can use to return a recovery key. Caller
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800426 * should use {@link KeyChainProtectionParams#clearSecret} to override the secret value in
Robert Berry81ee34b2018-01-23 11:59:59 +0000427 * memory.
428 *
429 * @param recoverySecret user generated secret together with parameters necessary to regenerate
430 * it on a new device.
431 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
432 * service.
433 */
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800434 public void recoverySecretAvailable(@NonNull KeyChainProtectionParams recoverySecret)
Robert Berry81ee34b2018-01-23 11:59:59 +0000435 throws InternalRecoveryServiceException {
436 try {
437 mBinder.recoverySecretAvailable(recoverySecret);
438 } catch (RemoteException e) {
439 throw e.rethrowFromSystemServer();
440 } catch (ServiceSpecificException e) {
441 throw wrapUnexpectedServiceSpecificException(e);
442 }
443 }
444
445 /**
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800446 * Deprecated.
Dmitry Dementyevf8ae5de2018-01-08 18:08:23 -0800447 * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable
448 * key store. Returns the raw material of the key.
Robert Berry81ee34b2018-01-23 11:59:59 +0000449 *
450 * @param alias The key alias.
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800451 * @param account The account associated with the key
Robert Berry81ee34b2018-01-23 11:59:59 +0000452 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
453 * service.
454 * @throws LockScreenRequiredException if the user has not set a lock screen. This is required
455 * to generate recoverable keys, as the snapshots are encrypted using a key derived from the
456 * lock screen.
457 */
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800458 public byte[] generateAndStoreKey(@NonNull String alias, byte[] account)
Robert Berry81ee34b2018-01-23 11:59:59 +0000459 throws InternalRecoveryServiceException, LockScreenRequiredException {
460 try {
461 return mBinder.generateAndStoreKey(alias);
462 } catch (RemoteException e) {
463 throw e.rethrowFromSystemServer();
464 } catch (ServiceSpecificException e) {
465 if (e.errorCode == ERROR_INSECURE_USER) {
466 throw new LockScreenRequiredException(e.getMessage());
467 }
468 throw wrapUnexpectedServiceSpecificException(e);
469 }
470 }
471
Bo Zhu2c8e5382018-02-26 15:54:25 -0800472 // TODO: Unhide the following APIs, generateKey(), importKey(), and getKey()
Robert Berry81ee34b2018-01-23 11:59:59 +0000473 /**
Robert Berrya3b99472018-02-23 15:59:02 +0000474 * @deprecated Use {@link #generateKey(String)}.
475 * @removed
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800476 */
Robert Berrya3b99472018-02-23 15:59:02 +0000477 @Deprecated
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800478 public Key generateKey(@NonNull String alias, byte[] account)
479 throws InternalRecoveryServiceException, LockScreenRequiredException {
Robert Berrya3b99472018-02-23 15:59:02 +0000480 return generateKey(alias);
481 }
482
483 /**
484 * Generates a recoverable key with the given {@code alias}.
485 *
486 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
487 * service.
488 * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
489 * screen is required to generate recoverable keys.
490 */
491 public Key generateKey(@NonNull String alias) throws InternalRecoveryServiceException,
492 LockScreenRequiredException {
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800493 try {
Robert Berrya3b99472018-02-23 15:59:02 +0000494 String grantAlias = mBinder.generateKey(alias);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800495 if (grantAlias == null) {
Robert Berrya3b99472018-02-23 15:59:02 +0000496 throw new InternalRecoveryServiceException("null grant alias");
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800497 }
Robert Berrya3b99472018-02-23 15:59:02 +0000498 return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800499 mKeyStore,
500 grantAlias,
501 KeyStore.UID_SELF);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800502 } catch (RemoteException e) {
503 throw e.rethrowFromSystemServer();
504 } catch (UnrecoverableKeyException e) {
Robert Berrya3b99472018-02-23 15:59:02 +0000505 throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800506 } catch (ServiceSpecificException e) {
507 if (e.errorCode == ERROR_INSECURE_USER) {
508 throw new LockScreenRequiredException(e.getMessage());
509 }
510 throw wrapUnexpectedServiceSpecificException(e);
511 }
512 }
513
514 /**
Bo Zhu2c8e5382018-02-26 15:54:25 -0800515 * Imports a 256-bit recoverable AES key with the given {@code alias} and the raw bytes {@code
516 * keyBytes}.
517 *
518 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
519 * service.
520 * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock
521 * screen is required to generate recoverable keys.
522 *
523 * @hide
524 */
525 public Key importKey(@NonNull String alias, byte[] keyBytes)
526 throws InternalRecoveryServiceException, LockScreenRequiredException {
527 try {
528 String grantAlias = mBinder.importKey(alias, keyBytes);
529 if (grantAlias == null) {
530 throw new InternalRecoveryServiceException("Null grant alias");
531 }
532 return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
533 mKeyStore,
534 grantAlias,
535 KeyStore.UID_SELF);
536 } catch (RemoteException e) {
537 throw e.rethrowFromSystemServer();
538 } catch (UnrecoverableKeyException e) {
539 throw new InternalRecoveryServiceException("Failed to get key from keystore", e);
540 } catch (ServiceSpecificException e) {
541 if (e.errorCode == ERROR_INSECURE_USER) {
542 throw new LockScreenRequiredException(e.getMessage());
543 }
544 throw wrapUnexpectedServiceSpecificException(e);
545 }
546 }
547
548 /**
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800549 * Gets a key called {@code alias} from the recoverable key store.
550 *
551 * @param alias The key alias.
552 * @return The key.
553 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
554 * service.
555 * @throws UnrecoverableKeyException if key is permanently invalidated or not found.
556 * @hide
557 */
558 public @Nullable Key getKey(@NonNull String alias)
559 throws InternalRecoveryServiceException, UnrecoverableKeyException {
560 try {
561 String grantAlias = mBinder.getKey(alias);
562 if (grantAlias == null) {
563 return null;
564 }
565 return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
566 mKeyStore,
567 grantAlias,
568 KeyStore.UID_SELF);
569 } catch (RemoteException e) {
570 throw e.rethrowFromSystemServer();
571 } catch (ServiceSpecificException e) {
572 throw wrapUnexpectedServiceSpecificException(e);
573 }
574 }
575
576 /**
Robert Berry81ee34b2018-01-23 11:59:59 +0000577 * Removes a key called {@code alias} from the recoverable key store.
578 *
579 * @param alias The key alias.
580 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
581 * service.
582 */
583 public void removeKey(@NonNull String alias) throws InternalRecoveryServiceException {
584 try {
585 mBinder.removeKey(alias);
586 } catch (RemoteException e) {
587 throw e.rethrowFromSystemServer();
588 } catch (ServiceSpecificException e) {
589 throw wrapUnexpectedServiceSpecificException(e);
590 }
591 }
592
Robert Berrye04e09a2018-02-22 15:24:05 +0000593 /**
594 * Returns a new {@link RecoverySession}.
595 *
596 * <p>A recovery session is required to restore keys from a remote store.
597 */
598 public RecoverySession createRecoverySession() {
599 return RecoverySession.newInstance(this);
600 }
601
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800602 InternalRecoveryServiceException wrapUnexpectedServiceSpecificException(
Robert Berry81ee34b2018-01-23 11:59:59 +0000603 ServiceSpecificException e) {
604 if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) {
605 return new InternalRecoveryServiceException(e.getMessage());
606 }
607
608 // Should never happen. If it does, it's a bug, and we need to update how the method that
609 // called this throws its exceptions.
610 return new InternalRecoveryServiceException("Unexpected error code for method: "
611 + e.errorCode, e);
612 }
613}