blob: da0b0d03b54dc76c281bef4c13927d13330ae67f [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 Berryd9f11a92018-02-26 16:37:06 +000019import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
20import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
21import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
Bo Zhu2c8e5382018-02-26 15:54:25 -080022import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
Robert Berryd9f11a92018-02-26 16:37:06 +000023import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
24import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
25import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED;
Robert Berry97e55582018-01-05 12:43:13 +000026
Bo Zhu14d993d2018-02-03 21:38:48 -080027import android.Manifest;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080028import android.annotation.NonNull;
29import android.annotation.Nullable;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080030import android.app.PendingIntent;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080031import android.content.Context;
32import android.os.Binder;
33import android.os.RemoteException;
34import android.os.ServiceSpecificException;
35import android.os.UserHandle;
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080036import android.security.keystore.recovery.KeyChainProtectionParams;
37import android.security.keystore.recovery.KeyChainSnapshot;
Bo Zhu7c1972f2018-02-22 21:43:52 -080038import android.security.keystore.recovery.RecoveryCertPath;
Robert Berry81ee34b2018-01-23 11:59:59 +000039import android.security.keystore.recovery.RecoveryController;
40import android.security.keystore.recovery.WrappedApplicationKey;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080041import android.security.KeyStore;
Robert Berry4a534ec2017-12-21 15:44:02 +000042import android.util.Log;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080043
44import com.android.internal.annotations.VisibleForTesting;
Bo Zhu31a40c02018-01-24 17:40:29 -080045import com.android.internal.util.HexDump;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080046import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
Bo Zhu14d993d2018-02-03 21:38:48 -080047import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
48import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
49import com.android.server.locksettings.recoverablekeystore.certificate.CertXml;
50import com.android.server.locksettings.recoverablekeystore.certificate.TrustedRootCert;
Robert Berrye16fa982017-12-20 15:59:37 +000051import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
52import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
Robert Berrybd086f12017-12-27 13:29:39 +000053import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080054
Robert Berrye16fa982017-12-20 15:59:37 +000055import java.security.InvalidKeyException;
Bo Zhu5b81fa62017-12-21 14:36:11 -080056import java.security.KeyFactory;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080057import java.security.KeyStoreException;
Robert Berrye16fa982017-12-20 15:59:37 +000058import java.security.NoSuchAlgorithmException;
59import java.security.PublicKey;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080060import java.security.UnrecoverableKeyException;
Bo Zhu14d993d2018-02-03 21:38:48 -080061import java.security.cert.CertPath;
62import java.security.cert.CertificateEncodingException;
Bo Zhu7c1972f2018-02-22 21:43:52 -080063import java.security.cert.CertificateException;
Bo Zhu14d993d2018-02-03 21:38:48 -080064import java.security.spec.InvalidKeySpecException;
Bo Zhu5b81fa62017-12-21 14:36:11 -080065import java.security.spec.X509EncodedKeySpec;
Bo Zhudef7ffd2018-01-05 14:50:52 -080066import java.util.Arrays;
Robert Berrybd4c43c2017-12-22 11:35:14 +000067import java.util.HashMap;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080068import java.util.List;
Robert Berryb9a220b2017-12-21 12:41:01 +000069import java.util.Locale;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080070import java.util.Map;
Robert Berry4a534ec2017-12-21 15:44:02 +000071import java.util.concurrent.ExecutorService;
72import java.util.concurrent.Executors;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080073
Robert Berryb9a220b2017-12-21 12:41:01 +000074import javax.crypto.AEADBadTagException;
75
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080076/**
Robert Berry74928a12018-01-18 17:49:07 +000077 * Class with {@link RecoveryController} API implementation and internal methods to interact
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080078 * with {@code LockSettingsService}.
79 *
80 * @hide
81 */
82public class RecoverableKeyStoreManager {
Robert Berry4a534ec2017-12-21 15:44:02 +000083 private static final String TAG = "RecoverableKeyStoreMgr";
Robert Berrycfc990a2017-12-22 15:54:30 +000084
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080085 private static RecoverableKeyStoreManager mInstance;
Robert Berrye16fa982017-12-20 15:59:37 +000086
87 private final Context mContext;
88 private final RecoverableKeyStoreDb mDatabase;
89 private final RecoverySessionStorage mRecoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000090 private final ExecutorService mExecutorService;
Robert Berry91044042017-12-27 12:05:58 +000091 private final RecoverySnapshotListenersStorage mListenersStorage;
Robert Berrycfc990a2017-12-22 15:54:30 +000092 private final RecoverableKeyGenerator mRecoverableKeyGenerator;
Robert Berrybd086f12017-12-27 13:29:39 +000093 private final RecoverySnapshotStorage mSnapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -080094 private final PlatformKeyManager mPlatformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080095 private final KeyStore mKeyStore;
96 private final ApplicationKeyStorage mApplicationKeyStorage;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080097
98 /**
99 * Returns a new or existing instance.
100 *
101 * @hide
102 */
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800103 public static synchronized RecoverableKeyStoreManager
104 getInstance(Context context, KeyStore keystore) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800105 if (mInstance == null) {
Bo Zhu3462c832018-01-04 22:42:36 -0800106 RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
107 PlatformKeyManager platformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800108 ApplicationKeyStorage applicationKeyStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800109 try {
110 platformKeyManager = PlatformKeyManager.getInstance(context, db);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800111 applicationKeyStorage = ApplicationKeyStorage.getInstance(keystore);
Bo Zhu3462c832018-01-04 22:42:36 -0800112 } catch (NoSuchAlgorithmException e) {
113 // Impossible: all algorithms must be supported by AOSP
114 throw new RuntimeException(e);
115 } catch (KeyStoreException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000116 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhu3462c832018-01-04 22:42:36 -0800117 }
118
Robert Berrye16fa982017-12-20 15:59:37 +0000119 mInstance = new RecoverableKeyStoreManager(
Bo Zhu3462c832018-01-04 22:42:36 -0800120 context.getApplicationContext(),
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800121 keystore,
Robert Berrye16fa982017-12-20 15:59:37 +0000122 db,
Robert Berry4a534ec2017-12-21 15:44:02 +0000123 new RecoverySessionStorage(),
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800124 Executors.newSingleThreadExecutor(),
Robert Berry91044042017-12-27 12:05:58 +0000125 new RecoverySnapshotStorage(),
Bo Zhu3462c832018-01-04 22:42:36 -0800126 new RecoverySnapshotListenersStorage(),
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800127 platformKeyManager,
128 applicationKeyStorage);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800129 }
130 return mInstance;
131 }
132
133 @VisibleForTesting
Robert Berrye16fa982017-12-20 15:59:37 +0000134 RecoverableKeyStoreManager(
135 Context context,
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800136 KeyStore keystore,
Robert Berrye16fa982017-12-20 15:59:37 +0000137 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berry4a534ec2017-12-21 15:44:02 +0000138 RecoverySessionStorage recoverySessionStorage,
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800139 ExecutorService executorService,
Robert Berry91044042017-12-27 12:05:58 +0000140 RecoverySnapshotStorage snapshotStorage,
Bo Zhu3462c832018-01-04 22:42:36 -0800141 RecoverySnapshotListenersStorage listenersStorage,
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800142 PlatformKeyManager platformKeyManager,
143 ApplicationKeyStorage applicationKeyStorage) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800144 mContext = context;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800145 mKeyStore = keystore;
Robert Berrye16fa982017-12-20 15:59:37 +0000146 mDatabase = recoverableKeyStoreDb;
147 mRecoverySessionStorage = recoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000148 mExecutorService = executorService;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800149 mListenersStorage = listenersStorage;
Robert Berrybd086f12017-12-27 13:29:39 +0000150 mSnapshotStorage = snapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800151 mPlatformKeyManager = platformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800152 mApplicationKeyStorage = applicationKeyStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800153
Robert Berrycfc990a2017-12-22 15:54:30 +0000154 try {
155 mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
156 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000157 Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000158 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000159 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800160 }
161
Bo Zhu5b81fa62017-12-21 14:36:11 -0800162 public void initRecoveryService(
Bo Zhu14d993d2018-02-03 21:38:48 -0800163 @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800164 throws RemoteException {
165 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800166 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800167 int uid = Binder.getCallingUid();
Bo Zhu14d993d2018-02-03 21:38:48 -0800168
169 // TODO: Check the public-key signature on the whole file before parsing it
170
171 CertXml certXml;
172 try {
173 certXml = CertXml.parse(recoveryServiceCertFile);
174 } catch (CertParsingException e) {
175 // TODO: Do not use raw key bytes anymore once the other components are updated
Bo Zhued00d252018-02-14 11:50:57 -0800176 Log.d(TAG, "Failed to parse the input as a cert file: " + HexDump.toHexString(
177 recoveryServiceCertFile));
Bo Zhu14d993d2018-02-03 21:38:48 -0800178 PublicKey publicKey = parseEcPublicKey(recoveryServiceCertFile);
179 if (mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey) > 0) {
180 mDatabase.setShouldCreateSnapshot(userId, uid, true);
181 }
Bo Zhued00d252018-02-14 11:50:57 -0800182 Log.d(TAG, "Successfully set the input as the raw public key");
Bo Zhu14d993d2018-02-03 21:38:48 -0800183 return;
184 }
185
186 // Check serial number
187 long newSerial = certXml.getSerial();
188 Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid);
189 if (oldSerial != null && oldSerial >= newSerial) {
190 if (oldSerial == newSerial) {
191 Log.i(TAG, "The cert file serial number is the same, so skip updating.");
192 } else {
193 Log.e(TAG, "The cert file serial number is older than the one in database.");
194 }
195 return;
196 }
197 Log.i(TAG, "Updating the certificate with the new serial number " + newSerial);
198
199 CertPath certPath;
200 try {
201 Log.d(TAG, "Getting and validating a random endpoint certificate");
202 certPath = certXml.getRandomEndpointCert(TrustedRootCert.TRUSTED_ROOT_CERT);
203 } catch (CertValidationException e) {
204 Log.e(TAG, "Invalid endpoint cert", e);
205 throw new ServiceSpecificException(
206 ERROR_BAD_CERTIFICATE_FORMAT, "Failed to validate certificate.");
207 }
208 try {
209 Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
210 if (mDatabase.setRecoveryServiceCertPath(userId, uid, certPath) > 0) {
211 mDatabase.setRecoveryServiceCertSerial(userId, uid, newSerial);
212 mDatabase.setShouldCreateSnapshot(userId, uid, true);
213 }
214 } catch (CertificateEncodingException e) {
215 Log.e(TAG, "Failed to encode CertPath", e);
216 throw new ServiceSpecificException(
217 ERROR_BAD_CERTIFICATE_FORMAT, "Failed to encode CertPath.");
218 }
219 }
220
221 private PublicKey parseEcPublicKey(@NonNull byte[] bytes) throws ServiceSpecificException {
Bo Zhu5b81fa62017-12-21 14:36:11 -0800222 try {
223 KeyFactory kf = KeyFactory.getInstance("EC");
Bo Zhu14d993d2018-02-03 21:38:48 -0800224 X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(bytes);
225 return kf.generatePublic(pkSpec);
Bo Zhu5b81fa62017-12-21 14:36:11 -0800226 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000227 Log.wtf(TAG, "EC algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000228 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhu5b81fa62017-12-21 14:36:11 -0800229 } catch (InvalidKeySpecException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000230 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000231 ERROR_BAD_CERTIFICATE_FORMAT, "Not a valid X509 certificate.");
Bo Zhu5b81fa62017-12-21 14:36:11 -0800232 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800233 }
234
235 /**
236 * Gets all data necessary to recover application keys on new device.
237 *
238 * @return recovery data
239 * @hide
240 */
Robert Berry5f138702018-01-17 15:18:05 +0000241 public @NonNull
Dmitry Dementyevb4fb9872018-01-26 11:49:34 -0800242 KeyChainSnapshot getKeyChainSnapshot()
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800243 throws RemoteException {
244 checkRecoverKeyStorePermission();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800245 int uid = Binder.getCallingUid();
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800246 KeyChainSnapshot snapshot = mSnapshotStorage.get(uid);
Robert Berrybd086f12017-12-27 13:29:39 +0000247 if (snapshot == null) {
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800248 throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
Robert Berrybd086f12017-12-27 13:29:39 +0000249 }
250 return snapshot;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800251 }
252
Dmitry Dementyev14298312018-01-04 15:19:19 -0800253 public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800254 throws RemoteException {
255 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800256 int uid = Binder.getCallingUid();
257 mListenersStorage.setSnapshotListener(uid, intent);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800258 }
259
260 /**
261 * Gets recovery snapshot versions for all accounts. Note that snapshot may have 0 application
262 * keys, but it still needs to be synced, if previous versions were not empty.
263 *
264 * @return Map from Recovery agent account to snapshot version.
265 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800266 public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800267 throws RemoteException {
268 checkRecoverKeyStorePermission();
269 throw new UnsupportedOperationException();
270 }
271
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800272 public void setServerParams(byte[] serverParams) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800273 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800274 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800275 int uid = Binder.getCallingUid();
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800276 long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800277 if (updatedRows > 0) {
278 mDatabase.setShouldCreateSnapshot(userId, uid, true);
279 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800280 }
281
Dmitry Dementyevad884712017-12-20 12:38:36 -0800282 /**
Robert Berrybbe02ae2018-02-20 19:47:43 +0000283 * Sets the recovery status of key with {@code alias} to {@code status}.
Dmitry Dementyevad884712017-12-20 12:38:36 -0800284 */
Robert Berrybbe02ae2018-02-20 19:47:43 +0000285 public void setRecoveryStatus(String alias, int status) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800286 checkRecoverKeyStorePermission();
Robert Berrybbe02ae2018-02-20 19:47:43 +0000287 mDatabase.setRecoveryStatus(Binder.getCallingUid(), alias, status);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800288 }
289
290 /**
Robert Berry56f06b42018-02-23 13:31:32 +0000291 * Returns recovery statuses for all keys belonging to the calling uid.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800292 *
Robert Berry56f06b42018-02-23 13:31:32 +0000293 * @return {@link Map} from key alias to recovery status. Recovery status is one of
294 * {@link RecoveryController#RECOVERY_STATUS_SYNCED},
295 * {@link RecoveryController#RECOVERY_STATUS_SYNC_IN_PROGRESS} or
296 * {@link RecoveryController#RECOVERY_STATUS_PERMANENT_FAILURE}.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800297 */
Robert Berry56f06b42018-02-23 13:31:32 +0000298 public @NonNull Map<String, Integer> getRecoveryStatus() throws RemoteException {
Dmitry Dementyevad884712017-12-20 12:38:36 -0800299 return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800300 }
301
302 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800303 * Sets recovery secrets list used by all recovery agents for given {@code userId}
304 *
305 * @hide
306 */
307 public void setRecoverySecretTypes(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800308 @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800309 throws RemoteException {
310 checkRecoverKeyStorePermission();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800311 int userId = UserHandle.getCallingUserId();
312 int uid = Binder.getCallingUid();
313 long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes);
314 if (updatedRows > 0) {
315 mDatabase.setShouldCreateSnapshot(userId, uid, true);
316 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800317 }
318
319 /**
320 * Gets secret types necessary to create Recovery Data.
321 *
322 * @return secret types
323 * @hide
324 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800325 public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800326 checkRecoverKeyStorePermission();
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800327 return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
328 Binder.getCallingUid());
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800329 }
330
331 /**
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800332 * Gets secret types RecoveryManagers is waiting for to create new Recovery Data.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800333 *
334 * @return secret types
335 * @hide
336 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800337 public @NonNull int[] getPendingRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800338 checkRecoverKeyStorePermission();
339 throw new UnsupportedOperationException();
340 }
341
342 public void recoverySecretAvailable(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800343 @NonNull KeyChainProtectionParams recoverySecret) throws RemoteException {
Dmitry Dementyev14298312018-01-04 15:19:19 -0800344 int uid = Binder.getCallingUid();
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800345 if (recoverySecret.getLockScreenUiFormat() == KeyChainProtectionParams.TYPE_LOCKSCREEN) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800346 throw new SecurityException(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800347 "Caller " + uid + " is not allowed to set lock screen secret");
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800348 }
349 checkRecoverKeyStorePermission();
350 // TODO: add hook from LockSettingsService to set lock screen secret.
351 throw new UnsupportedOperationException();
352 }
353
354 /**
Bo Zhu7c1972f2018-02-22 21:43:52 -0800355 * Initializes recovery session given the X509-encoded public key of the recovery service.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800356 *
Robert Berrye16fa982017-12-20 15:59:37 +0000357 * @param sessionId A unique ID to identify the recovery session.
358 * @param verifierPublicKey X509-encoded public key.
359 * @param vaultParams Additional params associated with vault.
360 * @param vaultChallenge Challenge issued by vault service.
Robert Berryb9a220b2017-12-21 12:41:01 +0000361 * @param secrets Lock-screen hashes. For now only a single secret is supported.
Robert Berrye16fa982017-12-20 15:59:37 +0000362 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
Bo Zhu7c1972f2018-02-22 21:43:52 -0800363 * @deprecated Use {@link #startRecoverySessionWithCertPath(String, RecoveryCertPath, byte[],
364 * byte[], List)} instead.
Robert Berrye16fa982017-12-20 15:59:37 +0000365 *
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800366 * @hide
367 */
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800368 public @NonNull byte[] startRecoverySession(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800369 @NonNull String sessionId,
370 @NonNull byte[] verifierPublicKey,
371 @NonNull byte[] vaultParams,
372 @NonNull byte[] vaultChallenge,
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800373 @NonNull List<KeyChainProtectionParams> secrets)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800374 throws RemoteException {
375 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800376 int uid = Binder.getCallingUid();
Robert Berrye16fa982017-12-20 15:59:37 +0000377
378 if (secrets.size() != 1) {
Robert Berry9e1bd362018-01-17 23:28:45 +0000379 throw new UnsupportedOperationException(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800380 "Only a single KeyChainProtectionParams is supported");
Robert Berrye16fa982017-12-20 15:59:37 +0000381 }
382
Bo Zhudef7ffd2018-01-05 14:50:52 -0800383 PublicKey publicKey;
384 try {
385 publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
Bo Zhudef7ffd2018-01-05 14:50:52 -0800386 } catch (InvalidKeySpecException e) {
Bo Zhu7c1972f2018-02-22 21:43:52 -0800387 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
388 "Not a valid X509 key");
Bo Zhudef7ffd2018-01-05 14:50:52 -0800389 }
390 // The raw public key bytes contained in vaultParams must match the ones given in
391 // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned
392 // by the original recovery service.
393 if (!publicKeysMatch(publicKey, vaultParams)) {
Robert Berrya16cd592018-01-17 14:43:09 +0000394 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
Bo Zhudef7ffd2018-01-05 14:50:52 -0800395 "The public keys given in verifierPublicKey and vaultParams do not match.");
396 }
397
Robert Berrye16fa982017-12-20 15:59:37 +0000398 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
399 byte[] kfHash = secrets.get(0).getSecret();
400 mRecoverySessionStorage.add(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800401 uid,
Robert Berryb9a220b2017-12-21 12:41:01 +0000402 new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
Robert Berrye16fa982017-12-20 15:59:37 +0000403
404 try {
405 byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
Robert Berrye16fa982017-12-20 15:59:37 +0000406 return KeySyncUtils.encryptRecoveryClaim(
407 publicKey,
408 vaultParams,
409 vaultChallenge,
410 thmKfHash,
411 keyClaimant);
412 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000413 Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000414 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhudef7ffd2018-01-05 14:50:52 -0800415 } catch (InvalidKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000416 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000417 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800418 }
419
Robert Berryb9a220b2017-12-21 12:41:01 +0000420 /**
Bo Zhu7c1972f2018-02-22 21:43:52 -0800421 * Initializes recovery session given the certificate path of the recovery service.
422 *
423 * @param sessionId A unique ID to identify the recovery session.
424 * @param verifierCertPath The certificate path of the recovery service.
425 * @param vaultParams Additional params associated with vault.
426 * @param vaultChallenge Challenge issued by vault service.
427 * @param secrets Lock-screen hashes. For now only a single secret is supported.
428 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
429 *
430 * @hide
431 */
432 public @NonNull byte[] startRecoverySessionWithCertPath(
433 @NonNull String sessionId,
434 @NonNull RecoveryCertPath verifierCertPath,
435 @NonNull byte[] vaultParams,
436 @NonNull byte[] vaultChallenge,
437 @NonNull List<KeyChainProtectionParams> secrets)
438 throws RemoteException {
439 checkRecoverKeyStorePermission();
440
441 CertPath certPath;
442 try {
443 certPath = verifierCertPath.getCertPath();
444 } catch (CertificateException e) {
445 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
446 "Failed decode the certificate path");
447 }
448
449 // TODO: Validate the cert path according to the root of trust
450
451 if (certPath.getCertificates().isEmpty()) {
452 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
453 "The given CertPath is empty");
454 }
455 byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded();
456 if (verifierPublicKey == null) {
457 Log.e(TAG, "Failed to encode verifierPublicKey");
458 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
459 "Failed to encode verifierPublicKey");
460 }
461
462 return startRecoverySession(
463 sessionId, verifierPublicKey, vaultParams, vaultChallenge, secrets);
464 }
465
466 /**
Robert Berryb9a220b2017-12-21 12:41:01 +0000467 * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
468 * service.
469 *
Robert Berryb9a220b2017-12-21 12:41:01 +0000470 * @param sessionId The session ID used to generate the claim. See
Bo Zhudef7ffd2018-01-05 14:50:52 -0800471 * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
Robert Berryb9a220b2017-12-21 12:41:01 +0000472 * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
473 * service.
474 * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
475 * were wrapped with the recovery key.
Robert Berrybd4c43c2017-12-22 11:35:14 +0000476 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000477 * @throws RemoteException if an error occurred recovering the keys.
478 */
Robert Berrybd4c43c2017-12-22 11:35:14 +0000479 public Map<String, byte[]> recoverKeys(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800480 @NonNull String sessionId,
Robert Berryb9a220b2017-12-21 12:41:01 +0000481 @NonNull byte[] encryptedRecoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000482 @NonNull List<WrappedApplicationKey> applicationKeys)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800483 throws RemoteException {
484 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800485 int uid = Binder.getCallingUid();
Robert Berryb9a220b2017-12-21 12:41:01 +0000486 RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
487 if (sessionEntry == null) {
Robert Berrya16cd592018-01-17 14:43:09 +0000488 throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800489 String.format(Locale.US,
490 "Application uid=%d does not have pending session '%s'", uid, sessionId));
Robert Berryb9a220b2017-12-21 12:41:01 +0000491 }
492
493 try {
494 byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
Robert Berrybd4c43c2017-12-22 11:35:14 +0000495 return recoverApplicationKeys(recoveryKey, applicationKeys);
Robert Berryb9a220b2017-12-21 12:41:01 +0000496 } finally {
497 sessionEntry.destroy();
498 mRecoverySessionStorage.remove(uid);
499 }
500 }
501
Robert Berrycfc990a2017-12-22 15:54:30 +0000502 /**
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800503 * Deprecated
Robert Berrycfc990a2017-12-22 15:54:30 +0000504 * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
505 * returns the raw key material.
506 *
507 * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
508 *
Bo Zhu2c8e5382018-02-26 15:54:25 -0800509 * @deprecated
Robert Berrycfc990a2017-12-22 15:54:30 +0000510 * @hide
511 */
512 public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
513 int uid = Binder.getCallingUid();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800514 int userId = UserHandle.getCallingUserId();
Robert Berrycfc990a2017-12-22 15:54:30 +0000515
Bo Zhu3462c832018-01-04 22:42:36 -0800516 PlatformEncryptionKey encryptionKey;
Robert Berrycfc990a2017-12-22 15:54:30 +0000517 try {
Bo Zhu3462c832018-01-04 22:42:36 -0800518 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
Robert Berrycfc990a2017-12-22 15:54:30 +0000519 } catch (NoSuchAlgorithmException e) {
520 // Impossible: all algorithms must be supported by AOSP
521 throw new RuntimeException(e);
522 } catch (KeyStoreException | UnrecoverableKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000523 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000524 } catch (InsecureUserException e) {
525 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
526 }
527
528 try {
529 return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
Robert Berrya16cd592018-01-17 14:43:09 +0000530 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
531 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000532 }
533 }
534
Robert Berry2bcdad92018-01-18 12:53:29 +0000535 /**
536 * Destroys the session with the given {@code sessionId}.
537 */
538 public void closeSession(@NonNull String sessionId) throws RemoteException {
539 mRecoverySessionStorage.remove(Binder.getCallingUid(), sessionId);
540 }
541
Robert Berry5daccec2018-01-06 19:16:25 +0000542 public void removeKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800543 int uid = Binder.getCallingUid();
544 int userId = UserHandle.getCallingUserId();
545
546 boolean wasRemoved = mDatabase.removeKey(uid, alias);
547 if (wasRemoved) {
548 mDatabase.setShouldCreateSnapshot(userId, uid, true);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800549 mApplicationKeyStorage.deleteEntry(userId, uid, alias);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800550 }
Robert Berry5daccec2018-01-06 19:16:25 +0000551 }
552
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800553 /**
554 * Generates a key named {@code alias} in caller's namespace.
555 * The key is stored in system service keystore namespace.
556 *
557 * @return grant alias, which caller can use to access the key.
558 */
Robert Berrya3b99472018-02-23 15:59:02 +0000559 public String generateKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800560 int uid = Binder.getCallingUid();
561 int userId = UserHandle.getCallingUserId();
562
563 PlatformEncryptionKey encryptionKey;
564 try {
565 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
566 } catch (NoSuchAlgorithmException e) {
567 // Impossible: all algorithms must be supported by AOSP
568 throw new RuntimeException(e);
569 } catch (KeyStoreException | UnrecoverableKeyException e) {
570 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
571 } catch (InsecureUserException e) {
572 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
573 }
574
575 try {
576 byte[] secretKey =
577 mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
578 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey);
Robert Berrya3b99472018-02-23 15:59:02 +0000579 return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800580 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
581 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
582 }
583 }
584
585 /**
Bo Zhu2c8e5382018-02-26 15:54:25 -0800586 * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service
587 * keystore namespace.
588 *
589 * @param alias the alias provided by caller as a reference to the key.
590 * @param keyBytes the raw bytes of the 256-bit AES key.
591 * @return grant alias, which caller can use to access the key.
592 * @throws RemoteException if the given key is invalid or some internal errors occur.
593 *
594 * @hide
595 */
596 public String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
597 throws RemoteException {
598 if (keyBytes == null ||
599 keyBytes.length != RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE) {
600 Log.e(TAG, "The given key for import doesn't have the required length "
601 + RecoverableKeyGenerator.KEY_SIZE_BITS);
602 throw new ServiceSpecificException(ERROR_INVALID_KEY_FORMAT,
603 "The given key does not contain " + RecoverableKeyGenerator.KEY_SIZE_BITS
604 + " bits.");
605 }
606
607 int uid = Binder.getCallingUid();
608 int userId = UserHandle.getCallingUserId();
609
610 // TODO: Refactor RecoverableKeyGenerator to wrap the PlatformKey logic
611
612 PlatformEncryptionKey encryptionKey;
613 try {
614 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
615 } catch (NoSuchAlgorithmException e) {
616 // Impossible: all algorithms must be supported by AOSP
617 throw new RuntimeException(e);
618 } catch (KeyStoreException | UnrecoverableKeyException e) {
619 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
620 } catch (InsecureUserException e) {
621 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
622 }
623
624 try {
625 // Wrap the key by the platform key and store the wrapped key locally
626 mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes);
627
628 // Import the key to Android KeyStore and get grant
629 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes);
630 return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
631 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
632 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
633 }
634 }
635
636 /**
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800637 * Gets a key named {@code alias} in caller's namespace.
638 *
639 * @return grant alias, which caller can use to access the key.
640 */
641 public String getKey(@NonNull String alias) throws RemoteException {
642 int uid = Binder.getCallingUid();
643 int userId = UserHandle.getCallingUserId();
644 String grantAlias = mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
645 return grantAlias;
646 }
647
Robert Berryb9a220b2017-12-21 12:41:01 +0000648 private byte[] decryptRecoveryKey(
649 RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
Dmitry Dementyev14298312018-01-04 15:19:19 -0800650 throws RemoteException, ServiceSpecificException {
Bo Zhu31a40c02018-01-24 17:40:29 -0800651 byte[] locallyEncryptedKey;
Robert Berryb9a220b2017-12-21 12:41:01 +0000652 try {
Bo Zhu31a40c02018-01-24 17:40:29 -0800653 locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
Robert Berryb9a220b2017-12-21 12:41:01 +0000654 sessionEntry.getKeyClaimant(),
655 sessionEntry.getVaultParams(),
656 encryptedClaimResponse);
Bo Zhu31a40c02018-01-24 17:40:29 -0800657 } catch (InvalidKeyException e) {
658 Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000659 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800660 "Failed to decrypt recovery key " + e.getMessage());
Bo Zhu31a40c02018-01-24 17:40:29 -0800661 } catch (AEADBadTagException e) {
662 Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800663 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
664 "Failed to decrypt recovery key " + e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000665 } catch (NoSuchAlgorithmException e) {
666 // Should never happen: all the algorithms used are required by AOSP implementations
Robert Berrya16cd592018-01-17 14:43:09 +0000667 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000668 }
Bo Zhu31a40c02018-01-24 17:40:29 -0800669
670 try {
Bo Zhu9d05bfb2018-01-29 17:22:03 -0800671 return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
672 } catch (InvalidKeyException e) {
673 Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800674 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
675 "Failed to decrypt recovery key " + e.getMessage());
676 } catch (AEADBadTagException e) {
677 Log.e(TAG, "Got AEADBadTagException during decrypting recovery key", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800678 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
679 "Failed to decrypt recovery key " + e.getMessage());
680 } catch (NoSuchAlgorithmException e) {
681 // Should never happen: all the algorithms used are required by AOSP implementations
682 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
683 }
684 }
685
Robert Berryb9a220b2017-12-21 12:41:01 +0000686 /**
687 * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
688 *
Robert Berrybd4c43c2017-12-22 11:35:14 +0000689 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000690 * @throws RemoteException if an error occurred decrypting the keys.
691 */
Robert Berrybd4c43c2017-12-22 11:35:14 +0000692 private Map<String, byte[]> recoverApplicationKeys(
Robert Berryb9a220b2017-12-21 12:41:01 +0000693 @NonNull byte[] recoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000694 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000695 HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
Robert Berry5f138702018-01-17 15:18:05 +0000696 for (WrappedApplicationKey applicationKey : applicationKeys) {
Dmitry Dementyev07c765552018-01-08 17:31:59 -0800697 String alias = applicationKey.getAlias();
Robert Berryb9a220b2017-12-21 12:41:01 +0000698 byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
699
700 try {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000701 byte[] keyMaterial =
702 KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
703 keyMaterialByAlias.put(alias, keyMaterial);
Robert Berryb9a220b2017-12-21 12:41:01 +0000704 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000705 Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
706 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000707 ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhua9a04e22018-01-26 11:38:11 -0800708 } catch (InvalidKeyException e) {
Bo Zhua9a04e22018-01-26 11:38:11 -0800709 Log.e(TAG, "Got InvalidKeyException during decrypting application key with alias: "
710 + alias, e);
Bo Zhua9a04e22018-01-26 11:38:11 -0800711 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
712 "Failed to recover key with alias '" + alias + "': " + e.getMessage());
713 } catch (AEADBadTagException e) {
Bo Zhua9a04e22018-01-26 11:38:11 -0800714 Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: "
715 + alias, e);
Bo Zhu4857cb52018-02-06 14:34:48 -0800716 // Ignore the exception to continue to recover the other application keys.
Robert Berryb9a220b2017-12-21 12:41:01 +0000717 }
718 }
Bo Zhuae0682d2018-02-13 10:23:39 -0800719 if (!applicationKeys.isEmpty() && keyMaterialByAlias.isEmpty()) {
Bo Zhu4857cb52018-02-06 14:34:48 -0800720 Log.e(TAG, "Failed to recover any of the application keys.");
721 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
722 "Failed to recover any of the application keys.");
723 }
Robert Berrybd4c43c2017-12-22 11:35:14 +0000724 return keyMaterialByAlias;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800725 }
726
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800727 /**
728 * This function can only be used inside LockSettingsService.
729 *
Robert Berry91044042017-12-27 12:05:58 +0000730 * @param storedHashType from {@code CredentialHash}
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800731 * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
732 * mPasswordMaxLength}
733 * @param userId for user who just unlocked the device.
734 * @hide
735 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800736 public void lockScreenSecretAvailable(
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800737 int storedHashType, @NonNull String credential, int userId) {
Robert Berry4a534ec2017-12-21 15:44:02 +0000738 // So as not to block the critical path unlocking the phone, defer to another thread.
739 try {
740 mExecutorService.execute(KeySyncTask.newInstance(
Robert Berry91044042017-12-27 12:05:58 +0000741 mContext,
742 mDatabase,
743 mSnapshotStorage,
744 mListenersStorage,
745 userId,
746 storedHashType,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800747 credential,
748 /*credentialUpdated=*/ false));
Robert Berry4a534ec2017-12-21 15:44:02 +0000749 } catch (NoSuchAlgorithmException e) {
750 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
751 } catch (KeyStoreException e) {
752 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
753 } catch (InsecureUserException e) {
754 Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800755 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800756 }
757
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800758 /**
759 * This function can only be used inside LockSettingsService.
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800760 *
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800761 * @param storedHashType from {@code CredentialHash}
762 * @param credential - unencrypted String
763 * @param userId for the user whose lock screen credentials were changed.
764 * @hide
765 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800766 public void lockScreenSecretChanged(
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800767 int storedHashType,
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800768 @Nullable String credential,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800769 int userId) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800770 // So as not to block the critical path unlocking the phone, defer to another thread.
771 try {
772 mExecutorService.execute(KeySyncTask.newInstance(
773 mContext,
774 mDatabase,
775 mSnapshotStorage,
776 mListenersStorage,
777 userId,
778 storedHashType,
779 credential,
780 /*credentialUpdated=*/ true));
781 } catch (NoSuchAlgorithmException e) {
782 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
783 } catch (KeyStoreException e) {
784 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
785 } catch (InsecureUserException e) {
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800786 Log.e(TAG, "InsecureUserException during lock screen secret update", e);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800787 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800788 }
789
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800790 private void checkRecoverKeyStorePermission() {
791 mContext.enforceCallingOrSelfPermission(
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800792 Manifest.permission.RECOVER_KEYSTORE,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800793 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
794 }
Bo Zhudef7ffd2018-01-05 14:50:52 -0800795
796 private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
797 byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
798 return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length));
799 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800800}