blob: 30125f8199deea45dc2669dbfb4f1335bb36a266 [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;
Bo Zhu7f414d92018-02-28 09:28:19 -080023import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
Robert Berryd9f11a92018-02-26 16:37:06 +000024import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
25import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
26import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED;
Robert Berry97e55582018-01-05 12:43:13 +000027
Bo Zhu14d993d2018-02-03 21:38:48 -080028import android.Manifest;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080029import android.annotation.NonNull;
30import android.annotation.Nullable;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080031import android.app.PendingIntent;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080032import android.content.Context;
33import android.os.Binder;
Robert Berry4a5c87d2018-03-19 18:00:46 +000034import android.os.Process;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080035import android.os.RemoteException;
36import android.os.ServiceSpecificException;
37import android.os.UserHandle;
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -080038import android.security.keystore.recovery.KeyChainProtectionParams;
39import android.security.keystore.recovery.KeyChainSnapshot;
Bo Zhu7c1972f2018-02-22 21:43:52 -080040import android.security.keystore.recovery.RecoveryCertPath;
Robert Berry81ee34b2018-01-23 11:59:59 +000041import android.security.keystore.recovery.RecoveryController;
Bo Zhub31ab672018-03-20 22:44:18 -070042import android.security.keystore.recovery.TrustedRootCertificates;
Robert Berry81ee34b2018-01-23 11:59:59 +000043import android.security.keystore.recovery.WrappedApplicationKey;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080044import android.security.KeyStore;
Robert Berry4a5c87d2018-03-19 18:00:46 +000045import android.util.ArrayMap;
Robert Berry4a534ec2017-12-21 15:44:02 +000046import android.util.Log;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080047
48import com.android.internal.annotations.VisibleForTesting;
Bo Zhu31a40c02018-01-24 17:40:29 -080049import com.android.internal.util.HexDump;
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -070050import com.android.internal.util.Preconditions;
Bo Zhu7ce4ea52018-02-27 23:52:19 -080051import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
Bo Zhu7f414d92018-02-28 09:28:19 -080052import com.android.server.locksettings.recoverablekeystore.certificate.SigXml;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -080053import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
Bo Zhu14d993d2018-02-03 21:38:48 -080054import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
55import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
56import com.android.server.locksettings.recoverablekeystore.certificate.CertXml;
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
Robert Berrye16fa982017-12-20 15:59:37 +000061import java.security.InvalidKeyException;
Bo Zhu5b81fa62017-12-21 14:36:11 -080062import java.security.KeyFactory;
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 Zhu5b81fa62017-12-21 14:36:11 -080073import java.security.spec.X509EncodedKeySpec;
Bo Zhudef7ffd2018-01-05 14:50:52 -080074import java.util.Arrays;
Robert Berrybd4c43c2017-12-22 11:35:14 +000075import java.util.HashMap;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080076import java.util.List;
Robert Berryb9a220b2017-12-21 12:41:01 +000077import java.util.Locale;
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -080078import java.util.Map;
Robert Berry4a534ec2017-12-21 15:44:02 +000079import java.util.concurrent.ExecutorService;
80import java.util.concurrent.Executors;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080081
Robert Berryb9a220b2017-12-21 12:41:01 +000082import javax.crypto.AEADBadTagException;
83
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080084/**
Robert Berry74928a12018-01-18 17:49:07 +000085 * Class with {@link RecoveryController} API implementation and internal methods to interact
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080086 * with {@code LockSettingsService}.
87 *
88 * @hide
89 */
90public class RecoverableKeyStoreManager {
Robert Berry4a534ec2017-12-21 15:44:02 +000091 private static final String TAG = "RecoverableKeyStoreMgr";
Robert Berrycfc990a2017-12-22 15:54:30 +000092
Dmitry Dementyev1aa96132017-12-11 11:33:12 -080093 private static RecoverableKeyStoreManager mInstance;
Robert Berrye16fa982017-12-20 15:59:37 +000094
95 private final Context mContext;
96 private final RecoverableKeyStoreDb mDatabase;
97 private final RecoverySessionStorage mRecoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +000098 private final ExecutorService mExecutorService;
Robert Berry91044042017-12-27 12:05:58 +000099 private final RecoverySnapshotListenersStorage mListenersStorage;
Robert Berrycfc990a2017-12-22 15:54:30 +0000100 private final RecoverableKeyGenerator mRecoverableKeyGenerator;
Robert Berrybd086f12017-12-27 13:29:39 +0000101 private final RecoverySnapshotStorage mSnapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800102 private final PlatformKeyManager mPlatformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800103 private final KeyStore mKeyStore;
104 private final ApplicationKeyStorage mApplicationKeyStorage;
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
Robert Berrye16fa982017-12-20 15:59:37 +0000127 mInstance = new RecoverableKeyStoreManager(
Bo Zhu3462c832018-01-04 22:42:36 -0800128 context.getApplicationContext(),
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800129 keystore,
Robert Berrye16fa982017-12-20 15:59:37 +0000130 db,
Robert Berry4a534ec2017-12-21 15:44:02 +0000131 new RecoverySessionStorage(),
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800132 Executors.newSingleThreadExecutor(),
Robert Berry91044042017-12-27 12:05:58 +0000133 new RecoverySnapshotStorage(),
Bo Zhu3462c832018-01-04 22:42:36 -0800134 new RecoverySnapshotListenersStorage(),
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800135 platformKeyManager,
136 applicationKeyStorage);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800137 }
138 return mInstance;
139 }
140
141 @VisibleForTesting
Robert Berrye16fa982017-12-20 15:59:37 +0000142 RecoverableKeyStoreManager(
143 Context context,
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800144 KeyStore keystore,
Robert Berrye16fa982017-12-20 15:59:37 +0000145 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berry4a534ec2017-12-21 15:44:02 +0000146 RecoverySessionStorage recoverySessionStorage,
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800147 ExecutorService executorService,
Robert Berry91044042017-12-27 12:05:58 +0000148 RecoverySnapshotStorage snapshotStorage,
Bo Zhu3462c832018-01-04 22:42:36 -0800149 RecoverySnapshotListenersStorage listenersStorage,
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800150 PlatformKeyManager platformKeyManager,
151 ApplicationKeyStorage applicationKeyStorage) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800152 mContext = context;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800153 mKeyStore = keystore;
Robert Berrye16fa982017-12-20 15:59:37 +0000154 mDatabase = recoverableKeyStoreDb;
155 mRecoverySessionStorage = recoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000156 mExecutorService = executorService;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800157 mListenersStorage = listenersStorage;
Robert Berrybd086f12017-12-27 13:29:39 +0000158 mSnapshotStorage = snapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800159 mPlatformKeyManager = platformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800160 mApplicationKeyStorage = applicationKeyStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800161
Robert Berrycfc990a2017-12-22 15:54:30 +0000162 try {
163 mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
164 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000165 Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000166 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000167 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800168 }
169
Bo Zhu7f414d92018-02-28 09:28:19 -0800170 /**
171 * @deprecated Use {@link #initRecoveryServiceWithSigFile(String, byte[], byte[])} instead.
172 */
Bo Zhu5b81fa62017-12-21 14:36:11 -0800173 public void initRecoveryService(
Bo Zhu14d993d2018-02-03 21:38:48 -0800174 @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800175 throws RemoteException {
176 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800177 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800178 int uid = Binder.getCallingUid();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700179 rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
180
181 // Always set active alias to the argument of the last call to initRecoveryService method,
182 // even if cert file is incorrect.
183 String activeRootAlias = mDatabase.getActiveRootOfTrust(userId, uid);
184 if (activeRootAlias == null) {
185 Log.d(TAG, "Root of trust for recovery agent + " + uid
186 + " is assigned for the first time to " + rootCertificateAlias);
187 mDatabase.setActiveRootOfTrust(userId, uid, rootCertificateAlias);
188 } else if (!activeRootAlias.equals(rootCertificateAlias)) {
189 Log.i(TAG, "Root of trust for recovery agent " + uid + " is changed to "
190 + rootCertificateAlias + " from " + activeRootAlias);
191 mDatabase.setActiveRootOfTrust(userId, uid, rootCertificateAlias);
192 }
Bo Zhu14d993d2018-02-03 21:38:48 -0800193
Bo Zhu14d993d2018-02-03 21:38:48 -0800194 CertXml certXml;
195 try {
196 certXml = CertXml.parse(recoveryServiceCertFile);
197 } catch (CertParsingException e) {
198 // TODO: Do not use raw key bytes anymore once the other components are updated
Bo Zhued00d252018-02-14 11:50:57 -0800199 Log.d(TAG, "Failed to parse the input as a cert file: " + HexDump.toHexString(
200 recoveryServiceCertFile));
Bo Zhu14d993d2018-02-03 21:38:48 -0800201 PublicKey publicKey = parseEcPublicKey(recoveryServiceCertFile);
202 if (mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey) > 0) {
203 mDatabase.setShouldCreateSnapshot(userId, uid, true);
204 }
Bo Zhued00d252018-02-14 11:50:57 -0800205 Log.d(TAG, "Successfully set the input as the raw public key");
Bo Zhu14d993d2018-02-03 21:38:48 -0800206 return;
207 }
208
209 // Check serial number
210 long newSerial = certXml.getSerial();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700211 Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid, rootCertificateAlias);
Bo Zhu14d993d2018-02-03 21:38:48 -0800212 if (oldSerial != null && oldSerial >= newSerial) {
213 if (oldSerial == newSerial) {
214 Log.i(TAG, "The cert file serial number is the same, so skip updating.");
215 } else {
216 Log.e(TAG, "The cert file serial number is older than the one in database.");
217 }
218 return;
219 }
220 Log.i(TAG, "Updating the certificate with the new serial number " + newSerial);
221
Bo Zhub31ab672018-03-20 22:44:18 -0700222 // Randomly choose and validate an endpoint certificate from the list
Bo Zhu14d993d2018-02-03 21:38:48 -0800223 CertPath certPath;
Bo Zhub31ab672018-03-20 22:44:18 -0700224 X509Certificate rootCert = getRootCertificate(rootCertificateAlias);
Bo Zhu14d993d2018-02-03 21:38:48 -0800225 try {
226 Log.d(TAG, "Getting and validating a random endpoint certificate");
Bo Zhub31ab672018-03-20 22:44:18 -0700227 certPath = certXml.getRandomEndpointCert(rootCert);
Bo Zhu14d993d2018-02-03 21:38:48 -0800228 } catch (CertValidationException e) {
229 Log.e(TAG, "Invalid endpoint cert", e);
230 throw new ServiceSpecificException(
Bo Zhu7f414d92018-02-28 09:28:19 -0800231 ERROR_INVALID_CERTIFICATE, "Failed to validate certificate.");
Bo Zhu14d993d2018-02-03 21:38:48 -0800232 }
Bo Zhub31ab672018-03-20 22:44:18 -0700233
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700234 boolean wasInitialized = mDatabase.getRecoveryServiceCertPath(userId, uid,
235 rootCertificateAlias) != null;
Robert Berrye8edf972018-03-27 11:45:11 +0100236
Bo Zhub31ab672018-03-20 22:44:18 -0700237 // Save the chosen and validated certificate into database
Bo Zhu14d993d2018-02-03 21:38:48 -0800238 try {
239 Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700240 if (mDatabase.setRecoveryServiceCertPath(userId, uid, rootCertificateAlias,
241 certPath) > 0) {
242 mDatabase.setRecoveryServiceCertSerial(userId, uid, rootCertificateAlias,
243 newSerial);
Robert Berrye8edf972018-03-27 11:45:11 +0100244 if (wasInitialized) {
245 Log.i(TAG, "This is a certificate change. Snapshot pending.");
246 mDatabase.setShouldCreateSnapshot(userId, uid, true);
247 }
Bo Zhu8d6861e2018-03-21 20:45:09 -0700248 mDatabase.setCounterId(userId, uid, new SecureRandom().nextLong());
Bo Zhu14d993d2018-02-03 21:38:48 -0800249 }
250 } catch (CertificateEncodingException e) {
251 Log.e(TAG, "Failed to encode CertPath", e);
252 throw new ServiceSpecificException(
253 ERROR_BAD_CERTIFICATE_FORMAT, "Failed to encode CertPath.");
254 }
255 }
256
Bo Zhu7f414d92018-02-28 09:28:19 -0800257 /**
258 * Initializes the recovery service with the two files {@code recoveryServiceCertFile} and
259 * {@code recoveryServiceSigFile}.
260 *
261 * @param rootCertificateAlias the alias for the root certificate that is used for validating
262 * the recovery service certificates.
263 * @param recoveryServiceCertFile the content of the XML file containing a list of certificates
264 * for the recovery service.
265 * @param recoveryServiceSigFile the content of the XML file containing the public-key signature
266 * over the entire content of {@code recoveryServiceCertFile}.
267 */
268 public void initRecoveryServiceWithSigFile(
269 @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile,
270 @NonNull byte[] recoveryServiceSigFile)
271 throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700272 checkRecoverKeyStorePermission();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700273 rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700274 Preconditions.checkNotNull(recoveryServiceCertFile, "recoveryServiceCertFile is null");
275 Preconditions.checkNotNull(recoveryServiceSigFile, "recoveryServiceSigFile is null");
Bo Zhu7f414d92018-02-28 09:28:19 -0800276
277 SigXml sigXml;
278 try {
279 sigXml = SigXml.parse(recoveryServiceSigFile);
280 } catch (CertParsingException e) {
281 Log.d(TAG, "Failed to parse the sig file: " + HexDump.toHexString(
282 recoveryServiceSigFile));
283 throw new ServiceSpecificException(
284 ERROR_BAD_CERTIFICATE_FORMAT, "Failed to parse the sig file.");
285 }
286
Bo Zhub31ab672018-03-20 22:44:18 -0700287 X509Certificate rootCert = getRootCertificate(rootCertificateAlias);
Bo Zhu7f414d92018-02-28 09:28:19 -0800288 try {
Bo Zhub31ab672018-03-20 22:44:18 -0700289 sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile);
Bo Zhu7f414d92018-02-28 09:28:19 -0800290 } catch (CertValidationException e) {
291 Log.d(TAG, "The signature over the cert file is invalid."
292 + " Cert: " + HexDump.toHexString(recoveryServiceCertFile)
293 + " Sig: " + HexDump.toHexString(recoveryServiceSigFile));
294 throw new ServiceSpecificException(
295 ERROR_INVALID_CERTIFICATE, "The signature over the cert file is invalid.");
296 }
297
298 initRecoveryService(rootCertificateAlias, recoveryServiceCertFile);
299 }
300
Bo Zhu14d993d2018-02-03 21:38:48 -0800301 private PublicKey parseEcPublicKey(@NonNull byte[] bytes) throws ServiceSpecificException {
Bo Zhu5b81fa62017-12-21 14:36:11 -0800302 try {
303 KeyFactory kf = KeyFactory.getInstance("EC");
Bo Zhu14d993d2018-02-03 21:38:48 -0800304 X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(bytes);
305 return kf.generatePublic(pkSpec);
Bo Zhu5b81fa62017-12-21 14:36:11 -0800306 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000307 Log.wtf(TAG, "EC algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000308 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhu5b81fa62017-12-21 14:36:11 -0800309 } catch (InvalidKeySpecException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000310 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000311 ERROR_BAD_CERTIFICATE_FORMAT, "Not a valid X509 certificate.");
Bo Zhu5b81fa62017-12-21 14:36:11 -0800312 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800313 }
314
315 /**
316 * Gets all data necessary to recover application keys on new device.
317 *
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700318 * @return KeyChain Snapshot.
319 * @throws ServiceSpecificException if no snapshot is pending.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800320 * @hide
321 */
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700322 public @NonNull KeyChainSnapshot getKeyChainSnapshot()
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800323 throws RemoteException {
324 checkRecoverKeyStorePermission();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800325 int uid = Binder.getCallingUid();
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800326 KeyChainSnapshot snapshot = mSnapshotStorage.get(uid);
Robert Berrybd086f12017-12-27 13:29:39 +0000327 if (snapshot == null) {
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800328 throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
Robert Berrybd086f12017-12-27 13:29:39 +0000329 }
330 return snapshot;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800331 }
332
Dmitry Dementyev14298312018-01-04 15:19:19 -0800333 public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800334 throws RemoteException {
335 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800336 int uid = Binder.getCallingUid();
337 mListenersStorage.setSnapshotListener(uid, intent);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800338 }
339
Robert Berry8f9038c2018-03-26 11:36:40 +0100340 /**
341 * Set the server params for the user's key chain. This is used to uniquely identify a key
342 * chain. Along with the counter ID, it is used to uniquely identify an instance of a vault.
343 */
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700344 public void setServerParams(@NonNull byte[] serverParams) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800345 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800346 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800347 int uid = Binder.getCallingUid();
Robert Berry8f9038c2018-03-26 11:36:40 +0100348
349 byte[] currentServerParams = mDatabase.getServerParams(userId, uid);
350
351 if (Arrays.equals(serverParams, currentServerParams)) {
352 Log.v(TAG, "Not updating server params - same as old value.");
353 return;
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800354 }
Robert Berry8f9038c2018-03-26 11:36:40 +0100355
356 long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
357 if (updatedRows < 1) {
358 throw new ServiceSpecificException(
359 ERROR_SERVICE_INTERNAL_ERROR, "Database failure trying to set server params.");
360 }
361
362 if (currentServerParams == null) {
363 Log.i(TAG, "Initialized server params.");
364 return;
365 }
366
367 Log.i(TAG, "Updated server params. Snapshot pending.");
368 mDatabase.setShouldCreateSnapshot(userId, uid, true);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800369 }
370
Dmitry Dementyevad884712017-12-20 12:38:36 -0800371 /**
Robert Berrybbe02ae2018-02-20 19:47:43 +0000372 * Sets the recovery status of key with {@code alias} to {@code status}.
Dmitry Dementyevad884712017-12-20 12:38:36 -0800373 */
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700374 public void setRecoveryStatus(@NonNull String alias, int status) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800375 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700376 Preconditions.checkNotNull(alias, "alias is null");
Robert Berrybbe02ae2018-02-20 19:47:43 +0000377 mDatabase.setRecoveryStatus(Binder.getCallingUid(), alias, status);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800378 }
379
380 /**
Robert Berry56f06b42018-02-23 13:31:32 +0000381 * Returns recovery statuses for all keys belonging to the calling uid.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800382 *
Robert Berry56f06b42018-02-23 13:31:32 +0000383 * @return {@link Map} from key alias to recovery status. Recovery status is one of
384 * {@link RecoveryController#RECOVERY_STATUS_SYNCED},
385 * {@link RecoveryController#RECOVERY_STATUS_SYNC_IN_PROGRESS} or
386 * {@link RecoveryController#RECOVERY_STATUS_PERMANENT_FAILURE}.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800387 */
Robert Berry56f06b42018-02-23 13:31:32 +0000388 public @NonNull Map<String, Integer> getRecoveryStatus() throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700389 checkRecoverKeyStorePermission();
Dmitry Dementyevad884712017-12-20 12:38:36 -0800390 return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800391 }
392
393 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800394 * Sets recovery secrets list used by all recovery agents for given {@code userId}
395 *
396 * @hide
397 */
398 public void setRecoverySecretTypes(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800399 @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800400 throws RemoteException {
401 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700402 Preconditions.checkNotNull(secretTypes, "secretTypes is null");
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800403 int userId = UserHandle.getCallingUserId();
404 int uid = Binder.getCallingUid();
Robert Berry5a1acefb2018-03-26 14:41:30 +0100405
406 int[] currentSecretTypes = mDatabase.getRecoverySecretTypes(userId, uid);
407 if (Arrays.equals(secretTypes, currentSecretTypes)) {
408 Log.v(TAG, "Not updating secret types - same as old value.");
409 return;
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800410 }
Robert Berry5a1acefb2018-03-26 14:41:30 +0100411
412 long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes);
413 if (updatedRows < 1) {
414 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
415 "Database error trying to set secret types.");
416 }
417
418 if (currentSecretTypes.length == 0) {
419 Log.i(TAG, "Initialized secret types.");
420 return;
421 }
422
423 Log.i(TAG, "Updated secret types. Snapshot pending.");
424 mDatabase.setShouldCreateSnapshot(userId, uid, true);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800425 }
426
427 /**
428 * Gets secret types necessary to create Recovery Data.
429 *
430 * @return secret types
431 * @hide
432 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800433 public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800434 checkRecoverKeyStorePermission();
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800435 return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
436 Binder.getCallingUid());
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800437 }
438
439 /**
Bo Zhu7c1972f2018-02-22 21:43:52 -0800440 * Initializes recovery session given the X509-encoded public key of the recovery service.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800441 *
Robert Berrye16fa982017-12-20 15:59:37 +0000442 * @param sessionId A unique ID to identify the recovery session.
443 * @param verifierPublicKey X509-encoded public key.
444 * @param vaultParams Additional params associated with vault.
445 * @param vaultChallenge Challenge issued by vault service.
Robert Berryb9a220b2017-12-21 12:41:01 +0000446 * @param secrets Lock-screen hashes. For now only a single secret is supported.
Robert Berrye16fa982017-12-20 15:59:37 +0000447 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
Bo Zhuf23c2032018-03-21 22:46:55 -0700448 * @deprecated Use {@link #startRecoverySessionWithCertPath(String, String, RecoveryCertPath,
449 * byte[], byte[], List)} instead.
Robert Berrye16fa982017-12-20 15:59:37 +0000450 *
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800451 * @hide
452 */
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800453 public @NonNull byte[] startRecoverySession(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800454 @NonNull String sessionId,
455 @NonNull byte[] verifierPublicKey,
456 @NonNull byte[] vaultParams,
457 @NonNull byte[] vaultChallenge,
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800458 @NonNull List<KeyChainProtectionParams> secrets)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800459 throws RemoteException {
460 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800461 int uid = Binder.getCallingUid();
Robert Berrye16fa982017-12-20 15:59:37 +0000462
463 if (secrets.size() != 1) {
Robert Berry9e1bd362018-01-17 23:28:45 +0000464 throw new UnsupportedOperationException(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800465 "Only a single KeyChainProtectionParams is supported");
Robert Berrye16fa982017-12-20 15:59:37 +0000466 }
467
Bo Zhudef7ffd2018-01-05 14:50:52 -0800468 PublicKey publicKey;
469 try {
470 publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
Bo Zhudef7ffd2018-01-05 14:50:52 -0800471 } catch (InvalidKeySpecException e) {
Bo Zhu7c1972f2018-02-22 21:43:52 -0800472 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
473 "Not a valid X509 key");
Bo Zhudef7ffd2018-01-05 14:50:52 -0800474 }
475 // The raw public key bytes contained in vaultParams must match the ones given in
476 // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned
477 // by the original recovery service.
478 if (!publicKeysMatch(publicKey, vaultParams)) {
Bo Zhu7f414d92018-02-28 09:28:19 -0800479 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE,
Bo Zhudef7ffd2018-01-05 14:50:52 -0800480 "The public keys given in verifierPublicKey and vaultParams do not match.");
481 }
482
Robert Berrye16fa982017-12-20 15:59:37 +0000483 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
484 byte[] kfHash = secrets.get(0).getSecret();
485 mRecoverySessionStorage.add(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800486 uid,
Robert Berryb9a220b2017-12-21 12:41:01 +0000487 new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
Robert Berrye16fa982017-12-20 15:59:37 +0000488
Bo Zhuf23c2032018-03-21 22:46:55 -0700489 Log.i(TAG, "Received VaultParams for recovery: " + HexDump.toHexString(vaultParams));
Robert Berrye16fa982017-12-20 15:59:37 +0000490 try {
491 byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
Robert Berrye16fa982017-12-20 15:59:37 +0000492 return KeySyncUtils.encryptRecoveryClaim(
493 publicKey,
494 vaultParams,
495 vaultChallenge,
496 thmKfHash,
497 keyClaimant);
498 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000499 Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000500 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhudef7ffd2018-01-05 14:50:52 -0800501 } catch (InvalidKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000502 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000503 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800504 }
505
Robert Berryb9a220b2017-12-21 12:41:01 +0000506 /**
Bo Zhu7c1972f2018-02-22 21:43:52 -0800507 * Initializes recovery session given the certificate path of the recovery service.
508 *
509 * @param sessionId A unique ID to identify the recovery session.
510 * @param verifierCertPath The certificate path of the recovery service.
511 * @param vaultParams Additional params associated with vault.
512 * @param vaultChallenge Challenge issued by vault service.
513 * @param secrets Lock-screen hashes. For now only a single secret is supported.
514 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
515 *
516 * @hide
517 */
518 public @NonNull byte[] startRecoverySessionWithCertPath(
519 @NonNull String sessionId,
Bo Zhub31ab672018-03-20 22:44:18 -0700520 @NonNull String rootCertificateAlias,
Bo Zhu7c1972f2018-02-22 21:43:52 -0800521 @NonNull RecoveryCertPath verifierCertPath,
522 @NonNull byte[] vaultParams,
523 @NonNull byte[] vaultChallenge,
524 @NonNull List<KeyChainProtectionParams> secrets)
525 throws RemoteException {
526 checkRecoverKeyStorePermission();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700527 rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700528 Preconditions.checkNotNull(sessionId, "invalid session");
529 Preconditions.checkNotNull(verifierCertPath, "verifierCertPath is null");
530 Preconditions.checkNotNull(vaultParams, "vaultParams is null");
531 Preconditions.checkNotNull(vaultChallenge, "vaultChallenge is null");
532 Preconditions.checkNotNull(secrets, "secrets is null");
Bo Zhu7c1972f2018-02-22 21:43:52 -0800533 CertPath certPath;
534 try {
535 certPath = verifierCertPath.getCertPath();
536 } catch (CertificateException e) {
537 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
538 "Failed decode the certificate path");
539 }
540
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800541 try {
Bo Zhub31ab672018-03-20 22:44:18 -0700542 CertUtils.validateCertPath(getRootCertificate(rootCertificateAlias), certPath);
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800543 } catch (CertValidationException e) {
544 Log.e(TAG, "Failed to validate the given cert path", e);
Bo Zhub31ab672018-03-20 22:44:18 -0700545 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
Bo Zhu7c1972f2018-02-22 21:43:52 -0800546 }
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800547
Bo Zhu7c1972f2018-02-22 21:43:52 -0800548 byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded();
549 if (verifierPublicKey == null) {
550 Log.e(TAG, "Failed to encode verifierPublicKey");
551 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
552 "Failed to encode verifierPublicKey");
553 }
554
555 return startRecoverySession(
556 sessionId, verifierPublicKey, vaultParams, vaultChallenge, secrets);
557 }
558
559 /**
Robert Berryb9a220b2017-12-21 12:41:01 +0000560 * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
561 * service.
562 *
Robert Berryb9a220b2017-12-21 12:41:01 +0000563 * @param sessionId The session ID used to generate the claim. See
Bo Zhudef7ffd2018-01-05 14:50:52 -0800564 * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
Robert Berryb9a220b2017-12-21 12:41:01 +0000565 * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
566 * service.
567 * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
568 * were wrapped with the recovery key.
Robert Berrybd4c43c2017-12-22 11:35:14 +0000569 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000570 * @throws RemoteException if an error occurred recovering the keys.
571 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700572 public @NonNull Map<String, byte[]> recoverKeys(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800573 @NonNull String sessionId,
Robert Berryb9a220b2017-12-21 12:41:01 +0000574 @NonNull byte[] encryptedRecoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000575 @NonNull List<WrappedApplicationKey> applicationKeys)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800576 throws RemoteException {
577 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700578 Preconditions.checkNotNull(sessionId, "invalid session");
579 Preconditions.checkNotNull(encryptedRecoveryKey, "encryptedRecoveryKey is null");
580 Preconditions.checkNotNull(applicationKeys, "encryptedRecoveryKey is null");
Dmitry Dementyev14298312018-01-04 15:19:19 -0800581 int uid = Binder.getCallingUid();
Robert Berryb9a220b2017-12-21 12:41:01 +0000582 RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
583 if (sessionEntry == null) {
Robert Berrya16cd592018-01-17 14:43:09 +0000584 throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800585 String.format(Locale.US,
586 "Application uid=%d does not have pending session '%s'", uid, sessionId));
Robert Berryb9a220b2017-12-21 12:41:01 +0000587 }
588
589 try {
590 byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
Robert Berrybd4c43c2017-12-22 11:35:14 +0000591 return recoverApplicationKeys(recoveryKey, applicationKeys);
Robert Berryb9a220b2017-12-21 12:41:01 +0000592 } finally {
593 sessionEntry.destroy();
594 mRecoverySessionStorage.remove(uid);
595 }
596 }
597
Robert Berrycfc990a2017-12-22 15:54:30 +0000598 /**
Robert Berry4a5c87d2018-03-19 18:00:46 +0000599 * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
600 * service.
601 *
602 * @param sessionId The session ID used to generate the claim. See
603 * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
604 * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
605 * service.
606 * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
607 * were wrapped with the recovery key.
608 * @throws RemoteException if an error occurred recovering the keys.
609 */
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700610 public @NonNull Map<String, String> recoverKeyChainSnapshot(
Robert Berry4a5c87d2018-03-19 18:00:46 +0000611 @NonNull String sessionId,
612 @NonNull byte[] encryptedRecoveryKey,
613 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
614 checkRecoverKeyStorePermission();
615 int userId = UserHandle.getCallingUserId();
616 int uid = Binder.getCallingUid();
617 RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
618 if (sessionEntry == null) {
619 throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
620 String.format(Locale.US,
621 "Application uid=%d does not have pending session '%s'",
622 uid,
623 sessionId));
624 }
625
626 try {
627 byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
628 Map<String, byte[]> keysByAlias = recoverApplicationKeys(recoveryKey, applicationKeys);
629 return importKeyMaterials(userId, uid, keysByAlias);
630 } catch (KeyStoreException e) {
631 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
632 } finally {
633 sessionEntry.destroy();
634 mRecoverySessionStorage.remove(uid);
635 }
636 }
637
638 /**
639 * Imports the key materials, returning a map from alias to grant alias for the calling user.
640 *
641 * @param userId The calling user ID.
642 * @param uid The calling uid.
643 * @param keysByAlias The key materials, keyed by alias.
644 * @throws KeyStoreException if an error occurs importing the key or getting the grant.
645 */
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700646 private @NonNull Map<String, String> importKeyMaterials(
Robert Berry4a5c87d2018-03-19 18:00:46 +0000647 int userId, int uid, Map<String, byte[]> keysByAlias) throws KeyStoreException {
648 ArrayMap<String, String> grantAliasesByAlias = new ArrayMap<>(keysByAlias.size());
649 for (String alias : keysByAlias.keySet()) {
650 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keysByAlias.get(alias));
651 String grantAlias = getAlias(userId, uid, alias);
652 Log.i(TAG, String.format(Locale.US, "Import %s -> %s", alias, grantAlias));
653 grantAliasesByAlias.put(alias, grantAlias);
654 }
655 return grantAliasesByAlias;
656 }
657
658 /**
659 * Returns an alias for the key.
660 *
661 * @param userId The user ID of the calling process.
662 * @param uid The uid of the calling process.
663 * @param alias The alias of the key.
664 * @return The alias in the calling process's keystore.
665 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700666 private @Nullable String getAlias(int userId, int uid, String alias) {
Robert Berry4a5c87d2018-03-19 18:00:46 +0000667 return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
668 }
669
670 /**
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800671 * Deprecated
Robert Berrycfc990a2017-12-22 15:54:30 +0000672 * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
673 * returns the raw key material.
674 *
675 * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
676 *
Bo Zhu2c8e5382018-02-26 15:54:25 -0800677 * @deprecated
Robert Berrycfc990a2017-12-22 15:54:30 +0000678 * @hide
679 */
680 public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700681 checkRecoverKeyStorePermission();
Robert Berrycfc990a2017-12-22 15:54:30 +0000682 int uid = Binder.getCallingUid();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800683 int userId = UserHandle.getCallingUserId();
Robert Berrycfc990a2017-12-22 15:54:30 +0000684
Bo Zhu3462c832018-01-04 22:42:36 -0800685 PlatformEncryptionKey encryptionKey;
Robert Berrycfc990a2017-12-22 15:54:30 +0000686 try {
Bo Zhu3462c832018-01-04 22:42:36 -0800687 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
Robert Berrycfc990a2017-12-22 15:54:30 +0000688 } catch (NoSuchAlgorithmException e) {
689 // Impossible: all algorithms must be supported by AOSP
690 throw new RuntimeException(e);
691 } catch (KeyStoreException | UnrecoverableKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000692 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000693 } catch (InsecureUserException e) {
694 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
695 }
696
697 try {
698 return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
Robert Berrya16cd592018-01-17 14:43:09 +0000699 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
700 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000701 }
702 }
703
Robert Berry2bcdad92018-01-18 12:53:29 +0000704 /**
705 * Destroys the session with the given {@code sessionId}.
706 */
707 public void closeSession(@NonNull String sessionId) throws RemoteException {
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700708 checkRecoverKeyStorePermission();
709 Preconditions.checkNotNull(sessionId, "invalid session");
Robert Berry2bcdad92018-01-18 12:53:29 +0000710 mRecoverySessionStorage.remove(Binder.getCallingUid(), sessionId);
711 }
712
Robert Berry5daccec2018-01-06 19:16:25 +0000713 public void removeKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700714 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700715 Preconditions.checkNotNull(alias, "alias is null");
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800716 int uid = Binder.getCallingUid();
717 int userId = UserHandle.getCallingUserId();
718
719 boolean wasRemoved = mDatabase.removeKey(uid, alias);
720 if (wasRemoved) {
721 mDatabase.setShouldCreateSnapshot(userId, uid, true);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800722 mApplicationKeyStorage.deleteEntry(userId, uid, alias);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800723 }
Robert Berry5daccec2018-01-06 19:16:25 +0000724 }
725
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800726 /**
727 * Generates a key named {@code alias} in caller's namespace.
728 * The key is stored in system service keystore namespace.
729 *
730 * @return grant alias, which caller can use to access the key.
731 */
Robert Berrya3b99472018-02-23 15:59:02 +0000732 public String generateKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700733 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700734 Preconditions.checkNotNull(alias, "alias is null");
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800735 int uid = Binder.getCallingUid();
736 int userId = UserHandle.getCallingUserId();
737
738 PlatformEncryptionKey encryptionKey;
739 try {
740 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
741 } catch (NoSuchAlgorithmException e) {
742 // Impossible: all algorithms must be supported by AOSP
743 throw new RuntimeException(e);
744 } catch (KeyStoreException | UnrecoverableKeyException e) {
745 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
746 } catch (InsecureUserException e) {
747 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
748 }
749
750 try {
751 byte[] secretKey =
752 mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
753 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey);
Robert Berry4a5c87d2018-03-19 18:00:46 +0000754 return getAlias(userId, uid, alias);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800755 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
756 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
757 }
758 }
759
760 /**
Bo Zhu2c8e5382018-02-26 15:54:25 -0800761 * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service
762 * keystore namespace.
763 *
764 * @param alias the alias provided by caller as a reference to the key.
765 * @param keyBytes the raw bytes of the 256-bit AES 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 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700771 public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
Bo Zhu2c8e5382018-02-26 15:54:25 -0800772 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
787 // TODO: Refactor RecoverableKeyGenerator to wrap the PlatformKey logic
788
789 PlatformEncryptionKey encryptionKey;
790 try {
791 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
792 } catch (NoSuchAlgorithmException e) {
793 // Impossible: all algorithms must be supported by AOSP
794 throw new RuntimeException(e);
795 } catch (KeyStoreException | UnrecoverableKeyException e) {
796 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
797 } catch (InsecureUserException e) {
798 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
799 }
800
801 try {
802 // Wrap the key by the platform key and store the wrapped key locally
803 mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes);
804
805 // Import the key to Android KeyStore and get grant
806 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes);
Robert Berry4a5c87d2018-03-19 18:00:46 +0000807 return getAlias(userId, uid, alias);
Bo Zhu2c8e5382018-02-26 15:54:25 -0800808 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
809 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
810 }
811 }
812
813 /**
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800814 * Gets a key named {@code alias} in caller's namespace.
815 *
816 * @return grant alias, which caller can use to access the key.
817 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700818 public @Nullable String getKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700819 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700820 Preconditions.checkNotNull(alias, "alias is null");
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800821 int uid = Binder.getCallingUid();
822 int userId = UserHandle.getCallingUserId();
Robert Berry4a5c87d2018-03-19 18:00:46 +0000823 return getAlias(userId, uid, alias);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800824 }
825
Robert Berryb9a220b2017-12-21 12:41:01 +0000826 private byte[] decryptRecoveryKey(
827 RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
Dmitry Dementyev14298312018-01-04 15:19:19 -0800828 throws RemoteException, ServiceSpecificException {
Bo Zhu31a40c02018-01-24 17:40:29 -0800829 byte[] locallyEncryptedKey;
Robert Berryb9a220b2017-12-21 12:41:01 +0000830 try {
Bo Zhu31a40c02018-01-24 17:40:29 -0800831 locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
Robert Berryb9a220b2017-12-21 12:41:01 +0000832 sessionEntry.getKeyClaimant(),
833 sessionEntry.getVaultParams(),
834 encryptedClaimResponse);
Bo Zhu31a40c02018-01-24 17:40:29 -0800835 } catch (InvalidKeyException e) {
836 Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000837 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800838 "Failed to decrypt recovery key " + e.getMessage());
Bo Zhu31a40c02018-01-24 17:40:29 -0800839 } catch (AEADBadTagException e) {
840 Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800841 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
842 "Failed to decrypt recovery key " + e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000843 } catch (NoSuchAlgorithmException e) {
844 // Should never happen: all the algorithms used are required by AOSP implementations
Robert Berrya16cd592018-01-17 14:43:09 +0000845 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000846 }
Bo Zhu31a40c02018-01-24 17:40:29 -0800847
848 try {
Bo Zhu9d05bfb2018-01-29 17:22:03 -0800849 return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
850 } catch (InvalidKeyException e) {
851 Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800852 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
853 "Failed to decrypt recovery key " + e.getMessage());
854 } catch (AEADBadTagException e) {
855 Log.e(TAG, "Got AEADBadTagException during decrypting recovery key", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800856 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
857 "Failed to decrypt recovery key " + e.getMessage());
858 } catch (NoSuchAlgorithmException e) {
859 // Should never happen: all the algorithms used are required by AOSP implementations
860 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
861 }
862 }
863
Robert Berryb9a220b2017-12-21 12:41:01 +0000864 /**
865 * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
866 *
Robert Berrybd4c43c2017-12-22 11:35:14 +0000867 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000868 * @throws RemoteException if an error occurred decrypting the keys.
869 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700870 private @NonNull Map<String, byte[]> recoverApplicationKeys(
Robert Berryb9a220b2017-12-21 12:41:01 +0000871 @NonNull byte[] recoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000872 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000873 HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
Robert Berry5f138702018-01-17 15:18:05 +0000874 for (WrappedApplicationKey applicationKey : applicationKeys) {
Dmitry Dementyev07c765552018-01-08 17:31:59 -0800875 String alias = applicationKey.getAlias();
Robert Berryb9a220b2017-12-21 12:41:01 +0000876 byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
877
878 try {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000879 byte[] keyMaterial =
880 KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
881 keyMaterialByAlias.put(alias, keyMaterial);
Robert Berryb9a220b2017-12-21 12:41:01 +0000882 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000883 Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
884 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000885 ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhua9a04e22018-01-26 11:38:11 -0800886 } catch (InvalidKeyException e) {
Bo Zhua9a04e22018-01-26 11:38:11 -0800887 Log.e(TAG, "Got InvalidKeyException during decrypting application key with alias: "
888 + alias, e);
Bo Zhua9a04e22018-01-26 11:38:11 -0800889 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
890 "Failed to recover key with alias '" + alias + "': " + e.getMessage());
891 } catch (AEADBadTagException e) {
Bo Zhua9a04e22018-01-26 11:38:11 -0800892 Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: "
893 + alias, e);
Bo Zhu4857cb52018-02-06 14:34:48 -0800894 // Ignore the exception to continue to recover the other application keys.
Robert Berryb9a220b2017-12-21 12:41:01 +0000895 }
896 }
Bo Zhuae0682d2018-02-13 10:23:39 -0800897 if (!applicationKeys.isEmpty() && keyMaterialByAlias.isEmpty()) {
Bo Zhu4857cb52018-02-06 14:34:48 -0800898 Log.e(TAG, "Failed to recover any of the application keys.");
899 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
900 "Failed to recover any of the application keys.");
901 }
Robert Berrybd4c43c2017-12-22 11:35:14 +0000902 return keyMaterialByAlias;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800903 }
904
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800905 /**
906 * This function can only be used inside LockSettingsService.
907 *
Robert Berry91044042017-12-27 12:05:58 +0000908 * @param storedHashType from {@code CredentialHash}
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800909 * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
910 * mPasswordMaxLength}
911 * @param userId for user who just unlocked the device.
912 * @hide
913 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800914 public void lockScreenSecretAvailable(
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800915 int storedHashType, @NonNull String credential, int userId) {
Robert Berry4a534ec2017-12-21 15:44:02 +0000916 // So as not to block the critical path unlocking the phone, defer to another thread.
917 try {
918 mExecutorService.execute(KeySyncTask.newInstance(
Robert Berry91044042017-12-27 12:05:58 +0000919 mContext,
920 mDatabase,
921 mSnapshotStorage,
922 mListenersStorage,
923 userId,
924 storedHashType,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800925 credential,
926 /*credentialUpdated=*/ false));
Robert Berry4a534ec2017-12-21 15:44:02 +0000927 } catch (NoSuchAlgorithmException e) {
928 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
929 } catch (KeyStoreException e) {
930 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
931 } catch (InsecureUserException e) {
932 Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800933 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800934 }
935
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800936 /**
937 * This function can only be used inside LockSettingsService.
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800938 *
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800939 * @param storedHashType from {@code CredentialHash}
940 * @param credential - unencrypted String
941 * @param userId for the user whose lock screen credentials were changed.
942 * @hide
943 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800944 public void lockScreenSecretChanged(
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800945 int storedHashType,
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800946 @Nullable String credential,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800947 int userId) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800948 // So as not to block the critical path unlocking the phone, defer to another thread.
949 try {
950 mExecutorService.execute(KeySyncTask.newInstance(
951 mContext,
952 mDatabase,
953 mSnapshotStorage,
954 mListenersStorage,
955 userId,
956 storedHashType,
957 credential,
958 /*credentialUpdated=*/ true));
959 } catch (NoSuchAlgorithmException e) {
960 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
961 } catch (KeyStoreException e) {
962 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
963 } catch (InsecureUserException e) {
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800964 Log.e(TAG, "InsecureUserException during lock screen secret update", e);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800965 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800966 }
967
Bo Zhub31ab672018-03-20 22:44:18 -0700968 private X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException {
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700969 rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
Bo Zhub31ab672018-03-20 22:44:18 -0700970 X509Certificate rootCertificate =
971 TrustedRootCertificates.getRootCertificate(rootCertificateAlias);
972 if (rootCertificate == null) {
973 throw new ServiceSpecificException(
974 ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid");
975 }
976 return rootCertificate;
977 }
978
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700979 private @NonNull String replaceEmptyValueWithSecureDefault(
980 @Nullable String rootCertificateAlias) {
981 if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
982 Log.e(TAG, "rootCertificateAlias is null or empty");
983 // Use the default Google Key Vault Service CA certificate if the alias is not provided
984 rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
985 }
986 return rootCertificateAlias;
987 }
988
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800989 private void checkRecoverKeyStorePermission() {
990 mContext.enforceCallingOrSelfPermission(
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800991 Manifest.permission.RECOVER_KEYSTORE,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800992 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
993 }
Bo Zhudef7ffd2018-01-05 14:50:52 -0800994
995 private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
996 byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
997 return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length));
998 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800999}