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

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

Merge "Add storage for snapshots in KeySyncTask"

parents cb37da8b bd086f19
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