blob: 8a79e4c680d2abc381508a44363487a9a1719926 [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 ApplicationKeyStorage mApplicationKeyStorage;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800104
105 /**
106 * Returns a new or existing instance.
107 *
108 * @hide
109 */
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800110 public static synchronized RecoverableKeyStoreManager
111 getInstance(Context context, KeyStore keystore) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800112 if (mInstance == null) {
Bo Zhu3462c832018-01-04 22:42:36 -0800113 RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
114 PlatformKeyManager platformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800115 ApplicationKeyStorage applicationKeyStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800116 try {
117 platformKeyManager = PlatformKeyManager.getInstance(context, db);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800118 applicationKeyStorage = ApplicationKeyStorage.getInstance(keystore);
Bo Zhu3462c832018-01-04 22:42:36 -0800119 } catch (NoSuchAlgorithmException e) {
120 // Impossible: all algorithms must be supported by AOSP
121 throw new RuntimeException(e);
122 } catch (KeyStoreException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000123 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhu3462c832018-01-04 22:42:36 -0800124 }
125
Robert Berrye16fa982017-12-20 15:59:37 +0000126 mInstance = new RecoverableKeyStoreManager(
Bo Zhu3462c832018-01-04 22:42:36 -0800127 context.getApplicationContext(),
Robert Berrye16fa982017-12-20 15:59:37 +0000128 db,
Robert Berry4a534ec2017-12-21 15:44:02 +0000129 new RecoverySessionStorage(),
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800130 Executors.newSingleThreadExecutor(),
Robert Berry91044042017-12-27 12:05:58 +0000131 new RecoverySnapshotStorage(),
Bo Zhu3462c832018-01-04 22:42:36 -0800132 new RecoverySnapshotListenersStorage(),
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800133 platformKeyManager,
134 applicationKeyStorage);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800135 }
136 return mInstance;
137 }
138
139 @VisibleForTesting
Robert Berrye16fa982017-12-20 15:59:37 +0000140 RecoverableKeyStoreManager(
141 Context context,
142 RecoverableKeyStoreDb recoverableKeyStoreDb,
Robert Berry4a534ec2017-12-21 15:44:02 +0000143 RecoverySessionStorage recoverySessionStorage,
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800144 ExecutorService executorService,
Robert Berry91044042017-12-27 12:05:58 +0000145 RecoverySnapshotStorage snapshotStorage,
Bo Zhu3462c832018-01-04 22:42:36 -0800146 RecoverySnapshotListenersStorage listenersStorage,
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800147 PlatformKeyManager platformKeyManager,
148 ApplicationKeyStorage applicationKeyStorage) {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800149 mContext = context;
Robert Berrye16fa982017-12-20 15:59:37 +0000150 mDatabase = recoverableKeyStoreDb;
151 mRecoverySessionStorage = recoverySessionStorage;
Robert Berry4a534ec2017-12-21 15:44:02 +0000152 mExecutorService = executorService;
Dmitry Dementyev3b17c632017-12-21 17:30:48 -0800153 mListenersStorage = listenersStorage;
Robert Berrybd086f12017-12-27 13:29:39 +0000154 mSnapshotStorage = snapshotStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800155 mPlatformKeyManager = platformKeyManager;
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800156 mApplicationKeyStorage = applicationKeyStorage;
Bo Zhu3462c832018-01-04 22:42:36 -0800157
Robert Berrycfc990a2017-12-22 15:54:30 +0000158 try {
159 mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
160 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000161 Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000162 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000163 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800164 }
165
Bo Zhu7f414d92018-02-28 09:28:19 -0800166 /**
167 * @deprecated Use {@link #initRecoveryServiceWithSigFile(String, byte[], byte[])} instead.
168 */
Bo Zhu5b81fa62017-12-21 14:36:11 -0800169 public void initRecoveryService(
Bo Zhu14d993d2018-02-03 21:38:48 -0800170 @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800171 throws RemoteException {
172 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800173 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800174 int uid = Binder.getCallingUid();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700175 rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
176
177 // Always set active alias to the argument of the last call to initRecoveryService method,
178 // even if cert file is incorrect.
179 String activeRootAlias = mDatabase.getActiveRootOfTrust(userId, uid);
180 if (activeRootAlias == null) {
181 Log.d(TAG, "Root of trust for recovery agent + " + uid
182 + " is assigned for the first time to " + rootCertificateAlias);
183 mDatabase.setActiveRootOfTrust(userId, uid, rootCertificateAlias);
184 } else if (!activeRootAlias.equals(rootCertificateAlias)) {
185 Log.i(TAG, "Root of trust for recovery agent " + uid + " is changed to "
186 + rootCertificateAlias + " from " + activeRootAlias);
187 mDatabase.setActiveRootOfTrust(userId, uid, rootCertificateAlias);
188 }
Bo Zhu14d993d2018-02-03 21:38:48 -0800189
Bo Zhu14d993d2018-02-03 21:38:48 -0800190 CertXml certXml;
191 try {
192 certXml = CertXml.parse(recoveryServiceCertFile);
193 } catch (CertParsingException e) {
194 // TODO: Do not use raw key bytes anymore once the other components are updated
Bo Zhued00d252018-02-14 11:50:57 -0800195 Log.d(TAG, "Failed to parse the input as a cert file: " + HexDump.toHexString(
196 recoveryServiceCertFile));
Bo Zhu14d993d2018-02-03 21:38:48 -0800197 PublicKey publicKey = parseEcPublicKey(recoveryServiceCertFile);
198 if (mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey) > 0) {
199 mDatabase.setShouldCreateSnapshot(userId, uid, true);
200 }
Bo Zhued00d252018-02-14 11:50:57 -0800201 Log.d(TAG, "Successfully set the input as the raw public key");
Bo Zhu14d993d2018-02-03 21:38:48 -0800202 return;
203 }
204
205 // Check serial number
206 long newSerial = certXml.getSerial();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700207 Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid, rootCertificateAlias);
Bo Zhu14d993d2018-02-03 21:38:48 -0800208 if (oldSerial != null && oldSerial >= newSerial) {
209 if (oldSerial == newSerial) {
210 Log.i(TAG, "The cert file serial number is the same, so skip updating.");
211 } else {
212 Log.e(TAG, "The cert file serial number is older than the one in database.");
213 }
214 return;
215 }
216 Log.i(TAG, "Updating the certificate with the new serial number " + newSerial);
217
Bo Zhub31ab672018-03-20 22:44:18 -0700218 // Randomly choose and validate an endpoint certificate from the list
Bo Zhu14d993d2018-02-03 21:38:48 -0800219 CertPath certPath;
Bo Zhub31ab672018-03-20 22:44:18 -0700220 X509Certificate rootCert = getRootCertificate(rootCertificateAlias);
Bo Zhu14d993d2018-02-03 21:38:48 -0800221 try {
222 Log.d(TAG, "Getting and validating a random endpoint certificate");
Bo Zhub31ab672018-03-20 22:44:18 -0700223 certPath = certXml.getRandomEndpointCert(rootCert);
Bo Zhu14d993d2018-02-03 21:38:48 -0800224 } catch (CertValidationException e) {
225 Log.e(TAG, "Invalid endpoint cert", e);
226 throw new ServiceSpecificException(
Bo Zhu7f414d92018-02-28 09:28:19 -0800227 ERROR_INVALID_CERTIFICATE, "Failed to validate certificate.");
Bo Zhu14d993d2018-02-03 21:38:48 -0800228 }
Bo Zhub31ab672018-03-20 22:44:18 -0700229
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700230 boolean wasInitialized = mDatabase.getRecoveryServiceCertPath(userId, uid,
231 rootCertificateAlias) != null;
Robert Berrye8edf972018-03-27 11:45:11 +0100232
Bo Zhub31ab672018-03-20 22:44:18 -0700233 // Save the chosen and validated certificate into database
Bo Zhu14d993d2018-02-03 21:38:48 -0800234 try {
235 Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700236 if (mDatabase.setRecoveryServiceCertPath(userId, uid, rootCertificateAlias,
237 certPath) > 0) {
238 mDatabase.setRecoveryServiceCertSerial(userId, uid, rootCertificateAlias,
239 newSerial);
Robert Berrye8edf972018-03-27 11:45:11 +0100240 if (wasInitialized) {
241 Log.i(TAG, "This is a certificate change. Snapshot pending.");
242 mDatabase.setShouldCreateSnapshot(userId, uid, true);
243 }
Bo Zhu8d6861e2018-03-21 20:45:09 -0700244 mDatabase.setCounterId(userId, uid, new SecureRandom().nextLong());
Bo Zhu14d993d2018-02-03 21:38:48 -0800245 }
246 } catch (CertificateEncodingException e) {
247 Log.e(TAG, "Failed to encode CertPath", e);
248 throw new ServiceSpecificException(
249 ERROR_BAD_CERTIFICATE_FORMAT, "Failed to encode CertPath.");
250 }
251 }
252
Bo Zhu7f414d92018-02-28 09:28:19 -0800253 /**
254 * Initializes the recovery service with the two files {@code recoveryServiceCertFile} and
255 * {@code recoveryServiceSigFile}.
256 *
257 * @param rootCertificateAlias the alias for the root certificate that is used for validating
258 * the recovery service certificates.
259 * @param recoveryServiceCertFile the content of the XML file containing a list of certificates
260 * for the recovery service.
261 * @param recoveryServiceSigFile the content of the XML file containing the public-key signature
262 * over the entire content of {@code recoveryServiceCertFile}.
263 */
264 public void initRecoveryServiceWithSigFile(
265 @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile,
266 @NonNull byte[] recoveryServiceSigFile)
267 throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700268 checkRecoverKeyStorePermission();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700269 rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700270 Preconditions.checkNotNull(recoveryServiceCertFile, "recoveryServiceCertFile is null");
271 Preconditions.checkNotNull(recoveryServiceSigFile, "recoveryServiceSigFile is null");
Bo Zhu7f414d92018-02-28 09:28:19 -0800272
273 SigXml sigXml;
274 try {
275 sigXml = SigXml.parse(recoveryServiceSigFile);
276 } catch (CertParsingException e) {
277 Log.d(TAG, "Failed to parse the sig file: " + HexDump.toHexString(
278 recoveryServiceSigFile));
279 throw new ServiceSpecificException(
280 ERROR_BAD_CERTIFICATE_FORMAT, "Failed to parse the sig file.");
281 }
282
Bo Zhub31ab672018-03-20 22:44:18 -0700283 X509Certificate rootCert = getRootCertificate(rootCertificateAlias);
Bo Zhu7f414d92018-02-28 09:28:19 -0800284 try {
Bo Zhub31ab672018-03-20 22:44:18 -0700285 sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile);
Bo Zhu7f414d92018-02-28 09:28:19 -0800286 } catch (CertValidationException e) {
287 Log.d(TAG, "The signature over the cert file is invalid."
288 + " Cert: " + HexDump.toHexString(recoveryServiceCertFile)
289 + " Sig: " + HexDump.toHexString(recoveryServiceSigFile));
290 throw new ServiceSpecificException(
291 ERROR_INVALID_CERTIFICATE, "The signature over the cert file is invalid.");
292 }
293
294 initRecoveryService(rootCertificateAlias, recoveryServiceCertFile);
295 }
296
Bo Zhu14d993d2018-02-03 21:38:48 -0800297 private PublicKey parseEcPublicKey(@NonNull byte[] bytes) throws ServiceSpecificException {
Bo Zhu5b81fa62017-12-21 14:36:11 -0800298 try {
299 KeyFactory kf = KeyFactory.getInstance("EC");
Bo Zhu14d993d2018-02-03 21:38:48 -0800300 X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(bytes);
301 return kf.generatePublic(pkSpec);
Bo Zhu5b81fa62017-12-21 14:36:11 -0800302 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000303 Log.wtf(TAG, "EC algorithm not available. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000304 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhu5b81fa62017-12-21 14:36:11 -0800305 } catch (InvalidKeySpecException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000306 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000307 ERROR_BAD_CERTIFICATE_FORMAT, "Not a valid X509 certificate.");
Bo Zhu5b81fa62017-12-21 14:36:11 -0800308 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800309 }
310
311 /**
312 * Gets all data necessary to recover application keys on new device.
313 *
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700314 * @return KeyChain Snapshot.
315 * @throws ServiceSpecificException if no snapshot is pending.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800316 * @hide
317 */
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700318 public @NonNull KeyChainSnapshot getKeyChainSnapshot()
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800319 throws RemoteException {
320 checkRecoverKeyStorePermission();
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800321 int uid = Binder.getCallingUid();
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800322 KeyChainSnapshot snapshot = mSnapshotStorage.get(uid);
Robert Berrybd086f12017-12-27 13:29:39 +0000323 if (snapshot == null) {
Dmitry Dementyev7d8c78a2018-01-12 19:14:07 -0800324 throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
Robert Berrybd086f12017-12-27 13:29:39 +0000325 }
326 return snapshot;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800327 }
328
Dmitry Dementyev14298312018-01-04 15:19:19 -0800329 public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800330 throws RemoteException {
331 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800332 int uid = Binder.getCallingUid();
333 mListenersStorage.setSnapshotListener(uid, intent);
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800334 }
335
Robert Berry8f9038c2018-03-26 11:36:40 +0100336 /**
337 * Set the server params for the user's key chain. This is used to uniquely identify a key
338 * chain. Along with the counter ID, it is used to uniquely identify an instance of a vault.
339 */
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700340 public void setServerParams(@NonNull byte[] serverParams) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800341 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800342 int userId = UserHandle.getCallingUserId();
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800343 int uid = Binder.getCallingUid();
Robert Berry8f9038c2018-03-26 11:36:40 +0100344
345 byte[] currentServerParams = mDatabase.getServerParams(userId, uid);
346
347 if (Arrays.equals(serverParams, currentServerParams)) {
348 Log.v(TAG, "Not updating server params - same as old value.");
349 return;
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800350 }
Robert Berry8f9038c2018-03-26 11:36:40 +0100351
352 long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
353 if (updatedRows < 1) {
354 throw new ServiceSpecificException(
355 ERROR_SERVICE_INTERNAL_ERROR, "Database failure trying to set server params.");
356 }
357
358 if (currentServerParams == null) {
359 Log.i(TAG, "Initialized server params.");
360 return;
361 }
362
363 Log.i(TAG, "Updated server params. Snapshot pending.");
364 mDatabase.setShouldCreateSnapshot(userId, uid, true);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800365 }
366
Dmitry Dementyevad884712017-12-20 12:38:36 -0800367 /**
Robert Berrybbe02ae2018-02-20 19:47:43 +0000368 * Sets the recovery status of key with {@code alias} to {@code status}.
Dmitry Dementyevad884712017-12-20 12:38:36 -0800369 */
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700370 public void setRecoveryStatus(@NonNull String alias, int status) throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800371 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700372 Preconditions.checkNotNull(alias, "alias is null");
Robert Berrybbe02ae2018-02-20 19:47:43 +0000373 mDatabase.setRecoveryStatus(Binder.getCallingUid(), alias, status);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800374 }
375
376 /**
Robert Berry56f06b42018-02-23 13:31:32 +0000377 * Returns recovery statuses for all keys belonging to the calling uid.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800378 *
Robert Berry56f06b42018-02-23 13:31:32 +0000379 * @return {@link Map} from key alias to recovery status. Recovery status is one of
380 * {@link RecoveryController#RECOVERY_STATUS_SYNCED},
381 * {@link RecoveryController#RECOVERY_STATUS_SYNC_IN_PROGRESS} or
382 * {@link RecoveryController#RECOVERY_STATUS_PERMANENT_FAILURE}.
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800383 */
Robert Berry56f06b42018-02-23 13:31:32 +0000384 public @NonNull Map<String, Integer> getRecoveryStatus() throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700385 checkRecoverKeyStorePermission();
Dmitry Dementyevad884712017-12-20 12:38:36 -0800386 return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800387 }
388
389 /**
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800390 * Sets recovery secrets list used by all recovery agents for given {@code userId}
391 *
392 * @hide
393 */
394 public void setRecoverySecretTypes(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800395 @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800396 throws RemoteException {
397 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700398 Preconditions.checkNotNull(secretTypes, "secretTypes is null");
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800399 int userId = UserHandle.getCallingUserId();
400 int uid = Binder.getCallingUid();
Robert Berry5a1acefb2018-03-26 14:41:30 +0100401
402 int[] currentSecretTypes = mDatabase.getRecoverySecretTypes(userId, uid);
403 if (Arrays.equals(secretTypes, currentSecretTypes)) {
404 Log.v(TAG, "Not updating secret types - same as old value.");
405 return;
Dmitry Dementyev40dadb02018-01-10 18:03:37 -0800406 }
Robert Berry5a1acefb2018-03-26 14:41:30 +0100407
408 long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes);
409 if (updatedRows < 1) {
410 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
411 "Database error trying to set secret types.");
412 }
413
414 if (currentSecretTypes.length == 0) {
415 Log.i(TAG, "Initialized secret types.");
416 return;
417 }
418
419 Log.i(TAG, "Updated secret types. Snapshot pending.");
420 mDatabase.setShouldCreateSnapshot(userId, uid, true);
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800421 }
422
423 /**
424 * Gets secret types necessary to create Recovery Data.
425 *
426 * @return secret types
427 * @hide
428 */
Dmitry Dementyev14298312018-01-04 15:19:19 -0800429 public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800430 checkRecoverKeyStorePermission();
Dmitry Dementyevbdfdf532017-12-27 11:58:45 -0800431 return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
432 Binder.getCallingUid());
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800433 }
434
435 /**
Bo Zhu7c1972f2018-02-22 21:43:52 -0800436 * Initializes recovery session given the X509-encoded public key of the recovery service.
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800437 *
Robert Berrye16fa982017-12-20 15:59:37 +0000438 * @param sessionId A unique ID to identify the recovery session.
439 * @param verifierPublicKey X509-encoded public key.
440 * @param vaultParams Additional params associated with vault.
441 * @param vaultChallenge Challenge issued by vault service.
Robert Berryb9a220b2017-12-21 12:41:01 +0000442 * @param secrets Lock-screen hashes. For now only a single secret is supported.
Robert Berrye16fa982017-12-20 15:59:37 +0000443 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
Bo Zhuf23c2032018-03-21 22:46:55 -0700444 * @deprecated Use {@link #startRecoverySessionWithCertPath(String, String, RecoveryCertPath,
445 * byte[], byte[], List)} instead.
Robert Berrye16fa982017-12-20 15:59:37 +0000446 *
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800447 * @hide
448 */
Dmitry Dementyevb8b030b2017-12-19 11:02:54 -0800449 public @NonNull byte[] startRecoverySession(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800450 @NonNull String sessionId,
451 @NonNull byte[] verifierPublicKey,
452 @NonNull byte[] vaultParams,
453 @NonNull byte[] vaultChallenge,
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800454 @NonNull List<KeyChainProtectionParams> secrets)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800455 throws RemoteException {
456 checkRecoverKeyStorePermission();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800457 int uid = Binder.getCallingUid();
Robert Berrye16fa982017-12-20 15:59:37 +0000458
459 if (secrets.size() != 1) {
Robert Berry9e1bd362018-01-17 23:28:45 +0000460 throw new UnsupportedOperationException(
Dmitry Dementyev0916e7c2018-01-23 13:02:08 -0800461 "Only a single KeyChainProtectionParams is supported");
Robert Berrye16fa982017-12-20 15:59:37 +0000462 }
463
Bo Zhudef7ffd2018-01-05 14:50:52 -0800464 PublicKey publicKey;
465 try {
466 publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
Bo Zhudef7ffd2018-01-05 14:50:52 -0800467 } catch (InvalidKeySpecException e) {
Bo Zhu7c1972f2018-02-22 21:43:52 -0800468 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
469 "Not a valid X509 key");
Bo Zhudef7ffd2018-01-05 14:50:52 -0800470 }
471 // The raw public key bytes contained in vaultParams must match the ones given in
472 // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned
473 // by the original recovery service.
474 if (!publicKeysMatch(publicKey, vaultParams)) {
Bo Zhu7f414d92018-02-28 09:28:19 -0800475 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE,
Bo Zhudef7ffd2018-01-05 14:50:52 -0800476 "The public keys given in verifierPublicKey and vaultParams do not match.");
477 }
478
Robert Berrye16fa982017-12-20 15:59:37 +0000479 byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
480 byte[] kfHash = secrets.get(0).getSecret();
481 mRecoverySessionStorage.add(
Dmitry Dementyev14298312018-01-04 15:19:19 -0800482 uid,
Robert Berryb9a220b2017-12-21 12:41:01 +0000483 new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
Robert Berrye16fa982017-12-20 15:59:37 +0000484
Bo Zhuf23c2032018-03-21 22:46:55 -0700485 Log.i(TAG, "Received VaultParams for recovery: " + HexDump.toHexString(vaultParams));
Robert Berrye16fa982017-12-20 15:59:37 +0000486 try {
487 byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
Robert Berrye16fa982017-12-20 15:59:37 +0000488 return KeySyncUtils.encryptRecoveryClaim(
489 publicKey,
490 vaultParams,
491 vaultChallenge,
492 thmKfHash,
493 keyClaimant);
494 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000495 Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000496 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhudef7ffd2018-01-05 14:50:52 -0800497 } catch (InvalidKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000498 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
Robert Berrye16fa982017-12-20 15:59:37 +0000499 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800500 }
501
Robert Berryb9a220b2017-12-21 12:41:01 +0000502 /**
Bo Zhu7c1972f2018-02-22 21:43:52 -0800503 * Initializes recovery session given the certificate path of the recovery service.
504 *
505 * @param sessionId A unique ID to identify the recovery session.
506 * @param verifierCertPath The certificate path of the recovery service.
507 * @param vaultParams Additional params associated with vault.
508 * @param vaultChallenge Challenge issued by vault service.
509 * @param secrets Lock-screen hashes. For now only a single secret is supported.
510 * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
511 *
512 * @hide
513 */
514 public @NonNull byte[] startRecoverySessionWithCertPath(
515 @NonNull String sessionId,
Bo Zhub31ab672018-03-20 22:44:18 -0700516 @NonNull String rootCertificateAlias,
Bo Zhu7c1972f2018-02-22 21:43:52 -0800517 @NonNull RecoveryCertPath verifierCertPath,
518 @NonNull byte[] vaultParams,
519 @NonNull byte[] vaultChallenge,
520 @NonNull List<KeyChainProtectionParams> secrets)
521 throws RemoteException {
522 checkRecoverKeyStorePermission();
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700523 rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700524 Preconditions.checkNotNull(sessionId, "invalid session");
525 Preconditions.checkNotNull(verifierCertPath, "verifierCertPath is null");
526 Preconditions.checkNotNull(vaultParams, "vaultParams is null");
527 Preconditions.checkNotNull(vaultChallenge, "vaultChallenge is null");
528 Preconditions.checkNotNull(secrets, "secrets is null");
Bo Zhu7c1972f2018-02-22 21:43:52 -0800529 CertPath certPath;
530 try {
531 certPath = verifierCertPath.getCertPath();
532 } catch (CertificateException e) {
533 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
534 "Failed decode the certificate path");
535 }
536
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800537 try {
Bo Zhub31ab672018-03-20 22:44:18 -0700538 CertUtils.validateCertPath(getRootCertificate(rootCertificateAlias), certPath);
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800539 } catch (CertValidationException e) {
540 Log.e(TAG, "Failed to validate the given cert path", e);
Bo Zhub31ab672018-03-20 22:44:18 -0700541 throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
Bo Zhu7c1972f2018-02-22 21:43:52 -0800542 }
Bo Zhu7ce4ea52018-02-27 23:52:19 -0800543
Bo Zhu7c1972f2018-02-22 21:43:52 -0800544 byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded();
545 if (verifierPublicKey == null) {
546 Log.e(TAG, "Failed to encode verifierPublicKey");
547 throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
548 "Failed to encode verifierPublicKey");
549 }
550
551 return startRecoverySession(
552 sessionId, verifierPublicKey, vaultParams, vaultChallenge, secrets);
553 }
554
555 /**
Robert Berryb9a220b2017-12-21 12:41:01 +0000556 * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
557 * service.
558 *
Robert Berryb9a220b2017-12-21 12:41:01 +0000559 * @param sessionId The session ID used to generate the claim. See
Bo Zhudef7ffd2018-01-05 14:50:52 -0800560 * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
Robert Berryb9a220b2017-12-21 12:41:01 +0000561 * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
562 * service.
563 * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
564 * were wrapped with the recovery key.
Robert Berrybd4c43c2017-12-22 11:35:14 +0000565 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000566 * @throws RemoteException if an error occurred recovering the keys.
567 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700568 public @NonNull Map<String, byte[]> recoverKeys(
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800569 @NonNull String sessionId,
Robert Berryb9a220b2017-12-21 12:41:01 +0000570 @NonNull byte[] encryptedRecoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000571 @NonNull List<WrappedApplicationKey> applicationKeys)
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800572 throws RemoteException {
573 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700574 Preconditions.checkNotNull(sessionId, "invalid session");
575 Preconditions.checkNotNull(encryptedRecoveryKey, "encryptedRecoveryKey is null");
576 Preconditions.checkNotNull(applicationKeys, "encryptedRecoveryKey is null");
Dmitry Dementyev14298312018-01-04 15:19:19 -0800577 int uid = Binder.getCallingUid();
Robert Berryb9a220b2017-12-21 12:41:01 +0000578 RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
579 if (sessionEntry == null) {
Robert Berrya16cd592018-01-17 14:43:09 +0000580 throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800581 String.format(Locale.US,
582 "Application uid=%d does not have pending session '%s'", uid, sessionId));
Robert Berryb9a220b2017-12-21 12:41:01 +0000583 }
584
585 try {
586 byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
Robert Berrybd4c43c2017-12-22 11:35:14 +0000587 return recoverApplicationKeys(recoveryKey, applicationKeys);
Robert Berryb9a220b2017-12-21 12:41:01 +0000588 } finally {
589 sessionEntry.destroy();
590 mRecoverySessionStorage.remove(uid);
591 }
592 }
593
Robert Berrycfc990a2017-12-22 15:54:30 +0000594 /**
Robert Berry4a5c87d2018-03-19 18:00:46 +0000595 * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
596 * service.
597 *
598 * @param sessionId The session ID used to generate the claim. See
599 * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
600 * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
601 * service.
602 * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
603 * were wrapped with the recovery key.
604 * @throws RemoteException if an error occurred recovering the keys.
605 */
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700606 public @NonNull Map<String, String> recoverKeyChainSnapshot(
Robert Berry4a5c87d2018-03-19 18:00:46 +0000607 @NonNull String sessionId,
608 @NonNull byte[] encryptedRecoveryKey,
609 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
610 checkRecoverKeyStorePermission();
611 int userId = UserHandle.getCallingUserId();
612 int uid = Binder.getCallingUid();
613 RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
614 if (sessionEntry == null) {
615 throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
616 String.format(Locale.US,
617 "Application uid=%d does not have pending session '%s'",
618 uid,
619 sessionId));
620 }
621
622 try {
623 byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
624 Map<String, byte[]> keysByAlias = recoverApplicationKeys(recoveryKey, applicationKeys);
625 return importKeyMaterials(userId, uid, keysByAlias);
626 } catch (KeyStoreException e) {
627 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
628 } finally {
629 sessionEntry.destroy();
630 mRecoverySessionStorage.remove(uid);
631 }
632 }
633
634 /**
635 * Imports the key materials, returning a map from alias to grant alias for the calling user.
636 *
637 * @param userId The calling user ID.
638 * @param uid The calling uid.
639 * @param keysByAlias The key materials, keyed by alias.
640 * @throws KeyStoreException if an error occurs importing the key or getting the grant.
641 */
Dmitry Dementyevfd4ae0b2018-03-23 11:06:24 -0700642 private @NonNull Map<String, String> importKeyMaterials(
Robert Berry4a5c87d2018-03-19 18:00:46 +0000643 int userId, int uid, Map<String, byte[]> keysByAlias) throws KeyStoreException {
644 ArrayMap<String, String> grantAliasesByAlias = new ArrayMap<>(keysByAlias.size());
645 for (String alias : keysByAlias.keySet()) {
646 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keysByAlias.get(alias));
647 String grantAlias = getAlias(userId, uid, alias);
648 Log.i(TAG, String.format(Locale.US, "Import %s -> %s", alias, grantAlias));
649 grantAliasesByAlias.put(alias, grantAlias);
650 }
651 return grantAliasesByAlias;
652 }
653
654 /**
655 * Returns an alias for the key.
656 *
657 * @param userId The user ID of the calling process.
658 * @param uid The uid of the calling process.
659 * @param alias The alias of the key.
660 * @return The alias in the calling process's keystore.
661 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700662 private @Nullable String getAlias(int userId, int uid, String alias) {
Robert Berry4a5c87d2018-03-19 18:00:46 +0000663 return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
664 }
665
666 /**
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800667 * Deprecated
Robert Berrycfc990a2017-12-22 15:54:30 +0000668 * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
669 * returns the raw key material.
670 *
671 * <p>TODO: Once AndroidKeyStore has added move api, do not return raw bytes.
672 *
Bo Zhu2c8e5382018-02-26 15:54:25 -0800673 * @deprecated
Robert Berrycfc990a2017-12-22 15:54:30 +0000674 * @hide
675 */
676 public byte[] generateAndStoreKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700677 checkRecoverKeyStorePermission();
Robert Berrycfc990a2017-12-22 15:54:30 +0000678 int uid = Binder.getCallingUid();
Dmitry Dementyev14298312018-01-04 15:19:19 -0800679 int userId = UserHandle.getCallingUserId();
Robert Berrycfc990a2017-12-22 15:54:30 +0000680
Bo Zhu3462c832018-01-04 22:42:36 -0800681 PlatformEncryptionKey encryptionKey;
Robert Berrycfc990a2017-12-22 15:54:30 +0000682 try {
Bo Zhu3462c832018-01-04 22:42:36 -0800683 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
Robert Berrycfc990a2017-12-22 15:54:30 +0000684 } catch (NoSuchAlgorithmException e) {
685 // Impossible: all algorithms must be supported by AOSP
686 throw new RuntimeException(e);
687 } catch (KeyStoreException | UnrecoverableKeyException e) {
Robert Berrya16cd592018-01-17 14:43:09 +0000688 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000689 } catch (InsecureUserException e) {
690 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
691 }
692
693 try {
694 return mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
Robert Berrya16cd592018-01-17 14:43:09 +0000695 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
696 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berrycfc990a2017-12-22 15:54:30 +0000697 }
698 }
699
Robert Berry2bcdad92018-01-18 12:53:29 +0000700 /**
701 * Destroys the session with the given {@code sessionId}.
702 */
703 public void closeSession(@NonNull String sessionId) throws RemoteException {
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700704 checkRecoverKeyStorePermission();
705 Preconditions.checkNotNull(sessionId, "invalid session");
Robert Berry2bcdad92018-01-18 12:53:29 +0000706 mRecoverySessionStorage.remove(Binder.getCallingUid(), sessionId);
707 }
708
Robert Berry5daccec2018-01-06 19:16:25 +0000709 public void removeKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700710 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700711 Preconditions.checkNotNull(alias, "alias is null");
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800712 int uid = Binder.getCallingUid();
713 int userId = UserHandle.getCallingUserId();
714
715 boolean wasRemoved = mDatabase.removeKey(uid, alias);
716 if (wasRemoved) {
717 mDatabase.setShouldCreateSnapshot(userId, uid, true);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800718 mApplicationKeyStorage.deleteEntry(userId, uid, alias);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800719 }
Robert Berry5daccec2018-01-06 19:16:25 +0000720 }
721
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800722 /**
723 * Generates a key named {@code alias} in caller's namespace.
724 * The key is stored in system service keystore namespace.
725 *
726 * @return grant alias, which caller can use to access the key.
727 */
Robert Berrya3b99472018-02-23 15:59:02 +0000728 public String generateKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700729 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700730 Preconditions.checkNotNull(alias, "alias is null");
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800731 int uid = Binder.getCallingUid();
732 int userId = UserHandle.getCallingUserId();
733
734 PlatformEncryptionKey encryptionKey;
735 try {
736 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
737 } catch (NoSuchAlgorithmException e) {
738 // Impossible: all algorithms must be supported by AOSP
739 throw new RuntimeException(e);
740 } catch (KeyStoreException | UnrecoverableKeyException e) {
741 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
742 } catch (InsecureUserException e) {
743 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
744 }
745
746 try {
747 byte[] secretKey =
748 mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
749 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey);
Robert Berry4a5c87d2018-03-19 18:00:46 +0000750 return getAlias(userId, uid, alias);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800751 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
752 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
753 }
754 }
755
756 /**
Bo Zhu2c8e5382018-02-26 15:54:25 -0800757 * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service
758 * keystore namespace.
759 *
760 * @param alias the alias provided by caller as a reference to the key.
761 * @param keyBytes the raw bytes of the 256-bit AES key.
762 * @return grant alias, which caller can use to access the key.
763 * @throws RemoteException if the given key is invalid or some internal errors occur.
764 *
765 * @hide
766 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700767 public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
Bo Zhu2c8e5382018-02-26 15:54:25 -0800768 throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700769 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700770 Preconditions.checkNotNull(alias, "alias is null");
771 Preconditions.checkNotNull(keyBytes, "keyBytes is null");
772 if (keyBytes.length != RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE) {
Bo Zhu2c8e5382018-02-26 15:54:25 -0800773 Log.e(TAG, "The given key for import doesn't have the required length "
774 + RecoverableKeyGenerator.KEY_SIZE_BITS);
775 throw new ServiceSpecificException(ERROR_INVALID_KEY_FORMAT,
776 "The given key does not contain " + RecoverableKeyGenerator.KEY_SIZE_BITS
777 + " bits.");
778 }
779
780 int uid = Binder.getCallingUid();
781 int userId = UserHandle.getCallingUserId();
782
783 // TODO: Refactor RecoverableKeyGenerator to wrap the PlatformKey logic
784
785 PlatformEncryptionKey encryptionKey;
786 try {
787 encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
788 } catch (NoSuchAlgorithmException e) {
789 // Impossible: all algorithms must be supported by AOSP
790 throw new RuntimeException(e);
791 } catch (KeyStoreException | UnrecoverableKeyException e) {
792 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
793 } catch (InsecureUserException e) {
794 throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
795 }
796
797 try {
798 // Wrap the key by the platform key and store the wrapped key locally
799 mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes);
800
801 // Import the key to Android KeyStore and get grant
802 mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes);
Robert Berry4a5c87d2018-03-19 18:00:46 +0000803 return getAlias(userId, uid, alias);
Bo Zhu2c8e5382018-02-26 15:54:25 -0800804 } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
805 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
806 }
807 }
808
809 /**
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800810 * Gets a key named {@code alias} in caller's namespace.
811 *
812 * @return grant alias, which caller can use to access the key.
813 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700814 public @Nullable String getKey(@NonNull String alias) throws RemoteException {
Dmitry Dementyeva5945d52018-03-23 12:10:09 -0700815 checkRecoverKeyStorePermission();
Dmitry Dementyev1e6a9dc2018-03-21 13:52:00 -0700816 Preconditions.checkNotNull(alias, "alias is null");
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800817 int uid = Binder.getCallingUid();
818 int userId = UserHandle.getCallingUserId();
Robert Berry4a5c87d2018-03-19 18:00:46 +0000819 return getAlias(userId, uid, alias);
Dmitry Dementyev29b9de52018-01-31 16:09:32 -0800820 }
821
Robert Berryb9a220b2017-12-21 12:41:01 +0000822 private byte[] decryptRecoveryKey(
823 RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
Dmitry Dementyev14298312018-01-04 15:19:19 -0800824 throws RemoteException, ServiceSpecificException {
Bo Zhu31a40c02018-01-24 17:40:29 -0800825 byte[] locallyEncryptedKey;
Robert Berryb9a220b2017-12-21 12:41:01 +0000826 try {
Bo Zhu31a40c02018-01-24 17:40:29 -0800827 locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
Robert Berryb9a220b2017-12-21 12:41:01 +0000828 sessionEntry.getKeyClaimant(),
829 sessionEntry.getVaultParams(),
830 encryptedClaimResponse);
Bo Zhu31a40c02018-01-24 17:40:29 -0800831 } catch (InvalidKeyException e) {
832 Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e);
Robert Berrya16cd592018-01-17 14:43:09 +0000833 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
Dmitry Dementyev14298312018-01-04 15:19:19 -0800834 "Failed to decrypt recovery key " + e.getMessage());
Bo Zhu31a40c02018-01-24 17:40:29 -0800835 } catch (AEADBadTagException e) {
836 Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800837 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
838 "Failed to decrypt recovery key " + e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000839 } catch (NoSuchAlgorithmException e) {
840 // Should never happen: all the algorithms used are required by AOSP implementations
Robert Berrya16cd592018-01-17 14:43:09 +0000841 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Robert Berryb9a220b2017-12-21 12:41:01 +0000842 }
Bo Zhu31a40c02018-01-24 17:40:29 -0800843
844 try {
Bo Zhu9d05bfb2018-01-29 17:22:03 -0800845 return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
846 } catch (InvalidKeyException e) {
847 Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
Bo Zhu31a40c02018-01-24 17:40:29 -0800848 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
849 "Failed to decrypt recovery key " + e.getMessage());
850 } catch (AEADBadTagException e) {
851 Log.e(TAG, "Got AEADBadTagException 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 (NoSuchAlgorithmException e) {
855 // Should never happen: all the algorithms used are required by AOSP implementations
856 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
857 }
858 }
859
Robert Berryb9a220b2017-12-21 12:41:01 +0000860 /**
861 * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
862 *
Robert Berrybd4c43c2017-12-22 11:35:14 +0000863 * @return Map from alias to raw key material.
Robert Berryb9a220b2017-12-21 12:41:01 +0000864 * @throws RemoteException if an error occurred decrypting the keys.
865 */
Dmitry Dementyev4da14e02018-03-23 15:18:33 -0700866 private @NonNull Map<String, byte[]> recoverApplicationKeys(
Robert Berryb9a220b2017-12-21 12:41:01 +0000867 @NonNull byte[] recoveryKey,
Robert Berry5f138702018-01-17 15:18:05 +0000868 @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000869 HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
Robert Berry5f138702018-01-17 15:18:05 +0000870 for (WrappedApplicationKey applicationKey : applicationKeys) {
Dmitry Dementyev07c765552018-01-08 17:31:59 -0800871 String alias = applicationKey.getAlias();
Robert Berryb9a220b2017-12-21 12:41:01 +0000872 byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
873
874 try {
Robert Berrybd4c43c2017-12-22 11:35:14 +0000875 byte[] keyMaterial =
876 KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
877 keyMaterialByAlias.put(alias, keyMaterial);
Robert Berryb9a220b2017-12-21 12:41:01 +0000878 } catch (NoSuchAlgorithmException e) {
Robert Berry97e55582018-01-05 12:43:13 +0000879 Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
880 throw new ServiceSpecificException(
Robert Berrya16cd592018-01-17 14:43:09 +0000881 ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
Bo Zhua9a04e22018-01-26 11:38:11 -0800882 } catch (InvalidKeyException e) {
Bo Zhua9a04e22018-01-26 11:38:11 -0800883 Log.e(TAG, "Got InvalidKeyException during decrypting application key with alias: "
884 + alias, e);
Bo Zhua9a04e22018-01-26 11:38:11 -0800885 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
886 "Failed to recover key with alias '" + alias + "': " + e.getMessage());
887 } catch (AEADBadTagException e) {
Bo Zhua9a04e22018-01-26 11:38:11 -0800888 Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: "
889 + alias, e);
Bo Zhu4857cb52018-02-06 14:34:48 -0800890 // Ignore the exception to continue to recover the other application keys.
Robert Berryb9a220b2017-12-21 12:41:01 +0000891 }
892 }
Bo Zhuae0682d2018-02-13 10:23:39 -0800893 if (!applicationKeys.isEmpty() && keyMaterialByAlias.isEmpty()) {
Bo Zhu4857cb52018-02-06 14:34:48 -0800894 Log.e(TAG, "Failed to recover any of the application keys.");
895 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
896 "Failed to recover any of the application keys.");
897 }
Robert Berrybd4c43c2017-12-22 11:35:14 +0000898 return keyMaterialByAlias;
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800899 }
900
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800901 /**
902 * This function can only be used inside LockSettingsService.
903 *
Robert Berry91044042017-12-27 12:05:58 +0000904 * @param storedHashType from {@code CredentialHash}
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800905 * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
906 * mPasswordMaxLength}
907 * @param userId for user who just unlocked the device.
908 * @hide
909 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800910 public void lockScreenSecretAvailable(
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800911 int storedHashType, @NonNull String credential, int userId) {
Robert Berry4a534ec2017-12-21 15:44:02 +0000912 // So as not to block the critical path unlocking the phone, defer to another thread.
913 try {
914 mExecutorService.execute(KeySyncTask.newInstance(
Robert Berry91044042017-12-27 12:05:58 +0000915 mContext,
916 mDatabase,
917 mSnapshotStorage,
918 mListenersStorage,
919 userId,
920 storedHashType,
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800921 credential,
922 /*credentialUpdated=*/ false));
Robert Berry4a534ec2017-12-21 15:44:02 +0000923 } catch (NoSuchAlgorithmException e) {
924 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
925 } catch (KeyStoreException e) {
926 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
927 } catch (InsecureUserException e) {
928 Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800929 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800930 }
931
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800932 /**
933 * This function can only be used inside LockSettingsService.
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800934 *
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800935 * @param storedHashType from {@code CredentialHash}
936 * @param credential - unencrypted String
937 * @param userId for the user whose lock screen credentials were changed.
938 * @hide
939 */
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800940 public void lockScreenSecretChanged(
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800941 int storedHashType,
Dmitry Dementyev6a509e42017-12-19 14:47:26 -0800942 @Nullable String credential,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800943 int userId) {
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800944 // So as not to block the critical path unlocking the phone, defer to another thread.
945 try {
946 mExecutorService.execute(KeySyncTask.newInstance(
947 mContext,
948 mDatabase,
949 mSnapshotStorage,
950 mListenersStorage,
951 userId,
952 storedHashType,
953 credential,
954 /*credentialUpdated=*/ true));
955 } catch (NoSuchAlgorithmException e) {
956 Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
957 } catch (KeyStoreException e) {
958 Log.e(TAG, "Key store error encountered during recoverable key sync", e);
959 } catch (InsecureUserException e) {
Dmitry Dementyev6e167242018-01-25 15:29:50 -0800960 Log.e(TAG, "InsecureUserException during lock screen secret update", e);
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800961 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800962 }
963
Bo Zhub31ab672018-03-20 22:44:18 -0700964 private X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException {
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700965 rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
Bo Zhub31ab672018-03-20 22:44:18 -0700966 X509Certificate rootCertificate =
967 TrustedRootCertificates.getRootCertificate(rootCertificateAlias);
968 if (rootCertificate == null) {
969 throw new ServiceSpecificException(
970 ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid");
971 }
972 return rootCertificate;
973 }
974
Dmitry Dementyevf34fc7e2018-03-26 17:31:29 -0700975 private @NonNull String replaceEmptyValueWithSecureDefault(
976 @Nullable String rootCertificateAlias) {
977 if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
978 Log.e(TAG, "rootCertificateAlias is null or empty");
979 // Use the default Google Key Vault Service CA certificate if the alias is not provided
980 rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
981 }
982 return rootCertificateAlias;
983 }
984
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800985 private void checkRecoverKeyStorePermission() {
986 mContext.enforceCallingOrSelfPermission(
Dmitry Dementyeved89ea02018-01-11 13:53:52 -0800987 Manifest.permission.RECOVER_KEYSTORE,
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800988 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
989 }
Bo Zhudef7ffd2018-01-05 14:50:52 -0800990
991 private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
992 byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
993 return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length));
994 }
Dmitry Dementyev1aa96132017-12-11 11:33:12 -0800995}