Home | History | Annotate | Download | only in recoverablekeystore
      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_KEY_FORMAT;
     24 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
     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.PendingIntent;
     33 import android.content.Context;
     34 import android.os.Binder;
     35 import android.os.RemoteException;
     36 import android.os.ServiceSpecificException;
     37 import android.os.UserHandle;
     38 import android.security.keystore.recovery.KeyChainProtectionParams;
     39 import android.security.keystore.recovery.KeyChainSnapshot;
     40 import android.security.keystore.recovery.RecoveryCertPath;
     41 import android.security.keystore.recovery.RecoveryController;
     42 import android.security.keystore.recovery.WrappedApplicationKey;
     43 import android.security.KeyStore;
     44 import android.util.ArrayMap;
     45 import android.util.Log;
     46 
     47 import com.android.internal.annotations.VisibleForTesting;
     48 import com.android.internal.util.HexDump;
     49 import com.android.internal.util.Preconditions;
     50 import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
     51 import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
     52 import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
     53 import com.android.server.locksettings.recoverablekeystore.certificate.CertXml;
     54 import com.android.server.locksettings.recoverablekeystore.certificate.SigXml;
     55 import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
     56 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
     57 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
     58 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
     59 
     60 import java.io.IOException;
     61 import java.security.InvalidKeyException;
     62 import java.security.KeyFactory;
     63 import java.security.KeyStoreException;
     64 import java.security.NoSuchAlgorithmException;
     65 import java.security.PublicKey;
     66 import java.security.SecureRandom;
     67 import java.security.UnrecoverableKeyException;
     68 import java.security.cert.CertPath;
     69 import java.security.cert.CertificateEncodingException;
     70 import java.security.cert.CertificateException;
     71 import java.security.cert.X509Certificate;
     72 import java.security.spec.InvalidKeySpecException;
     73 import java.security.spec.X509EncodedKeySpec;
     74 import java.util.Arrays;
     75 import java.util.HashMap;
     76 import java.util.List;
     77 import java.util.Locale;
     78 import java.util.Map;
     79 import java.util.concurrent.ExecutorService;
     80 import java.util.concurrent.Executors;
     81 
     82 import javax.crypto.AEADBadTagException;
     83 
     84 /**
     85  * Class with {@link RecoveryController} API implementation and internal methods to interact
     86  * with {@code LockSettingsService}.
     87  *
     88  * @hide
     89  */
     90 public class RecoverableKeyStoreManager {
     91     private static final String TAG = "RecoverableKeyStoreMgr";
     92 
     93     private static RecoverableKeyStoreManager mInstance;
     94 
     95     private final Context mContext;
     96     private final RecoverableKeyStoreDb mDatabase;
     97     private final RecoverySessionStorage mRecoverySessionStorage;
     98     private final ExecutorService mExecutorService;
     99     private final RecoverySnapshotListenersStorage mListenersStorage;
    100     private final RecoverableKeyGenerator mRecoverableKeyGenerator;
    101     private final RecoverySnapshotStorage mSnapshotStorage;
    102     private final PlatformKeyManager mPlatformKeyManager;
    103     private final ApplicationKeyStorage mApplicationKeyStorage;
    104     private final TestOnlyInsecureCertificateHelper mTestCertHelper;
    105 
    106     /**
    107      * Returns a new or existing instance.
    108      *
    109      * @hide
    110      */
    111     public static synchronized RecoverableKeyStoreManager
    112             getInstance(Context context, KeyStore keystore) {
    113         if (mInstance == null) {
    114             RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
    115             PlatformKeyManager platformKeyManager;
    116             ApplicationKeyStorage applicationKeyStorage;
    117             try {
    118                 platformKeyManager = PlatformKeyManager.getInstance(context, db);
    119                 applicationKeyStorage = ApplicationKeyStorage.getInstance(keystore);
    120             } catch (NoSuchAlgorithmException e) {
    121                 // Impossible: all algorithms must be supported by AOSP
    122                 throw new RuntimeException(e);
    123             } catch (KeyStoreException e) {
    124                 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    125             }
    126 
    127             mInstance = new RecoverableKeyStoreManager(
    128                     context.getApplicationContext(),
    129                     db,
    130                     new RecoverySessionStorage(),
    131                     Executors.newSingleThreadExecutor(),
    132                     RecoverySnapshotStorage.newInstance(),
    133                     new RecoverySnapshotListenersStorage(),
    134                     platformKeyManager,
    135                     applicationKeyStorage,
    136                     new TestOnlyInsecureCertificateHelper());
    137         }
    138         return mInstance;
    139     }
    140 
    141     @VisibleForTesting
    142     RecoverableKeyStoreManager(
    143             Context context,
    144             RecoverableKeyStoreDb recoverableKeyStoreDb,
    145             RecoverySessionStorage recoverySessionStorage,
    146             ExecutorService executorService,
    147             RecoverySnapshotStorage snapshotStorage,
    148             RecoverySnapshotListenersStorage listenersStorage,
    149             PlatformKeyManager platformKeyManager,
    150             ApplicationKeyStorage applicationKeyStorage,
    151             TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) {
    152         mContext = context;
    153         mDatabase = recoverableKeyStoreDb;
    154         mRecoverySessionStorage = recoverySessionStorage;
    155         mExecutorService = executorService;
    156         mListenersStorage = listenersStorage;
    157         mSnapshotStorage = snapshotStorage;
    158         mPlatformKeyManager = platformKeyManager;
    159         mApplicationKeyStorage = applicationKeyStorage;
    160         mTestCertHelper = TestOnlyInsecureCertificateHelper;
    161 
    162         try {
    163             mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
    164         } catch (NoSuchAlgorithmException e) {
    165             Log.wtf(TAG, "AES keygen algorithm not available. AOSP must support this.", e);
    166             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    167         }
    168     }
    169 
    170     /**
    171      * Used by {@link #initRecoveryServiceWithSigFile(String, byte[], byte[])}.
    172      */
    173     @VisibleForTesting
    174     void initRecoveryService(
    175             @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile)
    176             throws RemoteException {
    177         checkRecoverKeyStorePermission();
    178         int userId = UserHandle.getCallingUserId();
    179         int uid = Binder.getCallingUid();
    180 
    181         rootCertificateAlias
    182                 = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
    183         if (!mTestCertHelper.isValidRootCertificateAlias(rootCertificateAlias)) {
    184             throw new ServiceSpecificException(
    185                     ERROR_INVALID_CERTIFICATE, "Invalid root certificate alias");
    186         }
    187         // Always set active alias to the argument of the last call to initRecoveryService method,
    188         // even if cert file is incorrect.
    189         String activeRootAlias = mDatabase.getActiveRootOfTrust(userId, uid);
    190         if (activeRootAlias == null) {
    191             Log.d(TAG, "Root of trust for recovery agent + " + uid
    192                 + " is assigned for the first time to " + rootCertificateAlias);
    193         } else if (!activeRootAlias.equals(rootCertificateAlias)) {
    194             Log.i(TAG, "Root of trust for recovery agent " + uid + " is changed to "
    195                     + rootCertificateAlias + " from  " + activeRootAlias);
    196         }
    197         long updatedRows = mDatabase.setActiveRootOfTrust(userId, uid, rootCertificateAlias);
    198         if (updatedRows < 0) {
    199             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
    200                     "Failed to set the root of trust in the local DB.");
    201         }
    202 
    203         CertXml certXml;
    204         try {
    205             certXml = CertXml.parse(recoveryServiceCertFile);
    206         } catch (CertParsingException e) {
    207             Log.d(TAG, "Failed to parse the input as a cert file: " + HexDump.toHexString(
    208                     recoveryServiceCertFile));
    209             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
    210         }
    211 
    212         // Check serial number
    213         long newSerial = certXml.getSerial();
    214         Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid, rootCertificateAlias);
    215         if (oldSerial != null && oldSerial >= newSerial
    216                 && !mTestCertHelper.isTestOnlyCertificateAlias(rootCertificateAlias)) {
    217             if (oldSerial == newSerial) {
    218                 Log.i(TAG, "The cert file serial number is the same, so skip updating.");
    219             } else {
    220                 Log.e(TAG, "The cert file serial number is older than the one in database.");
    221                 throw new ServiceSpecificException(ERROR_DOWNGRADE_CERTIFICATE,
    222                         "The cert file serial number is older than the one in database.");
    223             }
    224             return;
    225         }
    226         Log.i(TAG, "Updating the certificate with the new serial number " + newSerial);
    227 
    228         // Randomly choose and validate an endpoint certificate from the list
    229         CertPath certPath;
    230         X509Certificate rootCert =
    231                 mTestCertHelper.getRootCertificate(rootCertificateAlias);
    232         try {
    233             Log.d(TAG, "Getting and validating a random endpoint certificate");
    234             certPath = certXml.getRandomEndpointCert(rootCert);
    235         } catch (CertValidationException e) {
    236             Log.e(TAG, "Invalid endpoint cert", e);
    237             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
    238         }
    239 
    240         // Save the chosen and validated certificate into database
    241         try {
    242             Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
    243             long updatedCertPathRows = mDatabase.setRecoveryServiceCertPath(userId, uid,
    244                     rootCertificateAlias, certPath);
    245             if (updatedCertPathRows > 0) {
    246                 long updatedCertSerialRows = mDatabase.setRecoveryServiceCertSerial(userId, uid,
    247                         rootCertificateAlias, newSerial);
    248                 if (updatedCertSerialRows < 0) {
    249                     // Ideally CertPath and CertSerial should be updated together in single
    250                     // transaction, but since their mismatch doesn't create many problems
    251                     // extra complexity is unnecessary.
    252                     throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
    253                         "Failed to set the certificate serial number in the local DB.");
    254                 }
    255                 if (mDatabase.getSnapshotVersion(userId, uid) != null) {
    256                     mDatabase.setShouldCreateSnapshot(userId, uid, true);
    257                     Log.i(TAG, "This is a certificate change. Snapshot must be updated");
    258                 } else {
    259                     Log.i(TAG, "This is a certificate change. Snapshot didn't exist");
    260                 }
    261                 long updatedCounterIdRows =
    262                         mDatabase.setCounterId(userId, uid, new SecureRandom().nextLong());
    263                 if (updatedCounterIdRows < 0) {
    264                     Log.e(TAG, "Failed to set the counter id in the local DB.");
    265                 }
    266             } else if (updatedCertPathRows < 0) {
    267                 throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
    268                         "Failed to set the certificate path in the local DB.");
    269             }
    270         } catch (CertificateEncodingException e) {
    271             Log.e(TAG, "Failed to encode CertPath", e);
    272             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
    273         }
    274     }
    275 
    276     /**
    277      * Initializes the recovery service with the two files {@code recoveryServiceCertFile} and
    278      * {@code recoveryServiceSigFile}.
    279      *
    280      * @param rootCertificateAlias the alias for the root certificate that is used for validating
    281      *     the recovery service certificates.
    282      * @param recoveryServiceCertFile the content of the XML file containing a list of certificates
    283      *     for the recovery service.
    284      * @param recoveryServiceSigFile the content of the XML file containing the public-key signature
    285      *     over the entire content of {@code recoveryServiceCertFile}.
    286      */
    287     public void initRecoveryServiceWithSigFile(
    288             @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile,
    289             @NonNull byte[] recoveryServiceSigFile)
    290             throws RemoteException {
    291         checkRecoverKeyStorePermission();
    292         rootCertificateAlias =
    293                 mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
    294         Preconditions.checkNotNull(recoveryServiceCertFile, "recoveryServiceCertFile is null");
    295         Preconditions.checkNotNull(recoveryServiceSigFile, "recoveryServiceSigFile is null");
    296 
    297         SigXml sigXml;
    298         try {
    299             sigXml = SigXml.parse(recoveryServiceSigFile);
    300         } catch (CertParsingException e) {
    301             Log.d(TAG, "Failed to parse the sig file: " + HexDump.toHexString(
    302                     recoveryServiceSigFile));
    303             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
    304         }
    305 
    306         X509Certificate rootCert =
    307                 mTestCertHelper.getRootCertificate(rootCertificateAlias);
    308         try {
    309             sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile);
    310         } catch (CertValidationException e) {
    311             Log.d(TAG, "The signature over the cert file is invalid."
    312                     + " Cert: " + HexDump.toHexString(recoveryServiceCertFile)
    313                     + " Sig: " + HexDump.toHexString(recoveryServiceSigFile));
    314             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
    315         }
    316 
    317         initRecoveryService(rootCertificateAlias, recoveryServiceCertFile);
    318     }
    319 
    320     /**
    321      * Gets all data necessary to recover application keys on new device.
    322      *
    323      * @return KeyChain Snapshot.
    324      * @throws ServiceSpecificException if no snapshot is pending.
    325      * @hide
    326      */
    327     public @NonNull KeyChainSnapshot getKeyChainSnapshot()
    328             throws RemoteException {
    329         checkRecoverKeyStorePermission();
    330         int uid = Binder.getCallingUid();
    331         KeyChainSnapshot snapshot = mSnapshotStorage.get(uid);
    332         if (snapshot == null) {
    333             throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
    334         }
    335         return snapshot;
    336     }
    337 
    338     public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
    339             throws RemoteException {
    340         checkRecoverKeyStorePermission();
    341         int uid = Binder.getCallingUid();
    342         mListenersStorage.setSnapshotListener(uid, intent);
    343     }
    344 
    345     /**
    346      * Set the server params for the user's key chain. This is used to uniquely identify a key
    347      * chain. Along with the counter ID, it is used to uniquely identify an instance of a vault.
    348      */
    349     public void setServerParams(@NonNull byte[] serverParams) throws RemoteException {
    350         checkRecoverKeyStorePermission();
    351         int userId = UserHandle.getCallingUserId();
    352         int uid = Binder.getCallingUid();
    353 
    354         byte[] currentServerParams = mDatabase.getServerParams(userId, uid);
    355 
    356         if (Arrays.equals(serverParams, currentServerParams)) {
    357             Log.v(TAG, "Not updating server params - same as old value.");
    358             return;
    359         }
    360 
    361         long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
    362         if (updatedRows < 0) {
    363             throw new ServiceSpecificException(
    364                     ERROR_SERVICE_INTERNAL_ERROR, "Database failure trying to set server params.");
    365         }
    366 
    367         if (currentServerParams == null) {
    368             Log.i(TAG, "Initialized server params.");
    369             return;
    370         }
    371 
    372         if (mDatabase.getSnapshotVersion(userId, uid) != null) {
    373             mDatabase.setShouldCreateSnapshot(userId, uid, true);
    374             Log.i(TAG, "Updated server params. Snapshot must be updated");
    375         } else {
    376             Log.i(TAG, "Updated server params. Snapshot didn't exist");
    377         }
    378     }
    379 
    380     /**
    381      * Sets the recovery status of key with {@code alias} to {@code status}.
    382      */
    383     public void setRecoveryStatus(@NonNull String alias, int status) throws RemoteException {
    384         checkRecoverKeyStorePermission();
    385         Preconditions.checkNotNull(alias, "alias is null");
    386         long updatedRows = mDatabase.setRecoveryStatus(Binder.getCallingUid(), alias, status);
    387         if (updatedRows < 0) {
    388             throw new ServiceSpecificException(
    389                     ERROR_SERVICE_INTERNAL_ERROR,
    390                     "Failed to set the key recovery status in the local DB.");
    391         }
    392     }
    393 
    394     /**
    395      * Returns recovery statuses for all keys belonging to the calling uid.
    396      *
    397      * @return {@link Map} from key alias to recovery status. Recovery status is one of
    398      *     {@link RecoveryController#RECOVERY_STATUS_SYNCED},
    399      *     {@link RecoveryController#RECOVERY_STATUS_SYNC_IN_PROGRESS} or
    400      *     {@link RecoveryController#RECOVERY_STATUS_PERMANENT_FAILURE}.
    401      */
    402     public @NonNull Map<String, Integer> getRecoveryStatus() throws RemoteException {
    403         checkRecoverKeyStorePermission();
    404         return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
    405     }
    406 
    407     /**
    408      * Sets recovery secrets list used by all recovery agents for given {@code userId}
    409      *
    410      * @hide
    411      */
    412     public void setRecoverySecretTypes(
    413             @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
    414             throws RemoteException {
    415         checkRecoverKeyStorePermission();
    416         Preconditions.checkNotNull(secretTypes, "secretTypes is null");
    417         int userId = UserHandle.getCallingUserId();
    418         int uid = Binder.getCallingUid();
    419 
    420         int[] currentSecretTypes = mDatabase.getRecoverySecretTypes(userId, uid);
    421         if (Arrays.equals(secretTypes, currentSecretTypes)) {
    422             Log.v(TAG, "Not updating secret types - same as old value.");
    423             return;
    424         }
    425 
    426         long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes);
    427         if (updatedRows < 0) {
    428             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR,
    429                     "Database error trying to set secret types.");
    430         }
    431 
    432         if (currentSecretTypes.length == 0) {
    433             Log.i(TAG, "Initialized secret types.");
    434             return;
    435         }
    436 
    437         Log.i(TAG, "Updated secret types. Snapshot pending.");
    438         if (mDatabase.getSnapshotVersion(userId, uid) != null) {
    439             mDatabase.setShouldCreateSnapshot(userId, uid, true);
    440             Log.i(TAG, "Updated secret types. Snapshot must be updated");
    441         } else {
    442             Log.i(TAG, "Updated secret types. Snapshot didn't exist");
    443         }
    444     }
    445 
    446     /**
    447      * Gets secret types necessary to create Recovery Data.
    448      *
    449      * @return secret types
    450      * @hide
    451      */
    452     public @NonNull int[] getRecoverySecretTypes() throws RemoteException {
    453         checkRecoverKeyStorePermission();
    454         return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
    455             Binder.getCallingUid());
    456     }
    457 
    458     /**
    459      * Initializes recovery session given the X509-encoded public key of the recovery service.
    460      *
    461      * @param sessionId A unique ID to identify the recovery session.
    462      * @param verifierPublicKey X509-encoded public key.
    463      * @param vaultParams Additional params associated with vault.
    464      * @param vaultChallenge Challenge issued by vault service.
    465      * @param secrets Lock-screen hashes. For now only a single secret is supported.
    466      * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
    467      * @deprecated Use {@link #startRecoverySessionWithCertPath(String, String, RecoveryCertPath,
    468      *         byte[], byte[], List)} instead.
    469      *
    470      * @hide
    471      */
    472     @VisibleForTesting
    473     @NonNull byte[] startRecoverySession(
    474             @NonNull String sessionId,
    475             @NonNull byte[] verifierPublicKey,
    476             @NonNull byte[] vaultParams,
    477             @NonNull byte[] vaultChallenge,
    478             @NonNull List<KeyChainProtectionParams> secrets)
    479             throws RemoteException {
    480         checkRecoverKeyStorePermission();
    481         int uid = Binder.getCallingUid();
    482 
    483         if (secrets.size() != 1) {
    484             throw new UnsupportedOperationException(
    485                     "Only a single KeyChainProtectionParams is supported");
    486         }
    487 
    488         PublicKey publicKey;
    489         try {
    490             publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
    491         } catch (InvalidKeySpecException e) {
    492             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
    493         }
    494         // The raw public key bytes contained in vaultParams must match the ones given in
    495         // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned
    496         // by the original recovery service.
    497         if (!publicKeysMatch(publicKey, vaultParams)) {
    498             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE,
    499                     "The public keys given in verifierPublicKey and vaultParams do not match.");
    500         }
    501 
    502         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
    503         byte[] kfHash = secrets.get(0).getSecret();
    504         mRecoverySessionStorage.add(
    505                 uid,
    506                 new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant, vaultParams));
    507 
    508         Log.i(TAG, "Received VaultParams for recovery: " + HexDump.toHexString(vaultParams));
    509         try {
    510             byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
    511             return KeySyncUtils.encryptRecoveryClaim(
    512                     publicKey,
    513                     vaultParams,
    514                     vaultChallenge,
    515                     thmKfHash,
    516                     keyClaimant);
    517         } catch (NoSuchAlgorithmException e) {
    518             Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e);
    519             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    520         } catch (InvalidKeyException e) {
    521             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
    522         }
    523     }
    524 
    525     /**
    526      * Initializes recovery session given the certificate path of the recovery service.
    527      *
    528      * @param sessionId A unique ID to identify the recovery session.
    529      * @param verifierCertPath The certificate path of the recovery service.
    530      * @param vaultParams Additional params associated with vault.
    531      * @param vaultChallenge Challenge issued by vault service.
    532      * @param secrets Lock-screen hashes. For now only a single secret is supported.
    533      * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
    534      *
    535      * @hide
    536      */
    537     public @NonNull byte[] startRecoverySessionWithCertPath(
    538             @NonNull String sessionId,
    539             @NonNull String rootCertificateAlias,
    540             @NonNull RecoveryCertPath verifierCertPath,
    541             @NonNull byte[] vaultParams,
    542             @NonNull byte[] vaultChallenge,
    543             @NonNull List<KeyChainProtectionParams> secrets)
    544             throws RemoteException {
    545         checkRecoverKeyStorePermission();
    546         rootCertificateAlias =
    547                 mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
    548         Preconditions.checkNotNull(sessionId, "invalid session");
    549         Preconditions.checkNotNull(verifierCertPath, "verifierCertPath is null");
    550         Preconditions.checkNotNull(vaultParams, "vaultParams is null");
    551         Preconditions.checkNotNull(vaultChallenge, "vaultChallenge is null");
    552         Preconditions.checkNotNull(secrets, "secrets is null");
    553         CertPath certPath;
    554         try {
    555             certPath = verifierCertPath.getCertPath();
    556         } catch (CertificateException e) {
    557             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
    558         }
    559 
    560         try {
    561             CertUtils.validateCertPath(
    562                     mTestCertHelper.getRootCertificate(rootCertificateAlias), certPath);
    563         } catch (CertValidationException e) {
    564             Log.e(TAG, "Failed to validate the given cert path", e);
    565             throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
    566         }
    567 
    568         byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded();
    569         if (verifierPublicKey == null) {
    570             Log.e(TAG, "Failed to encode verifierPublicKey");
    571             throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
    572                     "Failed to encode verifierPublicKey");
    573         }
    574 
    575         return startRecoverySession(
    576                 sessionId, verifierPublicKey, vaultParams, vaultChallenge, secrets);
    577     }
    578 
    579     /**
    580      * Invoked by a recovery agent after a successful recovery claim is sent to the remote vault
    581      * service.
    582      *
    583      * @param sessionId The session ID used to generate the claim. See
    584      *     {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
    585      * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault
    586      *     service.
    587      * @param applicationKeys The encrypted key blobs returned by the remote vault service. These
    588      *     were wrapped with the recovery key.
    589      * @throws RemoteException if an error occurred recovering the keys.
    590      */
    591     public @NonNull Map<String, String> recoverKeyChainSnapshot(
    592             @NonNull String sessionId,
    593             @NonNull byte[] encryptedRecoveryKey,
    594             @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
    595         checkRecoverKeyStorePermission();
    596         int userId = UserHandle.getCallingUserId();
    597         int uid = Binder.getCallingUid();
    598         RecoverySessionStorage.Entry sessionEntry = mRecoverySessionStorage.get(uid, sessionId);
    599         if (sessionEntry == null) {
    600             throw new ServiceSpecificException(ERROR_SESSION_EXPIRED,
    601                     String.format(Locale.US,
    602                             "Application uid=%d does not have pending session '%s'",
    603                             uid,
    604                             sessionId));
    605         }
    606 
    607         try {
    608             byte[] recoveryKey = decryptRecoveryKey(sessionEntry, encryptedRecoveryKey);
    609             Map<String, byte[]> keysByAlias = recoverApplicationKeys(recoveryKey, applicationKeys);
    610             return importKeyMaterials(userId, uid, keysByAlias);
    611         } catch (KeyStoreException e) {
    612             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    613         } finally {
    614             sessionEntry.destroy();
    615             mRecoverySessionStorage.remove(uid);
    616         }
    617     }
    618 
    619     /**
    620      * Imports the key materials, returning a map from alias to grant alias for the calling user.
    621      *
    622      * @param userId The calling user ID.
    623      * @param uid The calling uid.
    624      * @param keysByAlias The key materials, keyed by alias.
    625      * @throws KeyStoreException if an error occurs importing the key or getting the grant.
    626      */
    627     private @NonNull Map<String, String> importKeyMaterials(
    628             int userId, int uid, Map<String, byte[]> keysByAlias) throws KeyStoreException {
    629         ArrayMap<String, String> grantAliasesByAlias = new ArrayMap<>(keysByAlias.size());
    630         for (String alias : keysByAlias.keySet()) {
    631             mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keysByAlias.get(alias));
    632             String grantAlias = getAlias(userId, uid, alias);
    633             Log.i(TAG, String.format(Locale.US, "Import %s -> %s", alias, grantAlias));
    634             grantAliasesByAlias.put(alias, grantAlias);
    635         }
    636         return grantAliasesByAlias;
    637     }
    638 
    639     /**
    640      * Returns an alias for the key.
    641      *
    642      * @param userId The user ID of the calling process.
    643      * @param uid The uid of the calling process.
    644      * @param alias The alias of the key.
    645      * @return The alias in the calling process's keystore.
    646      */
    647     private @Nullable String getAlias(int userId, int uid, String alias) {
    648         return mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
    649     }
    650 
    651     /**
    652      * Destroys the session with the given {@code sessionId}.
    653      */
    654     public void closeSession(@NonNull String sessionId) throws RemoteException {
    655         checkRecoverKeyStorePermission();
    656         Preconditions.checkNotNull(sessionId, "invalid session");
    657         mRecoverySessionStorage.remove(Binder.getCallingUid(), sessionId);
    658     }
    659 
    660     public void removeKey(@NonNull String alias) throws RemoteException {
    661         checkRecoverKeyStorePermission();
    662         Preconditions.checkNotNull(alias, "alias is null");
    663         int uid = Binder.getCallingUid();
    664         int userId = UserHandle.getCallingUserId();
    665 
    666         boolean wasRemoved = mDatabase.removeKey(uid, alias);
    667         if (wasRemoved) {
    668             mDatabase.setShouldCreateSnapshot(userId, uid, true);
    669             mApplicationKeyStorage.deleteEntry(userId, uid, alias);
    670         }
    671     }
    672 
    673     /**
    674      * Generates a key named {@code alias} in caller's namespace.
    675      * The key is stored in system service keystore namespace.
    676      *
    677      * @return grant alias, which caller can use to access the key.
    678      */
    679     public String generateKey(@NonNull String alias) throws RemoteException {
    680         checkRecoverKeyStorePermission();
    681         Preconditions.checkNotNull(alias, "alias is null");
    682         int uid = Binder.getCallingUid();
    683         int userId = UserHandle.getCallingUserId();
    684 
    685         PlatformEncryptionKey encryptionKey;
    686         try {
    687             encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
    688         } catch (NoSuchAlgorithmException e) {
    689             // Impossible: all algorithms must be supported by AOSP
    690             throw new RuntimeException(e);
    691         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
    692             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    693         } catch (InsecureUserException e) {
    694             throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
    695         }
    696 
    697         try {
    698             byte[] secretKey =
    699                     mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
    700             mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey);
    701             return getAlias(userId, uid, alias);
    702         } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
    703             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    704         }
    705     }
    706 
    707     /**
    708      * Imports a 256-bit AES-GCM key named {@code alias}. The key is stored in system service
    709      * keystore namespace.
    710      *
    711      * @param alias the alias provided by caller as a reference to the key.
    712      * @param keyBytes the raw bytes of the 256-bit AES key.
    713      * @return grant alias, which caller can use to access the key.
    714      * @throws RemoteException if the given key is invalid or some internal errors occur.
    715      *
    716      * @hide
    717      */
    718     public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes)
    719             throws RemoteException {
    720         checkRecoverKeyStorePermission();
    721         Preconditions.checkNotNull(alias, "alias is null");
    722         Preconditions.checkNotNull(keyBytes, "keyBytes is null");
    723         if (keyBytes.length != RecoverableKeyGenerator.KEY_SIZE_BITS / Byte.SIZE) {
    724             Log.e(TAG, "The given key for import doesn't have the required length "
    725                     + RecoverableKeyGenerator.KEY_SIZE_BITS);
    726             throw new ServiceSpecificException(ERROR_INVALID_KEY_FORMAT,
    727                     "The given key does not contain " + RecoverableKeyGenerator.KEY_SIZE_BITS
    728                             + " bits.");
    729         }
    730 
    731         int uid = Binder.getCallingUid();
    732         int userId = UserHandle.getCallingUserId();
    733 
    734         PlatformEncryptionKey encryptionKey;
    735         try {
    736             encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
    737         } catch (NoSuchAlgorithmException e) {
    738             // Impossible: all algorithms must be supported by AOSP
    739             throw new RuntimeException(e);
    740         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
    741             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    742         } catch (InsecureUserException e) {
    743             throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
    744         }
    745 
    746         try {
    747             // Wrap the key by the platform key and store the wrapped key locally
    748             mRecoverableKeyGenerator.importKey(encryptionKey, userId, uid, alias, keyBytes);
    749 
    750             // Import the key to Android KeyStore and get grant
    751             mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, keyBytes);
    752             return getAlias(userId, uid, alias);
    753         } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
    754             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    755         }
    756     }
    757 
    758     /**
    759      * Gets a key named {@code alias} in caller's namespace.
    760      *
    761      * @return grant alias, which caller can use to access the key.
    762      */
    763     public @Nullable String getKey(@NonNull String alias) throws RemoteException {
    764         checkRecoverKeyStorePermission();
    765         Preconditions.checkNotNull(alias, "alias is null");
    766         int uid = Binder.getCallingUid();
    767         int userId = UserHandle.getCallingUserId();
    768         return getAlias(userId, uid, alias);
    769     }
    770 
    771     private byte[] decryptRecoveryKey(
    772             RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
    773             throws RemoteException, ServiceSpecificException {
    774         byte[] locallyEncryptedKey;
    775         try {
    776             locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
    777                     sessionEntry.getKeyClaimant(),
    778                     sessionEntry.getVaultParams(),
    779                     encryptedClaimResponse);
    780         } catch (InvalidKeyException e) {
    781             Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e);
    782             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
    783                     "Failed to decrypt recovery key " + e.getMessage());
    784         } catch (AEADBadTagException e) {
    785             Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e);
    786             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
    787                     "Failed to decrypt recovery key " + e.getMessage());
    788         } catch (NoSuchAlgorithmException e) {
    789             // Should never happen: all the algorithms used are required by AOSP implementations
    790             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    791         }
    792 
    793         try {
    794             return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
    795         } catch (InvalidKeyException e) {
    796             Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
    797             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
    798                     "Failed to decrypt recovery key " + e.getMessage());
    799         } catch (AEADBadTagException e) {
    800             Log.e(TAG, "Got AEADBadTagException during decrypting recovery key", e);
    801             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
    802                     "Failed to decrypt recovery key " + e.getMessage());
    803         } catch (NoSuchAlgorithmException e) {
    804             // Should never happen: all the algorithms used are required by AOSP implementations
    805             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    806         }
    807     }
    808 
    809     /**
    810      * Uses {@code recoveryKey} to decrypt {@code applicationKeys}.
    811      *
    812      * @return Map from alias to raw key material.
    813      * @throws RemoteException if an error occurred decrypting the keys.
    814      */
    815     private @NonNull Map<String, byte[]> recoverApplicationKeys(
    816             @NonNull byte[] recoveryKey,
    817             @NonNull List<WrappedApplicationKey> applicationKeys) throws RemoteException {
    818         HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
    819         for (WrappedApplicationKey applicationKey : applicationKeys) {
    820             String alias = applicationKey.getAlias();
    821             byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
    822 
    823             try {
    824                 byte[] keyMaterial =
    825                         KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
    826                 keyMaterialByAlias.put(alias, keyMaterial);
    827             } catch (NoSuchAlgorithmException e) {
    828                 Log.wtf(TAG, "Missing SecureBox algorithm. AOSP required to support this.", e);
    829                 throw new ServiceSpecificException(
    830                         ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
    831             } catch (InvalidKeyException e) {
    832                 Log.e(TAG, "Got InvalidKeyException during decrypting application key with alias: "
    833                         + alias, e);
    834                 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
    835                         "Failed to recover key with alias '" + alias + "': " + e.getMessage());
    836             } catch (AEADBadTagException e) {
    837                 Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: "
    838                         + alias, e);
    839                 // Ignore the exception to continue to recover the other application keys.
    840             }
    841         }
    842         if (!applicationKeys.isEmpty() && keyMaterialByAlias.isEmpty()) {
    843             Log.e(TAG, "Failed to recover any of the application keys.");
    844             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
    845                     "Failed to recover any of the application keys.");
    846         }
    847         return keyMaterialByAlias;
    848     }
    849 
    850     /**
    851      * This function can only be used inside LockSettingsService.
    852      *
    853      * @param storedHashType from {@code CredentialHash}
    854      * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
    855      *     mPasswordMaxLength}
    856      * @param userId for user who just unlocked the device.
    857      * @hide
    858      */
    859     public void lockScreenSecretAvailable(
    860             int storedHashType, @NonNull String credential, int userId) {
    861         // So as not to block the critical path unlocking the phone, defer to another thread.
    862         try {
    863             mExecutorService.execute(KeySyncTask.newInstance(
    864                     mContext,
    865                     mDatabase,
    866                     mSnapshotStorage,
    867                     mListenersStorage,
    868                     userId,
    869                     storedHashType,
    870                     credential,
    871                     /*credentialUpdated=*/ false));
    872         } catch (NoSuchAlgorithmException e) {
    873             Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
    874         } catch (KeyStoreException e) {
    875             Log.e(TAG, "Key store error encountered during recoverable key sync", e);
    876         } catch (InsecureUserException e) {
    877             Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
    878         }
    879     }
    880 
    881     /**
    882      * This function can only be used inside LockSettingsService.
    883      *
    884      * @param storedHashType from {@code CredentialHash}
    885      * @param credential - unencrypted String
    886      * @param userId for the user whose lock screen credentials were changed.
    887      * @hide
    888      */
    889     public void lockScreenSecretChanged(
    890             int storedHashType,
    891             @Nullable String credential,
    892             int userId) {
    893         // So as not to block the critical path unlocking the phone, defer to another thread.
    894         try {
    895             mExecutorService.execute(KeySyncTask.newInstance(
    896                     mContext,
    897                     mDatabase,
    898                     mSnapshotStorage,
    899                     mListenersStorage,
    900                     userId,
    901                     storedHashType,
    902                     credential,
    903                     /*credentialUpdated=*/ true));
    904         } catch (NoSuchAlgorithmException e) {
    905             Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
    906         } catch (KeyStoreException e) {
    907             Log.e(TAG, "Key store error encountered during recoverable key sync", e);
    908         } catch (InsecureUserException e) {
    909             Log.e(TAG, "InsecureUserException during lock screen secret update", e);
    910         }
    911     }
    912 
    913     private void checkRecoverKeyStorePermission() {
    914         mContext.enforceCallingOrSelfPermission(
    915                 Manifest.permission.RECOVER_KEYSTORE,
    916                 "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
    917     }
    918 
    919     private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
    920         byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey);
    921         return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length));
    922     }
    923 }
    924