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

Commit b5b22b19 authored by Dmitry Dementyev's avatar Dmitry Dementyev
Browse files

Use new params for platform key protection.

UnlockedDeviceRequired=true keys survive LSKF removal.
Generation id for keys created with new param will start from 1000000.
New parameter only works for primary user.
Added 2 seconds delay between phone unlock and running sync task.

Test: atest com.android.server.locksettings.recoverablekeystore
Bug: 111561213
Change-Id: Ifc48ef823a72fdbdff1a09563de0d0be0b004f79
parent a067a7bc
Loading
Loading
Loading
Loading
+17 −4
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_L

import android.content.Context;
import android.os.RemoteException;
import android.os.UserHandle;
import android.security.Scrypt;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
@@ -163,16 +164,28 @@ public class KeySyncTask implements Runnable {
    }

    private void syncKeys() throws RemoteException {
        int generation = mPlatformKeyManager.getGenerationId(mUserId);
        if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
            // Application keys for the user will not be available for sync.
            Log.w(TAG, "Credentials are not set for user " + mUserId);
            int generation = mPlatformKeyManager.getGenerationId(mUserId);
            if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
                    || mUserId != UserHandle.USER_SYSTEM) {
                // Only invalidate keys with legacy protection param.
                mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
            }
            return;
        }
        if (isCustomLockScreen()) {
            Log.w(TAG, "Unsupported credential type " + mCredentialType + " for user " + mUserId);
            // Keys will be synced when user starts using non custom screen lock.
            if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
                    || mUserId != UserHandle.USER_SYSTEM) {
                mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
            }
            return;
        }
        if (mPlatformKeyManager.isDeviceLocked(mUserId) && mUserId == UserHandle.USER_SYSTEM) {
            Log.w(TAG, "Can't sync keys for locked user " + mUserId);
            return;
        }

+21 −29
Original line number Diff line number Diff line
@@ -67,8 +67,9 @@ import javax.crypto.spec.GCMParameterSpec;
 * @hide
 */
public class PlatformKeyManager {
    private static final String TAG = "PlatformKeyManager";
    static final int MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED = 1000000;

