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