blob: 786d45417f6fb9052968012fefeadd253eeaeae8 [file] [log] [blame]
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -08001/*
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
Dmitry Dementyeved89ea02018-01-11 13:53:52 -080017package android.security.keystore;
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -080018
19import android.annotation.NonNull;
20import android.annotation.Nullable;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080021import android.app.PendingIntent;
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -080022import android.content.pm.PackageManager.NameNotFoundException;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080023import android.os.RemoteException;
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -080024import android.os.ServiceManager;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080025import android.os.ServiceSpecificException;
Robert Berry2bcdad92018-01-18 12:53:29 +000026import android.util.Log;
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -080027
28import com.android.internal.widget.ILockSettings;
29
30import java.util.List;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080031import java.util.Map;
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -080032
33/**
Robert Berry74928a12018-01-18 17:49:07 +000034 * An assistant for generating {@link javax.crypto.SecretKey} instances that can be recovered by
35 * other Android devices belonging to the user. The exported keychain is protected by the user's
36 * lock screen.
37 *
38 * <p>The RecoveryController must be paired with a recovery agent. The recovery agent is responsible
39 * for transporting the keychain to remote trusted hardware. This hardware must prevent brute force
40 * attempts against the user's lock screen by limiting the number of allowed guesses (to, e.g., 10).
41 * After that number of incorrect guesses, the trusted hardware no longer allows access to the
42 * key chain.
43 *
44 * <p>For now only the recovery agent itself is able to create keys, so it is expected that the
45 * recovery agent is itself the system app.
46 *
47 * <p>A recovery agent requires the privileged permission
48 * {@code android.Manifest.permission#RECOVER_KEYSTORE}.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -080049 *
Robert Berrybbe02ae2018-02-20 19:47:43 +000050 * @deprecated Use {@link android.security.keystore.recovery.RecoveryController}.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -080051 * @hide
52 */
Robert Berry74928a12018-01-18 17:49:07 +000053public class RecoveryController {
Robert Berry2bcdad92018-01-18 12:53:29 +000054 private static final String TAG = "RecoveryController";
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -080055
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080056 /** Key has been successfully synced. */
57 public static final int RECOVERY_STATUS_SYNCED = 0;
58 /** Waiting for recovery agent to sync the key. */
59 public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
60 /** Recovery account is not available. */
61 public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
62 /** Key cannot be synced. */
63 public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
64
Robert Berrya16cd592018-01-17 14:43:09 +000065 /**
66 * Failed because no snapshot is yet pending to be synced for the user.
67 *
68 * @hide
69 */
70 public static final int ERROR_NO_SNAPSHOT_PENDING = 21;
71
72 /**
73 * Failed due to an error internal to the recovery service. This is unexpected and indicates
74 * either a problem with the logic in the service, or a problem with a dependency of the
75 * service (such as AndroidKeyStore).
76 *
77 * @hide
78 */
79 public static final int ERROR_SERVICE_INTERNAL_ERROR = 22;
80
81 /**
82 * Failed because the user does not have a lock screen set.
83 *
84 * @hide
85 */
86 public static final int ERROR_INSECURE_USER = 23;
87
88 /**
89 * Error thrown when attempting to use a recovery session that has since been closed.
90 *
91 * @hide
92 */
93 public static final int ERROR_SESSION_EXPIRED = 24;
94
95 /**
96 * Failed because the provided certificate was not a valid X509 certificate.
97 *
98 * @hide
99 */
100 public static final int ERROR_BAD_CERTIFICATE_FORMAT = 25;
101
102 /**
103 * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong,
104 * the data has become corrupted, the data has been tampered with, etc.
105 *
106 * @hide
107 */
108 public static final int ERROR_DECRYPTION_FAILED = 26;
109
110
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800111 private final ILockSettings mBinder;
112
Robert Berry74928a12018-01-18 17:49:07 +0000113 private RecoveryController(ILockSettings binder) {
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800114 mBinder = binder;
115 }
116
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800117 /**
118 * Gets a new instance of the class.
119 */
Robert Berry74928a12018-01-18 17:49:07 +0000120 public static RecoveryController getInstance() {
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800121 ILockSettings lockSettings =
122 ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
Robert Berry74928a12018-01-18 17:49:07 +0000123 return new RecoveryController(lockSettings);
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800124 }
125
126 /**
Robert Berry74928a12018-01-18 17:49:07 +0000127 * Initializes key recovery service for the calling application. RecoveryController
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800128 * randomly chooses one of the keys from the list and keeps it to use for future key export
129 * operations. Collection of all keys in the list must be signed by the provided {@code
130 * rootCertificateAlias}, which must also be present in the list of root certificates
Robert Berry74928a12018-01-18 17:49:07 +0000131 * preinstalled on the device. The random selection allows RecoveryController to select
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800132 * which of a set of remote recovery service devices will be used.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800133 *
Robert Berry74928a12018-01-18 17:49:07 +0000134 * <p>In addition, RecoveryController enforces a delay of three months between
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800135 * consecutive initialization attempts, to limit the ability of an attacker to often switch
136 * remote recovery devices and significantly increase number of recovery attempts.
137 *
138 * @param rootCertificateAlias alias of a root certificate preinstalled on the device
139 * @param signedPublicKeyList binary blob a list of X509 certificates and signature
Robert Berrya16cd592018-01-17 14:43:09 +0000140 * @throws BadCertificateFormatException if the {@code signedPublicKeyList} is in a bad format.
141 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
142 * service.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800143 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800144 public void initRecoveryService(
145 @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
Robert Berrya16cd592018-01-17 14:43:09 +0000146 throws BadCertificateFormatException, InternalRecoveryServiceException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800147 try {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800148 mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800149 } catch (RemoteException e) {
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800150 throw e.rethrowFromSystemServer();
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800151 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000152 if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) {
153 throw new BadCertificateFormatException(e.getMessage());
154 }
155 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800156 }
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800157 }
158
159 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800160 * Returns data necessary to store all recoverable keys for given account. Key material is
161 * encrypted with user secret and recovery public key.
162 *
163 * @param account specific to Recovery agent.
164 * @return Data necessary to recover keystore.
Robert Berrya16cd592018-01-17 14:43:09 +0000165 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
166 * service.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800167 */
Robert Berrya16cd592018-01-17 14:43:09 +0000168 public @NonNull KeychainSnapshot getRecoveryData(@NonNull byte[] account)
169 throws InternalRecoveryServiceException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800170 try {
Dmitry Dementyevb4fb9872018-01-26 11:49:34 -0800171 return BackwardsCompat.toLegacyKeychainSnapshot(mBinder.getKeyChainSnapshot());
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800172 } catch (RemoteException e) {
173 throw e.rethrowFromSystemServer();
174 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000175 if (e.errorCode == ERROR_NO_SNAPSHOT_PENDING) {
176 return null;
177 }
178 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800179 }
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800180 }
181
182 /**
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800183 * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
184 * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
185 * most one registered listener at any time.
186 *
187 * @param intent triggered when new snapshot is available. Unregisters listener if the value is
188 * {@code null}.
Robert Berrya16cd592018-01-17 14:43:09 +0000189 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
190 * service.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800191 */
192 public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
Robert Berrya16cd592018-01-17 14:43:09 +0000193 throws InternalRecoveryServiceException {
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800194 try {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800195 mBinder.setSnapshotCreatedPendingIntent(intent);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800196 } catch (RemoteException e) {
197 throw e.rethrowFromSystemServer();
198 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000199 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800200 }
201 }
202
203 /**
204 * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
205 * version. Version zero is used, if no snapshots were created for the account.
206 *
207 * @return Map from recovery agent accounts to snapshot versions.
Robert Berry5f138702018-01-17 15:18:05 +0000208 * @see KeychainSnapshot#getSnapshotVersion
Robert Berrya16cd592018-01-17 14:43:09 +0000209 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
210 * service.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800211 */
212 public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
Robert Berrya16cd592018-01-17 14:43:09 +0000213 throws InternalRecoveryServiceException {
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800214 try {
215 // IPC doesn't support generic Maps.
216 @SuppressWarnings("unchecked")
217 Map<byte[], Integer> result =
Dmitry Dementyev14298312018-01-04 15:19:19 -0800218 (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions();
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800219 return result;
220 } catch (RemoteException e) {
221 throw e.rethrowFromSystemServer();
222 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000223 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800224 }
225 }
226
227 /**
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800228 * Server parameters used to generate new recovery key blobs. This value will be included in
Robert Berry5f138702018-01-17 15:18:05 +0000229 * {@code KeychainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included
Bo Zhu584b923f2017-12-22 16:05:15 -0800230 * in vaultParams {@link #startRecoverySession}
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800231 *
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800232 * @param serverParams included in recovery key blob.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800233 * @see #getRecoveryData
Robert Berrya16cd592018-01-17 14:43:09 +0000234 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
235 * service.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800236 */
Robert Berrya16cd592018-01-17 14:43:09 +0000237 public void setServerParams(byte[] serverParams) throws InternalRecoveryServiceException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800238 try {
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800239 mBinder.setServerParams(serverParams);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800240 } catch (RemoteException e) {
241 throw e.rethrowFromSystemServer();
242 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000243 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800244 }
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800245 }
246
247 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800248 * Updates recovery status for given keys. It is used to notify keystore that key was
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800249 * successfully stored on the server or there were an error. Application can check this value
250 * using {@code getRecoveyStatus}.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800251 *
252 * @param packageName Application whose recoverable keys' statuses are to be updated.
253 * @param aliases List of application-specific key aliases. If the array is empty, updates the
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800254 * status for all existing recoverable keys.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800255 * @param status Status specific to recovery agent.
Robert Berrya16cd592018-01-17 14:43:09 +0000256 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
257 * service.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800258 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800259 public void setRecoveryStatus(
260 @NonNull String packageName, @Nullable String[] aliases, int status)
Robert Berrya16cd592018-01-17 14:43:09 +0000261 throws NameNotFoundException, InternalRecoveryServiceException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800262 try {
Robert Berrybbe02ae2018-02-20 19:47:43 +0000263 for (String alias : aliases) {
264 mBinder.setRecoveryStatus(alias, status);
265 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800266 } catch (RemoteException e) {
267 throw e.rethrowFromSystemServer();
268 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000269 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800270 }
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800271 }
272
273 /**
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800274 * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
275 * Negative status values are reserved for recovery agent specific codes. List of common codes:
276 *
277 * <ul>
278 * <li>{@link #RECOVERY_STATUS_SYNCED}
279 * <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
280 * <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
281 * <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
282 * </ul>
283 *
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800284 * @return {@code Map} from KeyStore alias to recovery status.
285 * @see #setRecoveryStatus
Robert Berrya16cd592018-01-17 14:43:09 +0000286 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
287 * service.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800288 */
Robert Berrya16cd592018-01-17 14:43:09 +0000289 public Map<String, Integer> getRecoveryStatus() throws InternalRecoveryServiceException {
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800290 try {
291 // IPC doesn't support generic Maps.
292 @SuppressWarnings("unchecked")
293 Map<String, Integer> result =
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800294 (Map<String, Integer>) mBinder.getRecoveryStatus(/*packageName=*/ null);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800295 return result;
296 } catch (RemoteException e) {
297 throw e.rethrowFromSystemServer();
298 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000299 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800300 }
301 }
302
303 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800304 * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
305 * is necessary to recover data.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800306 *
Robert Berry9e1bd362018-01-17 23:28:45 +0000307 * @param secretTypes {@link KeychainProtectionParams#TYPE_LOCKSCREEN} or {@link
308 * KeychainProtectionParams#TYPE_CUSTOM_PASSWORD}
Robert Berrya16cd592018-01-17 14:43:09 +0000309 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
310 * service.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800311 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800312 public void setRecoverySecretTypes(
Robert Berry9e1bd362018-01-17 23:28:45 +0000313 @NonNull @KeychainProtectionParams.UserSecretType int[] secretTypes)
Robert Berrya16cd592018-01-17 14:43:09 +0000314 throws InternalRecoveryServiceException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800315 try {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800316 mBinder.setRecoverySecretTypes(secretTypes);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800317 } catch (RemoteException e) {
318 throw e.rethrowFromSystemServer();
319 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000320 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800321 }
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800322 }
323
324 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800325 * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
Robert Berry5f138702018-01-17 15:18:05 +0000326 * necessary to generate KeychainSnapshot.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800327 *
328 * @return list of recovery secret types
Robert Berry5f138702018-01-17 15:18:05 +0000329 * @see KeychainSnapshot
Robert Berrya16cd592018-01-17 14:43:09 +0000330 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
331 * service.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800332 */
Robert Berry9e1bd362018-01-17 23:28:45 +0000333 public @NonNull @KeychainProtectionParams.UserSecretType int[] getRecoverySecretTypes()
Robert Berrya16cd592018-01-17 14:43:09 +0000334 throws InternalRecoveryServiceException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800335 try {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800336 return mBinder.getRecoverySecretTypes();
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800337 } catch (RemoteException e) {
338 throw e.rethrowFromSystemServer();
339 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000340 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800341 }
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800342 }
343
344 /**
345 * Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800346 * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
347 * called.
348 *
349 * @return list of recovery secret types
Robert Berrya16cd592018-01-17 14:43:09 +0000350 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
351 * service.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800352 */
Robert Berry5f138702018-01-17 15:18:05 +0000353 @NonNull
Robert Berry9e1bd362018-01-17 23:28:45 +0000354 public @KeychainProtectionParams.UserSecretType int[] getPendingRecoverySecretTypes()
Robert Berrya16cd592018-01-17 14:43:09 +0000355 throws InternalRecoveryServiceException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800356 try {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800357 return mBinder.getPendingRecoverySecretTypes();
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800358 } catch (RemoteException e) {
359 throw e.rethrowFromSystemServer();
360 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000361 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800362 }
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800363 }
364
365 /**
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800366 * Initializes recovery session and returns a blob with proof of recovery secrets possession.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800367 * The method generates symmetric key for a session, which trusted remote device can use to
368 * return recovery key.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800369 *
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800370 * @param verifierPublicKey Encoded {@code java.security.cert.X509Certificate} with Public key
371 * used to create the recovery blob on the source device.
372 * Keystore will verify the certificate using root of trust.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800373 * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800374 * Used to limit number of guesses.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800375 * @param vaultChallenge Data passed from server for this recovery session and used to prevent
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800376 * replay attacks
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800377 * @param secrets Secrets provided by user, the method only uses type and secret fields.
Robert Berry2bcdad92018-01-18 12:53:29 +0000378 * @return The recovery claim. Claim provides a b binary blob with recovery claim. It is
379 * encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric
380 * key and parameters necessary to identify the counter with the number of failed recovery
381 * attempts.
Robert Berrya16cd592018-01-17 14:43:09 +0000382 * @throws BadCertificateFormatException if the {@code verifierPublicKey} is in an incorrect
383 * format.
384 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
385 * service.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800386 */
Robert Berry2bcdad92018-01-18 12:53:29 +0000387 @NonNull public RecoveryClaim startRecoverySession(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800388 @NonNull byte[] verifierPublicKey,
389 @NonNull byte[] vaultParams,
390 @NonNull byte[] vaultChallenge,
Robert Berry9e1bd362018-01-17 23:28:45 +0000391 @NonNull List<KeychainProtectionParams> secrets)
Robert Berrya16cd592018-01-17 14:43:09 +0000392 throws BadCertificateFormatException, InternalRecoveryServiceException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800393 try {
Robert Berry2bcdad92018-01-18 12:53:29 +0000394 RecoverySession recoverySession = RecoverySession.newInstance(this);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800395 byte[] recoveryClaim =
396 mBinder.startRecoverySession(
Robert Berry2bcdad92018-01-18 12:53:29 +0000397 recoverySession.getSessionId(),
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800398 verifierPublicKey,
399 vaultParams,
400 vaultChallenge,
Robert Berry81ee34b2018-01-23 11:59:59 +0000401 BackwardsCompat.fromLegacyKeychainProtectionParams(secrets));
Robert Berry2bcdad92018-01-18 12:53:29 +0000402 return new RecoveryClaim(recoverySession, recoveryClaim);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800403 } catch (RemoteException e) {
404 throw e.rethrowFromSystemServer();
405 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000406 if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) {
407 throw new BadCertificateFormatException(e.getMessage());
408 }
409 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800410 }
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800411 }
412
413 /**
414 * Imports keys.
415 *
Robert Berry2bcdad92018-01-18 12:53:29 +0000416 * @param session Related recovery session, as originally created by invoking
417 * {@link #startRecoverySession(byte[], byte[], byte[], List)}.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800418 * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
419 * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800420 * and session. KeyStore only uses package names from the application info in {@link
Robert Berry5f138702018-01-17 15:18:05 +0000421 * WrappedApplicationKey}. Caller is responsibility to perform certificates check.
Robert Berrybd4c43c2017-12-22 11:35:14 +0000422 * @return Map from alias to raw key material.
Robert Berrya16cd592018-01-17 14:43:09 +0000423 * @throws SessionExpiredException if {@code session} has since been closed.
424 * @throws DecryptionFailedException if unable to decrypt the snapshot.
425 * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service.
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800426 */
Robert Berrybd4c43c2017-12-22 11:35:14 +0000427 public Map<String, byte[]> recoverKeys(
Robert Berry2bcdad92018-01-18 12:53:29 +0000428 @NonNull RecoverySession session,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800429 @NonNull byte[] recoveryKeyBlob,
Robert Berry5f138702018-01-17 15:18:05 +0000430 @NonNull List<WrappedApplicationKey> applicationKeys)
Robert Berrya16cd592018-01-17 14:43:09 +0000431 throws SessionExpiredException, DecryptionFailedException,
432 InternalRecoveryServiceException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800433 try {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000434 return (Map<String, byte[]>) mBinder.recoverKeys(
Robert Berry81ee34b2018-01-23 11:59:59 +0000435 session.getSessionId(),
436 recoveryKeyBlob,
437 BackwardsCompat.fromLegacyWrappedApplicationKeys(applicationKeys));
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800438 } catch (RemoteException e) {
439 throw e.rethrowFromSystemServer();
440 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000441 if (e.errorCode == ERROR_DECRYPTION_FAILED) {
442 throw new DecryptionFailedException(e.getMessage());
443 }
444 if (e.errorCode == ERROR_SESSION_EXPIRED) {
445 throw new SessionExpiredException(e.getMessage());
446 }
447 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800448 }
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800449 }
Robert Berrycfc990a2017-12-22 15:54:30 +0000450
451 /**
Robert Berry2bcdad92018-01-18 12:53:29 +0000452 * Deletes all data associated with {@code session}. Should not be invoked directly but via
453 * {@link RecoverySession#close()}.
454 *
455 * @hide
456 */
457 void closeSession(RecoverySession session) {
458 try {
459 mBinder.closeSession(session.getSessionId());
460 } catch (RemoteException | ServiceSpecificException e) {
461 Log.e(TAG, "Unexpected error trying to close session", e);
462 }
463 }
464
465 /**
Robert Berrycfc990a2017-12-22 15:54:30 +0000466 * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
467 * raw material of the key.
468 *
Dmitry Dementyeve77a24b2018-01-10 12:26:05 -0800469 * @param alias The key alias.
Robert Berrya16cd592018-01-17 14:43:09 +0000470 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
471 * service.
472 * @throws LockScreenRequiredException if the user has not set a lock screen. This is required
473 * to generate recoverable keys, as the snapshots are encrypted using a key derived from the
474 * lock screen.
Robert Berrycfc990a2017-12-22 15:54:30 +0000475 */
Dmitry Dementyeve77a24b2018-01-10 12:26:05 -0800476 public byte[] generateAndStoreKey(@NonNull String alias)
Robert Berrya16cd592018-01-17 14:43:09 +0000477 throws InternalRecoveryServiceException, LockScreenRequiredException {
Robert Berrycfc990a2017-12-22 15:54:30 +0000478 try {
479 return mBinder.generateAndStoreKey(alias);
480 } catch (RemoteException e) {
481 throw e.rethrowFromSystemServer();
482 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000483 if (e.errorCode == ERROR_INSECURE_USER) {
484 throw new LockScreenRequiredException(e.getMessage());
485 }
486 throw wrapUnexpectedServiceSpecificException(e);
Robert Berrycfc990a2017-12-22 15:54:30 +0000487 }
488 }
Dmitry Dementyeve77a24b2018-01-10 12:26:05 -0800489
490 /**
491 * Removes a key called {@code alias} from the recoverable key store.
492 *
493 * @param alias The key alias.
Robert Berrya16cd592018-01-17 14:43:09 +0000494 * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
495 * service.
Dmitry Dementyeve77a24b2018-01-10 12:26:05 -0800496 */
Robert Berrya16cd592018-01-17 14:43:09 +0000497 public void removeKey(@NonNull String alias) throws InternalRecoveryServiceException {
Dmitry Dementyeve77a24b2018-01-10 12:26:05 -0800498 try {
499 mBinder.removeKey(alias);
500 } catch (RemoteException e) {
501 throw e.rethrowFromSystemServer();
502 } catch (ServiceSpecificException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000503 throw wrapUnexpectedServiceSpecificException(e);
Dmitry Dementyeve77a24b2018-01-10 12:26:05 -0800504 }
505 }
Robert Berrya16cd592018-01-17 14:43:09 +0000506
507 private InternalRecoveryServiceException wrapUnexpectedServiceSpecificException(
508 ServiceSpecificException e) {
509 if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) {
510 return new InternalRecoveryServiceException(e.getMessage());
511 }
512
513 // Should never happen. If it does, it's a bug, and we need to update how the method that
514 // called this throws its exceptions.
515 return new InternalRecoveryServiceException("Unexpected error code for method: "
516 + e.errorCode, e);
517 }
Dmitry Dementyev8eaf6072017-12-06 19:05:33 -0800518}