1 /*
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 
17 package com.android.server.locksettings.recoverablekeystore;
18 
19 import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
20 import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
21 import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
22 import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
23 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
24 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
25 import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
26 import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
27 import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED;
28 
29 import android.Manifest;
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.app.KeyguardManager;
33 import android.app.PendingIntent;
34 import android.app.RemoteLockscreenValidationResult;
35 import android.app.RemoteLockscreenValidationSession;
36 import android.content.Context;
37 import android.os.Binder;
38 import android.os.RemoteException;
39 import android.os.ServiceSpecificException;
40 import android.os.UserHandle;
41 import android.security.keystore.recovery.KeyChainProtectionParams;
42 import android.security.keystore.recovery.KeyChainSnapshot;
43 import android.security.keystore.recovery.RecoveryCertPath;
44 import android.security.keystore.recovery.RecoveryController;
45 import android.security.keystore.recovery.WrappedApplicationKey;
46 import android.util.ArrayMap;
47 import android.util.FeatureFlagUtils;
48 import android.util.Log;
49 
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.util.HexDump;
52 import com.android.internal.widget.LockPatternUtils;
53 import com.android.internal.widget.LockPatternView;
54 import com.android.internal.widget.LockscreenCredential;
55 import com.android.internal.widget.VerifyCredentialResponse;
56 import com.android.security.SecureBox;
57 import com.android.server.locksettings.LockSettingsService;
58 import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
59 import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
60 import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
61 import com.android.server.locksettings.recoverablekeystore.certificate.CertXml;
62 import com.android.server.locksettings.recoverablekeystore.certificate.SigXml;
63 import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
64 import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager;
65 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
66 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
67 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
68 import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage;
69 import com.android.server.locksettings.recoverablekeystore.storage.RemoteLockscreenValidationSessionStorage.LockscreenVerificationSession;
70 
71 import java.io.IOException;
72 import java.nio.charset.StandardCharsets;
73 import java.security.InvalidKeyException;
74 import java.security.KeyStoreException;
75 import java.security.NoSuchAlgorithmException;
76 import java.security.PublicKey;
77 import java.security.SecureRandom;
78 import java.security.UnrecoverableKeyException;
79 import java.security.cert.CertPath;
80 import java.security.cert.CertificateEncodingException;
81 import java.security.cert.CertificateException;
82 import java.security.cert.X509Certificate;
83 import java.security.spec.InvalidKeySpecException;
84 import java.util.Arrays;
85 import java.util.Date;
86 import java.util.HashMap;
87 import java.util.List;
88 import java.util.Locale;
89 import java.util.Map;
90 import java.util.Objects;
91 import java.util.concurrent.Executors;
92 import java.util.concurrent.ScheduledExecutorService;
93 import java.util.concurrent.TimeUnit;
94 
95 import javax.crypto.AEADBadTagException;
96 
97 /**
98  * Class with {@link RecoveryController} API implementation and internal methods to interact
99  * with {@code LockSettingsService}.
100  *
101  * @hide
102  */
103 public class RecoverableKeyStoreManager {
104     private static final String TAG = "RecoverableKeyStoreMgr";
105     private static final long SYNC_DELAY_MILLIS = 2000;
106     private static final int INVALID_REMOTE_GUESS_LIMIT = 5;
107 
108     private static RecoverableKeyStoreManager mInstance;
109 
110     private final Context mContext;
111     private final RecoverableKeyStoreDb mDatabase;
112     private final RecoverySessionStorage mRecoverySessionStorage;
113     private final ScheduledExecutorService mExecutorService;
114     private final RecoverySnapshotListenersStorage mListenersStorage;
115     private final RecoverableKeyGenerator mRecoverableKeyGenerator;
116     private final RecoverySnapshotStorage mSnapshotStorage;
117     private final PlatformKeyManager mPlatformKeyManager;
118     private final ApplicationKeyStorage mApplicationKeyStorage;
119     private final TestOnlyInsecureCertificateHelper mTestCertHelper;
120     private final CleanupManager mCleanupManager;
121     // only set if SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API is enabled.
122     @Nullable private final RemoteLockscreenValidationSessionStorage
123             mRemoteLockscreenValidationSessionStorage;
124 
125     /**
126      * Returns a new or existing instance.
127      *
128      * @hide
129      */
130     public static synchronized RecoverableKeyStoreManager
getInstance(Context context)131             getInstance(Context context) {
132         if (mInstance == null) {
133             RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
134             RemoteLockscreenValidationSessionStorage lockscreenCheckSessions;
135             if (FeatureFlagUtils.isEnabled(context,
136                     FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) {
137                 lockscreenCheckSessions = new RemoteLockscreenValidationSessionStorage();
138             } else {
139                 lockscreenCheckSessions = null;
140             }
141             PlatformKeyManager platformKeyManager;
142             ApplicationKeyStorage applicationKeyStorage;
143             try {
144                 platformKeyManager = PlatformKeyManager.getInstance(context, db);
145                 applicationKeyStorage = ApplicationKeyStorage.getInstance();
146             } catch (NoSuchAlgorithmException e) {
147                 // Impossible: all algorithms must be supported by AOSP
148                 throw new RuntimeException(e);
149             } catch (KeyStoreException e) {
150                 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
151             }
152 
153             RecoverySnapshotStorage snapshotStorage =
154                     RecoverySnapshotStorage.newInstance();
155             CleanupManager cleanupManager = CleanupManager.getInstance(
156                     context.getApplicationContext(),
157                     snapshotStorage,
158                     db,
159                     applicationKeyStorage);
160             mInstance = new RecoverableKeyStoreManager(
161                     context.getApplicationContext(),
162                     db,
163                     new RecoverySessionStorage(),
164                     Executors.newScheduledThreadPool(1),
165                     snapshotStorage,
166                     new RecoverySnapshotListenersStorage(),
167                     platformKeyManager,
168                     applicationKeyStorage,
169                     new TestOnlyInsecureCertificateHelper(),
170                     cleanupManager,
171                     lockscreenCheckSessions);
172         }
173         return mInstance;
174     }
175 
176     @VisibleForTesting
RecoverableKeyStoreManager( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage, ScheduledExecutorService executorService, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage, PlatformKeyManager platformKeyManager, ApplicationKeyStorage applicationKeyStorage, TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper, CleanupManager cleanupManager, RemoteLockscreenValidationSessionStorage remoteLockscreenValidationSessionStorage)177     RecoverableKeyStoreManager(
178             Context context,
179             RecoverableKeyStoreDb recoverableKeyStoreDb,
180             RecoverySessionStorage recoverySessionStorage,
181             ScheduledExecutorService executorService,
182             RecoverySnapshotStorage snapshotStorage,
183             RecoverySnapshotListenersStorage listenersStorage,
184             PlatformKeyManager platformKeyManager,
185             ApplicationKeyStorage applicationKeyStorage,
186             TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper,
187             CleanupManager cleanupManager,
188             RemoteLockscreenValidationSessionStorage remoteLockscreenValidationSessionStorage) {
189         mContext = context;
190         mDatabase = recoverableKeyStoreDb;
191         mRecoverySessionStorage = recoverySessionStorage;
192         mExecutorService = executorService;
193         mListenersStorage = listenersStorage;
194         mSnapshotStorage = snapshotStorage;
195         mPlatformKeyManager = platformKeyManager;
196         mApplicationKeyStorage = applicationKeyStorage;
197         mTestCertHelper = testOnlyInsecureCertificateHelper;
198         mCleanupManager = cleanupManager;
199         try {
200             // Clears data for removed users.
201             mCleanupManager.verifyKnownUsers();
202         } catch (Exception e) {
203             Log.e(TAG, "Failed to verify known users", e);
204         }
205         try {
206             mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
207         } catch (NoSuchAlgorithmException e) {
208             Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
209             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
210         }
211         mRemoteLockscreenValidationSessionStorage = remoteLockscreenValidationSessionStorage;
212     }
213 
214     /**
215      * Used by {@link #initRecoveryServiceWithSigFile(String, byte[], byte[])}.
216      */
217     @VisibleForTesting
initRecoveryService( @onNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile)218     void initRecoveryService(
219             @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile)
220             throws RemoteException {
221         checkRecoverKeyStorePermission();
222         int userId = UserHandle.getCallingUserId();
223         int uid = Binder.getCallingUid();
224 
225         rootCertificateAlias
226                 = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
227         if (!mTestCertHelper.isValidRootCertificateAlias(rootCertificateAlias)) {
228             throw new ServiceSpecificException(
229                     ERROR_INVALID_CERTIFICATE, "Invalid root certificate alias");
230         }
231         // Always set active alias to the argument of the last call to initRecoveryService method,
232         // even if cert file is incorrect.
233         String activeRootAlias = mDatabase.getActiveRootOfTrust(userId, uid);
234         if (activeRootAlias == null) {
235             Log.d(TAG, "Root of trust for recovery agent + " + uid
236                 + " is assigned for the first time to " + rootCertificateAlias);
237         } else if (!activeRootAlias.equals(rootCertificateAlias)) {
238             Log.i(TAG, "Root of trust for recovery agent " + uid + " is changed to "
239                     + rootCertificateAlias + " from  " + activeRootAlias);
240         }
241         long updatedRows = mDatabase.setActiveRootOfTrust(userId, uid, rootCertificateAlias);
242         if (updatedRows < 0) {
243             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
244                     "Failed to set the root of trust in the local DB.");
245         }
246 
247         CertXml certXml;
248         try {
249             certXml = CertXml.parse(recoveryServiceCertFile);
250         } catch (CertParsingException e) {
251             Log.d(TAG, "Failed to parse the input as a cert file: " + HexDump.toHexString(
252                     recoveryServiceCertFile));
253             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
254         }
255 
256         // Check serial number
257         long newSerial = certXml.getSerial();
258         Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid, rootCertificateAlias);
259         if (oldSerial != null && oldSerial >= newSerial
260                 && !mTestCertHelper.isTestOnlyCertificateAlias(rootCertificateAlias)) {
261             if (oldSerial == newSerial) {
262                 Log.i(TAG, "The cert file serial number is the same, so skip updating.");
263             } else {
264                 Log.e(TAG, "The cert file serial number is older than the one in database.");
265                 throw new ServiceSpecificException(ERROR_DOWNGRADE_CERTIFICATE,
266                         "The cert file serial number is older than the one in database.");
267             }
268             return;
269         }
270         Log.i(TAG, "Updating the certificate with the new serial number " + newSerial);
271 
272         // Randomly choose and validate an endpoint certificate from the list
273         CertPath certPath;
274         X509Certificate rootCert =
275                 mTestCertHelper.getRootCertificate(rootCertificateAlias);
276         Date validationDate = mTestCertHelper.getValidationDate(rootCertificateAlias);
277         try {
278             Log.d(TAG, "Getting and validating a random endpoint certificate");
279             certPath = certXml.getRandomEndpointCert(rootCert, validationDate);
280         } catch (CertValidationException e) {
281             Log.e(TAG, "Invalid endpoint cert", e);
282             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
283         }
284 
285         // Save the chosen and validated certificate into database
286         try {
287             Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
288             long updatedCertPathRows = mDatabase.setRecoveryServiceCertPath(userId, uid,
289                     rootCertificateAlias, certPath);
290             if (updatedCertPathRows > 0) {
291                 long updatedCertSerialRows = mDatabase.setRecoveryServiceCertSerial(userId, uid,
292                         rootCertificateAlias, newSerial);
293                 if (updatedCertSerialRows < 0) {
294                     // Ideally CertPath and CertSerial should be updated together in single
295                     // transaction, but since their mismatch doesn't create many problems
296                     // extra complexity is unnecessary.
297                     throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
298                         "Failed to set the certificate serial number in the local DB.");
299                 }
300                 if (mDatabase.getSnapshotVersion(userId, uid) != null) {
301                     mDatabase.setShouldCreateSnapshot(userId, uid, true);
302                     Log.i(TAG, "This is a certificate change. Snapshot must be updated");
303                 } else {
304                     Log.i(TAG, "This is a certificate change. Snapshot didn't exist");
305                 }
306                 long updatedCounterIdRows =
307                         mDatabase.setCounterId(userId, uid, new SecureRandom().nextLong());
308                 if (updatedCounterIdRows < 0) {
309                     Log.e(TAG, "Failed to set the counter id in the local DB.");
310                 }
311             } else if (updatedCertPathRows < 0) {
312                 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
313                         "Failed to set the certificate path in the local DB.");
314             }
315         } catch (CertificateEncodingException e) {
316             Log.e(TAG, "Failed to encode CertPath", e);
317             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
318         }
319     }
320 
321     /**
322      * Initializes the recovery service with the two files {@code recoveryServiceCertFile} and
323      * {@code recoveryServiceSigFile}.
324      *
325      * @param rootCertificateAlias the alias for the root certificate that is used for validating
326      *     the recovery service certificates.
327      * @param recoveryServiceCertFile the content of the XML file containing a list of certificates
328      *     for the recovery service.
329      * @param recoveryServiceSigFile the content of the XML file containing the public-key signature
330      *     over the entire content of {@code recoveryServiceCertFile}.
331      */
initRecoveryServiceWithSigFile( @onNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile, @NonNull byte[] recoveryServiceSigFile)332     public void initRecoveryServiceWithSigFile(
333             @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile,
334             @NonNull byte[] recoveryServiceSigFile)
335             throws RemoteException {
336         checkRecoverKeyStorePermission();
337         rootCertificateAlias =
338                 mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
339         Objects.requireNonNull(recoveryServiceCertFile, "recoveryServiceCertFile is null");
340         Objects.requireNonNull(recoveryServiceSigFile, "recoveryServiceSigFile is null");
341 
342         SigXml sigXml;
343         try {
344             sigXml = SigXml.parse(recoveryServiceSigFile);
345         } catch (CertParsingException e) {
346             Log.d(TAG, "Failed to parse the sig file: " + HexDump.toHexString(
347                     recoveryServiceSigFile));
348             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
349         }
350 
351         X509Certificate rootCert =
352                 mTestCertHelper.getRootCertificate(rootCertificateAlias);
353         Date validationDate = mTestCertHelper.getValidationDate(rootCertificateAlias);
354         try {
355             sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile, validationDate);
356         } catch (CertValidationException e) {
357             Log.e(TAG, "The signature over the cert file is invalid."
358                     + " Cert: " + HexDump.toHexString(recoveryServiceCertFile)
359                     + " Sig: " + HexDump.toHexString(recoveryServiceSigFile));
360             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
361         }
362 
363         initRecoveryService(rootCertificateAlias, recoveryServiceCertFile);
364     }
365 
366     /**
367      * Gets all data necessary to recover application keys on new device.
368      *
369      * @return KeyChain Snapshot.
370      * @throws ServiceSpecificException if no snapshot is pending.
371      * @hide
372      */
getKeyChainSnapshot()373     public @NonNull KeyChainSnapshot getKeyChainSnapshot()
374             throws RemoteException {
375         checkRecoverKeyStorePermission();
376         int uid = Binder.getCallingUid();
377         KeyChainSnapshot snapshot = mSnapshotStorage.get(uid);
378         if (snapshot == null) {
379             throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
380         }
381         return snapshot;
382     }
383 
setSnapshotCreatedPendingIntent(@ullable PendingIntent intent)384     public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
385             throws RemoteException {
386         checkRecoverKeyStorePermission();
387         int uid = Binder.getCallingUid();
388         mListenersStorage.setSnapshotListener(uid, intent);
389     }
390 
391     /**
392      * Set the server params for the user's key chain. This is used to uniquely identify a key
393      * chain. Along with the counter ID, it is used to uniquely identify an instance of a vault.
394      */
setServerParams(@onNull byte[] serverParams)395     public void setServerParams(@NonNull byte[] serverParams) throws RemoteException {
396         checkRecoverKeyStorePermission();
397         int userId = UserHandle.getCallingUserId();
398         int uid = Binder.getCallingUid();
399 
400         byte[] currentServerParams = mDatabase.getServerParams(userId, uid);
401 
402         if (Arrays.equals(serverParams, currentServerParams)) {
403             Log.v(TAG, "Not updating server params - same as old value.");
404             return;
405         }
406 
407         long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
408         if (updatedRows < 0) {
409             throw new ServiceSpecificException(
410                     ERROR_SERVICE_INTERNAL_ERROR, "Database failure trying to set server params.");
411         }
412 
413         if (currentServerParams == null) {
414             Log.i(TAG, "Initialized server params.");
415             return;
416         }
417 
418         if (mDatabase.getSnapshotVersion(userId, uid) != null) {
419             mDatabase.setShouldCreateSnapshot(userId, uid, true);
420             Log.i(TAG, "Updated server params. Snapshot must be updated");
421         } else {
422             Log.i(TAG, "Updated server params. Snapshot didn't exist");
423         }
424     }
425 
426     /**
427      * Sets the recovery status of key with {@code alias} to {@code status}.
428      */
setRecoveryStatus(@onNull String alias, int status)429     public void setRecoveryStatus(@NonNull String alias, int status) throws RemoteException {
430         checkRecoverKeyStorePermission();
431         Objects.requireNonNull(alias, "alias is null");
432         long updatedRows = mDatabase.setRecoveryStatus(Binder.getCallingUid(), alias, status);
433         if (updatedRows < 0) {
434             throw new ServiceSpecificException(
435                     ERROR_SERVICE_INTERNAL_ERROR,
436                     "Failed to set the key recovery status in the local DB.");
437         }
438     }
439 
440     /**
441      * Returns recovery statuses for all keys belonging to the calling uid.
442      *
443      * @return {@link Map} from key alias to recovery status. Recovery status is one of
444      *     {@link RecoveryController#RECOVERY_STATUS_SYNCED},
445      *     {@link RecoveryController#RECOVERY_STATUS_SYNC_IN_PROGRESS} or
446      *     {@link RecoveryController#RECOVERY_STATUS_PERMANENT_FAILURE}.
447      */
getRecoveryStatus()448     public @NonNull Map<String, Integer> getRecoveryStatus() throws RemoteException {
449         checkRecoverKeyStorePermission();
450         return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
451     }
452 
453     /**
454      * Sets recovery secrets list used by all recovery agents for given {@code userId}
455      *
456      * @hide
457      */
setRecoverySecretTypes( @onNull @eyChainProtectionParams.UserSecretType int[] secretTypes)458     public void setRecoverySecretTypes(
459             @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
460             throws RemoteException {
461         checkRecoverKeyStorePermission();
462         Objects.requireNonNull(secretTypes, "secretTypes is null");
463         int userId = UserHandle.getCallingUserId();
464         int uid = Binder.getCallingUid();
465 
466         int[] currentSecretTypes = mDatabase.getRecoverySecretTypes(userId, uid);
467         if (Arrays.equals(secretTypes, currentSecretTypes)) {
468             Log.v(TAG, "Not updating secret types - same as old value.");
469             return;
470         }
471 
472         long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes);
473         if (updatedRows < 0) {
474             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
475                     "Database error trying to set secret types.");
476         }
477 
478         if (currentSecretTypes.length == 0) {
479             Log.i(TAG, "Initialized secret types.");
480             return;
481         }
482 
483         Log.i(TAG, "Updated secret types. Snapshot pending.");
484         if (mDatabase.getSnapshotVersion(userId, uid) != null) {
485             mDatabase.setShouldCreateSnapshot(userId, uid, true);
486             Log.i(TAG, "Updated secret types. Snapshot must be updated");
487         } else {
488             Log.i(TAG, "Updated secret types. Snapshot didn't exist");
489         }
490     }
491 
492     /**
493      * Gets secret types necessary to create Recovery Data.
494      *
495      * @return secret types
496      * @hide
497      */
getRecoverySecretTypes()498     public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
499         checkRecoverKeyStorePermission();
500         return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
501             Binder.getCallingUid());
502     }
503 
504     /**
505      * Initializes recovery session given the X509-encoded public key of the recovery service.
506      *
507      * @param sessionId A unique ID to identify the recovery session.
508      * @param verifierPublicKey X509-encoded public key.
509      * @param vaultParams Additional params associated with vault.
510      * @param vaultChallenge Challenge issued by vault service.
511      * @param secrets Lock-screen hashes. For now only a single secret is supported.
512      * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
513      * @deprecated Use {@link #startRecoverySessionWithCertPath(String, String, RecoveryCertPath,
514      *         byte[], byte[], List)} instead.
515      *
516      * @hide
517      */
518     @VisibleForTesting
startRecoverySession( @onNull String sessionId, @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, @NonNull List<KeyChainProtectionParams> secrets)519     @NonNull byte[] startRecoverySession(
520             @NonNull String sessionId,
521             @NonNull byte[] verifierPublicKey,
522             @NonNull byte[] vaultParams,
523             @NonNull byte[] vaultChallenge,
524             @NonNull List<KeyChainProtectionParams> secrets)
525             throws RemoteException {
526         checkRecoverKeyStorePermission();
527         int uid = Binder.getCallingUid();
528 
529         if (secrets.size() != 1) {
530             throw new UnsupportedOperationException(
531                     "Only a single KeyChainProtectionParams is supported");
532         }
533 
534         PublicKey publicKey;
535         try {
536             publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
537         } catch (InvalidKeySpecException e) {
538             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
539         }
540         // The raw public key bytes contained in vaultParams must match the ones given in
541         // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned
542         // by the original recovery service.
543         if (!publicKeysMatch(publicKey, vaultParams)) {
544             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE,
545                     "The public keys given in verifierPublicKey and vaultParams do not match.");
546         }
547 
548         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
549         byte[] kfHash = secrets.get(0).getSecret();
550         mRecoverySessionStorage.add(
551                 uid,
552                 new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
553 
554         Log.i(TAG, "Received VaultParams for recovery: " + HexDump.toHexString(vaultParams));
555         try {
556             byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
557             return KeySyncUtils.encryptRecoveryClaim(
558                     publicKey,
559                     vaultParams,
560                     vaultChallenge,
561                     thmKfHash,
562                     keyClaimant);
563         } catch (NoSuchAlgorithmException e) {
564             Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e);
565             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
566         } catch (InvalidKeyException e) {
567             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
568         }
569     }
570 
571     /**
572      * Initializes recovery session given the certificate path of the recovery service.
573      *
574      * @param sessionId A unique ID to identify the recovery session.
575      * @param verifierCertPath The certificate path of the recovery service.
576      * @param vaultParams Additional params associated with vault.
577      * @param vaultChallenge Challenge issued by vault service.
578      * @param secrets Lock-screen hashes. For now only a single secret is supported.
579      * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
580      *
581      * @hide
582      */
startRecoverySessionWithCertPath( @onNull String sessionId, @NonNull String rootCertificateAlias, @NonNull RecoveryCertPath verifierCertPath, @NonNull byte[] vaultParams, @NonNull byte[] vaultChallenge, @NonNull List<KeyChainProtectionParams> secrets)583     public @NonNull byte[] startRecoverySessionWithCertPath(
584             @NonNull String sessionId,
585             @NonNull String rootCertificateAlias,
586             @NonNull RecoveryCertPath verifierCertPath,
587             @NonNull byte[] vaultParams,
588             @NonNull byte[] vaultChallenge,
589             @NonNull List<KeyChainProtectionParams> secrets)
590             throws RemoteException {
591         checkRecoverKeyStorePermission();
592         rootCertificateAlias =
593                 mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
594         Objects.requireNonNull(sessionId, "invalid session");
595         Objects.requireNonNull(verifierCertPath, "verifierCertPath is null");
596         Objects.requireNonNull(vaultParams, "vaultParams is null");
597         Objects.requireNonNull(vaultChallenge, "vaultChallenge is null");
598         Objects.requireNonNull(secrets, "secrets is null");
599         CertPath certPath;
600         try {
601             certPath = verifierCertPath.getCertPath();
602         } catch (CertificateException e) {
603             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
604         }
605 
606         try {
607             Date validationDate = mTestCertHelper.getValidationDate(rootCertificateAlias);
608             CertUtils.validateCertPath(mTestCertHelper.getRootCertificate(rootCertificateAlias),
609                     certPath, validationDate);
610         } catch (CertValidationException e) {
611             Log.e(TAG, "Failed to validate the given cert path", e);
612             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
613         }
614 
615         byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded();
616         if (verifierPublicKey == null) {
617             Log.e(TAG, "Failed to encode verifierPublicKey");
618             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
619                     "Failed to encode verifierPublicKey");
620         }
621 
622         return startRecoverySession(
623                 sessionId, verifierPublicKey, vaultParams, vaultChallenge, secrets);
624     }
625 
626     /**
627      * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
628      * service.
629      *
630      * @param sessionId The session ID used to generate the claim. See
631      *     {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
632      * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
633      *     service.
634      * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
635      *     were wrapped with the recovery key.
636      * @throws RemoteException if an error occurred recovering the keys.
637      */
recoverKeyChainSnapshot( @onNull String sessionId, @NonNull byte[] encryptedRecoveryKey, @NonNull List<WrappedApplicationKey> applicationKeys)638     public @NonNull Map<String, String> recoverKeyChainSnapshot(
639             @NonNull String sessionId,
640             @NonNull byte[] encryptedRecoveryKey,
641             @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
642         checkRecoverKeyStorePermission();
643         int userId = UserHandle.getCallingUserId();
644         int uid = Binder.getCallingUid();
645         RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
646         if (sessionEntry == null) {
647             throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
648                     String.format(Locale.US,
649                             "Application uid=%d does not have pending session '%s'",
650                             uid,
651                             sessionId));
652         }
653 
654         try {
655             byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
656             Map<String, byte[]> keysByAlias = recoverApplicationKeys(recoveryKey,
657                     applicationKeys);
658             return importKeyMaterials(userId, uid, keysByAlias);
659         } catch (KeyStoreException e) {
660             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
661         } finally {
662             sessionEntry.destroy();
663             mRecoverySessionStorage.remove(uid);
664         }
665     }
666 
667     /**
668      * Imports the key materials, returning a map from alias to grant alias for the calling user.
669      *
670      * @param userId The calling user ID.
671      * @param uid The calling uid.
672      * @param keysByAlias The key materials, keyed by alias.
673      * @throws KeyStoreException if an error occurs importing the key or getting the grant.
674      */
importKeyMaterials( int userId, int uid, Map<String, byte[]> keysByAlias)675     private @NonNull Map<String, String> importKeyMaterials(
676             int userId, int uid, Map<String, byte[]> keysByAlias)
677             throws KeyStoreException {
678         ArrayMap<String, String> grantAliasesByAlias = new ArrayMap<>(keysByAlias.size());
679         for (String alias : keysByAlias.keySet()) {
680             mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keysByAlias.get(alias));
681             String grantAlias = getAlias(userId, uid, alias);
682             Log.i(TAG, String.format(Locale.US, "Import %s -> %s", alias, grantAlias));
683             grantAliasesByAlias.put(alias, grantAlias);
684         }
685         return grantAliasesByAlias;
686     }
687 
688     /**
689      * Returns an alias for the key.
690      *
691      * @param userId The user ID of the calling process.
692      * @param uid The uid of the calling process.
693      * @param alias The alias of the key.
694      * @return The alias in the calling process's keystore.
695      */
getAlias(int userId, int uid, String alias)696     private @Nullable String getAlias(int userId, int uid, String alias) {
697         return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
698     }
699 
700     /**
701      * Destroys the session with the given {@code sessionId}.
702      */
closeSession(@onNull String sessionId)703     public void closeSession(@NonNull String sessionId) throws RemoteException {
704         checkRecoverKeyStorePermission();
705         Objects.requireNonNull(sessionId, "invalid session");
706         mRecoverySessionStorage.remove(Binder.getCallingUid(), sessionId);
707     }
708 
removeKey(@onNull String alias)709     public void removeKey(@NonNull String alias) throws RemoteException {
710         checkRecoverKeyStorePermission();
711         Objects.requireNonNull(alias, "alias is null");
712         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);
718             mApplicationKeyStorage.deleteEntry(userId, uid, alias);
719         }
720     }
721 
722     /**
723      * Generates a key named {@code alias} in caller's namespace.
724      * The key is stored in system service keystore namespace.
725      *
726      * @param alias the alias provided by caller as a reference to the key.
727      * @return grant alias, which caller can use to access the key.
728      * @throws RemoteException if certain internal errors occur.
729      *
730      * @deprecated Use {@link #generateKeyWithMetadata(String, byte[])} instead.
731      */
732     @Deprecated
generateKey(@onNull String alias)733     public String generateKey(@NonNull String alias) throws RemoteException {
734         return generateKeyWithMetadata(alias, /*metadata=*/ null);
735     }
736 
737     /**
738      * Generates a key named {@code alias} with the {@code metadata} in caller's namespace.
739      * The key is stored in system service keystore namespace.
740      *
741      * @param alias the alias provided by caller as a reference to the key.
742      * @param metadata the optional metadata blob that will authenticated (but unencrypted) together
743      *         with the key material when the key is uploaded to cloud.
744      * @return grant alias, which caller can use to access the key.
745      * @throws RemoteException if certain internal errors occur.
746      */
generateKeyWithMetadata(@onNull String alias, @Nullable byte[] metadata)747     public String generateKeyWithMetadata(@NonNull String alias, @Nullable byte[] metadata)
748             throws RemoteException {
749         checkRecoverKeyStorePermission();
750         Objects.requireNonNull(alias, "alias is null");
751         int uid = Binder.getCallingUid();
752         int userId = UserHandle.getCallingUserId();
753 
754         PlatformEncryptionKey encryptionKey;
755         try {
756             encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
757         } catch (NoSuchAlgorithmException e) {
758             // Impossible: all algorithms must be supported by AOSP
759             throw new RuntimeException(e);
760         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
761             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
762         } catch (InsecureUserException e) {
763             throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
764         }
765 
766         try {
767             byte[] secretKey = mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId,
768                     uid, alias, metadata);
769             mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey);
770             return getAlias(userId, uid, alias);
771         } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
772             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
773         }
774     }
775 
776     /**
777      * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service
778      * keystore namespace.
779      *
780      * @param alias the alias provided by caller as a reference to the key.
781      * @param keyBytes the raw bytes of the 256-bit AES key.
782      * @return grant alias, which caller can use to access the key.
783      * @throws RemoteException if the given key is invalid or some internal errors occur.
784      *
785      * @deprecated Use {{@link #importKeyWithMetadata(String, byte[], byte[])}} instead.
786      *
787      * @hide
788      */
789     @Deprecated
importKey(@onNull String alias, @NonNull byte[] keyBytes)790     public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
791             throws RemoteException {
792         return importKeyWithMetadata(alias, keyBytes, /*metadata=*/ null);
793     }
794 
795     /**
796      * Imports a 256-bit AES-GCM key named {@code alias} with the given {@code metadata}. The key is
797      * stored in system service keystore namespace.
798      *
799      * @param alias the alias provided by caller as a reference to the key.
800      * @param keyBytes the raw bytes of the 256-bit AES key.
801      * @param metadata the metadata to be authenticated (but unencrypted) together with the key.
802      * @return grant alias, which caller can use to access the key.
803      * @throws RemoteException if the given key is invalid or some internal errors occur.
804      *
805      * @hide
806      */
importKeyWithMetadata(@onNull String alias, @NonNull byte[] keyBytes, @Nullable byte[] metadata)807     public @Nullable String importKeyWithMetadata(@NonNull String alias, @NonNull byte[] keyBytes,
808             @Nullable byte[] metadata) throws RemoteException {
809         checkRecoverKeyStorePermission();
810         Objects.requireNonNull(alias, "alias is null");
811         Objects.requireNonNull(keyBytes, "keyBytes is null");
812         if (keyBytes.length != RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE) {
813             Log.e(TAG, "The given key for import doesn't have the required length "
814                     + RecoverableKeyGenerator.KEY_SIZE_BITS);
815             throw new ServiceSpecificException(ERROR_INVALID_KEY_FORMAT,
816                     "The given key does not contain " + RecoverableKeyGenerator.KEY_SIZE_BITS
817                             + " bits.");
818         }
819 
820         int uid = Binder.getCallingUid();
821         int userId = UserHandle.getCallingUserId();
822 
823         PlatformEncryptionKey encryptionKey;
824         try {
825             encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
826         } catch (NoSuchAlgorithmException e) {
827             // Impossible: all algorithms must be supported by AOSP
828             throw new RuntimeException(e);
829         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
830             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
831         } catch (InsecureUserException e) {
832             throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
833         }
834 
835         try {
836             // Wrap the key by the platform key and store the wrapped key locally
837             mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes,
838                     metadata);
839 
840             // Import the key to Android KeyStore and get grant
841             mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes);
842             return getAlias(userId, uid, alias);
843         } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
844             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
845         }
846     }
847 
848     /**
849      * Gets a key named {@code alias} in caller's namespace.
850      *
851      * @return grant alias, which caller can use to access the key.
852      */
getKey(@onNull String alias)853     public @Nullable String getKey(@NonNull String alias) throws RemoteException {
854         checkRecoverKeyStorePermission();
855         Objects.requireNonNull(alias, "alias is null");
856         int uid = Binder.getCallingUid();
857         int userId = UserHandle.getCallingUserId();
858         return getAlias(userId, uid, alias);
859     }
860 
decryptRecoveryKey( RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)861     private byte[] decryptRecoveryKey(
862             RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
863             throws RemoteException, ServiceSpecificException {
864         byte[] locallyEncryptedKey;
865         try {
866             locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
867                     sessionEntry.getKeyClaimant(),
868                     sessionEntry.getVaultParams(),
869                     encryptedClaimResponse);
870         } catch (InvalidKeyException e) {
871             Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e);
872             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
873                     "Failed to decrypt recovery key " + e.getMessage());
874         } catch (AEADBadTagException e) {
875             Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e);
876             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
877                     "Failed to decrypt recovery key " + e.getMessage());
878         } catch (NoSuchAlgorithmException e) {
879             // Should never happen: all the algorithms used are required by AOSP implementations
880             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
881         }
882 
883         try {
884             return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
885         } catch (InvalidKeyException e) {
886             Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
887             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
888                     "Failed to decrypt recovery key " + e.getMessage());
889         } catch (AEADBadTagException e) {
890             Log.e(TAG, "Got AEADBadTagException during decrypting recovery key", e);
891             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
892                     "Failed to decrypt recovery key " + e.getMessage());
893         } catch (NoSuchAlgorithmException e) {
894             // Should never happen: all the algorithms used are required by AOSP implementations
895             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
896         }
897     }
898 
899     /**
900      * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
901      *
902      * @return Map from alias to raw key material.
903      * @throws RemoteException if an error occurred decrypting the keys.
904      */
recoverApplicationKeys(@onNull byte[] recoveryKey, @NonNull List<WrappedApplicationKey> applicationKeys)905     private @NonNull Map<String, byte[]> recoverApplicationKeys(@NonNull byte[] recoveryKey,
906             @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
907         HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
908         for (WrappedApplicationKey applicationKey : applicationKeys) {
909             String alias = applicationKey.getAlias();
910             byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
911             byte[] keyMetadata = applicationKey.getMetadata();
912 
913             try {
914                 byte[] keyMaterial = KeySyncUtils.decryptApplicationKey(recoveryKey,
915                         encryptedKeyMaterial, keyMetadata);
916                 keyMaterialByAlias.put(alias, keyMaterial);
917             } catch (NoSuchAlgorithmException e) {
918                 Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
919                 throw new ServiceSpecificException(
920                         ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
921             } catch (InvalidKeyException e) {
922                 Log.e(TAG, "Got InvalidKeyException during decrypting application key with alias: "
923                         + alias, e);
924                 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
925                         "Failed to recover key with alias '" + alias + "': " + e.getMessage());
926             } catch (AEADBadTagException e) {
927                 Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: "
928                         + alias, e);
929                 // Ignore the exception to continue to recover the other application keys.
930             }
931         }
932         if (!applicationKeys.isEmpty() && keyMaterialByAlias.isEmpty()) {
933             Log.e(TAG, "Failed to recover any of the application keys.");
934             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
935                     "Failed to recover any of the application keys.");
936         }
937         return keyMaterialByAlias;
938     }
939 
940     /**
941      * This function can only be used inside LockSettingsService.
942      *
943      * @param credentialType the type of credential, as defined in {@code LockPatternUtils}
944      * @param credential the credential, encoded as a byte array
945      * @param userId the ID of the user to whom the credential belongs
946      * @hide
947      */
lockScreenSecretAvailable( int credentialType, @NonNull byte[] credential, int userId)948     public void lockScreenSecretAvailable(
949             int credentialType, @NonNull byte[] credential, int userId) {
950         // So as not to block the critical path unlocking the phone, defer to another thread.
951         try {
952             mExecutorService.schedule(KeySyncTask.newInstance(
953                     mContext,
954                     mDatabase,
955                     mSnapshotStorage,
956                     mListenersStorage,
957                     userId,
958                     credentialType,
959                     credential,
960                     /*credentialUpdated=*/ false),
961                     SYNC_DELAY_MILLIS,
962                     TimeUnit.MILLISECONDS
963             );
964         } catch (NoSuchAlgorithmException e) {
965             Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
966         } catch (KeyStoreException e) {
967             Log.e(TAG, "Key store error encountered during recoverable key sync", e);
968         } catch (InsecureUserException e) {
969             Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
970         }
971     }
972 
973     /**
974      * This function can only be used inside LockSettingsService.
975      *
976      * @param credentialType the type of the new credential, as defined in {@code LockPatternUtils}
977      * @param credential the new credential, encoded as a byte array
978      * @param userId the ID of the user whose credential was changed
979      * @hide
980      */
lockScreenSecretChanged( int credentialType, @Nullable byte[] credential, int userId)981     public void lockScreenSecretChanged(
982             int credentialType,
983             @Nullable byte[] credential,
984             int userId) {
985         // So as not to block the critical path unlocking the phone, defer to another thread.
986         try {
987             mExecutorService.schedule(KeySyncTask.newInstance(
988                     mContext,
989                     mDatabase,
990                     mSnapshotStorage,
991                     mListenersStorage,
992                     userId,
993                     credentialType,
994                     credential,
995                     /*credentialUpdated=*/ true),
996                     SYNC_DELAY_MILLIS,
997                     TimeUnit.MILLISECONDS
998             );
999         } catch (NoSuchAlgorithmException e) {
1000             Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
1001         } catch (KeyStoreException e) {
1002             Log.e(TAG, "Key store error encountered during recoverable key sync", e);
1003         } catch (InsecureUserException e) {
1004             Log.e(TAG, "InsecureUserException during lock screen secret update", e);
1005         }
1006     }
1007 
1008     /**
1009      * Starts a session to verify lock screen credentials provided by a remote device.
1010      */
startRemoteLockscreenValidation( LockSettingsService lockSettingsService)1011     public RemoteLockscreenValidationSession startRemoteLockscreenValidation(
1012             LockSettingsService lockSettingsService) {
1013         if (mRemoteLockscreenValidationSessionStorage == null) {
1014             throw new UnsupportedOperationException("Under development");
1015         }
1016         checkVerifyRemoteLockscreenPermission();
1017         int userId = UserHandle.getCallingUserId();
1018         int savedCredentialType;
1019         final long token = Binder.clearCallingIdentity();
1020         try {
1021             savedCredentialType = lockSettingsService.getCredentialType(userId);
1022         } finally {
1023             Binder.restoreCallingIdentity(token);
1024         }
1025         int keyguardCredentialsType = lockPatternUtilsToKeyguardType(savedCredentialType);
1026         LockscreenVerificationSession session =
1027                 mRemoteLockscreenValidationSessionStorage.startSession(userId);
1028         PublicKey publicKey = session.getKeyPair().getPublic();
1029         byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
1030         int badGuesses = mDatabase.getBadRemoteGuessCounter(userId);
1031         int remainingAttempts = Math.max(INVALID_REMOTE_GUESS_LIMIT - badGuesses, 0);
1032         // TODO(b/254335492): Schedule task to remove inactive session
1033         return new RemoteLockscreenValidationSession.Builder()
1034                 .setLockType(keyguardCredentialsType)
1035                 .setRemainingAttempts(remainingAttempts)
1036                 .setSourcePublicKey(encodedPublicKey)
1037                 .build();
1038     }
1039 
1040     /**
1041      * Verifies encrypted credentials guess from a remote device.
1042      */
validateRemoteLockscreen( @onNull byte[] encryptedCredential, LockSettingsService lockSettingsService)1043     public synchronized RemoteLockscreenValidationResult validateRemoteLockscreen(
1044             @NonNull byte[] encryptedCredential,
1045             LockSettingsService lockSettingsService) {
1046         checkVerifyRemoteLockscreenPermission();
1047         int userId = UserHandle.getCallingUserId();
1048         LockscreenVerificationSession session =
1049                 mRemoteLockscreenValidationSessionStorage.get(userId);
1050         int badGuesses = mDatabase.getBadRemoteGuessCounter(userId);
1051         int remainingAttempts = INVALID_REMOTE_GUESS_LIMIT - badGuesses;
1052         if (remainingAttempts <= 0) {
1053             return new RemoteLockscreenValidationResult.Builder()
1054                 .setResultCode(RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS)
1055                 .build();
1056         }
1057         if (session == null) {
1058             return new RemoteLockscreenValidationResult.Builder()
1059                 .setResultCode(RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED)
1060                 .build();
1061         }
1062         byte[] decryptedCredentials;
1063         try {
1064             decryptedCredentials = SecureBox.decrypt(
1065                 session.getKeyPair().getPrivate(),
1066                 /* sharedSecret= */ null,
1067                 LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER,
1068                 encryptedCredential);
1069         } catch (NoSuchAlgorithmException e) {
1070             Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
1071             throw new IllegalStateException(e);
1072         } catch (InvalidKeyException e) {
1073             Log.e(TAG, "Got InvalidKeyException during lock screen credentials decryption");
1074             throw new IllegalStateException(e);
1075         } catch (AEADBadTagException e) {
1076             throw new IllegalStateException("Could not decrypt credentials guess", e);
1077         }
1078         int savedCredentialType;
1079         final long token = Binder.clearCallingIdentity();
1080         try {
1081             savedCredentialType = lockSettingsService.getCredentialType(userId);
1082             int keyguardCredentialsType = lockPatternUtilsToKeyguardType(savedCredentialType);
1083             try (LockscreenCredential credential =
1084                     createLockscreenCredential(keyguardCredentialsType, decryptedCredentials)) {
1085                 Arrays.fill(decryptedCredentials, (byte) 0);
1086                 decryptedCredentials = null;
1087                 VerifyCredentialResponse verifyResponse =
1088                         lockSettingsService.verifyCredential(credential, userId, 0);
1089                 return handleVerifyCredentialResponse(verifyResponse, userId);
1090             }
1091         } finally {
1092             Binder.restoreCallingIdentity(token);
1093         }
1094     }
1095 
handleVerifyCredentialResponse( VerifyCredentialResponse response, int userId)1096     private RemoteLockscreenValidationResult handleVerifyCredentialResponse(
1097             VerifyCredentialResponse response, int userId) {
1098         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
1099             mDatabase.setBadRemoteGuessCounter(userId, 0);
1100             mRemoteLockscreenValidationSessionStorage.finishSession(userId);
1101             return new RemoteLockscreenValidationResult.Builder()
1102                     .setResultCode(RemoteLockscreenValidationResult.RESULT_GUESS_VALID)
1103                     .build();
1104         }
1105         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
1106             long timeout = (long) response.getTimeout();
1107             return new RemoteLockscreenValidationResult.Builder()
1108                     .setResultCode(RemoteLockscreenValidationResult.RESULT_LOCKOUT)
1109                     .setTimeoutMillis(timeout)
1110                     .build();
1111         }
1112         // Invalid guess
1113         int badGuesses = mDatabase.getBadRemoteGuessCounter(userId);
1114         mDatabase.setBadRemoteGuessCounter(userId, badGuesses + 1);
1115         return new RemoteLockscreenValidationResult.Builder()
1116                 .setResultCode(RemoteLockscreenValidationResult.RESULT_GUESS_INVALID)
1117                 .build();
1118     }
1119 
createLockscreenCredential( int lockType, byte[] password)1120     private LockscreenCredential createLockscreenCredential(
1121             int lockType, byte[] password) {
1122         switch (lockType) {
1123             case KeyguardManager.PASSWORD:
1124                 CharSequence passwordStr = new String(password, StandardCharsets.UTF_8);
1125                 return LockscreenCredential.createPassword(passwordStr);
1126             case KeyguardManager.PIN:
1127                 CharSequence pinStr = new String(password);
1128                 return LockscreenCredential.createPin(pinStr);
1129             case KeyguardManager.PATTERN:
1130                 List<LockPatternView.Cell> pattern =
1131                         LockPatternUtils.byteArrayToPattern(password);
1132                 return LockscreenCredential.createPattern(pattern);
1133             default:
1134                 throw new IllegalStateException("Lockscreen is not set");
1135         }
1136     }
1137 
checkVerifyRemoteLockscreenPermission()1138     private void checkVerifyRemoteLockscreenPermission() {
1139         mContext.enforceCallingOrSelfPermission(
1140                 Manifest.permission.CHECK_REMOTE_LOCKSCREEN,
1141                 "Caller " + Binder.getCallingUid()
1142                         + " doesn't have CHECK_REMOTE_LOCKSCREEN permission.");
1143         int userId = UserHandle.getCallingUserId();
1144         int uid = Binder.getCallingUid();
1145         mCleanupManager.registerRecoveryAgent(userId, uid);
1146     }
1147 
lockPatternUtilsToKeyguardType(int credentialsType)1148     private int lockPatternUtilsToKeyguardType(int credentialsType) {
1149         switch(credentialsType) {
1150             case LockPatternUtils.CREDENTIAL_TYPE_NONE:
1151                 throw new IllegalStateException("Screen lock is not set");
1152             case LockPatternUtils.CREDENTIAL_TYPE_PATTERN:
1153                 return KeyguardManager.PATTERN;
1154             case LockPatternUtils.CREDENTIAL_TYPE_PIN:
1155                 return KeyguardManager.PIN;
1156             case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD:
1157                 return KeyguardManager.PASSWORD;
1158             default:
1159                 throw new IllegalStateException("Screen lock is not set");
1160         }
1161     }
1162 
checkRecoverKeyStorePermission()1163     private void checkRecoverKeyStorePermission() {
1164         mContext.enforceCallingOrSelfPermission(
1165                 Manifest.permission.RECOVER_KEYSTORE,
1166                 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
1167         int userId = UserHandle.getCallingUserId();
1168         int uid = Binder.getCallingUid();
1169         mCleanupManager.registerRecoveryAgent(userId, uid);
1170     }
1171 
publicKeysMatch(PublicKey publicKey, byte[] vaultParams)1172     private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
1173         byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
1174         return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length));
1175     }
1176 }
1177