Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 63e027d0 authored by Dmitry Dementyev's avatar Dmitry Dementyev Committed by Android (Google) Code Review
Browse files

Merge "Update RecoveryController to use KeyStore grant API."

parents 98b136c9 29b9de5b
Loading
Loading
Loading
Loading
+75 −3
Original line number Diff line number Diff line
@@ -26,9 +26,13 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.security.KeyStore;
import android.security.keystore.AndroidKeyStoreProvider;

import com.android.internal.widget.ILockSettings;

import java.security.Key;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
@@ -113,9 +117,11 @@ public class RecoveryController {


    private final ILockSettings mBinder;
    private final KeyStore mKeyStore;

    private RecoveryController(ILockSettings binder) {
    private RecoveryController(ILockSettings binder, KeyStore keystore) {
        mBinder = binder;
        mKeyStore = keystore;
    }

    /**
@@ -133,7 +139,7 @@ public class RecoveryController {
    public static RecoveryController getInstance(Context context) {
        ILockSettings lockSettings =
                ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
        return new RecoveryController(lockSettings);
        return new RecoveryController(lockSettings, KeyStore.getInstance());
    }

    /**
@@ -430,6 +436,7 @@ public class RecoveryController {
    }

    /**
     * Deprecated.
     * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable
     * key store. Returns the raw material of the key.
     *
@@ -444,7 +451,6 @@ public class RecoveryController {
    public byte[] generateAndStoreKey(@NonNull String alias, byte[] account)
            throws InternalRecoveryServiceException, LockScreenRequiredException {
        try {
            // TODO: add account
            return mBinder.generateAndStoreKey(alias);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
@@ -456,6 +462,72 @@ public class RecoveryController {
        }
    }

    /**
     * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable
     * key store. Returns {@link javax.crypto.SecretKey}.
     *
     * @param alias The key alias.
     * @param account The account associated with the key.
     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
     *     service.
     * @throws LockScreenRequiredException if the user has not set a lock screen. This is required
     *     to generate recoverable keys, as the snapshots are encrypted using a key derived from the
     *     lock screen.
     * @hide
     */
    public Key generateKey(@NonNull String alias, byte[] account)
            throws InternalRecoveryServiceException, LockScreenRequiredException {
        // TODO: update RecoverySession.recoverKeys
        try {
            String grantAlias = mBinder.generateKey(alias, account);
            if (grantAlias == null) {
                return null;
            }
            Key result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
                    mKeyStore,
                    grantAlias,
                    KeyStore.UID_SELF);
            return result;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (UnrecoverableKeyException e) {
            throw new InternalRecoveryServiceException("Access to newly generated key failed for");
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ERROR_INSECURE_USER) {
                throw new LockScreenRequiredException(e.getMessage());
            }
            throw wrapUnexpectedServiceSpecificException(e);
        }
    }

    /**
     * Gets a key called {@code alias} from the recoverable key store.
     *
     * @param alias The key alias.
     * @return The key.
     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
     *     service.
     * @throws UnrecoverableKeyException if key is permanently invalidated or not found.
     * @hide
     */
    public @Nullable Key getKey(@NonNull String alias)
            throws InternalRecoveryServiceException, UnrecoverableKeyException {
        try {
            String grantAlias = mBinder.getKey(alias);
            if (grantAlias == null) {
                return null;
            }
            return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
                    mKeyStore,
                    grantAlias,
                    KeyStore.UID_SELF);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (ServiceSpecificException e) {
            throw wrapUnexpectedServiceSpecificException(e);
        }
    }

    /**
     * Removes a key called {@code alias} from the recoverable key store.
     *
+2 −0
Original line number Diff line number Diff line
@@ -66,6 +66,8 @@ interface ILockSettings {
    void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList);
    KeyChainSnapshot getKeyChainSnapshot();
    byte[] generateAndStoreKey(String alias);
    String generateKey(String alias, in byte[] account);
    String getKey(String alias);
    void removeKey(String alias);
    void setSnapshotCreatedPendingIntent(in PendingIntent intent);
    Map getRecoverySnapshotVersions();
+13 −3
Original line number Diff line number Diff line
@@ -383,8 +383,8 @@ public class LockSettingsService extends ILockSettings.Stub {
            return KeyStore.getInstance();
        }

        public RecoverableKeyStoreManager getRecoverableKeyStoreManager() {
            return RecoverableKeyStoreManager.getInstance(mContext);
        public RecoverableKeyStoreManager getRecoverableKeyStoreManager(KeyStore keyStore) {
            return RecoverableKeyStoreManager.getInstance(mContext, keyStore);
        }

        public IStorageManager getStorageManager() {
@@ -413,7 +413,7 @@ public class LockSettingsService extends ILockSettings.Stub {
        mInjector = injector;
        mContext = injector.getContext();
        mKeyStore = injector.getKeyStore();
        mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
        mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager(mKeyStore);
        mHandler = injector.getHandler();
        mStrongAuth = injector.getStrongAuth();
        mActivityManager = injector.getActivityManager();
@@ -2064,6 +2064,16 @@ public class LockSettingsService extends ILockSettings.Stub {
        return mRecoverableKeyStoreManager.generateAndStoreKey(alias);
    }

    @Override
    public String generateKey(@NonNull String alias, byte[] account) throws RemoteException {
        return mRecoverableKeyStoreManager.generateKey(alias, account);
    }

    @Override
    public String getKey(@NonNull String alias) throws RemoteException {
        return mRecoverableKeyStoreManager.getKey(alias);
    }

    private static final String[] VALID_SETTINGS = new String[] {
            LockPatternUtils.LOCKOUT_PERMANENT_KEY,
            LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
+21 −0
Original line number Diff line number Diff line
@@ -16,10 +16,13 @@

package com.android.server.locksettings.recoverablekeystore;

import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;

/**
@@ -27,6 +30,7 @@ import java.security.UnrecoverableKeyException;
 */
public class KeyStoreProxyImpl implements KeyStoreProxy {

    private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
    private final KeyStore mKeyStore;

    /**
@@ -57,4 +61,21 @@ public class KeyStoreProxyImpl implements KeyStoreProxy {
    public void deleteEntry(String alias) throws KeyStoreException {
        mKeyStore.deleteEntry(alias);
    }

    /**
     * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked
     * {@link KeyStore#load(KeyStore.LoadStoreParameter)}.
     *
     * @throws KeyStoreException if there was a problem getting or initializing the key store.
     */
    public static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException {
        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
        try {
            keyStore.load(/*param=*/ null);
        } catch (CertificateException | IOException | NoSuchAlgorithmException e) {
            // Should never happen.
            throw new KeyStoreException("Unable to load keystore.", e);
        }
        return keyStore;
    }
}
+65 −5
Original line number Diff line number Diff line
@@ -37,21 +37,23 @@ import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryController;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.KeyStore;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;

import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.KeyFactory;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.spec.InvalidKeySpecException;
import java.security.UnrecoverableKeyException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
@@ -82,18 +84,23 @@ public class RecoverableKeyStoreManager {
    private final RecoverableKeyGenerator mRecoverableKeyGenerator;
    private final RecoverySnapshotStorage mSnapshotStorage;
    private final PlatformKeyManager mPlatformKeyManager;
    private final KeyStore mKeyStore;
    private final ApplicationKeyStorage mApplicationKeyStorage;

    /**
     * Returns a new or existing instance.
     *
     * @hide
     */
    public static synchronized RecoverableKeyStoreManager getInstance(Context context) {
    public static synchronized RecoverableKeyStoreManager
            getInstance(Context context, KeyStore keystore) {
        if (mInstance == null) {
            RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
            PlatformKeyManager platformKeyManager;
            ApplicationKeyStorage applicationKeyStorage;
            try {
                platformKeyManager = PlatformKeyManager.getInstance(context, db);
                applicationKeyStorage = ApplicationKeyStorage.getInstance(keystore);
            } catch (NoSuchAlgorithmException e) {
                // Impossible: all algorithms must be supported by AOSP
                throw new RuntimeException(e);
@@ -103,12 +110,14 @@ public class RecoverableKeyStoreManager {

            mInstance = new RecoverableKeyStoreManager(
                    context.getApplicationContext(),
                    keystore,
                    db,
                    new RecoverySessionStorage(),
                    Executors.newSingleThreadExecutor(),
                    new RecoverySnapshotStorage(),
                    new RecoverySnapshotListenersStorage(),
                    platformKeyManager);
                    platformKeyManager,
                    applicationKeyStorage);
        }
        return mInstance;
    }
@@ -116,19 +125,23 @@ public class RecoverableKeyStoreManager {
    @VisibleForTesting
    RecoverableKeyStoreManager(
            Context context,
            KeyStore keystore,
            RecoverableKeyStoreDb recoverableKeyStoreDb,
            RecoverySessionStorage recoverySessionStorage,
            ExecutorService executorService,
            RecoverySnapshotStorage snapshotStorage,
            RecoverySnapshotListenersStorage listenersStorage,
            PlatformKeyManager platformKeyManager) {
            PlatformKeyManager platformKeyManager,
            ApplicationKeyStorage applicationKeyStorage) {
        mContext = context;
        mKeyStore = keystore;
        mDatabase = recoverableKeyStoreDb;
        mRecoverySessionStorage = recoverySessionStorage;
        mExecutorService = executorService;
        mListenersStorage = listenersStorage;
        mSnapshotStorage = snapshotStorage;
        mPlatformKeyManager = platformKeyManager;
        mApplicationKeyStorage = applicationKeyStorage;

        try {
            mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
@@ -406,6 +419,7 @@ public class RecoverableKeyStoreManager {
    }

    /**
     * Deprecated
     * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
     * returns the raw key material.
     *
@@ -450,7 +464,53 @@ public class RecoverableKeyStoreManager {
        boolean wasRemoved = mDatabase.removeKey(uid, alias);
        if (wasRemoved) {
            mDatabase.setShouldCreateSnapshot(userId, uid, true);
            mApplicationKeyStorage.deleteEntry(userId, uid, alias);
        }
    }

    /**
     * Generates a key named {@code alias} in caller's namespace.
     * The key is stored in system service keystore namespace.
     *
     * @return grant alias, which caller can use to access the key.
     */
    public String generateKey(@NonNull String alias, byte[] account) throws RemoteException {
        int uid = Binder.getCallingUid();
        int userId = UserHandle.getCallingUserId();

        PlatformEncryptionKey encryptionKey;
        try {
            encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
        } catch (NoSuchAlgorithmException e) {
            // Impossible: all algorithms must be supported by AOSP
            throw new RuntimeException(e);
        } catch (KeyStoreException | UnrecoverableKeyException e) {
            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
        } catch (InsecureUserException e) {
            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
        }

        try {
            byte[] secretKey =
                    mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
            mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey);
            String grantAlias = mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
            return grantAlias;
        } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
        }
    }

    /**
     * Gets a key named {@code alias} in caller's namespace.
     *
     * @return grant alias, which caller can use to access the key.
     */
    public String getKey(@NonNull String alias) throws RemoteException {
        int uid = Binder.getCallingUid();
        int userId = UserHandle.getCallingUserId();
        String grantAlias = mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
        return grantAlias;
    }

    private byte[] decryptRecoveryKey(
Loading