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

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

Merge "Handle case when PlatformKey is invalid."

parents a6216a86 3f2d1713
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -305,7 +305,7 @@ public class KeySyncTask implements Runnable {
            NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException,
            InvalidKeyException, InvalidAlgorithmParameterException {
        PlatformKeyManager platformKeyManager = mPlatformKeyManagerFactory.newInstance();
        PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey(mUserId);
        PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey(mUserId);;
        Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
                mUserId, recoveryAgentUid, decryptKey.getGenerationId());
        return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
+60 −7
Original line number Diff line number Diff line
@@ -131,6 +131,7 @@ public class PlatformKeyManager {
    /**
     * Generates a new key and increments the generation ID. Should be invoked if the platform key
     * is corrupted and needs to be rotated.
     * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
     *
     * @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.
@@ -158,6 +159,7 @@ public class PlatformKeyManager {

    /**
     * Returns the platform key used for encryption.
     * Tries to regenerate key one time if it is permanently invalid.
     *
     * @param userId The ID of the user to whose lock screen the platform key must be bound.
     * @throws KeyStoreException if there was an AndroidKeyStore error.
@@ -170,6 +172,30 @@ public class PlatformKeyManager {
    public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException,
           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
        init(userId);
        try {
            return getEncryptKeyInternal(userId);
        } catch (UnrecoverableKeyException e) {
            Log.i(TAG, String.format(Locale.US,
                    "Regenerating permanently invalid Platform key for user %d.",
                    userId));
            regenerate(userId);
            return getEncryptKeyInternal(userId);
        }
    }

    /**
     * Returns the platform key used for encryption.
     *
     * @param userId The ID of the user to whose lock screen the platform key must be bound.
     * @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 {
        int generationId = getGenerationId(userId);
        AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
                getEncryptAlias(userId, generationId), /*password=*/ null);
@@ -178,6 +204,7 @@ public class PlatformKeyManager {

    /**
     * Returns the platform key used for decryption. Only works after a recent screen unlock.
     * Tries to regenerate key one time if it is permanently invalid.
     *
     * @param userId The ID of the user to whose lock screen the platform key must be bound.
     * @throws KeyStoreException if there was an AndroidKeyStore error.
@@ -190,6 +217,30 @@ public class PlatformKeyManager {
    public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException,
           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
        init(userId);
        try {
            return getDecryptKeyInternal(userId);
        } catch (UnrecoverableKeyException e) {
            Log.i(TAG, String.format(Locale.US,
                    "Regenerating permanently invalid Platform key for user %d.",
                    userId));
            regenerate(userId);
            return getDecryptKeyInternal(userId);
        }
    }

    /**
     * Returns the platform key used for decryption. Only works after a recent screen unlock.
     *
     * @param userId The ID of the user to whose lock screen the platform key must be bound.
     * @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 {
        int generationId = getGenerationId(userId);
        AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
                getDecryptAlias(userId, generationId), /*password=*/ null);
@@ -294,13 +345,7 @@ public class PlatformKeyManager {
        String decryptAlias = getDecryptAlias(userId, generationId);
        SecretKey secretKey = generateAesKey();

        mKeyStore.setEntry(
                encryptAlias,
                new KeyStore.SecretKeyEntry(secretKey),
                new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                    .build());
        // Store Since decryption key first since it is more likely to fail.
        mKeyStore.setEntry(
                decryptAlias,
                new KeyStore.SecretKeyEntry(secretKey),
@@ -313,6 +358,14 @@ public class PlatformKeyManager {
                    .setBoundToSpecificSecureUserId(userId)
                    .build());

        mKeyStore.setEntry(
                encryptAlias,
                new KeyStore.SecretKeyEntry(secretKey),
                new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                    .build());

        setGenerationId(userId, generationId);

        try {
+21 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.security.keystore.recovery.RecoveryController;
import android.text.TextUtils;
import android.util.Log;

@@ -289,8 +290,27 @@ public class RecoverableKeyStoreDb {
        ContentValues values = new ContentValues();
        values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
        values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId);
        return db.replace(
        long result = db.replace(
                UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
        if (result != -1) {
            invalidateKeysWithOldGenerationId(userId, generationId);
        }
        return result;
    }

    /**
     * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
     */
    public void invalidateKeysWithOldGenerationId(int userId, int newGenerationId) {
        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS,
                RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
        String selection =
                KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
                + KeysEntry.COLUMN_NAME_GENERATION_ID + " < ?";
        db.update(KeysEntry.TABLE_NAME, values, selection,
            new String[] {String.valueOf(userId), String.valueOf(newGenerationId)});
    }

    /**
+36 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -48,6 +49,7 @@ import org.mockito.MockitoAnnotations;

import java.io.File;
import java.security.KeyStore;
import java.security.UnrecoverableKeyException;
import java.util.List;

@SmallTest
@@ -257,6 +259,40 @@ public class PlatformKeyManagerTest {
                any());
    }

    @Test
    public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
        doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
                any());

        mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);

        verify(mKeyStoreProxy).getKey(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
                any());
        // Attempt to get regenerated key.
        verify(mKeyStoreProxy).getKey(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
                any());
    }

    @Test
    public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
        doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
                any());

        mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);

        verify(mKeyStoreProxy).getKey(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
                any());
        // Attempt to get regenerated key.
        verify(mKeyStoreProxy).getKey(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
                any());
    }

    @Test
    public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
        mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
+23 −0
Original line number Diff line number Diff line
@@ -315,6 +315,29 @@ public class RecoverableKeyStoreDbTest {
        assertThat(statuses).hasSize(0);
    }

    public void testInvalidateKeysWithOldGenerationId_withSingleKey() {
        int userId = 12;
        int uid = 1009;
        int generationId = 6;
        int status = 120;
        int status2 = 121;
        String alias = "test";
        byte[] nonce = getUtf8Bytes("nonce");
        byte[] keyMaterial = getUtf8Bytes("keymaterial");
        WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId, status);
        mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);

        WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
        assertThat(retrievedKey.getRecoveryStatus()).isEqualTo(status);

        mRecoverableKeyStoreDb.setRecoveryStatus(uid, alias, status2);
        mRecoverableKeyStoreDb.invalidateKeysWithOldGenerationId(userId, generationId + 1);

        retrievedKey = mRecoverableKeyStoreDb.getKey(uid, alias);
        assertThat(retrievedKey.getRecoveryStatus())
                .isEqualTo(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
    }

    @Test
    public void setRecoveryServicePublicKey_replaceOldKey() throws Exception {
        int userId = 12;