blob: 1b14ce21bb922f52ec4f8dca6143101dae72465f [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;
Aseem Kumar23174b72018-04-03 11:35:51 -070021import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
Robert Berryd9f11a92018-02-26 16:37:06 +000022import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
Bo Zhu7f414d92018-02-28 09:28:19 -080023import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
Bo Zhuc7048342019-01-03 14:04:58 -080024import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
Robert Berryd9f11a92018-02-26 16:37:06 +000025import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
26import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
27import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED;
Robert Berry97e55582018-01-05 12:43:13 +000028
Bo Zhu14d993d2018-02-03 21:38:48 -080029import android.Manifest;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080030import android.annotation.NonNull;
31import android.annotation.Nullable;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080032import android.app.PendingIntent;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080033import android.content.Context;
34import android.os.Binder;
35import android.os.RemoteException;
36import android.os.ServiceSpecificException;
37import android.os.UserHandle;
Bo Zhuc7048342019-01-03 14:04:58 -080038import android.security.KeyStore;
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080039import android.security.keystore.recovery.KeyChainProtectionParams;
40import android.security.keystore.recovery.KeyChainSnapshot;
Bo Zhu7c1972f2018-02-22 21:43:52 -080041import android.security.keystore.recovery.RecoveryCertPath;
Robert Berry81ee34b2018-01-23 11:59:59 +000042import android.security.keystore.recovery.RecoveryController;
43import android.security.keystore.recovery.WrappedApplicationKey;
Robert Berry4a5c87d2018-03-19 18:00:46 +000044import android.util.ArrayMap;
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;
Bo Zhu31a40c02018-01-24 17:40:29 -080048import com.android.internal.util.HexDump;
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -070049import com.android.internal.util.Preconditions;
Bo Zhu14d993d2018-02-03 21:38:48 -080050import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
Dmitry Dementyev482633f2018-04-03 16:47:24 -070051import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
Bo Zhu14d993d2018-02-03 21:38:48 -080052import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
53import com.android.server.locksettings.recoverablekeystore.certificate.CertXml;
Dmitry Dementyev482633f2018-04-03 16:47:24 -070054import com.android.server.locksettings.recoverablekeystore.certificate.SigXml;
55import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
Dmitry Dementyev89f12d52019-02-28 12:26:01 -080056import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager;
Robert Berrye16fa982017-12-20 15:59:37 +000057import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
58import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
Robert Berrybd086f12017-12-27 13:29:39 +000059import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080060
Dmitry Dementyev20eaaa42018-05-09 13:05:04 -070061import java.io.IOException;
Robert Berrye16fa982017-12-20 15:59:37 +000062import java.security.InvalidKeyException;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080063import java.security.KeyStoreException;
Robert Berrye16fa982017-12-20 15:59:37 +000064import java.security.NoSuchAlgorithmException;
65import java.security.PublicKey;
Bo Zhu8d6861e2018-03-21 20:45:09 -070066import java.security.SecureRandom;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080067import java.security.UnrecoverableKeyException;
Bo Zhu14d993d2018-02-03 21:38:48 -080068import java.security.cert.CertPath;
69import java.security.cert.CertificateEncodingException;
Bo Zhu7c1972f2018-02-22 21:43:52 -080070import java.security.cert.CertificateException;
Bo Zhub31ab672018-03-20 22:44:18 -070071import java.security.cert.X509Certificate;
Bo Zhu14d993d2018-02-03 21:38:48 -080072import java.security.spec.InvalidKeySpecException;
Bo Zhudef7ffd2018-01-05 14:50:52 -080073import java.util.Arrays;
Robert Berrybd4c43c2017-12-22 11:35:14 +000074import java.util.HashMap;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080075import java.util.List;
Robert Berryb9a220b2017-12-21 12:41:01 +000076import java.util.Locale;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080077import java.util.Map;
Robert Berry4a534ec2017-12-21 15:44:02 +000078import java.util.concurrent.ExecutorService;
79import java.util.concurrent.Executors;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080080
Robert Berryb9a220b2017-12-21 12:41:01 +000081import javax.crypto.AEADBadTagException;
82
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080083/**
Robert Berry74928a12018-01-18 17:49:07 +000084 * Class with {@link RecoveryController} API implementation and internal methods to interact
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080085 * with {@code LockSettingsService}.
86 *
87 * @hide
88 */
89public class RecoverableKeyStoreManager {
Robert Berry4a534ec2017-12-21 15:44:02 +000090 private static final String TAG = "RecoverableKeyStoreMgr";
Robert Berrycfc990a2017-12-22 15:54:30 +000091
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080092 private static RecoverableKeyStoreManager mInstance;
Robert Berrye16fa982017-12-20 15:59:37 +000093
94 private final Context mContext;
95 private final RecoverableKeyStoreDb mDatabase;
96 private final RecoverySessionStorage mRecoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000097 private final ExecutorService mExecutorService;
Robert Berry91044042017-12-27 12:05:58 +000098 private final RecoverySnapshotListenersStorage mListenersStorage;
Robert Berrycfc990a2017-12-22 15:54:30 +000099 private final RecoverableKeyGenerator mRecoverableKeyGenerator;
Robert Berrybd086f12017-12-27 13:29:39 +0000100 private final RecoverySnapshotStorage mSnapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800101 private final PlatformKeyManager mPlatformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800102 private final ApplicationKeyStorage mApplicationKeyStorage;
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700103 private final TestOnlyInsecureCertificateHelper mTestCertHelper;
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800104 private final CleanupManager mCleanupManager;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800105
106 /**
107 * Returns a new or existing instance.
108 *
109 * @hide
110 */
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800111 public static synchronized RecoverableKeyStoreManager
112 getInstance(Context context, KeyStore keystore) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800113 if (mInstance == null) {
Bo Zhu3462c832018-01-04 22:42:36 -0800114 RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
115 PlatformKeyManager platformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800116 ApplicationKeyStorage applicationKeyStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800117 try {
118 platformKeyManager = PlatformKeyManager.getInstance(context, db);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800119 applicationKeyStorage = ApplicationKeyStorage.getInstance(keystore);
Bo Zhu3462c832018-01-04 22:42:36 -0800120 } catch (NoSuchAlgorithmException e) {
121 // Impossible: all algorithms must be supported by AOSP
122 throw new RuntimeException(e);
123 } catch (KeyStoreException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000124 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhu3462c832018-01-04 22:42:36 -0800125 }
126
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800127 RecoverySnapshotStorage snapshotStorage =
128 RecoverySnapshotStorage.newInstance();
129 CleanupManager cleanupManager = CleanupManager.getInstance(
130 context.getApplicationContext(),
131 snapshotStorage,
132 db,
133 applicationKeyStorage);
Robert Berrye16fa982017-12-20 15:59:37 +0000134 mInstance = new RecoverableKeyStoreManager(
Bo Zhu3462c832018-01-04 22:42:36 -0800135 context.getApplicationContext(),
Robert Berrye16fa982017-12-20 15:59:37 +0000136 db,
Robert Berry4a534ec2017-12-21 15:44:02 +0000137 new RecoverySessionStorage(),
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800138 Executors.newSingleThreadExecutor(),
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800139 snapshotStorage,
Bo Zhu3462c832018-01-04 22:42:36 -0800140 new RecoverySnapshotListenersStorage(),
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800141 platformKeyManager,
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700142 applicationKeyStorage,
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800143 new TestOnlyInsecureCertificateHelper(),
144 cleanupManager);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800145 }
146 return mInstance;
147 }
148
149 @VisibleForTesting
Robert Berrye16fa982017-12-20 15:59:37 +0000150 RecoverableKeyStoreManager(
151 Context context,
152 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berry4a534ec2017-12-21 15:44:02 +0000153 RecoverySessionStorage recoverySessionStorage,
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800154 ExecutorService executorService,
Robert Berry91044042017-12-27 12:05:58 +0000155 RecoverySnapshotStorage snapshotStorage,
Bo Zhu3462c832018-01-04 22:42:36 -0800156 RecoverySnapshotListenersStorage listenersStorage,
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800157 PlatformKeyManager platformKeyManager,
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700158 ApplicationKeyStorage applicationKeyStorage,
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800159 TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper,
160 CleanupManager cleanupManager) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800161 mContext = context;
Robert Berrye16fa982017-12-20 15:59:37 +0000162 mDatabase = recoverableKeyStoreDb;
163 mRecoverySessionStorage = recoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000164 mExecutorService = executorService;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800165 mListenersStorage = listenersStorage;
Robert Berrybd086f12017-12-27 13:29:39 +0000166 mSnapshotStorage = snapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800167 mPlatformKeyManager = platformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800168 mApplicationKeyStorage = applicationKeyStorage;
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800169 mTestCertHelper = testOnlyInsecureCertificateHelper;
170 mCleanupManager = cleanupManager;
171 // Clears data for removed users.
172 mCleanupManager.verifyKnownUsers();
Robert Berrycfc990a2017-12-22 15:54:30 +0000173 try {
174 mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
175 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000176 Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000177 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000178 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800179 }
180
Bo Zhu7f414d92018-02-28 09:28:19 -0800181 /**
Dmitry Dementyev745d2c92018-04-13 14:10:05 -0700182 * Used by {@link #initRecoveryServiceWithSigFile(String, byte[], byte[])}.
Bo Zhu7f414d92018-02-28 09:28:19 -0800183 */
Dmitry Dementyev745d2c92018-04-13 14:10:05 -0700184 @VisibleForTesting
185 void initRecoveryService(
Bo Zhu14d993d2018-02-03 21:38:48 -0800186 @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800187 throws RemoteException {
188 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800189 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800190 int uid = Binder.getCallingUid();
Bo Zhu0b8c82e2018-03-30 11:31:53 -0700191
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700192 rootCertificateAlias
193 = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
Bo Zhu0b8c82e2018-03-30 11:31:53 -0700194 if (!mTestCertHelper.isValidRootCertificateAlias(rootCertificateAlias)) {
195 throw new ServiceSpecificException(
196 ERROR_INVALID_CERTIFICATE, "Invalid root certificate alias");
197 }
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700198 // Always set active alias to the argument of the last call to initRecoveryService method,
199 // even if cert file is incorrect.
200 String activeRootAlias = mDatabase.getActiveRootOfTrust(userId, uid);
201 if (activeRootAlias == null) {
202 Log.d(TAG, "Root of trust for recovery agent + " + uid
203 + " is assigned for the first time to " + rootCertificateAlias);
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700204 } else if (!activeRootAlias.equals(rootCertificateAlias)) {
205 Log.i(TAG, "Root of trust for recovery agent " + uid + " is changed to "
206 + rootCertificateAlias + " from " + activeRootAlias);
Dmitry Dementyev20eaaa42018-05-09 13:05:04 -0700207 }
208 long updatedRows = mDatabase.setActiveRootOfTrust(userId, uid, rootCertificateAlias);
209 if (updatedRows < 0) {
210 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
211 "Failed to set the root of trust in the local DB.");
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700212 }
Bo Zhu14d993d2018-02-03 21:38:48 -0800213
Bo Zhu14d993d2018-02-03 21:38:48 -0800214 CertXml certXml;
215 try {
216 certXml = CertXml.parse(recoveryServiceCertFile);
217 } catch (CertParsingException e) {
Bo Zhued00d252018-02-14 11:50:57 -0800218 Log.d(TAG, "Failed to parse the input as a cert file: " + HexDump.toHexString(
219 recoveryServiceCertFile));
Bo Zhuc98c8432018-03-31 13:08:49 -0700220 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Bo Zhu14d993d2018-02-03 21:38:48 -0800221 }
222
223 // Check serial number
224 long newSerial = certXml.getSerial();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700225 Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid, rootCertificateAlias);
Bo Zhub10ba442018-03-31 17:13:46 -0700226 if (oldSerial != null && oldSerial >= newSerial
227 && !mTestCertHelper.isTestOnlyCertificateAlias(rootCertificateAlias)) {
Bo Zhu14d993d2018-02-03 21:38:48 -0800228 if (oldSerial == newSerial) {
229 Log.i(TAG, "The cert file serial number is the same, so skip updating.");
230 } else {
231 Log.e(TAG, "The cert file serial number is older than the one in database.");
Aseem Kumar23174b72018-04-03 11:35:51 -0700232 throw new ServiceSpecificException(ERROR_DOWNGRADE_CERTIFICATE,
233 "The cert file serial number is older than the one in database.");
Bo Zhu14d993d2018-02-03 21:38:48 -0800234 }
235 return;
236 }
237 Log.i(TAG, "Updating the certificate with the new serial number " + newSerial);
238
Bo Zhub31ab672018-03-20 22:44:18 -0700239 // Randomly choose and validate an endpoint certificate from the list
Bo Zhu14d993d2018-02-03 21:38:48 -0800240 CertPath certPath;
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700241 X509Certificate rootCert =
242 mTestCertHelper.getRootCertificate(rootCertificateAlias);
Bo Zhu14d993d2018-02-03 21:38:48 -0800243 try {
244 Log.d(TAG, "Getting and validating a random endpoint certificate");
Bo Zhub31ab672018-03-20 22:44:18 -0700245 certPath = certXml.getRandomEndpointCert(rootCert);
Bo Zhu14d993d2018-02-03 21:38:48 -0800246 } catch (CertValidationException e) {
247 Log.e(TAG, "Invalid endpoint cert", e);
Bo Zhu41d2dd22018-03-30 12:20:06 -0700248 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
Bo Zhu14d993d2018-02-03 21:38:48 -0800249 }
Bo Zhub31ab672018-03-20 22:44:18 -0700250
251 // Save the chosen and validated certificate into database
Bo Zhu14d993d2018-02-03 21:38:48 -0800252 try {
253 Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
Dmitry Dementyev20eaaa42018-05-09 13:05:04 -0700254 long updatedCertPathRows = mDatabase.setRecoveryServiceCertPath(userId, uid,
255 rootCertificateAlias, certPath);
256 if (updatedCertPathRows > 0) {
257 long updatedCertSerialRows = mDatabase.setRecoveryServiceCertSerial(userId, uid,
258 rootCertificateAlias, newSerial);
259 if (updatedCertSerialRows < 0) {
260 // Ideally CertPath and CertSerial should be updated together in single
261 // transaction, but since their mismatch doesn't create many problems
262 // extra complexity is unnecessary.
263 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
264 "Failed to set the certificate serial number in the local DB.");
265 }
Dmitry Dementyev925f0262018-04-12 12:10:50 -0700266 if (mDatabase.getSnapshotVersion(userId, uid) != null) {
Robert Berrye8edf972018-03-27 11:45:11 +0100267 mDatabase.setShouldCreateSnapshot(userId, uid, true);
Dmitry Dementyev925f0262018-04-12 12:10:50 -0700268 Log.i(TAG, "This is a certificate change. Snapshot must be updated");
269 } else {
270 Log.i(TAG, "This is a certificate change. Snapshot didn't exist");
Robert Berrye8edf972018-03-27 11:45:11 +0100271 }
Dmitry Dementyev20eaaa42018-05-09 13:05:04 -0700272 long updatedCounterIdRows =
273 mDatabase.setCounterId(userId, uid, new SecureRandom().nextLong());
274 if (updatedCounterIdRows < 0) {
275 Log.e(TAG, "Failed to set the counter id in the local DB.");
276 }
277 } else if (updatedCertPathRows < 0) {
278 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
279 "Failed to set the certificate path in the local DB.");
Bo Zhu14d993d2018-02-03 21:38:48 -0800280 }
281 } catch (CertificateEncodingException e) {
282 Log.e(TAG, "Failed to encode CertPath", e);
Bo Zhu41d2dd22018-03-30 12:20:06 -0700283 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Bo Zhu14d993d2018-02-03 21:38:48 -0800284 }
285 }
286
Bo Zhu7f414d92018-02-28 09:28:19 -0800287 /**
288 * Initializes the recovery service with the two files {@code recoveryServiceCertFile} and
289 * {@code recoveryServiceSigFile}.
290 *
291 * @param rootCertificateAlias the alias for the root certificate that is used for validating
292 * the recovery service certificates.
293 * @param recoveryServiceCertFile the content of the XML file containing a list of certificates
294 * for the recovery service.
295 * @param recoveryServiceSigFile the content of the XML file containing the public-key signature
296 * over the entire content of {@code recoveryServiceCertFile}.
297 */
298 public void initRecoveryServiceWithSigFile(
299 @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile,
300 @NonNull byte[] recoveryServiceSigFile)
301 throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700302 checkRecoverKeyStorePermission();
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700303 rootCertificateAlias =
304 mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700305 Preconditions.checkNotNull(recoveryServiceCertFile, "recoveryServiceCertFile is null");
306 Preconditions.checkNotNull(recoveryServiceSigFile, "recoveryServiceSigFile is null");
Bo Zhu7f414d92018-02-28 09:28:19 -0800307
308 SigXml sigXml;
309 try {
310 sigXml = SigXml.parse(recoveryServiceSigFile);
311 } catch (CertParsingException e) {
312 Log.d(TAG, "Failed to parse the sig file: " + HexDump.toHexString(
313 recoveryServiceSigFile));
Bo Zhu41d2dd22018-03-30 12:20:06 -0700314 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Bo Zhu7f414d92018-02-28 09:28:19 -0800315 }
316
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700317 X509Certificate rootCert =
318 mTestCertHelper.getRootCertificate(rootCertificateAlias);
Bo Zhu7f414d92018-02-28 09:28:19 -0800319 try {
Bo Zhub31ab672018-03-20 22:44:18 -0700320 sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile);
Bo Zhu7f414d92018-02-28 09:28:19 -0800321 } catch (CertValidationException e) {
322 Log.d(TAG, "The signature over the cert file is invalid."
323 + " Cert: " + HexDump.toHexString(recoveryServiceCertFile)
324 + " Sig: " + HexDump.toHexString(recoveryServiceSigFile));
Bo Zhu41d2dd22018-03-30 12:20:06 -0700325 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
Bo Zhu7f414d92018-02-28 09:28:19 -0800326 }
327
328 initRecoveryService(rootCertificateAlias, recoveryServiceCertFile);
329 }
330
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800331 /**
332 * Gets all data necessary to recover application keys on new device.
333 *
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700334 * @return KeyChain Snapshot.
335 * @throws ServiceSpecificException if no snapshot is pending.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800336 * @hide
337 */
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700338 public @NonNull KeyChainSnapshot getKeyChainSnapshot()
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800339 throws RemoteException {
340 checkRecoverKeyStorePermission();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800341 int uid = Binder.getCallingUid();
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800342 KeyChainSnapshot snapshot = mSnapshotStorage.get(uid);
Robert Berrybd086f12017-12-27 13:29:39 +0000343 if (snapshot == null) {
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800344 throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
Robert Berrybd086f12017-12-27 13:29:39 +0000345 }
346 return snapshot;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800347 }
348
Dmitry Dementyev14298312018-01-04 15:19:19 -0800349 public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800350 throws RemoteException {
351 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800352 int uid = Binder.getCallingUid();
353 mListenersStorage.setSnapshotListener(uid, intent);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800354 }
355
Robert Berry8f9038c2018-03-26 11:36:40 +0100356 /**
357 * Set the server params for the user's key chain. This is used to uniquely identify a key
358 * chain. Along with the counter ID, it is used to uniquely identify an instance of a vault.
359 */
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700360 public void setServerParams(@NonNull byte[] serverParams) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800361 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800362 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800363 int uid = Binder.getCallingUid();
Robert Berry8f9038c2018-03-26 11:36:40 +0100364
365 byte[] currentServerParams = mDatabase.getServerParams(userId, uid);
366
367 if (Arrays.equals(serverParams, currentServerParams)) {
368 Log.v(TAG, "Not updating server params - same as old value.");
369 return;
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800370 }
Robert Berry8f9038c2018-03-26 11:36:40 +0100371
372 long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
Dmitry Dementyev20eaaa42018-05-09 13:05:04 -0700373 if (updatedRows < 0) {
Robert Berry8f9038c2018-03-26 11:36:40 +0100374 throw new ServiceSpecificException(
375 ERROR_SERVICE_INTERNAL_ERROR, "Database failure trying to set server params.");
376 }
377
378 if (currentServerParams == null) {
379 Log.i(TAG, "Initialized server params.");
380 return;
381 }
382
Dmitry Dementyev925f0262018-04-12 12:10:50 -0700383 if (mDatabase.getSnapshotVersion(userId, uid) != null) {
384 mDatabase.setShouldCreateSnapshot(userId, uid, true);
385 Log.i(TAG, "Updated server params. Snapshot must be updated");
386 } else {
387 Log.i(TAG, "Updated server params. Snapshot didn't exist");
388 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800389 }
390
Dmitry Dementyevad884712017-12-20 12:38:36 -0800391 /**
Robert Berrybbe02ae2018-02-20 19:47:43 +0000392 * Sets the recovery status of key with {@code alias} to {@code status}.
Dmitry Dementyevad884712017-12-20 12:38:36 -0800393 */
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700394 public void setRecoveryStatus(@NonNull String alias, int status) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800395 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700396 Preconditions.checkNotNull(alias, "alias is null");
Dmitry Dementyev20eaaa42018-05-09 13:05:04 -0700397 long updatedRows = mDatabase.setRecoveryStatus(Binder.getCallingUid(), alias, status);
398 if (updatedRows < 0) {
399 throw new ServiceSpecificException(
400 ERROR_SERVICE_INTERNAL_ERROR,
401 "Failed to set the key recovery status in the local DB.");
402 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800403 }
404
405 /**
Robert Berry56f06b42018-02-23 13:31:32 +0000406 * Returns recovery statuses for all keys belonging to the calling uid.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800407 *
Robert Berry56f06b42018-02-23 13:31:32 +0000408 * @return {@link Map} from key alias to recovery status. Recovery status is one of
409 * {@link RecoveryController#RECOVERY_STATUS_SYNCED},
410 * {@link RecoveryController#RECOVERY_STATUS_SYNC_IN_PROGRESS} or
411 * {@link RecoveryController#RECOVERY_STATUS_PERMANENT_FAILURE}.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800412 */
Robert Berry56f06b42018-02-23 13:31:32 +0000413 public @NonNull Map<String, Integer> getRecoveryStatus() throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700414 checkRecoverKeyStorePermission();
Dmitry Dementyevad884712017-12-20 12:38:36 -0800415 return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800416 }
417
418 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800419 * Sets recovery secrets list used by all recovery agents for given {@code userId}
420 *
421 * @hide
422 */
423 public void setRecoverySecretTypes(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800424 @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800425 throws RemoteException {
426 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700427 Preconditions.checkNotNull(secretTypes, "secretTypes is null");
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800428 int userId = UserHandle.getCallingUserId();
429 int uid = Binder.getCallingUid();
Robert Berry5a1acefb2018-03-26 14:41:30 +0100430
431 int[] currentSecretTypes = mDatabase.getRecoverySecretTypes(userId, uid);
432 if (Arrays.equals(secretTypes, currentSecretTypes)) {
433 Log.v(TAG, "Not updating secret types - same as old value.");
434 return;
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800435 }
Robert Berry5a1acefb2018-03-26 14:41:30 +0100436
437 long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes);
Dmitry Dementyev20eaaa42018-05-09 13:05:04 -0700438 if (updatedRows < 0) {
Robert Berry5a1acefb2018-03-26 14:41:30 +0100439 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
440 "Database error trying to set secret types.");
441 }
442
443 if (currentSecretTypes.length == 0) {
444 Log.i(TAG, "Initialized secret types.");
445 return;
446 }
447
448 Log.i(TAG, "Updated secret types. Snapshot pending.");
Dmitry Dementyev925f0262018-04-12 12:10:50 -0700449 if (mDatabase.getSnapshotVersion(userId, uid) != null) {
450 mDatabase.setShouldCreateSnapshot(userId, uid, true);
451 Log.i(TAG, "Updated secret types. Snapshot must be updated");
452 } else {
453 Log.i(TAG, "Updated secret types. Snapshot didn't exist");
454 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800455 }
456
457 /**
458 * Gets secret types necessary to create Recovery Data.
459 *
460 * @return secret types
461 * @hide
462 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800463 public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800464 checkRecoverKeyStorePermission();
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800465 return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
466 Binder.getCallingUid());
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800467 }
468
469 /**
Bo Zhu7c1972f2018-02-22 21:43:52 -0800470 * Initializes recovery session given the X509-encoded public key of the recovery service.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800471 *
Robert Berrye16fa982017-12-20 15:59:37 +0000472 * @param sessionId A unique ID to identify the recovery session.
473 * @param verifierPublicKey X509-encoded public key.
474 * @param vaultParams Additional params associated with vault.
475 * @param vaultChallenge Challenge issued by vault service.
Robert Berryb9a220b2017-12-21 12:41:01 +0000476 * @param secrets Lock-screen hashes. For now only a single secret is supported.
Robert Berrye16fa982017-12-20 15:59:37 +0000477 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
Bo Zhuf23c2032018-03-21 22:46:55 -0700478 * @deprecated Use {@link #startRecoverySessionWithCertPath(String, String, RecoveryCertPath,
479 * byte[], byte[], List)} instead.
Robert Berrye16fa982017-12-20 15:59:37 +0000480 *
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800481 * @hide
482 */
Dmitry Dementyev745d2c92018-04-13 14:10:05 -0700483 @VisibleForTesting
484 @NonNull byte[] startRecoverySession(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800485 @NonNull String sessionId,
486 @NonNull byte[] verifierPublicKey,
487 @NonNull byte[] vaultParams,
488 @NonNull byte[] vaultChallenge,
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800489 @NonNull List<KeyChainProtectionParams> secrets)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800490 throws RemoteException {
491 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800492 int uid = Binder.getCallingUid();
Robert Berrye16fa982017-12-20 15:59:37 +0000493
494 if (secrets.size() != 1) {
Robert Berry9e1bd362018-01-17 23:28:45 +0000495 throw new UnsupportedOperationException(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800496 "Only a single KeyChainProtectionParams is supported");
Robert Berrye16fa982017-12-20 15:59:37 +0000497 }
498
Bo Zhudef7ffd2018-01-05 14:50:52 -0800499 PublicKey publicKey;
500 try {
501 publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
Bo Zhudef7ffd2018-01-05 14:50:52 -0800502 } catch (InvalidKeySpecException e) {
Bo Zhu41d2dd22018-03-30 12:20:06 -0700503 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Bo Zhudef7ffd2018-01-05 14:50:52 -0800504 }
505 // The raw public key bytes contained in vaultParams must match the ones given in
506 // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned
507 // by the original recovery service.
508 if (!publicKeysMatch(publicKey, vaultParams)) {
Bo Zhu7f414d92018-02-28 09:28:19 -0800509 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE,
Bo Zhudef7ffd2018-01-05 14:50:52 -0800510 "The public keys given in verifierPublicKey and vaultParams do not match.");
511 }
512
Robert Berrye16fa982017-12-20 15:59:37 +0000513 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
514 byte[] kfHash = secrets.get(0).getSecret();
515 mRecoverySessionStorage.add(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800516 uid,
Robert Berryb9a220b2017-12-21 12:41:01 +0000517 new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
Robert Berrye16fa982017-12-20 15:59:37 +0000518
Bo Zhuf23c2032018-03-21 22:46:55 -0700519 Log.i(TAG, "Received VaultParams for recovery: " + HexDump.toHexString(vaultParams));
Robert Berrye16fa982017-12-20 15:59:37 +0000520 try {
521 byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
Robert Berrye16fa982017-12-20 15:59:37 +0000522 return KeySyncUtils.encryptRecoveryClaim(
523 publicKey,
524 vaultParams,
525 vaultChallenge,
526 thmKfHash,
527 keyClaimant);
528 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000529 Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000530 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhudef7ffd2018-01-05 14:50:52 -0800531 } catch (InvalidKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000532 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000533 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800534 }
535
Robert Berryb9a220b2017-12-21 12:41:01 +0000536 /**
Bo Zhu7c1972f2018-02-22 21:43:52 -0800537 * Initializes recovery session given the certificate path of the recovery service.
538 *
539 * @param sessionId A unique ID to identify the recovery session.
540 * @param verifierCertPath The certificate path of the recovery service.
541 * @param vaultParams Additional params associated with vault.
542 * @param vaultChallenge Challenge issued by vault service.
543 * @param secrets Lock-screen hashes. For now only a single secret is supported.
544 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
545 *
546 * @hide
547 */
548 public @NonNull byte[] startRecoverySessionWithCertPath(
549 @NonNull String sessionId,
Bo Zhub31ab672018-03-20 22:44:18 -0700550 @NonNull String rootCertificateAlias,
Bo Zhu7c1972f2018-02-22 21:43:52 -0800551 @NonNull RecoveryCertPath verifierCertPath,
552 @NonNull byte[] vaultParams,
553 @NonNull byte[] vaultChallenge,
554 @NonNull List<KeyChainProtectionParams> secrets)
555 throws RemoteException {
556 checkRecoverKeyStorePermission();
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700557 rootCertificateAlias =
558 mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700559 Preconditions.checkNotNull(sessionId, "invalid session");
560 Preconditions.checkNotNull(verifierCertPath, "verifierCertPath is null");
561 Preconditions.checkNotNull(vaultParams, "vaultParams is null");
562 Preconditions.checkNotNull(vaultChallenge, "vaultChallenge is null");
563 Preconditions.checkNotNull(secrets, "secrets is null");
Bo Zhu7c1972f2018-02-22 21:43:52 -0800564 CertPath certPath;
565 try {
566 certPath = verifierCertPath.getCertPath();
567 } catch (CertificateException e) {
Bo Zhu41d2dd22018-03-30 12:20:06 -0700568 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Bo Zhu7c1972f2018-02-22 21:43:52 -0800569 }
570
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800571 try {
Dmitry Dementyev57ca3da2018-03-28 12:36:45 -0700572 CertUtils.validateCertPath(
573 mTestCertHelper.getRootCertificate(rootCertificateAlias), certPath);
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800574 } catch (CertValidationException e) {
575 Log.e(TAG, "Failed to validate the given cert path", e);
Bo Zhub31ab672018-03-20 22:44:18 -0700576 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
Bo Zhu7c1972f2018-02-22 21:43:52 -0800577 }
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800578
Bo Zhu7c1972f2018-02-22 21:43:52 -0800579 byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded();
580 if (verifierPublicKey == null) {
581 Log.e(TAG, "Failed to encode verifierPublicKey");
582 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
583 "Failed to encode verifierPublicKey");
584 }
585
586 return startRecoverySession(
587 sessionId, verifierPublicKey, vaultParams, vaultChallenge, secrets);
588 }
589
590 /**
Robert Berryb9a220b2017-12-21 12:41:01 +0000591 * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
592 * service.
593 *
Robert Berryb9a220b2017-12-21 12:41:01 +0000594 * @param sessionId The session ID used to generate the claim. See
Bo Zhudef7ffd2018-01-05 14:50:52 -0800595 * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
Robert Berryb9a220b2017-12-21 12:41:01 +0000596 * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
597 * service.
598 * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
599 * were wrapped with the recovery key.
Robert Berry4a5c87d2018-03-19 18:00:46 +0000600 * @throws RemoteException if an error occurred recovering the keys.
601 */
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700602 public @NonNull Map<String, String> recoverKeyChainSnapshot(
Robert Berry4a5c87d2018-03-19 18:00:46 +0000603 @NonNull String sessionId,
604 @NonNull byte[] encryptedRecoveryKey,
605 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
606 checkRecoverKeyStorePermission();
607 int userId = UserHandle.getCallingUserId();
608 int uid = Binder.getCallingUid();
609 RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
610 if (sessionEntry == null) {
611 throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
612 String.format(Locale.US,
613 "Application uid=%d does not have pending session '%s'",
614 uid,
615 sessionId));
616 }
617
618 try {
619 byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
Bo Zhu7ebcd662019-01-04 17:00:58 -0800620 Map<String, byte[]> keysByAlias = recoverApplicationKeys(recoveryKey,
621 applicationKeys);
Robert Berry4a5c87d2018-03-19 18:00:46 +0000622 return importKeyMaterials(userId, uid, keysByAlias);
623 } catch (KeyStoreException e) {
624 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
625 } finally {
626 sessionEntry.destroy();
627 mRecoverySessionStorage.remove(uid);
628 }
629 }
630
631 /**
632 * Imports the key materials, returning a map from alias to grant alias for the calling user.
633 *
634 * @param userId The calling user ID.
635 * @param uid The calling uid.
636 * @param keysByAlias The key materials, keyed by alias.
637 * @throws KeyStoreException if an error occurs importing the key or getting the grant.
638 */
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700639 private @NonNull Map<String, String> importKeyMaterials(
Bo Zhu7ebcd662019-01-04 17:00:58 -0800640 int userId, int uid, Map<String, byte[]> keysByAlias)
641 throws KeyStoreException {
Robert Berry4a5c87d2018-03-19 18:00:46 +0000642 ArrayMap<String, String> grantAliasesByAlias = new ArrayMap<>(keysByAlias.size());
643 for (String alias : keysByAlias.keySet()) {
644 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keysByAlias.get(alias));
645 String grantAlias = getAlias(userId, uid, alias);
646 Log.i(TAG, String.format(Locale.US, "Import %s -> %s", alias, grantAlias));
647 grantAliasesByAlias.put(alias, grantAlias);
648 }
649 return grantAliasesByAlias;
650 }
651
652 /**
653 * Returns an alias for the key.
654 *
655 * @param userId The user ID of the calling process.
656 * @param uid The uid of the calling process.
657 * @param alias The alias of the key.
658 * @return The alias in the calling process's keystore.
659 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700660 private @Nullable String getAlias(int userId, int uid, String alias) {
Robert Berry4a5c87d2018-03-19 18:00:46 +0000661 return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
662 }
663
664 /**
Robert Berry2bcdad92018-01-18 12:53:29 +0000665 * Destroys the session with the given {@code sessionId}.
666 */
667 public void closeSession(@NonNull String sessionId) throws RemoteException {
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700668 checkRecoverKeyStorePermission();
669 Preconditions.checkNotNull(sessionId, "invalid session");
Robert Berry2bcdad92018-01-18 12:53:29 +0000670 mRecoverySessionStorage.remove(Binder.getCallingUid(), sessionId);
671 }
672
Robert Berry5daccec2018-01-06 19:16:25 +0000673 public void removeKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700674 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700675 Preconditions.checkNotNull(alias, "alias is null");
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800676 int uid = Binder.getCallingUid();
677 int userId = UserHandle.getCallingUserId();
678
679 boolean wasRemoved = mDatabase.removeKey(uid, alias);
680 if (wasRemoved) {
681 mDatabase.setShouldCreateSnapshot(userId, uid, true);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800682 mApplicationKeyStorage.deleteEntry(userId, uid, alias);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800683 }
Robert Berry5daccec2018-01-06 19:16:25 +0000684 }
685
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800686 /**
687 * Generates a key named {@code alias} in caller's namespace.
688 * The key is stored in system service keystore namespace.
689 *
Bo Zhuc7048342019-01-03 14:04:58 -0800690 * @param alias the alias provided by caller as a reference to the key.
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800691 * @return grant alias, which caller can use to access the key.
Bo Zhuc7048342019-01-03 14:04:58 -0800692 * @throws RemoteException if certain internal errors occur.
693 *
694 * @deprecated Use {@link #generateKeyWithMetadata(String, byte[])} instead.
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800695 */
Bo Zhuc7048342019-01-03 14:04:58 -0800696 @Deprecated
Robert Berrya3b99472018-02-23 15:59:02 +0000697 public String generateKey(@NonNull String alias) throws RemoteException {
Bo Zhuc7048342019-01-03 14:04:58 -0800698 return generateKeyWithMetadata(alias, /*metadata=*/ null);
699 }
700
701 /**
702 * Generates a key named {@code alias} with the {@code metadata} in caller's namespace.
703 * The key is stored in system service keystore namespace.
704 *
705 * @param alias the alias provided by caller as a reference to the key.
706 * @param metadata the optional metadata blob that will authenticated (but unencrypted) together
707 * with the key material when the key is uploaded to cloud.
708 * @return grant alias, which caller can use to access the key.
709 * @throws RemoteException if certain internal errors occur.
710 */
711 public String generateKeyWithMetadata(@NonNull String alias, @Nullable byte[] metadata)
712 throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700713 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700714 Preconditions.checkNotNull(alias, "alias is null");
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800715 int uid = Binder.getCallingUid();
716 int userId = UserHandle.getCallingUserId();
717
718 PlatformEncryptionKey encryptionKey;
719 try {
720 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
721 } catch (NoSuchAlgorithmException e) {
722 // Impossible: all algorithms must be supported by AOSP
723 throw new RuntimeException(e);
Dmitry Dementyev20eaaa42018-05-09 13:05:04 -0700724 } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800725 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
726 } catch (InsecureUserException e) {
727 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
728 }
729
730 try {
Bo Zhu7ebcd662019-01-04 17:00:58 -0800731 byte[] secretKey = mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId,
732 uid, alias, metadata);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800733 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey);
Robert Berry4a5c87d2018-03-19 18:00:46 +0000734 return getAlias(userId, uid, alias);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800735 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
736 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
737 }
738 }
739
740 /**
Bo Zhu2c8e5382018-02-26 15:54:25 -0800741 * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service
742 * keystore namespace.
743 *
744 * @param alias the alias provided by caller as a reference to the key.
745 * @param keyBytes the raw bytes of the 256-bit AES key.
746 * @return grant alias, which caller can use to access the key.
747 * @throws RemoteException if the given key is invalid or some internal errors occur.
748 *
Bo Zhuc7048342019-01-03 14:04:58 -0800749 * @deprecated Use {{@link #importKeyWithMetadata(String, byte[], byte[])}} instead.
750 *
Bo Zhu2c8e5382018-02-26 15:54:25 -0800751 * @hide
752 */
Bo Zhuc7048342019-01-03 14:04:58 -0800753 @Deprecated
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700754 public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
Bo Zhu2c8e5382018-02-26 15:54:25 -0800755 throws RemoteException {
Bo Zhuc7048342019-01-03 14:04:58 -0800756 return importKeyWithMetadata(alias, keyBytes, /*metadata=*/ null);
757 }
758
759 /**
760 * Imports a 256-bit AES-GCM key named {@code alias} with the given {@code metadata}. The key is
761 * stored in system service keystore namespace.
762 *
763 * @param alias the alias provided by caller as a reference to the key.
764 * @param keyBytes the raw bytes of the 256-bit AES key.
765 * @param metadata the metadata to be authenticated (but unencrypted) together with the key.
766 * @return grant alias, which caller can use to access the key.
767 * @throws RemoteException if the given key is invalid or some internal errors occur.
768 *
769 * @hide
770 */
771 public @Nullable String importKeyWithMetadata(@NonNull String alias, @NonNull byte[] keyBytes,
772 @Nullable byte[] metadata) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700773 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700774 Preconditions.checkNotNull(alias, "alias is null");
775 Preconditions.checkNotNull(keyBytes, "keyBytes is null");
776 if (keyBytes.length != RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE) {
Bo Zhu2c8e5382018-02-26 15:54:25 -0800777 Log.e(TAG, "The given key for import doesn't have the required length "
778 + RecoverableKeyGenerator.KEY_SIZE_BITS);
779 throw new ServiceSpecificException(ERROR_INVALID_KEY_FORMAT,
780 "The given key does not contain " + RecoverableKeyGenerator.KEY_SIZE_BITS
781 + " bits.");
782 }
783
784 int uid = Binder.getCallingUid();
785 int userId = UserHandle.getCallingUserId();
786
Bo Zhu2c8e5382018-02-26 15:54:25 -0800787 PlatformEncryptionKey encryptionKey;
788 try {
789 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
790 } catch (NoSuchAlgorithmException e) {
791 // Impossible: all algorithms must be supported by AOSP
792 throw new RuntimeException(e);
Dmitry Dementyev20eaaa42018-05-09 13:05:04 -0700793 } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
Bo Zhu2c8e5382018-02-26 15:54:25 -0800794 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
795 } catch (InsecureUserException e) {
796 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
797 }
798
799 try {
800 // Wrap the key by the platform key and store the wrapped key locally
Bo Zhu7ebcd662019-01-04 17:00:58 -0800801 mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes,
802 metadata);
Bo Zhu2c8e5382018-02-26 15:54:25 -0800803
804 // Import the key to Android KeyStore and get grant
805 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes);
Robert Berry4a5c87d2018-03-19 18:00:46 +0000806 return getAlias(userId, uid, alias);
Bo Zhu2c8e5382018-02-26 15:54:25 -0800807 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
808 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
809 }
810 }
811
812 /**
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800813 * Gets a key named {@code alias} in caller's namespace.
814 *
815 * @return grant alias, which caller can use to access the key.
816 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700817 public @Nullable String getKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700818 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700819 Preconditions.checkNotNull(alias, "alias is null");
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800820 int uid = Binder.getCallingUid();
821 int userId = UserHandle.getCallingUserId();
Robert Berry4a5c87d2018-03-19 18:00:46 +0000822 return getAlias(userId, uid, alias);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800823 }
824
Robert Berryb9a220b2017-12-21 12:41:01 +0000825 private byte[] decryptRecoveryKey(
826 RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
Dmitry Dementyev14298312018-01-04 15:19:19 -0800827 throws RemoteException, ServiceSpecificException {
Bo Zhu31a40c02018-01-24 17:40:29 -0800828 byte[] locallyEncryptedKey;
Robert Berryb9a220b2017-12-21 12:41:01 +0000829 try {
Bo Zhu31a40c02018-01-24 17:40:29 -0800830 locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
Robert Berryb9a220b2017-12-21 12:41:01 +0000831 sessionEntry.getKeyClaimant(),
832 sessionEntry.getVaultParams(),
833 encryptedClaimResponse);
Bo Zhu31a40c02018-01-24 17:40:29 -0800834 } catch (InvalidKeyException e) {
835 Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000836 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800837 "Failed to decrypt recovery key " + e.getMessage());
Bo Zhu31a40c02018-01-24 17:40:29 -0800838 } catch (AEADBadTagException e) {
839 Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800840 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
841 "Failed to decrypt recovery key " + e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000842 } catch (NoSuchAlgorithmException e) {
843 // Should never happen: all the algorithms used are required by AOSP implementations
Robert Berrya16cd592018-01-17 14:43:09 +0000844 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000845 }
Bo Zhu31a40c02018-01-24 17:40:29 -0800846
847 try {
Bo Zhu9d05bfb2018-01-29 17:22:03 -0800848 return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
849 } catch (InvalidKeyException e) {
850 Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800851 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
852 "Failed to decrypt recovery key " + e.getMessage());
853 } catch (AEADBadTagException e) {
854 Log.e(TAG, "Got AEADBadTagException during decrypting recovery key", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800855 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
856 "Failed to decrypt recovery key " + e.getMessage());
857 } catch (NoSuchAlgorithmException e) {
858 // Should never happen: all the algorithms used are required by AOSP implementations
859 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
860 }
861 }
862
Robert Berryb9a220b2017-12-21 12:41:01 +0000863 /**
864 * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
865 *
Robert Berrybd4c43c2017-12-22 11:35:14 +0000866 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000867 * @throws RemoteException if an error occurred decrypting the keys.
868 */
Bo Zhu7ebcd662019-01-04 17:00:58 -0800869 private @NonNull Map<String, byte[]> recoverApplicationKeys(@NonNull byte[] recoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000870 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000871 HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
Robert Berry5f138702018-01-17 15:18:05 +0000872 for (WrappedApplicationKey applicationKey : applicationKeys) {
Dmitry Dementyev07c765552018-01-08 17:31:59 -0800873 String alias = applicationKey.getAlias();
Robert Berryb9a220b2017-12-21 12:41:01 +0000874 byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
Bo Zhu7ebcd662019-01-04 17:00:58 -0800875 byte[] keyMetadata = applicationKey.getMetadata();
Robert Berryb9a220b2017-12-21 12:41:01 +0000876
877 try {
Bo Zhu7ebcd662019-01-04 17:00:58 -0800878 byte[] keyMaterial = KeySyncUtils.decryptApplicationKey(recoveryKey,
879 encryptedKeyMaterial, keyMetadata);
Robert Berrybd4c43c2017-12-22 11:35:14 +0000880 keyMaterialByAlias.put(alias, keyMaterial);
Robert Berryb9a220b2017-12-21 12:41:01 +0000881 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000882 Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
883 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000884 ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhua9a04e22018-01-26 11:38:11 -0800885 } catch (InvalidKeyException e) {
Bo Zhua9a04e22018-01-26 11:38:11 -0800886 Log.e(TAG, "Got InvalidKeyException during decrypting application key with alias: "
887 + alias, e);
Bo Zhua9a04e22018-01-26 11:38:11 -0800888 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
889 "Failed to recover key with alias '" + alias + "': " + e.getMessage());
890 } catch (AEADBadTagException e) {
Bo Zhua9a04e22018-01-26 11:38:11 -0800891 Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: "
892 + alias, e);
Bo Zhu4857cb52018-02-06 14:34:48 -0800893 // Ignore the exception to continue to recover the other application keys.
Robert Berryb9a220b2017-12-21 12:41:01 +0000894 }
895 }
Bo Zhuae0682d2018-02-13 10:23:39 -0800896 if (!applicationKeys.isEmpty() && keyMaterialByAlias.isEmpty()) {
Bo Zhu4857cb52018-02-06 14:34:48 -0800897 Log.e(TAG, "Failed to recover any of the application keys.");
898 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
899 "Failed to recover any of the application keys.");
900 }
Robert Berrybd4c43c2017-12-22 11:35:14 +0000901 return keyMaterialByAlias;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800902 }
903
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800904 /**
905 * This function can only be used inside LockSettingsService.
906 *
Robert Berry91044042017-12-27 12:05:58 +0000907 * @param storedHashType from {@code CredentialHash}
Rich Canningsf64ec632019-02-21 12:40:36 -0800908 * @param credential - unencrypted byte array. Password length should be at most 16 symbols
909 * {@code mPasswordMaxLength}
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800910 * @param userId for user who just unlocked the device.
911 * @hide
912 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800913 public void lockScreenSecretAvailable(
Rich Canningsf64ec632019-02-21 12:40:36 -0800914 int storedHashType, @NonNull byte[] credential, int userId) {
Robert Berry4a534ec2017-12-21 15:44:02 +0000915 // So as not to block the critical path unlocking the phone, defer to another thread.
916 try {
917 mExecutorService.execute(KeySyncTask.newInstance(
Robert Berry91044042017-12-27 12:05:58 +0000918 mContext,
919 mDatabase,
920 mSnapshotStorage,
921 mListenersStorage,
922 userId,
923 storedHashType,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800924 credential,
925 /*credentialUpdated=*/ false));
Robert Berry4a534ec2017-12-21 15:44:02 +0000926 } catch (NoSuchAlgorithmException e) {
927 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
928 } catch (KeyStoreException e) {
929 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
930 } catch (InsecureUserException e) {
931 Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800932 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800933 }
934
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800935 /**
936 * This function can only be used inside LockSettingsService.
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800937 *
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800938 * @param storedHashType from {@code CredentialHash}
Rich Canningsf64ec632019-02-21 12:40:36 -0800939 * @param credential - unencrypted byte array
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800940 * @param userId for the user whose lock screen credentials were changed.
941 * @hide
942 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800943 public void lockScreenSecretChanged(
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800944 int storedHashType,
Rich Canningsf64ec632019-02-21 12:40:36 -0800945 @Nullable byte[] credential,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800946 int userId) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800947 // So as not to block the critical path unlocking the phone, defer to another thread.
948 try {
949 mExecutorService.execute(KeySyncTask.newInstance(
950 mContext,
951 mDatabase,
952 mSnapshotStorage,
953 mListenersStorage,
954 userId,
955 storedHashType,
956 credential,
957 /*credentialUpdated=*/ true));
958 } catch (NoSuchAlgorithmException e) {
959 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
960 } catch (KeyStoreException e) {
961 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
962 } catch (InsecureUserException e) {
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800963 Log.e(TAG, "InsecureUserException during lock screen secret update", e);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800964 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800965 }
966
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800967 private void checkRecoverKeyStorePermission() {
968 mContext.enforceCallingOrSelfPermission(
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800969 Manifest.permission.RECOVER_KEYSTORE,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800970 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
Dmitry Dementyev89f12d52019-02-28 12:26:01 -0800971 int userId = UserHandle.getCallingUserId();
972 int uid = Binder.getCallingUid();
973 mCleanupManager.registerRecoveryAgent(userId, uid);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800974 }
Bo Zhudef7ffd2018-01-05 14:50:52 -0800975
976 private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
977 byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
978 return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length));
979 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800980}