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

Commit a370128b authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Trigger recovery agent PendingIntent in KeySyncTask"

parents 16bac748 91044044
Loading
Loading
Loading
Loading
+18 −1
Original line number Diff line number Diff line
@@ -71,11 +71,13 @@ public class KeySyncTask implements Runnable {
    private final PlatformKeyManager.Factory mPlatformKeyManagerFactory;
    private final VaultKeySupplier mVaultKeySupplier;
    private final RecoverySnapshotStorage mRecoverySnapshotStorage;
    private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;

    public static KeySyncTask newInstance(
            Context context,
            RecoverableKeyStoreDb recoverableKeyStoreDb,
            RecoverySnapshotStorage snapshotStorage,
            RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
            int userId,
            int credentialType,
            String credential
@@ -83,6 +85,7 @@ public class KeySyncTask implements Runnable {
        return new KeySyncTask(
                recoverableKeyStoreDb,
                snapshotStorage,
                recoverySnapshotListenersStorage,
                userId,
                credentialType,
                credential,
@@ -107,11 +110,13 @@ public class KeySyncTask implements Runnable {
    KeySyncTask(
            RecoverableKeyStoreDb recoverableKeyStoreDb,
            RecoverySnapshotStorage snapshotStorage,
            RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
            int userId,
            int credentialType,
            String credential,
            PlatformKeyManager.Factory platformKeyManagerFactory,
            VaultKeySupplier vaultKeySupplier) {
        mSnapshotListenersStorage = recoverySnapshotListenersStorage;
        mRecoverableKeyStoreDb = recoverableKeyStoreDb;
        mUserId = userId;
        mCredentialType = credentialType;
@@ -136,6 +141,18 @@ public class KeySyncTask implements Runnable {
            return;
        }

        int recoveryAgentUid = mRecoverableKeyStoreDb.getRecoveryAgentUid(mUserId);

        if (recoveryAgentUid == -1) {
            Log.w(TAG, "No recovery agent initialized for user " + mUserId);
            return;
        }

        if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) {
            Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid);
            return;
        }

        byte[] salt = generateSalt();
        byte[] localLskfHash = hashCredentials(salt, mCredential);

@@ -192,7 +209,6 @@ public class KeySyncTask implements Runnable {
            return;
        }

        // TODO: why is the secret sent here? I thought it wasn't sent in the raw at all.
        KeyStoreRecoveryMetadata metadata = new KeyStoreRecoveryMetadata(
                /*userSecretType=*/ TYPE_LOCKSCREEN,
                /*lockScreenUiFormat=*/ mCredentialType,
@@ -207,6 +223,7 @@ public class KeySyncTask implements Runnable {
                /*recoveryMetadata=*/ metadataList,
                /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys),
                /*encryptedRecoveryKeyblob=*/ encryptedRecoveryKey));
        mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
    }

    private PublicKey getVaultPublicKey() {
+13 −8
Original line number Diff line number Diff line
@@ -45,7 +45,6 @@ import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -74,7 +73,7 @@ public class RecoverableKeyStoreManager {
    private final RecoverableKeyStoreDb mDatabase;
    private final RecoverySessionStorage mRecoverySessionStorage;
    private final ExecutorService mExecutorService;
    private final ListenersStorage mListenersStorage;
    private final RecoverySnapshotListenersStorage mListenersStorage;
    private final RecoverableKeyGenerator mRecoverableKeyGenerator;
    private final RecoverySnapshotStorage mSnapshotStorage;

@@ -91,8 +90,8 @@ public class RecoverableKeyStoreManager {
                    db,
                    new RecoverySessionStorage(),
                    Executors.newSingleThreadExecutor(),
                    ListenersStorage.getInstance(),
                    new RecoverySnapshotStorage());
                    new RecoverySnapshotStorage(),
                    new RecoverySnapshotListenersStorage());
        }
        return mInstance;
    }
@@ -103,8 +102,8 @@ public class RecoverableKeyStoreManager {
            RecoverableKeyStoreDb recoverableKeyStoreDb,
            RecoverySessionStorage recoverySessionStorage,
            ExecutorService executorService,
            ListenersStorage listenersStorage,
            RecoverySnapshotStorage snapshotStorage) {
            RecoverySnapshotStorage snapshotStorage,
            RecoverySnapshotListenersStorage listenersStorage) {
        mContext = context;
        mDatabase = recoverableKeyStoreDb;
        mRecoverySessionStorage = recoverySessionStorage;
@@ -462,7 +461,7 @@ public class RecoverableKeyStoreManager {
    /**
     * This function can only be used inside LockSettingsService.
     *
     * @param storedHashType from {@Code CredentialHash}
     * @param storedHashType from {@code CredentialHash}
     * @param credential - unencrypted String. Password length should be at most 16 symbols {@code
     *     mPasswordMaxLength}
     * @param userId for user who just unlocked the device.
@@ -473,7 +472,13 @@ public class RecoverableKeyStoreManager {
        // So as not to block the critical path unlocking the phone, defer to another thread.
        try {
            mExecutorService.execute(KeySyncTask.newInstance(
                    mContext, mDatabase, mSnapshotStorage, userId, storedHashType, credential));
                    mContext,
                    mDatabase,
                    mSnapshotStorage,
                    mListenersStorage,
                    userId,
                    storedHashType,
                    credential));
        } catch (NoSuchAlgorithmException e) {
            Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
        } catch (KeyStoreException e) {
+76 −0
Original line number Diff line number Diff line
@@ -18,50 +18,58 @@ package com.android.server.locksettings.recoverablekeystore;

import android.annotation.Nullable;
import android.app.PendingIntent;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;

import java.util.Map;
import java.util.HashMap;
import com.android.internal.annotations.GuardedBy;

/**
 * In memory storage for listeners to be notified when new recovery snapshot is available.
 * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent}
 * class.
 * In memory storage for listeners to be notified when new recovery snapshot is available. This
 * class is thread-safe. It is used on two threads - the service thread and the thread that runs the
 * {@link KeySyncTask}.
 *
 * @hide
 */
public class ListenersStorage {
    private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>();
public class RecoverySnapshotListenersStorage {
    private static final String TAG = "RecoverySnapshotLstnrs";

    private static final ListenersStorage mInstance = new ListenersStorage();
    public static ListenersStorage getInstance() {
        return mInstance;
    }
    @GuardedBy("this")
    private SparseArray<PendingIntent> mAgentIntents = new SparseArray<>();

    /**
     * Sets new listener for the recovery agent, identified by {@code uid}
     * Sets new listener for the recovery agent, identified by {@code uid}.
     *
     * @param recoveryAgentUid uid
     * @param intent PendingIntent which will be triggered than new snapshot is available.
     * @param recoveryAgentUid uid of the recovery agent.
     * @param intent PendingIntent which will be triggered when new snapshot is available.
     */
    public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) {
    public synchronized void setSnapshotListener(
            int recoveryAgentUid, @Nullable PendingIntent intent) {
        Log.i(TAG, "Registered listener for agent with uid " + recoveryAgentUid);
        mAgentIntents.put(recoveryAgentUid, intent);
    }

    /**
     * Notifies recovery agent, that new snapshot is available.
     * Does nothing if a listener was not registered.
     * Returns {@code true} if a listener has been set for the recovery agent.
     */
    public synchronized boolean hasListener(int recoveryAgentUid) {
        return mAgentIntents.get(recoveryAgentUid) != null;
    }

    /**
     * Notifies recovery agent that new snapshot is available. Does nothing if a listener was not
     * registered.
     *
     * @param recoveryAgentUid uid.
     * @param recoveryAgentUid uid of recovery agent.
     */
    public void recoverySnapshotAvailable(int recoveryAgentUid) {
    public synchronized void recoverySnapshotAvailable(int recoveryAgentUid) {
        PendingIntent intent = mAgentIntents.get(recoveryAgentUid);
        if (intent != null) {
            try {
                intent.send();
            } catch (PendingIntent.CanceledException e) {
                // Ignore - sending intent is not allowed.
                Log.e(TAG,
                        "Failed to trigger PendingIntent for " + recoveryAgentUid,
                        e);
            }
        }
    }
+30 −0
Original line number Diff line number Diff line
@@ -329,6 +329,36 @@ public class RecoverableKeyStoreDb {
                RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
    }

    /**
     * Returns the uid of the recovery agent for the given user, or -1 if none is set.
     */
    public int getRecoveryAgentUid(int userId) {
        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();

        String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID };
        String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
        String[] selectionArguments = { Integer.toString(userId) };

        try (
            Cursor cursor = db.query(
                    RecoveryServiceMetadataEntry.TABLE_NAME,
                    projection,
                    selection,
                    selectionArguments,
                    /*groupBy=*/ null,
                    /*having=*/ null,
                    /*orderBy=*/ null)
        ) {
            int count = cursor.getCount();
            if (count == 0) {
                return -1;
            }
            cursor.moveToFirst();
            return cursor.getInt(
                    cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID));
        }
    }

    /**
     * Returns the public key of the recovery service.
     *
+41 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
@@ -70,6 +71,7 @@ public class KeySyncTaskTest {
    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
    private static final int TEST_USER_ID = 1000;
    private static final int TEST_APP_UID = 10009;
    private static final int TEST_RECOVERY_AGENT_UID = 90873;
    private static final String TEST_APP_KEY_ALIAS = "rcleaver";
    private static final int TEST_GENERATION_ID = 2;
    private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD;
@@ -78,6 +80,7 @@ public class KeySyncTaskTest {
            "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8);

    @Mock private PlatformKeyManager mPlatformKeyManager;
    @Mock private RecoverySnapshotListenersStorage mSnapshotListenersStorage;

    private RecoverySnapshotStorage mRecoverySnapshotStorage;
    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -102,6 +105,7 @@ public class KeySyncTaskTest {
        mKeySyncTask = new KeySyncTask(
                mRecoverableKeyStoreDb,
                mRecoverySnapshotStorage,
                mSnapshotListenersStorage,
                TEST_USER_ID,
                TEST_CREDENTIAL_TYPE,
                TEST_CREDENTIAL,
@@ -201,6 +205,39 @@ public class KeySyncTaskTest {
        assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
    }

    @Test
    public void run_doesNotSendAnythingIfNoRecoveryAgentSet() throws Exception {
        SecretKey applicationKey = generateKey();
        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
        mRecoverableKeyStoreDb.insertKey(
                TEST_USER_ID,
                TEST_APP_UID,
                TEST_APP_KEY_ALIAS,
                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);

        mKeySyncTask.run();

        assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
    }

    @Test
    public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception {
        SecretKey applicationKey = generateKey();
        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
        mRecoverableKeyStoreDb.insertKey(
                TEST_USER_ID,
                TEST_APP_UID,
                TEST_APP_KEY_ALIAS,
                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());

        mKeySyncTask.run();

        assertNull(mRecoverySnapshotStorage.get(TEST_USER_ID));
    }

    @Test
    public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception {
        SecretKey applicationKey = generateKey();
@@ -210,6 +247,9 @@ public class KeySyncTaskTest {
                TEST_APP_UID,
                TEST_APP_KEY_ALIAS,
                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);

        mKeySyncTask.run();

@@ -218,6 +258,7 @@ public class KeySyncTaskTest {
                recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters();
        assertEquals(KeyDerivationParameters.ALGORITHM_SHA256,
                keyDerivationParameters.getAlgorithm());
        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
        byte[] lockScreenHash = KeySyncTask.hashCredentials(
                keyDerivationParameters.getSalt(),
                TEST_CREDENTIAL);
Loading