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

Commit 290606d6 authored by Annie Meng's avatar Annie Meng Committed by Android (Google) Code Review
Browse files

Merge "Set correct SID when generating a platform key"

parents 8c83a07c f5beb598
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_L

import android.annotation.Nullable;
import android.content.Context;
import android.os.RemoteException;
import android.security.Scrypt;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
@@ -162,7 +163,7 @@ public class KeySyncTask implements Runnable {
        }
    }

    private void syncKeys() {
    private void syncKeys() throws RemoteException {
        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);
@@ -195,7 +196,7 @@ public class KeySyncTask implements Runnable {
            && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
    }

    private void syncKeysForAgent(int recoveryAgentUid) throws IOException {
    private void syncKeysForAgent(int recoveryAgentUid) throws IOException, RemoteException {
        boolean shouldRecreateCurrentVersion = false;
        if (!shouldCreateSnapshot(recoveryAgentUid)) {
            shouldRecreateCurrentVersion =
@@ -412,7 +413,7 @@ public class KeySyncTask implements Runnable {
    private Map<String, Pair<SecretKey, byte[]>> getKeysToSync(int recoveryAgentUid)
            throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
            NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException,
            InvalidKeyException, InvalidAlgorithmParameterException, IOException {
            InvalidKeyException, InvalidAlgorithmParameterException, IOException, RemoteException {
        PlatformDecryptionKey decryptKey = mPlatformKeyManager.getDecryptKey(mUserId);;
        Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
                mUserId, recoveryAgentUid, decryptKey.getGenerationId());
+68 −10
Original line number Diff line number Diff line
@@ -18,15 +18,21 @@ package com.android.server.locksettings.recoverablekeystore;

import android.app.KeyguardManager;
import android.content.Context;
import android.os.RemoteException;
import android.security.GateKeeper;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.service.gatekeeper.IGateKeeperService;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@@ -34,9 +40,11 @@ import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Locale;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.security.auth.DestroyFailedException;
import javax.crypto.spec.GCMParameterSpec;

/**
 * Manages creating and checking the validity of the platform key.
@@ -67,6 +75,10 @@ public class PlatformKeyManager {
    private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt";
    private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt";
    private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
    private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
    private static final int GCM_TAG_LENGTH_BITS = 128;
    // Only used for checking if a key is usable
    private static final byte[] GCM_INSECURE_NONCE_BYTES = new byte[12];

    private final Context mContext;
    private final KeyStoreProxy mKeyStore;
@@ -158,12 +170,14 @@ public class PlatformKeyManager {
     * @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}.
     *
     * @hide
     */
    @VisibleForTesting
    void regenerate(int userId)
            throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException {
            throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException,
                    RemoteException {
        if (!isAvailable(userId)) {
            throw new InsecureUserException(String.format(
                    Locale.US, "%d does not have a lock screen set.", userId));
@@ -190,11 +204,13 @@ public class PlatformKeyManager {
     * @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}.
     *
     * @hide
     */
    public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException,
           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException {
    public PlatformEncryptionKey getEncryptKey(int userId)
            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
                    InsecureUserException, IOException, RemoteException {
        init(userId);
        try {
            // Try to see if the decryption key is still accessible before using the encryption key.
@@ -243,14 +259,18 @@ public class PlatformKeyManager {
     * @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}.
     *
     * @hide
     */
    public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException,
           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException {
    public PlatformDecryptionKey getDecryptKey(int userId)
            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
                    InsecureUserException, IOException, RemoteException {
        init(userId);
        try {
            return getDecryptKeyInternal(userId);
            PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
            ensureDecryptionKeyIsValid(userId, decryptionKey);
            return decryptionKey;
        } catch (UnrecoverableKeyException e) {
            Log.i(TAG, String.format(Locale.US,
                    "Regenerating permanently invalid Platform key for user %d.",
@@ -283,6 +303,29 @@ public class PlatformKeyManager {
        return new PlatformDecryptionKey(generationId, key);
    }

    /**
     * Tries to use the decryption key to make sure it is not permanently invalidated. The exception
     * {@code KeyPermanentlyInvalidatedException} is thrown only when the key is in use.
     *
     * <p>Note that we ignore all other InvalidKeyException exceptions, because such an exception
     * may be thrown for auth-bound keys if there's no recent unlock event.
     */
    private void ensureDecryptionKeyIsValid(int userId, PlatformDecryptionKey decryptionKey)
            throws UnrecoverableKeyException {
        try {
            Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM).init(Cipher.UNWRAP_MODE,
                    decryptionKey.getKey(),
                    new GCMParameterSpec(GCM_TAG_LENGTH_BITS, GCM_INSECURE_NONCE_BYTES));
        } catch (KeyPermanentlyInvalidatedException e) {
            Log.e(TAG, String.format(Locale.US, "The platform key for user %d became invalid.",
                    userId));
            throw new UnrecoverableKeyException(e.getMessage());
        } catch (NoSuchAlgorithmException | InvalidKeyException
                | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
            // Ignore all other exceptions
        }
    }

    /**
     * Initializes the class. If there is no current platform key, and the user has a lock screen
     * set, will create the platform key and set the generation ID.
@@ -291,11 +334,13 @@ public class PlatformKeyManager {
     * @throws KeyStoreException if there was an error in AndroidKeyStore.
     * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
     * @throws IOException if there was an issue with local database update.
     * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
     *
     * @hide
     */
    void init(int userId)
            throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException {
            throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException,
                    RemoteException {
        if (!isAvailable(userId)) {
            throw new InsecureUserException(String.format(
                    Locale.US, "%d does not have a lock screen set.", userId));
@@ -372,6 +417,11 @@ public class PlatformKeyManager {
                && mKeyStore.containsAlias(getDecryptAlias(userId, generationId));
    }

    @VisibleForTesting
    IGateKeeperService getGateKeeperService() {
        return GateKeeper.getService();
    }

    /**
     * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given
     * {@code generationId} determining its aliases.
@@ -380,15 +430,23 @@ public class PlatformKeyManager {
     *     available since API version 1.
     * @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore.
     * @throws IOException if there was an issue with local database update.
     * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
     */
    private void generateAndLoadKey(int userId, int generationId)
            throws NoSuchAlgorithmException, KeyStoreException, IOException {
            throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException {
        String encryptAlias = getEncryptAlias(userId, generationId);
        String decryptAlias = getDecryptAlias(userId, generationId);
        // SecretKey implementation doesn't provide reliable way to destroy the secret
        // so it may live in memory for some time.
        SecretKey secretKey = generateAesKey();

        long secureUserId = getGateKeeperService().getSecureUserId(userId);
        // TODO(b/124095438): Propagate this failure instead of silently failing.
        if (secureUserId == GateKeeper.INVALID_SECURE_USER_ID) {
            Log.e(TAG, "No SID available for user " + userId);
            return;
        }

        // Store decryption key first since it is more likely to fail.
        mKeyStore.setEntry(
                decryptAlias,
@@ -399,7 +457,7 @@ public class PlatformKeyManager {
                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                    .setBoundToSpecificSecureUserId(userId)
                    .setBoundToSpecificSecureUserId(secureUserId)
                    .build());
        mKeyStore.setEntry(
                encryptAlias,
+83 −4
Original line number Diff line number Diff line
@@ -24,14 +24,21 @@ 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.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.expectThrows;

import android.app.KeyguardManager;
import android.content.Context;
import android.os.RemoteException;
import android.security.GateKeeper;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.service.gatekeeper.IGateKeeperService;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -53,6 +60,8 @@ import java.security.KeyStore;
import java.security.UnrecoverableKeyException;
import java.util.List;

import javax.crypto.KeyGenerator;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class PlatformKeyManagerTest {
@@ -60,10 +69,15 @@ public class PlatformKeyManagerTest {
    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
    private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
    private static final int USER_ID_FIXTURE = 42;
    private static final long USER_SID = 4200L;
    private static final String KEY_ALGORITHM = "AES";
    private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
    private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias";

    @Mock private Context mContext;
    @Mock private KeyStoreProxy mKeyStoreProxy;
    @Mock private KeyguardManager mKeyguardManager;
    @Mock private IGateKeeperService mGateKeeperService;

    @Captor private ArgumentCaptor<KeyStore.ProtectionParameter> mProtectionParameterCaptor;
    @Captor private ArgumentCaptor<KeyStore.Entry> mEntryArgumentCaptor;
@@ -74,18 +88,19 @@ public class PlatformKeyManagerTest {
    private PlatformKeyManager mPlatformKeyManager;

    @Before
    public void setUp() {
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        Context context = InstrumentationRegistry.getTargetContext();
        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
        mPlatformKeyManager = new PlatformKeyManager(
                mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
        mPlatformKeyManager = new PlatformKeyManagerTestable(
                mContext, mKeyStoreProxy, mRecoverableKeyStoreDb, mGateKeeperService);

        when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
        when(mContext.getSystemServiceName(any())).thenReturn("test");
        when(mKeyguardManager.isDeviceSecure(USER_ID_FIXTURE)).thenReturn(true);
        when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE)).thenReturn(USER_SID);
    }

    @After
@@ -192,10 +207,35 @@ public class PlatformKeyManagerTest {
        mPlatformKeyManager.init(USER_ID_FIXTURE);

        assertEquals(
                USER_ID_FIXTURE,
                USER_SID,
                getDecryptKeyProtection().getBoundToSpecificSecureUserId());
    }

    @Test
    public void init_doesNotCreateDecryptKeyIfNoSid() throws Exception {
        when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE))
                .thenReturn(GateKeeper.INVALID_SECURE_USER_ID);

        mPlatformKeyManager.init(USER_ID_FIXTURE);

        verify(mKeyStoreProxy, never()).setEntry(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
                any(),
                any());
    }

    @Test
    public void init_doesNotCreateDecryptKeyOnGateKeeperException() throws Exception {
        when(mGateKeeperService.getSecureUserId(USER_ID_FIXTURE)).thenThrow(new RemoteException());

        expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE));

        verify(mKeyStoreProxy, never()).setEntry(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
                any(),
                any());
    }

    @Test
    public void init_createsBothKeysWithSameMaterial() throws Exception {
        mPlatformKeyManager.init(USER_ID_FIXTURE);
@@ -259,6 +299,9 @@ public class PlatformKeyManagerTest {
        when(mKeyStoreProxy
                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
                        + "platform/42/1/encrypt")).thenReturn(true);
        when(mKeyStoreProxy.getKey(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
                any())).thenReturn(generateAndroidKeyStoreKey());

        mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);

@@ -281,6 +324,9 @@ public class PlatformKeyManagerTest {
        when(mKeyStoreProxy
                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
                        + "platform/42/2/decrypt")).thenReturn(true);
        when(mKeyStoreProxy.getKey(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
                any())).thenReturn(generateAndroidKeyStoreKey());

        mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);

@@ -352,6 +398,9 @@ public class PlatformKeyManagerTest {
        doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
                any());
        when(mKeyStoreProxy.getKey(
                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
                any())).thenReturn(generateAndroidKeyStoreKey());

        when(mKeyStoreProxy
                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
@@ -536,4 +585,34 @@ public class PlatformKeyManagerTest {
                mProtectionParameterCaptor.capture());
        return (KeyProtection) mProtectionParameterCaptor.getValue();
    }

    private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(
                KEY_ALGORITHM,
                ANDROID_KEY_STORE_PROVIDER);
        keyGenerator.init(new KeyGenParameterSpec.Builder(TESTING_KEYSTORE_KEY_ALIAS,
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .build());
        return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
    }

    class PlatformKeyManagerTestable extends PlatformKeyManager {
        private IGateKeeperService mGateKeeperService;

        PlatformKeyManagerTestable(
                Context context,
                KeyStoreProxy keyStoreProxy,
                RecoverableKeyStoreDb database,
                IGateKeeperService gateKeeperService) {
            super(context, keyStoreProxy, database);
            mGateKeeperService = gateKeeperService;
        }

        @Override
        IGateKeeperService getGateKeeperService() {
            return mGateKeeperService;
        }
    }
}