blob: 76508d5817e299acfc4423d541ab8d5b53aa2008 [file] [log] [blame]
Dmitry Dementyev1aa96132017-12-11 11:33:12 -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
17package com.android.server.locksettings.recoverablekeystore;
18
Robert Berry74928a12018-01-18 17:49:07 +000019import static android.security.keystore.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
20import static android.security.keystore.RecoveryController.ERROR_DECRYPTION_FAILED;
21import static android.security.keystore.RecoveryController.ERROR_INSECURE_USER;
22import static android.security.keystore.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
23import static android.security.keystore.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
24import static android.security.keystore.RecoveryController.ERROR_SESSION_EXPIRED;
Robert Berry97e55582018-01-05 12:43:13 +000025
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080026import android.annotation.NonNull;
27import android.annotation.Nullable;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080028import android.app.PendingIntent;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080029import android.content.Context;
Dmitry Dementyeved89ea02018-01-11 13:53:52 -080030import android.Manifest;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080031import android.os.Binder;
32import android.os.RemoteException;
33import android.os.ServiceSpecificException;
34import android.os.UserHandle;
35
Robert Berry9e1bd362018-01-17 23:28:45 +000036import android.security.keystore.KeychainProtectionParams;
Robert Berry5f138702018-01-17 15:18:05 +000037import android.security.keystore.KeychainSnapshot;
Robert Berry74928a12018-01-18 17:49:07 +000038import android.security.keystore.RecoveryController;
Robert Berry5f138702018-01-17 15:18:05 +000039import android.security.keystore.WrappedApplicationKey;
Robert Berry4a534ec2017-12-21 15:44:02 +000040import android.util.Log;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080041
42import com.android.internal.annotations.VisibleForTesting;
Robert Berrye16fa982017-12-20 15:59:37 +000043import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
44import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
Robert Berrybd086f12017-12-27 13:29:39 +000045import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080046
Robert Berrye16fa982017-12-20 15:59:37 +000047import java.security.InvalidKeyException;
Robert Berry4a534ec2017-12-21 15:44:02 +000048import java.security.KeyStoreException;
Bo Zhu5b81fa62017-12-21 14:36:11 -080049import java.security.KeyFactory;
Robert Berrye16fa982017-12-20 15:59:37 +000050import java.security.NoSuchAlgorithmException;
51import java.security.PublicKey;
Robert Berrycfc990a2017-12-22 15:54:30 +000052import java.security.UnrecoverableKeyException;
Robert Berrye16fa982017-12-20 15:59:37 +000053import java.security.spec.InvalidKeySpecException;
Bo Zhu5b81fa62017-12-21 14:36:11 -080054import java.security.spec.X509EncodedKeySpec;
Bo Zhudef7ffd2018-01-05 14:50:52 -080055import java.util.Arrays;
Robert Berrybd4c43c2017-12-22 11:35:14 +000056import java.util.HashMap;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080057import java.util.List;
Robert Berryb9a220b2017-12-21 12:41:01 +000058import java.util.Locale;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080059import java.util.Map;
Robert Berry4a534ec2017-12-21 15:44:02 +000060import java.util.concurrent.ExecutorService;
61import java.util.concurrent.Executors;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080062
Robert Berryb9a220b2017-12-21 12:41:01 +000063import javax.crypto.AEADBadTagException;
64
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080065/**
Robert Berry74928a12018-01-18 17:49:07 +000066 * Class with {@link RecoveryController} API implementation and internal methods to interact
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080067 * with {@code LockSettingsService}.
68 *
69 * @hide
70 */
71public class RecoverableKeyStoreManager {
Robert Berry4a534ec2017-12-21 15:44:02 +000072 private static final String TAG = "RecoverableKeyStoreMgr";
Robert Berrycfc990a2017-12-22 15:54:30 +000073
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080074 private static RecoverableKeyStoreManager mInstance;
Robert Berrye16fa982017-12-20 15:59:37 +000075
76 private final Context mContext;
77 private final RecoverableKeyStoreDb mDatabase;
78 private final RecoverySessionStorage mRecoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000079 private final ExecutorService mExecutorService;
Robert Berry91044042017-12-27 12:05:58 +000080 private final RecoverySnapshotListenersStorage mListenersStorage;
Robert Berrycfc990a2017-12-22 15:54:30 +000081 private final RecoverableKeyGenerator mRecoverableKeyGenerator;
Robert Berrybd086f12017-12-27 13:29:39 +000082 private final RecoverySnapshotStorage mSnapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -080083 private final PlatformKeyManager mPlatformKeyManager;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080084
85 /**
86 * Returns a new or existing instance.
87 *
88 * @hide
89 */
Bo Zhu3462c832018-01-04 22:42:36 -080090 public static synchronized RecoverableKeyStoreManager getInstance(Context context) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080091 if (mInstance == null) {
Bo Zhu3462c832018-01-04 22:42:36 -080092 RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
93 PlatformKeyManager platformKeyManager;
94 try {
95 platformKeyManager = PlatformKeyManager.getInstance(context, db);
96 } catch (NoSuchAlgorithmException e) {
97 // Impossible: all algorithms must be supported by AOSP
98 throw new RuntimeException(e);
99 } catch (KeyStoreException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000100 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhu3462c832018-01-04 22:42:36 -0800101 }
102
Robert Berrye16fa982017-12-20 15:59:37 +0000103 mInstance = new RecoverableKeyStoreManager(
Bo Zhu3462c832018-01-04 22:42:36 -0800104 context.getApplicationContext(),
Robert Berrye16fa982017-12-20 15:59:37 +0000105 db,
Robert Berry4a534ec2017-12-21 15:44:02 +0000106 new RecoverySessionStorage(),
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800107 Executors.newSingleThreadExecutor(),
Robert Berry91044042017-12-27 12:05:58 +0000108 new RecoverySnapshotStorage(),
Bo Zhu3462c832018-01-04 22:42:36 -0800109 new RecoverySnapshotListenersStorage(),
110 platformKeyManager);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800111 }
112 return mInstance;
113 }
114
115 @VisibleForTesting
Robert Berrye16fa982017-12-20 15:59:37 +0000116 RecoverableKeyStoreManager(
117 Context context,
118 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berry4a534ec2017-12-21 15:44:02 +0000119 RecoverySessionStorage recoverySessionStorage,
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800120 ExecutorService executorService,
Robert Berry91044042017-12-27 12:05:58 +0000121 RecoverySnapshotStorage snapshotStorage,
Bo Zhu3462c832018-01-04 22:42:36 -0800122 RecoverySnapshotListenersStorage listenersStorage,
123 PlatformKeyManager platformKeyManager) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800124 mContext = context;
Robert Berrye16fa982017-12-20 15:59:37 +0000125 mDatabase = recoverableKeyStoreDb;
126 mRecoverySessionStorage = recoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000127 mExecutorService = executorService;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800128 mListenersStorage = listenersStorage;
Robert Berrybd086f12017-12-27 13:29:39 +0000129 mSnapshotStorage = snapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800130 mPlatformKeyManager = platformKeyManager;
131
Robert Berrycfc990a2017-12-22 15:54:30 +0000132 try {
133 mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
134 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000135 Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000136 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000137 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800138 }
139
Bo Zhu5b81fa62017-12-21 14:36:11 -0800140 public void initRecoveryService(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800141 @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800142 throws RemoteException {
143 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800144 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800145 int uid = Binder.getCallingUid();
Bo Zhu5b81fa62017-12-21 14:36:11 -0800146 // TODO: open /system/etc/security/... cert file, and check the signature on the public keys
147 PublicKey publicKey;
148 try {
149 KeyFactory kf = KeyFactory.getInstance("EC");
150 // TODO: Randomly choose a key from the list -- right now we just use the whole input
151 X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(signedPublicKeyList);
152 publicKey = kf.generatePublic(pkSpec);
153 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000154 Log.wtf(TAG, "EC algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000155 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhu5b81fa62017-12-21 14:36:11 -0800156 } catch (InvalidKeySpecException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000157 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000158 ERROR_BAD_CERTIFICATE_FORMAT, "Not a valid X509 certificate.");
Bo Zhu5b81fa62017-12-21 14:36:11 -0800159 }
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800160 long updatedRows = mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey);
161 if (updatedRows > 0) {
162 mDatabase.setShouldCreateSnapshot(userId, uid, true);
163 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800164 }
165
166 /**
167 * Gets all data necessary to recover application keys on new device.
168 *
169 * @return recovery data
170 * @hide
171 */
Robert Berry5f138702018-01-17 15:18:05 +0000172 public @NonNull
173 KeychainSnapshot getRecoveryData(@NonNull byte[] account)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800174 throws RemoteException {
175 checkRecoverKeyStorePermission();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800176 int uid = Binder.getCallingUid();
Robert Berry5f138702018-01-17 15:18:05 +0000177 KeychainSnapshot snapshot = mSnapshotStorage.get(uid);
Robert Berrybd086f12017-12-27 13:29:39 +0000178 if (snapshot == null) {
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800179 throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
Robert Berrybd086f12017-12-27 13:29:39 +0000180 }
181 return snapshot;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800182 }
183
Dmitry Dementyev14298312018-01-04 15:19:19 -0800184 public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800185 throws RemoteException {
186 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800187 int uid = Binder.getCallingUid();
188 mListenersStorage.setSnapshotListener(uid, intent);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800189 }
190
191 /**
192 * Gets recovery snapshot versions for all accounts. Note that snapshot may have 0 application
193 * keys, but it still needs to be synced, if previous versions were not empty.
194 *
195 * @return Map from Recovery agent account to snapshot version.
196 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800197 public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800198 throws RemoteException {
199 checkRecoverKeyStorePermission();
200 throw new UnsupportedOperationException();
201 }
202
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800203 public void setServerParams(byte[] serverParams) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800204 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800205 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800206 int uid = Binder.getCallingUid();
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800207 long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800208 if (updatedRows > 0) {
209 mDatabase.setShouldCreateSnapshot(userId, uid, true);
210 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800211 }
212
Dmitry Dementyevad884712017-12-20 12:38:36 -0800213 /**
214 * Updates recovery status for the application given its {@code packageName}.
215 *
216 * @param packageName which recoverable key statuses will be returned
217 * @param aliases - KeyStore aliases or {@code null} for all aliases of the app
218 * @param status - new status
219 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800220 public void setRecoveryStatus(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800221 @NonNull String packageName, @Nullable String[] aliases, int status)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800222 throws RemoteException {
223 checkRecoverKeyStorePermission();
Dmitry Dementyevad884712017-12-20 12:38:36 -0800224 int uid = Binder.getCallingUid();
225 if (packageName != null) {
226 // TODO: get uid for package name, when many apps are supported.
227 }
228 if (aliases == null) {
229 // Get all keys for the app.
230 Map<String, Integer> allKeys = mDatabase.getStatusForAllKeys(uid);
231 aliases = new String[allKeys.size()];
232 allKeys.keySet().toArray(aliases);
233 }
234 for (String alias: aliases) {
235 mDatabase.setRecoveryStatus(uid, alias, status);
236 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800237 }
238
239 /**
Dmitry Dementyevad884712017-12-20 12:38:36 -0800240 * Gets recovery status for caller or other application {@code packageName}.
241 * @param packageName which recoverable keys statuses will be returned.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800242 *
Dmitry Dementyevad884712017-12-20 12:38:36 -0800243 * @return {@code Map} from KeyStore alias to recovery status.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800244 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800245 public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800246 throws RemoteException {
247 // Any application should be able to check status for its own keys.
248 // If caller is a recovery agent it can check statuses for other packages, but
249 // only for recoverable keys it manages.
Dmitry Dementyevad884712017-12-20 12:38:36 -0800250 return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800251 }
252
253 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800254 * Sets recovery secrets list used by all recovery agents for given {@code userId}
255 *
256 * @hide
257 */
258 public void setRecoverySecretTypes(
Robert Berry9e1bd362018-01-17 23:28:45 +0000259 @NonNull @KeychainProtectionParams.UserSecretType int[] secretTypes)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800260 throws RemoteException {
261 checkRecoverKeyStorePermission();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800262 int userId = UserHandle.getCallingUserId();
263 int uid = Binder.getCallingUid();
264 long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes);
265 if (updatedRows > 0) {
266 mDatabase.setShouldCreateSnapshot(userId, uid, true);
267 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800268 }
269
270 /**
271 * Gets secret types necessary to create Recovery Data.
272 *
273 * @return secret types
274 * @hide
275 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800276 public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800277 checkRecoverKeyStorePermission();
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800278 return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
279 Binder.getCallingUid());
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800280 }
281
282 /**
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800283 * Gets secret types RecoveryManagers is waiting for to create new Recovery Data.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800284 *
285 * @return secret types
286 * @hide
287 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800288 public @NonNull int[] getPendingRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800289 checkRecoverKeyStorePermission();
290 throw new UnsupportedOperationException();
291 }
292
293 public void recoverySecretAvailable(
Robert Berry9e1bd362018-01-17 23:28:45 +0000294 @NonNull KeychainProtectionParams recoverySecret) throws RemoteException {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800295 int uid = Binder.getCallingUid();
Robert Berry9e1bd362018-01-17 23:28:45 +0000296 if (recoverySecret.getLockScreenUiFormat() == KeychainProtectionParams.TYPE_LOCKSCREEN) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800297 throw new SecurityException(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800298 "Caller " + uid + " is not allowed to set lock screen secret");
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800299 }
300 checkRecoverKeyStorePermission();
301 // TODO: add hook from LockSettingsService to set lock screen secret.
302 throw new UnsupportedOperationException();
303 }
304
305 /**
306 * Initializes recovery session.
307 *
Robert Berrye16fa982017-12-20 15:59:37 +0000308 * @param sessionId A unique ID to identify the recovery session.
309 * @param verifierPublicKey X509-encoded public key.
310 * @param vaultParams Additional params associated with vault.
311 * @param vaultChallenge Challenge issued by vault service.
Robert Berryb9a220b2017-12-21 12:41:01 +0000312 * @param secrets Lock-screen hashes. For now only a single secret is supported.
Robert Berrye16fa982017-12-20 15:59:37 +0000313 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
314 *
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800315 * @hide
316 */
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800317 public @NonNull byte[] startRecoverySession(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800318 @NonNull String sessionId,
319 @NonNull byte[] verifierPublicKey,
320 @NonNull byte[] vaultParams,
321 @NonNull byte[] vaultChallenge,
Robert Berry9e1bd362018-01-17 23:28:45 +0000322 @NonNull List<KeychainProtectionParams> secrets)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800323 throws RemoteException {
324 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800325 int uid = Binder.getCallingUid();
Robert Berrye16fa982017-12-20 15:59:37 +0000326
327 if (secrets.size() != 1) {
Robert Berry9e1bd362018-01-17 23:28:45 +0000328 throw new UnsupportedOperationException(
329 "Only a single KeychainProtectionParams is supported");
Robert Berrye16fa982017-12-20 15:59:37 +0000330 }
331
Bo Zhudef7ffd2018-01-05 14:50:52 -0800332 PublicKey publicKey;
333 try {
334 publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
335 } catch (NoSuchAlgorithmException e) {
336 // Should never happen
337 throw new RuntimeException(e);
338 } catch (InvalidKeySpecException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000339 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, "Not a valid X509 key");
Bo Zhudef7ffd2018-01-05 14:50:52 -0800340 }
341 // The raw public key bytes contained in vaultParams must match the ones given in
342 // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned
343 // by the original recovery service.
344 if (!publicKeysMatch(publicKey, vaultParams)) {
Robert Berrya16cd592018-01-17 14:43:09 +0000345 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
Bo Zhudef7ffd2018-01-05 14:50:52 -0800346 "The public keys given in verifierPublicKey and vaultParams do not match.");
347 }
348
Robert Berrye16fa982017-12-20 15:59:37 +0000349 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
350 byte[] kfHash = secrets.get(0).getSecret();
351 mRecoverySessionStorage.add(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800352 uid,
Robert Berryb9a220b2017-12-21 12:41:01 +0000353 new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
Robert Berrye16fa982017-12-20 15:59:37 +0000354
355 try {
356 byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
Robert Berrye16fa982017-12-20 15:59:37 +0000357 return KeySyncUtils.encryptRecoveryClaim(
358 publicKey,
359 vaultParams,
360 vaultChallenge,
361 thmKfHash,
362 keyClaimant);
363 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000364 Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000365 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhudef7ffd2018-01-05 14:50:52 -0800366 } catch (InvalidKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000367 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000368 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800369 }
370
Robert Berryb9a220b2017-12-21 12:41:01 +0000371 /**
372 * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
373 * service.
374 *
Robert Berryb9a220b2017-12-21 12:41:01 +0000375 * @param sessionId The session ID used to generate the claim. See
Bo Zhudef7ffd2018-01-05 14:50:52 -0800376 * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
Robert Berryb9a220b2017-12-21 12:41:01 +0000377 * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
378 * service.
379 * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
380 * were wrapped with the recovery key.
Robert Berrybd4c43c2017-12-22 11:35:14 +0000381 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000382 * @throws RemoteException if an error occurred recovering the keys.
383 */
Robert Berrybd4c43c2017-12-22 11:35:14 +0000384 public Map<String, byte[]> recoverKeys(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800385 @NonNull String sessionId,
Robert Berryb9a220b2017-12-21 12:41:01 +0000386 @NonNull byte[] encryptedRecoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000387 @NonNull List<WrappedApplicationKey> applicationKeys)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800388 throws RemoteException {
389 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800390 int uid = Binder.getCallingUid();
Robert Berryb9a220b2017-12-21 12:41:01 +0000391 RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
392 if (sessionEntry == null) {
Robert Berrya16cd592018-01-17 14:43:09 +0000393 throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800394 String.format(Locale.US,
395 "Application uid=%d does not have pending session '%s'", uid, sessionId));
Robert Berryb9a220b2017-12-21 12:41:01 +0000396 }
397
398 try {
399 byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
Robert Berrybd4c43c2017-12-22 11:35:14 +0000400 return recoverApplicationKeys(recoveryKey, applicationKeys);
Robert Berryb9a220b2017-12-21 12:41:01 +0000401 } finally {
402 sessionEntry.destroy();
403 mRecoverySessionStorage.remove(uid);
404 }
405 }
406
Robert Berrycfc990a2017-12-22 15:54:30 +0000407 /**
408 * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
409 * returns the raw key material.
410 *
411 * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
412 *
413 * @hide
414 */
415 public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
416 int uid = Binder.getCallingUid();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800417 int userId = UserHandle.getCallingUserId();
Robert Berrycfc990a2017-12-22 15:54:30 +0000418
Bo Zhu3462c832018-01-04 22:42:36 -0800419 PlatformEncryptionKey encryptionKey;
Robert Berrycfc990a2017-12-22 15:54:30 +0000420 try {
Bo Zhu3462c832018-01-04 22:42:36 -0800421 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
Robert Berrycfc990a2017-12-22 15:54:30 +0000422 } catch (NoSuchAlgorithmException e) {
423 // Impossible: all algorithms must be supported by AOSP
424 throw new RuntimeException(e);
425 } catch (KeyStoreException | UnrecoverableKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000426 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000427 } catch (InsecureUserException e) {
428 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
429 }
430
431 try {
432 return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
Robert Berrya16cd592018-01-17 14:43:09 +0000433 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
434 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000435 }
436 }
437
Robert Berry2bcdad92018-01-18 12:53:29 +0000438 /**
439 * Destroys the session with the given {@code sessionId}.
440 */
441 public void closeSession(@NonNull String sessionId) throws RemoteException {
442 mRecoverySessionStorage.remove(Binder.getCallingUid(), sessionId);
443 }
444
Robert Berry5daccec2018-01-06 19:16:25 +0000445 public void removeKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800446 int uid = Binder.getCallingUid();
447 int userId = UserHandle.getCallingUserId();
448
449 boolean wasRemoved = mDatabase.removeKey(uid, alias);
450 if (wasRemoved) {
451 mDatabase.setShouldCreateSnapshot(userId, uid, true);
452 }
Robert Berry5daccec2018-01-06 19:16:25 +0000453 }
454
Robert Berryb9a220b2017-12-21 12:41:01 +0000455 private byte[] decryptRecoveryKey(
456 RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
Dmitry Dementyev14298312018-01-04 15:19:19 -0800457 throws RemoteException, ServiceSpecificException {
Robert Berryb9a220b2017-12-21 12:41:01 +0000458 try {
459 byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
460 sessionEntry.getKeyClaimant(),
461 sessionEntry.getVaultParams(),
462 encryptedClaimResponse);
463 return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
464 } catch (InvalidKeyException | AEADBadTagException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000465 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800466 "Failed to decrypt recovery key " + e.getMessage());
467
Robert Berryb9a220b2017-12-21 12:41:01 +0000468 } catch (NoSuchAlgorithmException e) {
469 // Should never happen: all the algorithms used are required by AOSP implementations
Robert Berrya16cd592018-01-17 14:43:09 +0000470 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000471 }
472 }
473
474 /**
475 * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
476 *
Robert Berrybd4c43c2017-12-22 11:35:14 +0000477 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000478 * @throws RemoteException if an error occurred decrypting the keys.
479 */
Robert Berrybd4c43c2017-12-22 11:35:14 +0000480 private Map<String, byte[]> recoverApplicationKeys(
Robert Berryb9a220b2017-12-21 12:41:01 +0000481 @NonNull byte[] recoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000482 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000483 HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
Robert Berry5f138702018-01-17 15:18:05 +0000484 for (WrappedApplicationKey applicationKey : applicationKeys) {
Dmitry Dementyev07c765552018-01-08 17:31:59 -0800485 String alias = applicationKey.getAlias();
Robert Berryb9a220b2017-12-21 12:41:01 +0000486 byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
487
488 try {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000489 byte[] keyMaterial =
490 KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
491 keyMaterialByAlias.put(alias, keyMaterial);
Robert Berryb9a220b2017-12-21 12:41:01 +0000492 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000493 Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
494 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000495 ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000496 } catch (InvalidKeyException | AEADBadTagException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000497 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
498 "Failed to recover key with alias '" + alias + "': " + e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000499 }
500 }
Robert Berrybd4c43c2017-12-22 11:35:14 +0000501 return keyMaterialByAlias;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800502 }
503
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800504 /**
505 * This function can only be used inside LockSettingsService.
506 *
Robert Berry91044042017-12-27 12:05:58 +0000507 * @param storedHashType from {@code CredentialHash}
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800508 * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
509 * mPasswordMaxLength}
510 * @param userId for user who just unlocked the device.
511 * @hide
512 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800513 public void lockScreenSecretAvailable(
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800514 int storedHashType, @NonNull String credential, int userId) {
Robert Berry4a534ec2017-12-21 15:44:02 +0000515 // So as not to block the critical path unlocking the phone, defer to another thread.
516 try {
517 mExecutorService.execute(KeySyncTask.newInstance(
Robert Berry91044042017-12-27 12:05:58 +0000518 mContext,
519 mDatabase,
520 mSnapshotStorage,
521 mListenersStorage,
522 userId,
523 storedHashType,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800524 credential,
525 /*credentialUpdated=*/ false));
Robert Berry4a534ec2017-12-21 15:44:02 +0000526 } catch (NoSuchAlgorithmException e) {
527 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
528 } catch (KeyStoreException e) {
529 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
530 } catch (InsecureUserException e) {
531 Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800532 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800533 }
534
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800535 /**
536 * This function can only be used inside LockSettingsService.
537 * @param storedHashType from {@code CredentialHash}
538 * @param credential - unencrypted String
539 * @param userId for the user whose lock screen credentials were changed.
540 * @hide
541 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800542 public void lockScreenSecretChanged(
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800543 int storedHashType,
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800544 @Nullable String credential,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800545 int userId) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800546 // So as not to block the critical path unlocking the phone, defer to another thread.
547 try {
548 mExecutorService.execute(KeySyncTask.newInstance(
549 mContext,
550 mDatabase,
551 mSnapshotStorage,
552 mListenersStorage,
553 userId,
554 storedHashType,
555 credential,
556 /*credentialUpdated=*/ true));
557 } catch (NoSuchAlgorithmException e) {
558 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
559 } catch (KeyStoreException e) {
560 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
561 } catch (InsecureUserException e) {
562 Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
563 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800564 }
565
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800566 private void checkRecoverKeyStorePermission() {
567 mContext.enforceCallingOrSelfPermission(
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800568 Manifest.permission.RECOVER_KEYSTORE,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800569 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
570 }
Bo Zhudef7ffd2018-01-05 14:50:52 -0800571
572 private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
573 byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
574 return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length));
575 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800576}