    private static final String TAG = "PlatformKeyManager";
    private static final String KEY_ALGORITHM = "AES";
    private static final int KEY_SIZE_BITS = 256;
    private static final String KEY_ALIAS_PREFIX =
@@ -131,14 +132,14 @@ public class PlatformKeyManager {

    /**
     * Returns {@code true} if the platform key is available. A platform key won't be available if
     * the user has not set up a lock screen.
     * device is locked.
     *
     * @param userId The ID of the user to whose lock screen the platform key must be bound.
     *
     * @hide
     */
    public boolean isAvailable(int userId) {
        return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
    public boolean isDeviceLocked(int userId) {
        return mContext.getSystemService(KeyguardManager.class).isDeviceLocked(userId);
    }

    /**
@@ -169,7 +170,6 @@ public class PlatformKeyManager {
     * @param userId The ID of the user to whose lock screen the platform key must be bound.
     * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
     * @throws KeyStoreException if there is an error in AndroidKeyStore.
     * @throws InsecureUserException if the user does not have a lock screen set.
     * @throws IOException if there was an issue with local database update.
     * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
     *
@@ -177,13 +177,8 @@ public class PlatformKeyManager {
     */
    @VisibleForTesting
    void regenerate(int userId)
            throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException,
            throws NoSuchAlgorithmException, KeyStoreException, IOException,
                    RemoteException {
        if (!isAvailable(userId)) {
            throw new InsecureUserException(String.format(
                    Locale.US, "%d does not have a lock screen set.", userId));
        }

        int generationId = getGenerationId(userId);
        int nextId;
        if (generationId == -1) {
@@ -192,6 +187,7 @@ public class PlatformKeyManager {
            invalidatePlatformKey(userId, generationId);
            nextId = generationId + 1;
        }
        generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
        generateAndLoadKey(userId, nextId);
    }

@@ -203,7 +199,6 @@ public class PlatformKeyManager {
     * @throws KeyStoreException if there was an AndroidKeyStore error.
     * @throws UnrecoverableKeyException if the key could not be recovered.
     * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
     * @throws InsecureUserException if the user does not have a lock screen set.
     * @throws IOException if there was an issue with local database update.
     * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
     *
@@ -211,7 +206,7 @@ public class PlatformKeyManager {
     */
    public PlatformEncryptionKey getEncryptKey(int userId)
            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
                    InsecureUserException, IOException, RemoteException {
                    IOException, RemoteException {
        init(userId);
        try {
            // Try to see if the decryption key is still accessible before using the encryption key.
@@ -234,12 +229,11 @@ public class PlatformKeyManager {
     * @throws KeyStoreException if there was an AndroidKeyStore error.
     * @throws UnrecoverableKeyException if the key could not be recovered.
     * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
     * @throws InsecureUserException if the user does not have a lock screen set.
     *
     * @hide
     */
    private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException,
           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
            UnrecoverableKeyException, NoSuchAlgorithmException {
        int generationId = getGenerationId(userId);
        String alias = getEncryptAlias(userId, generationId);
        if (!isKeyLoaded(userId, generationId)) {
@@ -258,7 +252,6 @@ public class PlatformKeyManager {
     * @throws KeyStoreException if there was an AndroidKeyStore error.
     * @throws UnrecoverableKeyException if the key could not be recovered.
     * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
     * @throws InsecureUserException if the user does not have a lock screen set.
     * @throws IOException if there was an issue with local database update.
     * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
     *
@@ -266,7 +259,7 @@ public class PlatformKeyManager {
     */
    public PlatformDecryptionKey getDecryptKey(int userId)
            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
                    InsecureUserException, IOException, RemoteException {
                    IOException, RemoteException {
        init(userId);
        try {
            PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
@@ -288,12 +281,11 @@ public class PlatformKeyManager {
     * @throws KeyStoreException if there was an AndroidKeyStore error.
     * @throws UnrecoverableKeyException if the key could not be recovered.
     * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
     * @throws InsecureUserException if the user does not have a lock screen set.
     *
     * @hide
     */
    private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException,
           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
            UnrecoverableKeyException, NoSuchAlgorithmException {
        int generationId = getGenerationId(userId);
        String alias = getDecryptAlias(userId, generationId);
        if (!isKeyLoaded(userId, generationId)) {
@@ -340,13 +332,8 @@ public class PlatformKeyManager {
     * @hide
     */
    void init(int userId)
            throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException,
            throws KeyStoreException, NoSuchAlgorithmException, IOException,
                    RemoteException {
        if (!isAvailable(userId)) {
            throw new InsecureUserException(String.format(
                    Locale.US, "%d does not have a lock screen set.", userId));
        }

        int generationId = getGenerationId(userId);
        if (isKeyLoaded(userId, generationId)) {
            Log.i(TAG, String.format(
@@ -363,6 +350,7 @@ public class PlatformKeyManager {
            generationId++;
        }

        generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
        generateAndLoadKey(userId, generationId);
    }

@@ -440,12 +428,16 @@ public class PlatformKeyManager {

        KeyProtection.Builder decryptionKeyProtection =
                new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
                    .setUserAuthenticationRequired(true)
                    .setUserAuthenticationValidityDurationSeconds(
                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
        if (userId != UserHandle.USER_SYSTEM) {
        // Skip UserAuthenticationRequired for main user
        if (userId ==  UserHandle.USER_SYSTEM) {
            decryptionKeyProtection.setUnlockedDeviceRequired(true);
        } else {
            // With setUnlockedDeviceRequired, KeyStore thinks that device is locked .
            decryptionKeyProtection.setUserAuthenticationRequired(true);
            decryptionKeyProtection.setUserAuthenticationValidityDurationSeconds(
                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS);
            // Bind decryption key to secondary profile lock screen secret.
            long secureUserId = getGateKeeperService().getSecureUserId(userId);
            // TODO(b/124095438): Propagate this failure instead of silently failing.
+16 −14
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.server.locksettings.recoverablekeystore;
import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
@@ -46,7 +45,6 @@ import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
import com.android.internal.util.Preconditions;
import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
@@ -76,8 +74,9 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.crypto.AEADBadTagException;

@@ -89,13 +88,14 @@ import javax.crypto.AEADBadTagException;
 */
public class RecoverableKeyStoreManager {
    private static final String TAG = "RecoverableKeyStoreMgr";
    private static final long SYNC_DELAY_MILLIS = 2000;

    private static RecoverableKeyStoreManager mInstance;

    private final Context mContext;
    private final RecoverableKeyStoreDb mDatabase;
    private final RecoverySessionStorage mRecoverySessionStorage;
    private final ExecutorService mExecutorService;
    private final ScheduledExecutorService mExecutorService;
    private final RecoverySnapshotListenersStorage mListenersStorage;
    private final RecoverableKeyGenerator mRecoverableKeyGenerator;
    private final RecoverySnapshotStorage mSnapshotStorage;
@@ -136,7 +136,7 @@ public class RecoverableKeyStoreManager {
                    context.getApplicationContext(),
                    db,
                    new RecoverySessionStorage(),
                    Executors.newSingleThreadExecutor(),
                    Executors.newScheduledThreadPool(1),
                    snapshotStorage,
                    new RecoverySnapshotListenersStorage(),
                    platformKeyManager,
@@ -152,7 +152,7 @@ public class RecoverableKeyStoreManager {
            Context context,
            RecoverableKeyStoreDb recoverableKeyStoreDb,
            RecoverySessionStorage recoverySessionStorage,
            ExecutorService executorService,
            ScheduledExecutorService executorService,
            RecoverySnapshotStorage snapshotStorage,
            RecoverySnapshotListenersStorage listenersStorage,
            PlatformKeyManager platformKeyManager,
@@ -724,8 +724,6 @@ public class RecoverableKeyStoreManager {
            throw new RuntimeException(e);
        } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
        } catch (InsecureUserException e) {
            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
        }

        try {
@@ -793,8 +791,6 @@ public class RecoverableKeyStoreManager {
            throw new RuntimeException(e);
        } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
        } catch (InsecureUserException e) {
            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
        }

        try {
@@ -915,7 +911,7 @@ public class RecoverableKeyStoreManager {
            int storedHashType, @NonNull byte[] credential, int userId) {
        // So as not to block the critical path unlocking the phone, defer to another thread.
        try {
            mExecutorService.execute(KeySyncTask.newInstance(
            mExecutorService.schedule(KeySyncTask.newInstance(
                    mContext,
                    mDatabase,
                    mSnapshotStorage,
@@ -923,7 +919,10 @@ public class RecoverableKeyStoreManager {
                    userId,
                    storedHashType,
                    credential,
                    /*credentialUpdated=*/ false));
                    /*credentialUpdated=*/ false),
                    SYNC_DELAY_MILLIS,
                    TimeUnit.MILLISECONDS
            );
        } catch (NoSuchAlgorithmException e) {
            Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
        } catch (KeyStoreException e) {
@@ -947,7 +946,7 @@ public class RecoverableKeyStoreManager {
            int userId) {
        // So as not to block the critical path unlocking the phone, defer to another thread.
        try {
            mExecutorService.execute(KeySyncTask.newInstance(
            mExecutorService.schedule(KeySyncTask.newInstance(
                    mContext,
                    mDatabase,
                    mSnapshotStorage,
@@ -955,7 +954,10 @@ public class RecoverableKeyStoreManager {
                    userId,
                    storedHashType,
                    credential,
                    /*credentialUpdated=*/ true));
                    /*credentialUpdated=*/ true),
                    SYNC_DELAY_MILLIS,
                    TimeUnit.MILLISECONDS
            );
        } catch (NoSuchAlgorithmException e) {
            Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
        } catch (KeyStoreException e) {
+114 −114

File changed.

Preview size limit exceeded, changes collapsed.

+5 −4
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
@@ -84,7 +85,7 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -157,7 +158,7 @@ public class RecoverableKeyStoreManagerTest {
    @Mock private PlatformKeyManager mPlatformKeyManager;
    @Mock private ApplicationKeyStorage mApplicationKeyStorage;
    @Mock private CleanupManager mCleanupManager;
    @Mock private ExecutorService mExecutorService;
    @Mock private ScheduledExecutorService mExecutorService;
    @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;

    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -1253,7 +1254,7 @@ public class RecoverableKeyStoreManagerTest {
        mRecoverableKeyStoreManager.lockScreenSecretAvailable(
                LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11);

        verify(mExecutorService).execute(any());
        verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
    }

    @Test
@@ -1263,7 +1264,7 @@ public class RecoverableKeyStoreManagerTest {
                "password".getBytes(),
                11);

        verify(mExecutorService).execute(any());
        verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
    }

    private static byte[] encryptedApplicationKey(