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

Commit bd086f19 authored by Robert Berry's avatar Robert Berry
Browse files

Add storage for snapshots in KeySyncTask

Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
Change-Id: I25a9d6999bec5639cc91532da1b42a8d1f911b79
parent bbab9c4e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -45,6 +45,8 @@ public class RecoverableKeyStoreLoader {
    public static final int NO_ERROR = KeyStore.NO_ERROR;
    public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
    public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
    public static final int NO_SNAPSHOT_PENDING_ERROR = 21;

    /**
     * Rate limit is enforced to prevent using too many trusted remote devices, since each device
     * can have its own number of user secret guesses allowed.
+38 −16
Original line number Diff line number Diff line
@@ -16,14 +16,20 @@

package com.android.server.locksettings.recoverablekeystore;

import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;

import android.annotation.NonNull;
import android.content.Context;
import android.security.recoverablekeystore.KeyDerivationParameters;
import android.security.recoverablekeystore.KeyEntryRecoveryData;
import android.security.recoverablekeystore.KeyStoreRecoveryData;
import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
import android.util.Log;

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

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -36,6 +42,8 @@ import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.crypto.KeyGenerator;
@@ -58,28 +66,27 @@ public class KeySyncTask implements Runnable {

    private final RecoverableKeyStoreDb mRecoverableKeyStoreDb;
    private final int mUserId;
    private final RecoverableSnapshotConsumer mSnapshotConsumer;
    private final int mCredentialType;
    private final String mCredential;
    private final PlatformKeyManager.Factory mPlatformKeyManagerFactory;
    private final VaultKeySupplier mVaultKeySupplier;
    private final RecoverySnapshotStorage mRecoverySnapshotStorage;

    public static KeySyncTask newInstance(
            Context context,
            RecoverableKeyStoreDb recoverableKeyStoreDb,
            RecoverySnapshotStorage snapshotStorage,
            int userId,
            int credentialType,
            String credential
    ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
        return new KeySyncTask(
                recoverableKeyStoreDb,
                snapshotStorage,
                userId,
                credentialType,
                credential,
                () -> PlatformKeyManager.getInstance(context, recoverableKeyStoreDb, userId),
                (salt, recoveryKey, applicationKeys) -> {
                    // TODO: implement sending intent
                },
                () -> {
                    throw new UnsupportedOperationException("Not implemented vault key.");
                });
@@ -99,19 +106,19 @@ public class KeySyncTask implements Runnable {
    @VisibleForTesting
    KeySyncTask(
            RecoverableKeyStoreDb recoverableKeyStoreDb,
            RecoverySnapshotStorage snapshotStorage,
            int userId,
            int credentialType,
            String credential,
            PlatformKeyManager.Factory platformKeyManagerFactory,
            RecoverableSnapshotConsumer snapshotConsumer,
            VaultKeySupplier vaultKeySupplier) {
        mRecoverableKeyStoreDb = recoverableKeyStoreDb;
        mUserId = userId;
        mCredentialType = credentialType;
        mCredential = credential;
        mPlatformKeyManagerFactory = platformKeyManagerFactory;
        mSnapshotConsumer = snapshotConsumer;
        mVaultKeySupplier = vaultKeySupplier;
        mRecoverySnapshotStorage = snapshotStorage;
    }

    @Override
@@ -185,7 +192,21 @@ public class KeySyncTask implements Runnable {
            return;
        }

        mSnapshotConsumer.accept(salt, encryptedRecoveryKey, encryptedApplicationKeys);
        // 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,
                /*keyDerivationParameters=*/ KeyDerivationParameters.createSHA256Parameters(salt),
                /*secret=*/ new byte[0]);
        ArrayList<KeyStoreRecoveryMetadata> metadataList = new ArrayList<>();
        metadataList.add(metadata);

        // TODO: implement snapshot version
        mRecoverySnapshotStorage.put(mUserId, new KeyStoreRecoveryData(
                /*snapshotVersion=*/ 1,
                /*recoveryMetadata=*/ metadataList,
                /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys),
                /*encryptedRecoveryKeyblob=*/ encryptedRecoveryKey));
    }

    private PublicKey getVaultPublicKey() {
@@ -290,15 +311,16 @@ public class KeySyncTask implements Runnable {
        return keyGenerator.generateKey();
    }

    /**
     * TODO: just replace with the Intent call. I'm not sure exactly what this looks like, hence
     * this interface, so I can test in the meantime.
     */
    public interface RecoverableSnapshotConsumer {
        void accept(
                byte[] salt,
                byte[] encryptedRecoveryKey,
                Map<String, byte[]> encryptedApplicationKeys);
    private static List<KeyEntryRecoveryData> createApplicationKeyEntries(
            Map<String, byte[]> encryptedApplicationKeys) {
        ArrayList<KeyEntryRecoveryData> keyEntries = new ArrayList<>();
        for (String alias : encryptedApplicationKeys.keySet()) {
            keyEntries.add(
                    new KeyEntryRecoveryData(
                            alias.getBytes(StandardCharsets.UTF_8),
                            encryptedApplicationKeys.get(alias)));
        }
        return keyEntries;
    }

    /**
+13 −20
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
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.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
@@ -75,6 +76,7 @@ public class RecoverableKeyStoreManager {
    private final ExecutorService mExecutorService;
    private final ListenersStorage mListenersStorage;
    private final RecoverableKeyGenerator mRecoverableKeyGenerator;
    private final RecoverySnapshotStorage mSnapshotStorage;

    /**
     * Returns a new or existing instance.
@@ -89,7 +91,8 @@ public class RecoverableKeyStoreManager {
                    db,
                    new RecoverySessionStorage(),
                    Executors.newSingleThreadExecutor(),
                    ListenersStorage.getInstance());
                    ListenersStorage.getInstance(),
                    new RecoverySnapshotStorage());
        }
        return mInstance;
    }
@@ -100,12 +103,14 @@ public class RecoverableKeyStoreManager {
            RecoverableKeyStoreDb recoverableKeyStoreDb,
            RecoverySessionStorage recoverySessionStorage,
            ExecutorService executorService,
            ListenersStorage listenersStorage) {
            ListenersStorage listenersStorage,
            RecoverySnapshotStorage snapshotStorage) {
        mContext = context;
        mDatabase = recoverableKeyStoreDb;
        mRecoverySessionStorage = recoverySessionStorage;
        mExecutorService = executorService;
        mListenersStorage = listenersStorage;
        mSnapshotStorage = snapshotStorage;
        try {
            mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
        } catch (NoSuchAlgorithmException e) {
@@ -143,24 +148,12 @@ public class RecoverableKeyStoreManager {
    public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
            throws RemoteException {
        checkRecoverKeyStorePermission();
        final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
        final int callingUserId = UserHandle.getCallingUserId();
        final long callingIdentiy = Binder.clearCallingIdentity();
        try {
            // TODO: Return the latest snapshot for the calling recovery agent.
        } finally {
            Binder.restoreCallingIdentity(callingIdentiy);
        }

        // KeyStoreRecoveryData without application keys and empty recovery blob.
        KeyStoreRecoveryData recoveryData =
                new KeyStoreRecoveryData(
                        /*snapshotVersion=*/ 1,
                        new ArrayList<KeyStoreRecoveryMetadata>(),
                        new ArrayList<KeyEntryRecoveryData>(),
                        /*encryptedRecoveryKeyBlob=*/ new byte[] {});
        throw new ServiceSpecificException(
                RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY);
        KeyStoreRecoveryData snapshot = mSnapshotStorage.get(UserHandle.getCallingUserId());
        if (snapshot == null) {
            throw new ServiceSpecificException(RecoverableKeyStoreLoader.NO_SNAPSHOT_PENDING_ERROR);
        }
        return snapshot;
    }

    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
