blob: 54acb871fdd679da66748ce86a2aae63f9db811d [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 Berry97e55582018-01-05 12:43:13 +000019import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
20 .ERROR_BAD_X509_CERTIFICATE;
21import static android.security.recoverablekeystore.RecoverableKeyStoreLoader.ERROR_DATABASE_ERROR;
22import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
23 .ERROR_DECRYPTION_FAILED;
24import static android.security.recoverablekeystore.RecoverableKeyStoreLoader.ERROR_INSECURE_USER;
25import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
26 .ERROR_KEYSTORE_INTERNAL_ERROR;
27import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
28 .ERROR_NOT_YET_SUPPORTED;
29import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
30 .ERROR_UNEXPECTED_MISSING_ALGORITHM;
31
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080032import android.annotation.NonNull;
33import android.annotation.Nullable;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080034import android.app.PendingIntent;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080035import android.content.Context;
36import android.os.Binder;
37import android.os.RemoteException;
38import android.os.ServiceSpecificException;
39import android.os.UserHandle;
40
41import android.security.recoverablekeystore.KeyEntryRecoveryData;
42import android.security.recoverablekeystore.KeyStoreRecoveryData;
43import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
44import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
Robert Berry4a534ec2017-12-21 15:44:02 +000045import android.util.Log;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080046
47import com.android.internal.annotations.VisibleForTesting;
Robert Berrye16fa982017-12-20 15:59:37 +000048import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
49import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
Robert Berrybd086f12017-12-27 13:29:39 +000050import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080051
Robert Berryb9a220b2017-12-21 12:41:01 +000052import java.nio.charset.StandardCharsets;
Robert Berrye16fa982017-12-20 15:59:37 +000053import java.security.InvalidKeyException;
Robert Berry4a534ec2017-12-21 15:44:02 +000054import java.security.KeyStoreException;
Bo Zhu5b81fa62017-12-21 14:36:11 -080055import java.security.KeyFactory;
Robert Berrye16fa982017-12-20 15:59:37 +000056import java.security.NoSuchAlgorithmException;
57import java.security.PublicKey;
Robert Berrycfc990a2017-12-22 15:54:30 +000058import java.security.UnrecoverableKeyException;
Robert Berrye16fa982017-12-20 15:59:37 +000059import java.security.spec.InvalidKeySpecException;
Bo Zhu5b81fa62017-12-21 14:36:11 -080060import java.security.spec.X509EncodedKeySpec;
Bo Zhudef7ffd2018-01-05 14:50:52 -080061import java.util.Arrays;
Robert Berrybd4c43c2017-12-22 11:35:14 +000062import java.util.HashMap;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080063import java.util.List;
Robert Berryb9a220b2017-12-21 12:41:01 +000064import java.util.Locale;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080065import java.util.Map;
Robert Berry4a534ec2017-12-21 15:44:02 +000066import java.util.concurrent.ExecutorService;
67import java.util.concurrent.Executors;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080068
Robert Berryb9a220b2017-12-21 12:41:01 +000069import javax.crypto.AEADBadTagException;
70
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080071/**
72 * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact
73 * with {@code LockSettingsService}.
74 *
75 * @hide
76 */
77public class RecoverableKeyStoreManager {
Robert Berry4a534ec2017-12-21 15:44:02 +000078 private static final String TAG = "RecoverableKeyStoreMgr";
Robert Berrycfc990a2017-12-22 15:54:30 +000079
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080080 private static RecoverableKeyStoreManager mInstance;
Robert Berrye16fa982017-12-20 15:59:37 +000081
82 private final Context mContext;
83 private final RecoverableKeyStoreDb mDatabase;
84 private final RecoverySessionStorage mRecoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000085 private final ExecutorService mExecutorService;
Robert Berry91044042017-12-27 12:05:58 +000086 private final RecoverySnapshotListenersStorage mListenersStorage;
Robert Berrycfc990a2017-12-22 15:54:30 +000087 private final RecoverableKeyGenerator mRecoverableKeyGenerator;
Robert Berrybd086f12017-12-27 13:29:39 +000088 private final RecoverySnapshotStorage mSnapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -080089 private final PlatformKeyManager mPlatformKeyManager;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080090
91 /**
92 * Returns a new or existing instance.
93 *
94 * @hide
95 */
Bo Zhu3462c832018-01-04 22:42:36 -080096 public static synchronized RecoverableKeyStoreManager getInstance(Context context) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080097 if (mInstance == null) {
Bo Zhu3462c832018-01-04 22:42:36 -080098 RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
99 PlatformKeyManager platformKeyManager;
100 try {
101 platformKeyManager = PlatformKeyManager.getInstance(context, db);
102 } catch (NoSuchAlgorithmException e) {
103 // Impossible: all algorithms must be supported by AOSP
104 throw new RuntimeException(e);
105 } catch (KeyStoreException e) {
106 throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
107 }
108
Robert Berrye16fa982017-12-20 15:59:37 +0000109 mInstance = new RecoverableKeyStoreManager(
Bo Zhu3462c832018-01-04 22:42:36 -0800110 context.getApplicationContext(),
Robert Berrye16fa982017-12-20 15:59:37 +0000111 db,
Robert Berry4a534ec2017-12-21 15:44:02 +0000112 new RecoverySessionStorage(),
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800113 Executors.newSingleThreadExecutor(),
Robert Berry91044042017-12-27 12:05:58 +0000114 new RecoverySnapshotStorage(),
Bo Zhu3462c832018-01-04 22:42:36 -0800115 new RecoverySnapshotListenersStorage(),
116 platformKeyManager);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800117 }
118 return mInstance;
119 }
120
121 @VisibleForTesting
Robert Berrye16fa982017-12-20 15:59:37 +0000122 RecoverableKeyStoreManager(
123 Context context,
124 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berry4a534ec2017-12-21 15:44:02 +0000125 RecoverySessionStorage recoverySessionStorage,
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800126 ExecutorService executorService,
Robert Berry91044042017-12-27 12:05:58 +0000127 RecoverySnapshotStorage snapshotStorage,
Bo Zhu3462c832018-01-04 22:42:36 -0800128 RecoverySnapshotListenersStorage listenersStorage,
129 PlatformKeyManager platformKeyManager) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800130 mContext = context;
Robert Berrye16fa982017-12-20 15:59:37 +0000131 mDatabase = recoverableKeyStoreDb;
132 mRecoverySessionStorage = recoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000133 mExecutorService = executorService;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800134 mListenersStorage = listenersStorage;
Robert Berrybd086f12017-12-27 13:29:39 +0000135 mSnapshotStorage = snapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800136 mPlatformKeyManager = platformKeyManager;
137
Robert Berrycfc990a2017-12-22 15:54:30 +0000138 try {
139 mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
140 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000141 Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
142 throw new ServiceSpecificException(ERROR_UNEXPECTED_MISSING_ALGORITHM, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000143 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800144 }
145
Bo Zhu5b81fa62017-12-21 14:36:11 -0800146 public void initRecoveryService(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800147 @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800148 throws RemoteException {
149 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800150 int userId = UserHandle.getCallingUserId();
Bo Zhu5b81fa62017-12-21 14:36:11 -0800151 // TODO: open /system/etc/security/... cert file, and check the signature on the public keys
152 PublicKey publicKey;
153 try {
154 KeyFactory kf = KeyFactory.getInstance("EC");
155 // TODO: Randomly choose a key from the list -- right now we just use the whole input
156 X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(signedPublicKeyList);
157 publicKey = kf.generatePublic(pkSpec);
158 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000159 Log.wtf(TAG, "EC algorithm not available. AOSP must support this.", e);
160 throw new ServiceSpecificException(ERROR_UNEXPECTED_MISSING_ALGORITHM, e.getMessage());
Bo Zhu5b81fa62017-12-21 14:36:11 -0800161 } catch (InvalidKeySpecException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000162 throw new ServiceSpecificException(
163 ERROR_BAD_X509_CERTIFICATE, "Not a valid X509 certificate.");
Bo Zhu5b81fa62017-12-21 14:36:11 -0800164 }
165 mDatabase.setRecoveryServicePublicKey(userId, Binder.getCallingUid(), publicKey);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800166 }
167
168 /**
169 * Gets all data necessary to recover application keys on new device.
170 *
171 * @return recovery data
172 * @hide
173 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800174 public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800175 throws RemoteException {
176 checkRecoverKeyStorePermission();
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800177
Robert Berrybd086f12017-12-27 13:29:39 +0000178 KeyStoreRecoveryData snapshot = mSnapshotStorage.get(UserHandle.getCallingUserId());
179 if (snapshot == null) {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800180 throw new ServiceSpecificException(RecoverableKeyStoreLoader.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 Dementyev14298312018-01-04 15:19:19 -0800204 public void setServerParameters(long serverParameters) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800205 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800206 int userId = UserHandle.getCallingUserId();
Bo Zhu584b923f2017-12-22 16:05:15 -0800207 mDatabase.setServerParameters(userId, Binder.getCallingUid(), serverParameters);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800208 }
209
Dmitry Dementyevad884712017-12-20 12:38:36 -0800210 /**
211 * Updates recovery status for the application given its {@code packageName}.
212 *
213 * @param packageName which recoverable key statuses will be returned
214 * @param aliases - KeyStore aliases or {@code null} for all aliases of the app
215 * @param status - new status
216 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800217 public void setRecoveryStatus(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800218 @NonNull String packageName, @Nullable String[] aliases, int status)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800219 throws RemoteException {
220 checkRecoverKeyStorePermission();
Dmitry Dementyevad884712017-12-20 12:38:36 -0800221 int uid = Binder.getCallingUid();
222 if (packageName != null) {
223 // TODO: get uid for package name, when many apps are supported.
224 }
225 if (aliases == null) {
226 // Get all keys for the app.
227 Map<String, Integer> allKeys = mDatabase.getStatusForAllKeys(uid);
228 aliases = new String[allKeys.size()];
229 allKeys.keySet().toArray(aliases);
230 }
231 for (String alias: aliases) {
232 mDatabase.setRecoveryStatus(uid, alias, status);
233 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800234 }
235
236 /**
Dmitry Dementyevad884712017-12-20 12:38:36 -0800237 * Gets recovery status for caller or other application {@code packageName}.
238 * @param packageName which recoverable keys statuses will be returned.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800239 *
Dmitry Dementyevad884712017-12-20 12:38:36 -0800240 * @return {@code Map} from KeyStore alias to recovery status.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800241 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800242 public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800243 throws RemoteException {
244 // Any application should be able to check status for its own keys.
245 // If caller is a recovery agent it can check statuses for other packages, but
246 // only for recoverable keys it manages.
Dmitry Dementyevad884712017-12-20 12:38:36 -0800247 return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800248 }
249
250 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800251 * Sets recovery secrets list used by all recovery agents for given {@code userId}
252 *
253 * @hide
254 */
255 public void setRecoverySecretTypes(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800256 @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800257 throws RemoteException {
258 checkRecoverKeyStorePermission();
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800259 mDatabase.setRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid(),
260 secretTypes);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800261 }
262
263 /**
264 * Gets secret types necessary to create Recovery Data.
265 *
266 * @return secret types
267 * @hide
268 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800269 public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800270 checkRecoverKeyStorePermission();
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800271 return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
272 Binder.getCallingUid());
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800273 }
274
275 /**
276 * Gets secret types RecoverableKeyStoreLoaders is waiting for to create new Recovery Data.
277 *
278 * @return secret types
279 * @hide
280 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800281 public @NonNull int[] getPendingRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800282 checkRecoverKeyStorePermission();
283 throw new UnsupportedOperationException();
284 }
285
286 public void recoverySecretAvailable(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800287 @NonNull KeyStoreRecoveryMetadata recoverySecret) throws RemoteException {
288 int uid = Binder.getCallingUid();
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800289 if (recoverySecret.getLockScreenUiFormat() == KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN) {
290 throw new SecurityException(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800291 "Caller " + uid + " is not allowed to set lock screen secret");
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800292 }
293 checkRecoverKeyStorePermission();
294 // TODO: add hook from LockSettingsService to set lock screen secret.
295 throw new UnsupportedOperationException();
296 }
297
298 /**
299 * Initializes recovery session.
300 *
Robert Berrye16fa982017-12-20 15:59:37 +0000301 * @param sessionId A unique ID to identify the recovery session.
302 * @param verifierPublicKey X509-encoded public key.
303 * @param vaultParams Additional params associated with vault.
304 * @param vaultChallenge Challenge issued by vault service.
Robert Berryb9a220b2017-12-21 12:41:01 +0000305 * @param secrets Lock-screen hashes. For now only a single secret is supported.
Robert Berrye16fa982017-12-20 15:59:37 +0000306 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
307 *
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800308 * @hide
309 */
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800310 public @NonNull byte[] startRecoverySession(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800311 @NonNull String sessionId,
312 @NonNull byte[] verifierPublicKey,
313 @NonNull byte[] vaultParams,
314 @NonNull byte[] vaultChallenge,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800315 @NonNull List<KeyStoreRecoveryMetadata> secrets)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800316 throws RemoteException {
317 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800318 int uid = Binder.getCallingUid();
Robert Berrye16fa982017-12-20 15:59:37 +0000319
320 if (secrets.size() != 1) {
321 // TODO: support multiple secrets
Robert Berry97e55582018-01-05 12:43:13 +0000322 throw new ServiceSpecificException(
323 ERROR_NOT_YET_SUPPORTED,
324 "Only a single KeyStoreRecoveryMetadata is supported");
Robert Berrye16fa982017-12-20 15:59:37 +0000325 }
326
Bo Zhudef7ffd2018-01-05 14:50:52 -0800327 PublicKey publicKey;
328 try {
329 publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
330 } catch (NoSuchAlgorithmException e) {
331 // Should never happen
332 throw new RuntimeException(e);
333 } catch (InvalidKeySpecException e) {
334 throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, "Not a valid X509 key");
335 }
336 // The raw public key bytes contained in vaultParams must match the ones given in
337 // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned
338 // by the original recovery service.
339 if (!publicKeysMatch(publicKey, vaultParams)) {
340 throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE,
341 "The public keys given in verifierPublicKey and vaultParams do not match.");
342 }
343
Robert Berrye16fa982017-12-20 15:59:37 +0000344 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
345 byte[] kfHash = secrets.get(0).getSecret();
346 mRecoverySessionStorage.add(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800347 uid,
Robert Berryb9a220b2017-12-21 12:41:01 +0000348 new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
Robert Berrye16fa982017-12-20 15:59:37 +0000349
350 try {
351 byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
Robert Berrye16fa982017-12-20 15:59:37 +0000352 return KeySyncUtils.encryptRecoveryClaim(
353 publicKey,
354 vaultParams,
355 vaultChallenge,
356 thmKfHash,
357 keyClaimant);
358 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000359 Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e);
360 throw new ServiceSpecificException(ERROR_UNEXPECTED_MISSING_ALGORITHM, e.getMessage());
Bo Zhudef7ffd2018-01-05 14:50:52 -0800361 } catch (InvalidKeyException e) {
362 throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000363 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800364 }
365
Robert Berryb9a220b2017-12-21 12:41:01 +0000366 /**
367 * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
368 * service.
369 *
Robert Berryb9a220b2017-12-21 12:41:01 +0000370 * @param sessionId The session ID used to generate the claim. See
Bo Zhudef7ffd2018-01-05 14:50:52 -0800371 * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
Robert Berryb9a220b2017-12-21 12:41:01 +0000372 * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
373 * service.
374 * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
375 * were wrapped with the recovery key.
Robert Berrybd4c43c2017-12-22 11:35:14 +0000376 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000377 * @throws RemoteException if an error occurred recovering the keys.
378 */
Robert Berrybd4c43c2017-12-22 11:35:14 +0000379 public Map<String, byte[]> recoverKeys(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800380 @NonNull String sessionId,
Robert Berryb9a220b2017-12-21 12:41:01 +0000381 @NonNull byte[] encryptedRecoveryKey,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800382 @NonNull List<KeyEntryRecoveryData> applicationKeys)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800383 throws RemoteException {
384 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800385 int uid = Binder.getCallingUid();
Robert Berryb9a220b2017-12-21 12:41:01 +0000386 RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
387 if (sessionEntry == null) {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800388 throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR,
389 String.format(Locale.US,
390 "Application uid=%d does not have pending session '%s'", uid, sessionId));
Robert Berryb9a220b2017-12-21 12:41:01 +0000391 }
392
393 try {
394 byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
Robert Berrybd4c43c2017-12-22 11:35:14 +0000395 return recoverApplicationKeys(recoveryKey, applicationKeys);
Robert Berryb9a220b2017-12-21 12:41:01 +0000396 } finally {
397 sessionEntry.destroy();
398 mRecoverySessionStorage.remove(uid);
399 }
400 }
401
Robert Berrycfc990a2017-12-22 15:54:30 +0000402 /**
403 * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
404 * returns the raw key material.
405 *
406 * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
407 *
408 * @hide
409 */
410 public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
411 int uid = Binder.getCallingUid();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800412 int userId = UserHandle.getCallingUserId();
Robert Berrycfc990a2017-12-22 15:54:30 +0000413
Bo Zhu3462c832018-01-04 22:42:36 -0800414 PlatformEncryptionKey encryptionKey;
Robert Berrycfc990a2017-12-22 15:54:30 +0000415 try {
Bo Zhu3462c832018-01-04 22:42:36 -0800416 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
Robert Berrycfc990a2017-12-22 15:54:30 +0000417 } catch (NoSuchAlgorithmException e) {
418 // Impossible: all algorithms must be supported by AOSP
419 throw new RuntimeException(e);
420 } catch (KeyStoreException | UnrecoverableKeyException e) {
421 throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
422 } catch (InsecureUserException e) {
423 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
424 }
425
426 try {
427 return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
428 } catch (KeyStoreException | InvalidKeyException e) {
429 throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
430 } catch (RecoverableKeyStorageException e) {
431 throw new ServiceSpecificException(ERROR_DATABASE_ERROR, e.getMessage());
432 }
433 }
434
Robert Berry5daccec2018-01-06 19:16:25 +0000435 public void removeKey(@NonNull String alias) throws RemoteException {
436 mDatabase.removeKey(Binder.getCallingUid(), alias);
437 }
438
Robert Berryb9a220b2017-12-21 12:41:01 +0000439 private byte[] decryptRecoveryKey(
440 RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
Dmitry Dementyev14298312018-01-04 15:19:19 -0800441 throws RemoteException, ServiceSpecificException {
Robert Berryb9a220b2017-12-21 12:41:01 +0000442 try {
443 byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
444 sessionEntry.getKeyClaimant(),
445 sessionEntry.getVaultParams(),
446 encryptedClaimResponse);
447 return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
448 } catch (InvalidKeyException | AEADBadTagException e) {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800449 throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR,
450 "Failed to decrypt recovery key " + e.getMessage());
451
Robert Berryb9a220b2017-12-21 12:41:01 +0000452 } catch (NoSuchAlgorithmException e) {
453 // Should never happen: all the algorithms used are required by AOSP implementations
Dmitry Dementyev14298312018-01-04 15:19:19 -0800454 throw new ServiceSpecificException(ERROR_KEYSTORE_INTERNAL_ERROR, e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000455 }
456 }
457
458 /**
459 * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
460 *
Robert Berrybd4c43c2017-12-22 11:35:14 +0000461 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000462 * @throws RemoteException if an error occurred decrypting the keys.
463 */
Robert Berrybd4c43c2017-12-22 11:35:14 +0000464 private Map<String, byte[]> recoverApplicationKeys(
Robert Berryb9a220b2017-12-21 12:41:01 +0000465 @NonNull byte[] recoveryKey,
466 @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000467 HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
Robert Berryb9a220b2017-12-21 12:41:01 +0000468 for (KeyEntryRecoveryData applicationKey : applicationKeys) {
469 String alias = new String(applicationKey.getAlias(), StandardCharsets.UTF_8);
470 byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
471
472 try {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000473 byte[] keyMaterial =
474 KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
475 keyMaterialByAlias.put(alias, keyMaterial);
Robert Berryb9a220b2017-12-21 12:41:01 +0000476 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000477 Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
478 throw new ServiceSpecificException(
479 ERROR_UNEXPECTED_MISSING_ALGORITHM, e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000480 } catch (InvalidKeyException | AEADBadTagException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000481 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
482 "Failed to recover key with alias '" + alias + "': " + e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000483 }
484 }
Robert Berrybd4c43c2017-12-22 11:35:14 +0000485 return keyMaterialByAlias;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800486 }
487
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800488 /**
489 * This function can only be used inside LockSettingsService.
490 *
Robert Berry91044042017-12-27 12:05:58 +0000491 * @param storedHashType from {@code CredentialHash}
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800492 * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
493 * mPasswordMaxLength}
494 * @param userId for user who just unlocked the device.
495 * @hide
496 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800497 public void lockScreenSecretAvailable(
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800498 int storedHashType, @NonNull String credential, int userId) {
Robert Berry4a534ec2017-12-21 15:44:02 +0000499 // So as not to block the critical path unlocking the phone, defer to another thread.
500 try {
501 mExecutorService.execute(KeySyncTask.newInstance(
Robert Berry91044042017-12-27 12:05:58 +0000502 mContext,
503 mDatabase,
504 mSnapshotStorage,
505 mListenersStorage,
506 userId,
507 storedHashType,
508 credential));
Robert Berry4a534ec2017-12-21 15:44:02 +0000509 } catch (NoSuchAlgorithmException e) {
510 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
511 } catch (KeyStoreException e) {
512 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
513 } catch (InsecureUserException e) {
514 Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800515 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800516 }
517
518 /** This function can only be used inside LockSettingsService. */
519 public void lockScreenSecretChanged(
520 @KeyStoreRecoveryMetadata.LockScreenUiFormat int type,
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800521 @Nullable String credential,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800522 int userId) {
523 throw new UnsupportedOperationException();
524 }
525
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800526 private void checkRecoverKeyStorePermission() {
527 mContext.enforceCallingOrSelfPermission(
528 RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE,
529 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
530 }
Bo Zhudef7ffd2018-01-05 14:50:52 -0800531
532 private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
533 byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
534 return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length));
535 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800536}