blob: 0d567d1e971d6cf402b9f50b6efb1d6b18c3aee9 [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
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080036import android.security.keystore.recovery.KeyChainProtectionParams;
37import android.security.keystore.recovery.KeyChainSnapshot;
Robert Berry81ee34b2018-01-23 11:59:59 +000038import android.security.keystore.recovery.RecoveryController;
39import android.security.keystore.recovery.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;
Bo Zhu31a40c02018-01-24 17:40:29 -080043import com.android.internal.util.HexDump;
Robert Berrye16fa982017-12-20 15:59:37 +000044import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
45import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
Robert Berrybd086f12017-12-27 13:29:39 +000046import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080047
Robert Berrye16fa982017-12-20 15:59:37 +000048import java.security.InvalidKeyException;
Robert Berry4a534ec2017-12-21 15:44:02 +000049import java.security.KeyStoreException;
Bo Zhu5b81fa62017-12-21 14:36:11 -080050import java.security.KeyFactory;
Robert Berrye16fa982017-12-20 15:59:37 +000051import java.security.NoSuchAlgorithmException;
52import java.security.PublicKey;
Robert Berrycfc990a2017-12-22 15:54:30 +000053import java.security.UnrecoverableKeyException;
Robert Berrye16fa982017-12-20 15:59:37 +000054import java.security.spec.InvalidKeySpecException;
Bo Zhu5b81fa62017-12-21 14:36:11 -080055import java.security.spec.X509EncodedKeySpec;
Bo Zhudef7ffd2018-01-05 14:50:52 -080056import java.util.Arrays;
Robert Berrybd4c43c2017-12-22 11:35:14 +000057import java.util.HashMap;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080058import java.util.List;
Robert Berryb9a220b2017-12-21 12:41:01 +000059import java.util.Locale;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080060import java.util.Map;
Robert Berry4a534ec2017-12-21 15:44:02 +000061import java.util.concurrent.ExecutorService;
62import java.util.concurrent.Executors;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080063
Robert Berryb9a220b2017-12-21 12:41:01 +000064import javax.crypto.AEADBadTagException;
65
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080066/**
Robert Berry74928a12018-01-18 17:49:07 +000067 * Class with {@link RecoveryController} API implementation and internal methods to interact
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080068 * with {@code LockSettingsService}.
69 *
70 * @hide
71 */
72public class RecoverableKeyStoreManager {
Robert Berry4a534ec2017-12-21 15:44:02 +000073 private static final String TAG = "RecoverableKeyStoreMgr";
Robert Berrycfc990a2017-12-22 15:54:30 +000074
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080075 private static RecoverableKeyStoreManager mInstance;
Robert Berrye16fa982017-12-20 15:59:37 +000076
77 private final Context mContext;
78 private final RecoverableKeyStoreDb mDatabase;
79 private final RecoverySessionStorage mRecoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000080 private final ExecutorService mExecutorService;
Robert Berry91044042017-12-27 12:05:58 +000081 private final RecoverySnapshotListenersStorage mListenersStorage;
Robert Berrycfc990a2017-12-22 15:54:30 +000082 private final RecoverableKeyGenerator mRecoverableKeyGenerator;
Robert Berrybd086f12017-12-27 13:29:39 +000083 private final RecoverySnapshotStorage mSnapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -080084 private final PlatformKeyManager mPlatformKeyManager;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080085
86 /**
87 * Returns a new or existing instance.
88 *
89 * @hide
90 */
Bo Zhu3462c832018-01-04 22:42:36 -080091 public static synchronized RecoverableKeyStoreManager getInstance(Context context) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080092 if (mInstance == null) {
Bo Zhu3462c832018-01-04 22:42:36 -080093 RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
94 PlatformKeyManager platformKeyManager;
95 try {
96 platformKeyManager = PlatformKeyManager.getInstance(context, db);
97 } catch (NoSuchAlgorithmException e) {
98 // Impossible: all algorithms must be supported by AOSP
99 throw new RuntimeException(e);
100 } catch (KeyStoreException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000101 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhu3462c832018-01-04 22:42:36 -0800102 }
103
Robert Berrye16fa982017-12-20 15:59:37 +0000104 mInstance = new RecoverableKeyStoreManager(
Bo Zhu3462c832018-01-04 22:42:36 -0800105 context.getApplicationContext(),
Robert Berrye16fa982017-12-20 15:59:37 +0000106 db,
Robert Berry4a534ec2017-12-21 15:44:02 +0000107 new RecoverySessionStorage(),
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800108 Executors.newSingleThreadExecutor(),
Robert Berry91044042017-12-27 12:05:58 +0000109 new RecoverySnapshotStorage(),
Bo Zhu3462c832018-01-04 22:42:36 -0800110 new RecoverySnapshotListenersStorage(),
111 platformKeyManager);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800112 }
113 return mInstance;
114 }
115
116 @VisibleForTesting
Robert Berrye16fa982017-12-20 15:59:37 +0000117 RecoverableKeyStoreManager(
118 Context context,
119 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berry4a534ec2017-12-21 15:44:02 +0000120 RecoverySessionStorage recoverySessionStorage,
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800121 ExecutorService executorService,
Robert Berry91044042017-12-27 12:05:58 +0000122 RecoverySnapshotStorage snapshotStorage,
Bo Zhu3462c832018-01-04 22:42:36 -0800123 RecoverySnapshotListenersStorage listenersStorage,
124 PlatformKeyManager platformKeyManager) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800125 mContext = context;
Robert Berrye16fa982017-12-20 15:59:37 +0000126 mDatabase = recoverableKeyStoreDb;
127 mRecoverySessionStorage = recoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000128 mExecutorService = executorService;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800129 mListenersStorage = listenersStorage;
Robert Berrybd086f12017-12-27 13:29:39 +0000130 mSnapshotStorage = snapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800131 mPlatformKeyManager = platformKeyManager;
132
Robert Berrycfc990a2017-12-22 15:54:30 +0000133 try {
134 mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
135 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000136 Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000137 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000138 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800139 }
140
Bo Zhu5b81fa62017-12-21 14:36:11 -0800141 public void initRecoveryService(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800142 @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800143 throws RemoteException {
144 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800145 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800146 int uid = Binder.getCallingUid();
Bo Zhu5b81fa62017-12-21 14:36:11 -0800147 // TODO: open /system/etc/security/... cert file, and check the signature on the public keys
148 PublicKey publicKey;
149 try {
150 KeyFactory kf = KeyFactory.getInstance("EC");
151 // TODO: Randomly choose a key from the list -- right now we just use the whole input
152 X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(signedPublicKeyList);
153 publicKey = kf.generatePublic(pkSpec);
154 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000155 Log.wtf(TAG, "EC algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000156 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhu5b81fa62017-12-21 14:36:11 -0800157 } catch (InvalidKeySpecException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000158 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000159 ERROR_BAD_CERTIFICATE_FORMAT, "Not a valid X509 certificate.");
Bo Zhu5b81fa62017-12-21 14:36:11 -0800160 }
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800161 long updatedRows = mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey);
162 if (updatedRows > 0) {
163 mDatabase.setShouldCreateSnapshot(userId, uid, true);
164 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800165 }
166
167 /**
168 * Gets all data necessary to recover application keys on new device.
169 *
170 * @return recovery data
171 * @hide
172 */
Robert Berry5f138702018-01-17 15:18:05 +0000173 public @NonNull
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800174 KeyChainSnapshot getRecoveryData(@NonNull byte[] account)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800175 throws RemoteException {
176 checkRecoverKeyStorePermission();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800177 int uid = Binder.getCallingUid();
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800178 KeyChainSnapshot snapshot = mSnapshotStorage.get(uid);
Robert Berrybd086f12017-12-27 13:29:39 +0000179 if (snapshot == null) {
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800180 throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
Robert Berrybd086f12017-12-27 13:29:39 +0000181 }
182 return snapshot;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800183 }
184
Dmitry Dementyev14298312018-01-04 15:19:19 -0800185 public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800186 throws RemoteException {
187 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800188 int uid = Binder.getCallingUid();
189 mListenersStorage.setSnapshotListener(uid, intent);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800190 }
191
192 /**
193 * Gets recovery snapshot versions for all accounts. Note that snapshot may have 0 application
194 * keys, but it still needs to be synced, if previous versions were not empty.
195 *
196 * @return Map from Recovery agent account to snapshot version.
197 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800198 public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800199 throws RemoteException {
200 checkRecoverKeyStorePermission();
201 throw new UnsupportedOperationException();
202 }
203
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800204 public void setServerParams(byte[] serverParams) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800205 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800206 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800207 int uid = Binder.getCallingUid();
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800208 long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800209 if (updatedRows > 0) {
210 mDatabase.setShouldCreateSnapshot(userId, uid, true);
211 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800212 }
213
Dmitry Dementyevad884712017-12-20 12:38:36 -0800214 /**
215 * Updates recovery status for the application given its {@code packageName}.
216 *
217 * @param packageName which recoverable key statuses will be returned
218 * @param aliases - KeyStore aliases or {@code null} for all aliases of the app
219 * @param status - new status
220 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800221 public void setRecoveryStatus(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800222 @NonNull String packageName, @Nullable String[] aliases, int status)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800223 throws RemoteException {
224 checkRecoverKeyStorePermission();
Dmitry Dementyevad884712017-12-20 12:38:36 -0800225 int uid = Binder.getCallingUid();
226 if (packageName != null) {
227 // TODO: get uid for package name, when many apps are supported.
228 }
229 if (aliases == null) {
230 // Get all keys for the app.
231 Map<String, Integer> allKeys = mDatabase.getStatusForAllKeys(uid);
232 aliases = new String[allKeys.size()];
233 allKeys.keySet().toArray(aliases);
234 }
235 for (String alias: aliases) {
236 mDatabase.setRecoveryStatus(uid, alias, status);
237 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800238 }
239
240 /**
Dmitry Dementyevad884712017-12-20 12:38:36 -0800241 * Gets recovery status for caller or other application {@code packageName}.
242 * @param packageName which recoverable keys statuses will be returned.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800243 *
Dmitry Dementyevad884712017-12-20 12:38:36 -0800244 * @return {@code Map} from KeyStore alias to recovery status.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800245 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800246 public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800247 throws RemoteException {
248 // Any application should be able to check status for its own keys.
249 // If caller is a recovery agent it can check statuses for other packages, but
250 // only for recoverable keys it manages.
Dmitry Dementyevad884712017-12-20 12:38:36 -0800251 return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800252 }
253
254 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800255 * Sets recovery secrets list used by all recovery agents for given {@code userId}
256 *
257 * @hide
258 */
259 public void setRecoverySecretTypes(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800260 @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800261 throws RemoteException {
262 checkRecoverKeyStorePermission();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800263 int userId = UserHandle.getCallingUserId();
264 int uid = Binder.getCallingUid();
265 long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes);
266 if (updatedRows > 0) {
267 mDatabase.setShouldCreateSnapshot(userId, uid, true);
268 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800269 }
270
271 /**
272 * Gets secret types necessary to create Recovery Data.
273 *
274 * @return secret types
275 * @hide
276 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800277 public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800278 checkRecoverKeyStorePermission();
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800279 return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
280 Binder.getCallingUid());
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800281 }
282
283 /**
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800284 * Gets secret types RecoveryManagers is waiting for to create new Recovery Data.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800285 *
286 * @return secret types
287 * @hide
288 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800289 public @NonNull int[] getPendingRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800290 checkRecoverKeyStorePermission();
291 throw new UnsupportedOperationException();
292 }
293
294 public void recoverySecretAvailable(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800295 @NonNull KeyChainProtectionParams recoverySecret) throws RemoteException {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800296 int uid = Binder.getCallingUid();
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800297 if (recoverySecret.getLockScreenUiFormat() == KeyChainProtectionParams.TYPE_LOCKSCREEN) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800298 throw new SecurityException(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800299 "Caller " + uid + " is not allowed to set lock screen secret");
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800300 }
301 checkRecoverKeyStorePermission();
302 // TODO: add hook from LockSettingsService to set lock screen secret.
303 throw new UnsupportedOperationException();
304 }
305
306 /**
307 * Initializes recovery session.
308 *
Robert Berrye16fa982017-12-20 15:59:37 +0000309 * @param sessionId A unique ID to identify the recovery session.
310 * @param verifierPublicKey X509-encoded public key.
311 * @param vaultParams Additional params associated with vault.
312 * @param vaultChallenge Challenge issued by vault service.
Robert Berryb9a220b2017-12-21 12:41:01 +0000313 * @param secrets Lock-screen hashes. For now only a single secret is supported.
Robert Berrye16fa982017-12-20 15:59:37 +0000314 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
315 *
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800316 * @hide
317 */
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800318 public @NonNull byte[] startRecoverySession(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800319 @NonNull String sessionId,
320 @NonNull byte[] verifierPublicKey,
321 @NonNull byte[] vaultParams,
322 @NonNull byte[] vaultChallenge,
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800323 @NonNull List<KeyChainProtectionParams> secrets)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800324 throws RemoteException {
325 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800326 int uid = Binder.getCallingUid();
Robert Berrye16fa982017-12-20 15:59:37 +0000327
328 if (secrets.size() != 1) {
Robert Berry9e1bd362018-01-17 23:28:45 +0000329 throw new UnsupportedOperationException(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800330 "Only a single KeyChainProtectionParams is supported");
Robert Berrye16fa982017-12-20 15:59:37 +0000331 }
332
Bo Zhudef7ffd2018-01-05 14:50:52 -0800333 PublicKey publicKey;
334 try {
335 publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
336 } catch (NoSuchAlgorithmException e) {
337 // Should never happen
338 throw new RuntimeException(e);
339 } catch (InvalidKeySpecException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000340 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, "Not a valid X509 key");
Bo Zhudef7ffd2018-01-05 14:50:52 -0800341 }
342 // The raw public key bytes contained in vaultParams must match the ones given in
343 // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned
344 // by the original recovery service.
345 if (!publicKeysMatch(publicKey, vaultParams)) {
Robert Berrya16cd592018-01-17 14:43:09 +0000346 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
Bo Zhudef7ffd2018-01-05 14:50:52 -0800347 "The public keys given in verifierPublicKey and vaultParams do not match.");
348 }
349
Robert Berrye16fa982017-12-20 15:59:37 +0000350 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
351 byte[] kfHash = secrets.get(0).getSecret();
352 mRecoverySessionStorage.add(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800353 uid,
Robert Berryb9a220b2017-12-21 12:41:01 +0000354 new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
Robert Berrye16fa982017-12-20 15:59:37 +0000355
356 try {
357 byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
Robert Berrye16fa982017-12-20 15:59:37 +0000358 return KeySyncUtils.encryptRecoveryClaim(
359 publicKey,
360 vaultParams,
361 vaultChallenge,
362 thmKfHash,
363 keyClaimant);
364 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000365 Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000366 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhudef7ffd2018-01-05 14:50:52 -0800367 } catch (InvalidKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000368 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000369 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800370 }
371
Robert Berryb9a220b2017-12-21 12:41:01 +0000372 /**
373 * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
374 * service.
375 *
Robert Berryb9a220b2017-12-21 12:41:01 +0000376 * @param sessionId The session ID used to generate the claim. See
Bo Zhudef7ffd2018-01-05 14:50:52 -0800377 * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
Robert Berryb9a220b2017-12-21 12:41:01 +0000378 * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
379 * service.
380 * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
381 * were wrapped with the recovery key.
Robert Berrybd4c43c2017-12-22 11:35:14 +0000382 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000383 * @throws RemoteException if an error occurred recovering the keys.
384 */
Robert Berrybd4c43c2017-12-22 11:35:14 +0000385 public Map<String, byte[]> recoverKeys(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800386 @NonNull String sessionId,
Robert Berryb9a220b2017-12-21 12:41:01 +0000387 @NonNull byte[] encryptedRecoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000388 @NonNull List<WrappedApplicationKey> applicationKeys)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800389 throws RemoteException {
390 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800391 int uid = Binder.getCallingUid();
Robert Berryb9a220b2017-12-21 12:41:01 +0000392 RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
393 if (sessionEntry == null) {
Robert Berrya16cd592018-01-17 14:43:09 +0000394 throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800395 String.format(Locale.US,
396 "Application uid=%d does not have pending session '%s'", uid, sessionId));
Robert Berryb9a220b2017-12-21 12:41:01 +0000397 }
398
399 try {
400 byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
Robert Berrybd4c43c2017-12-22 11:35:14 +0000401 return recoverApplicationKeys(recoveryKey, applicationKeys);
Robert Berryb9a220b2017-12-21 12:41:01 +0000402 } finally {
403 sessionEntry.destroy();
404 mRecoverySessionStorage.remove(uid);
405 }
406 }
407
Robert Berrycfc990a2017-12-22 15:54:30 +0000408 /**
409 * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
410 * returns the raw key material.
411 *
412 * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
413 *
414 * @hide
415 */
416 public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
417 int uid = Binder.getCallingUid();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800418 int userId = UserHandle.getCallingUserId();
Robert Berrycfc990a2017-12-22 15:54:30 +0000419
Bo Zhu3462c832018-01-04 22:42:36 -0800420 PlatformEncryptionKey encryptionKey;
Robert Berrycfc990a2017-12-22 15:54:30 +0000421 try {
Bo Zhu3462c832018-01-04 22:42:36 -0800422 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
Robert Berrycfc990a2017-12-22 15:54:30 +0000423 } catch (NoSuchAlgorithmException e) {
424 // Impossible: all algorithms must be supported by AOSP
425 throw new RuntimeException(e);
426 } catch (KeyStoreException | UnrecoverableKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000427 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000428 } catch (InsecureUserException e) {
429 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
430 }
431
432 try {
433 return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
Robert Berrya16cd592018-01-17 14:43:09 +0000434 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
435 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000436 }
437 }
438
Robert Berry2bcdad92018-01-18 12:53:29 +0000439 /**
440 * Destroys the session with the given {@code sessionId}.
441 */
442 public void closeSession(@NonNull String sessionId) throws RemoteException {
443 mRecoverySessionStorage.remove(Binder.getCallingUid(), sessionId);
444 }
445
Robert Berry5daccec2018-01-06 19:16:25 +0000446 public void removeKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800447 int uid = Binder.getCallingUid();
448 int userId = UserHandle.getCallingUserId();
449
450 boolean wasRemoved = mDatabase.removeKey(uid, alias);
451 if (wasRemoved) {
452 mDatabase.setShouldCreateSnapshot(userId, uid, true);
453 }
Robert Berry5daccec2018-01-06 19:16:25 +0000454 }
455
Robert Berryb9a220b2017-12-21 12:41:01 +0000456 private byte[] decryptRecoveryKey(
457 RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
Dmitry Dementyev14298312018-01-04 15:19:19 -0800458 throws RemoteException, ServiceSpecificException {
Bo Zhu31a40c02018-01-24 17:40:29 -0800459 // TODO: Remove the extensive loggings in this function
460 byte[] locallyEncryptedKey;
Robert Berryb9a220b2017-12-21 12:41:01 +0000461 try {
Bo Zhu31a40c02018-01-24 17:40:29 -0800462 locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
Robert Berryb9a220b2017-12-21 12:41:01 +0000463 sessionEntry.getKeyClaimant(),
464 sessionEntry.getVaultParams(),
465 encryptedClaimResponse);
Bo Zhu31a40c02018-01-24 17:40:29 -0800466 } catch (InvalidKeyException e) {
467 Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e);
468 Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()",
469 sessionEntry.getKeyClaimant()));
470 Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()",
471 sessionEntry.getVaultParams()));
472 Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse));
Robert Berrya16cd592018-01-17 14:43:09 +0000473 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800474 "Failed to decrypt recovery key " + e.getMessage());
Bo Zhu31a40c02018-01-24 17:40:29 -0800475 } catch (AEADBadTagException e) {
476 Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e);
477 Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()",
478 sessionEntry.getKeyClaimant()));
479 Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()",
480 sessionEntry.getVaultParams()));
481 Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse));
482 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
483 "Failed to decrypt recovery key " + e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000484 } catch (NoSuchAlgorithmException e) {
485 // Should never happen: all the algorithms used are required by AOSP implementations
Robert Berrya16cd592018-01-17 14:43:09 +0000486 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000487 }
Bo Zhu31a40c02018-01-24 17:40:29 -0800488
489 try {
490 return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
491 } catch (InvalidKeyException e) {
492 Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
493 Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()",
494 sessionEntry.getLskfHash()));
495 Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey));
496 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
497 "Failed to decrypt recovery key " + e.getMessage());
498 } catch (AEADBadTagException e) {
499 Log.e(TAG, "Got AEADBadTagException during decrypting recovery key", e);
500 Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()",
501 sessionEntry.getLskfHash()));
502 Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey));
503 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
504 "Failed to decrypt recovery key " + e.getMessage());
505 } catch (NoSuchAlgorithmException e) {
506 // Should never happen: all the algorithms used are required by AOSP implementations
507 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
508 }
509 }
510
511 private String constructLoggingMessage(String key, byte[] value) {
512 if (value == null) {
513 return key + " is null";
514 } else {
515 return key + ": " + HexDump.toHexString(value);
516 }
Robert Berryb9a220b2017-12-21 12:41:01 +0000517 }
518
519 /**
520 * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
521 *
Robert Berrybd4c43c2017-12-22 11:35:14 +0000522 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000523 * @throws RemoteException if an error occurred decrypting the keys.
524 */
Robert Berrybd4c43c2017-12-22 11:35:14 +0000525 private Map<String, byte[]> recoverApplicationKeys(
Robert Berryb9a220b2017-12-21 12:41:01 +0000526 @NonNull byte[] recoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000527 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000528 HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
Robert Berry5f138702018-01-17 15:18:05 +0000529 for (WrappedApplicationKey applicationKey : applicationKeys) {
Dmitry Dementyev07c765552018-01-08 17:31:59 -0800530 String alias = applicationKey.getAlias();
Robert Berryb9a220b2017-12-21 12:41:01 +0000531 byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
532
533 try {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000534 byte[] keyMaterial =
535 KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
536 keyMaterialByAlias.put(alias, keyMaterial);
Robert Berryb9a220b2017-12-21 12:41:01 +0000537 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000538 Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
539 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000540 ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000541 } catch (InvalidKeyException | AEADBadTagException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000542 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
543 "Failed to recover key with alias '" + alias + "': " + e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000544 }
545 }
Robert Berrybd4c43c2017-12-22 11:35:14 +0000546 return keyMaterialByAlias;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800547 }
548
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800549 /**
550 * This function can only be used inside LockSettingsService.
551 *
Robert Berry91044042017-12-27 12:05:58 +0000552 * @param storedHashType from {@code CredentialHash}
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800553 * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
554 * mPasswordMaxLength}
555 * @param userId for user who just unlocked the device.
556 * @hide
557 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800558 public void lockScreenSecretAvailable(
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800559 int storedHashType, @NonNull String credential, int userId) {
Robert Berry4a534ec2017-12-21 15:44:02 +0000560 // So as not to block the critical path unlocking the phone, defer to another thread.
561 try {
562 mExecutorService.execute(KeySyncTask.newInstance(
Robert Berry91044042017-12-27 12:05:58 +0000563 mContext,
564 mDatabase,
565 mSnapshotStorage,
566 mListenersStorage,
567 userId,
568 storedHashType,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800569 credential,
570 /*credentialUpdated=*/ false));
Robert Berry4a534ec2017-12-21 15:44:02 +0000571 } catch (NoSuchAlgorithmException e) {
572 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
573 } catch (KeyStoreException e) {
574 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
575 } catch (InsecureUserException e) {
576 Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800577 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800578 }
579
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800580 /**
581 * This function can only be used inside LockSettingsService.
582 * @param storedHashType from {@code CredentialHash}
583 * @param credential - unencrypted String
584 * @param userId for the user whose lock screen credentials were changed.
585 * @hide
586 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800587 public void lockScreenSecretChanged(
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800588 int storedHashType,
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800589 @Nullable String credential,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800590 int userId) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800591 // So as not to block the critical path unlocking the phone, defer to another thread.
592 try {
593 mExecutorService.execute(KeySyncTask.newInstance(
594 mContext,
595 mDatabase,
596 mSnapshotStorage,
597 mListenersStorage,
598 userId,
599 storedHashType,
600 credential,
601 /*credentialUpdated=*/ true));
602 } catch (NoSuchAlgorithmException e) {
603 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
604 } catch (KeyStoreException e) {
605 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
606 } catch (InsecureUserException e) {
607 Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
608 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800609 }
610
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800611 private void checkRecoverKeyStorePermission() {
612 mContext.enforceCallingOrSelfPermission(
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800613 Manifest.permission.RECOVER_KEYSTORE,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800614 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
615 }
Bo Zhudef7ffd2018-01-05 14:50:52 -0800616
617 private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
618 byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
619 return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length));
620 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800621}