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

Commit 29b9de5b authored by Dmitry Dementyev's avatar Dmitry Dementyev
Browse files

Update RecoveryController to use KeyStore grant API.

Missing parts:
1) Whitelist locksettingsservice to use grant API.
2) Probably have similar update for recovered keys - they will live in
system service and RecoveryAgent will use getKey() method to access
them.
3) ApplicationKeyStorageTest

Bug: 66499222
Test: adb shell am instrument -w -e package \
com.android.server.locksettings.recoverablekeystore \
com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner

Change-Id: I584b89e3f777bed679b2eb5173750f3f1dee3635
parent c13b5436
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