@@ -480,7 +473,7 @@ 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, userId, storedHashType, credential));
                    mContext, mDatabase, mSnapshotStorage, userId, storedHashType, credential));
        } catch (NoSuchAlgorithmException e) {
            Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
        } catch (KeyStoreException e) {
+16 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.locksettings.recoverablekeystore.storage;

import android.content.Context;
+60 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.locksettings.recoverablekeystore.storage;

import android.annotation.Nullable;
import android.security.recoverablekeystore.KeyStoreRecoveryData;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;

/**
 * In-memory storage for recovery snapshots.
 *
 * <p>Recovery snapshots are generated after a successful screen unlock. They are only generated if
 * the recoverable keystore has been mutated since the previous snapshot. This class stores only the
 * latest snapshot for each user.
 *
 * <p>This class is thread-safe. It is used both on the service thread and the
 * {@link com.android.server.locksettings.recoverablekeystore.KeySyncTask} thread.
 */
public class RecoverySnapshotStorage {
    @GuardedBy("this")
    private final SparseArray<KeyStoreRecoveryData> mSnapshotByUserId = new SparseArray<>();

    /**
     * Sets the latest {@code snapshot} for the user {@code userId}.
     */
    public synchronized void put(int userId, KeyStoreRecoveryData snapshot) {
        mSnapshotByUserId.put(userId, snapshot);
    }

    /**
     * Returns the latest snapshot for user {@code userId}, or null if none exists.
     */
    @Nullable
    public synchronized KeyStoreRecoveryData get(int userId) {
        return mSnapshotByUserId.get(userId);
    }

    /**
     * Removes any (if any) snapshot associated with user {@code userId}.
     */
    public synchronized void remove(int userId) {
        mSnapshotByUserId.remove(userId);
    }
}
Loading