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

Commit 02c0bbdc authored by Robert Berry's avatar Robert Berry Committed by Android (Google) Code Review
Browse files

Merge "Save KeyChainSnapshots to disk" into pi-dev

parents 1b460069 5658837b
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Binder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
@@ -128,7 +127,7 @@ public class RecoverableKeyStoreManager {
                    db,
                    new RecoverySessionStorage(),
                    Executors.newSingleThreadExecutor(),
                    new RecoverySnapshotStorage(),
                    RecoverySnapshotStorage.newInstance(),
                    new RecoverySnapshotListenersStorage(),
                    platformKeyManager,
                    applicationKeyStorage);
+8 −0
Original line number Diff line number Diff line
@@ -23,6 +23,9 @@ import static com.android.server.locksettings.recoverablekeystore.serialization.
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS;

import static com.android.server.locksettings.recoverablekeystore.serialization
        .KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS;
@@ -128,6 +131,11 @@ public class KeyChainSnapshotDeserializer {
                    }
                    break;

                case TAG_BACKEND_PUBLIC_KEY:
                    builder.setTrustedHardwarePublicKey(
                            readBlobTag(parser, TAG_BACKEND_PUBLIC_KEY));
                    break;

                case TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST:
                    builder.setKeyChainProtectionParams(readKeyChainProtectionParamsList(parser));
                    break;
+1 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ class KeyChainSnapshotSchema {
    static final String TAG_RECOVERY_KEY_MATERIAL = "recoveryKeyMaterial";
    static final String TAG_SERVER_PARAMS = "serverParams";
    static final String TAG_TRUSTED_HARDWARE_CERT_PATH = "thmCertPath";
    static final String TAG_BACKEND_PUBLIC_KEY = "backendPublicKey";

    static final String TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST =
            "keyChainProtectionParamsList";
+7 −0
Original line number Diff line number Diff line
@@ -24,6 +24,9 @@ import static com.android.server.locksettings.recoverablekeystore.serialization.
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS;

import static com.android.server.locksettings.recoverablekeystore.serialization
        .KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS;
@@ -159,6 +162,10 @@ public class KeyChainSnapshotSerializer {
        writePropertyTag(xmlSerializer, TAG_SERVER_PARAMS, keyChainSnapshot.getServerParams());
        writePropertyTag(xmlSerializer, TAG_TRUSTED_HARDWARE_CERT_PATH,
                keyChainSnapshot.getTrustedHardwareCertPath());
        if (keyChainSnapshot.getTrustedHardwarePublicKey() != null) {
            writePropertyTag(xmlSerializer, TAG_BACKEND_PUBLIC_KEY,
                    keyChainSnapshot.getTrustedHardwarePublicKey());
        }
    }

    private static void writePropertyTag(
+120 −2
Original line number Diff line number Diff line
@@ -17,13 +17,28 @@
package com.android.server.locksettings.recoverablekeystore.storage;

import android.annotation.Nullable;
import android.os.Environment;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.locksettings.recoverablekeystore.serialization
        .KeyChainSnapshotDeserializer;
import com.android.server.locksettings.recoverablekeystore.serialization
        .KeyChainSnapshotParserException;
import com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSerializer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.util.Locale;

/**
 * In-memory storage for recovery snapshots.
 * Storage for recovery snapshots. Stores snapshots in memory, backed by disk storage.
 *
 * <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
@@ -33,14 +48,46 @@ import com.android.internal.annotations.GuardedBy;
 * {@link com.android.server.locksettings.recoverablekeystore.KeySyncTask} thread.
 */
public class RecoverySnapshotStorage {

    private static final String TAG = "RecoverySnapshotStorage";

    private static final String ROOT_PATH = "system";
    private static final String STORAGE_PATH = "recoverablekeystore/snapshots/";

    @GuardedBy("this")
    private final SparseArray<KeyChainSnapshot> mSnapshotByUid = new SparseArray<>();

    private final File rootDirectory;

    /**
     * A new instance, storing snapshots in /data/system/recoverablekeystore/snapshots.
     *
     * <p>NOTE: calling this multiple times DOES NOT return the same instance, so will NOT be backed
     * by the same in-memory store.
     */
    public static RecoverySnapshotStorage newInstance() {
        return new RecoverySnapshotStorage(
                new File(Environment.getDataDirectory(), ROOT_PATH));
    }

    @VisibleForTesting
    public RecoverySnapshotStorage(File rootDirectory) {
        this.rootDirectory = rootDirectory;
    }

    /**
     * Sets the latest {@code snapshot} for the recovery agent {@code uid}.
     */
    public synchronized void put(int uid, KeyChainSnapshot snapshot) {
        mSnapshotByUid.put(uid, snapshot);

        try {
            writeToDisk(uid, snapshot);
        } catch (IOException | CertificateEncodingException e) {
            Log.e(TAG,
                    String.format(Locale.US, "Error persisting snapshot for %d to disk", uid),
                    e);
        }
    }

    /**
@@ -48,7 +95,17 @@ public class RecoverySnapshotStorage {
     */
    @Nullable
    public synchronized KeyChainSnapshot get(int uid) {
        return mSnapshotByUid.get(uid);
        KeyChainSnapshot snapshot = mSnapshotByUid.get(uid);
        if (snapshot != null) {
            return snapshot;
        }

        try {
            return readFromDisk(uid);
        } catch (IOException | KeyChainSnapshotParserException e) {
            Log.e(TAG, String.format(Locale.US, "Error reading snapshot for %d from disk", uid), e);
            return null;
        }
    }

    /**
@@ -56,5 +113,66 @@ public class RecoverySnapshotStorage {
     */
    public synchronized void remove(int uid) {
        mSnapshotByUid.remove(uid);
        getSnapshotFile(uid).delete();
    }

    /**
     * Writes the snapshot for recovery agent {@code uid} to disk.
     *
     * @throws IOException if an IO error occurs writing to disk.
     */
    private void writeToDisk(int uid, KeyChainSnapshot snapshot)
            throws IOException, CertificateEncodingException {
        File snapshotFile = getSnapshotFile(uid);

        try (
            FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile)
        ) {
            KeyChainSnapshotSerializer.serialize(snapshot, fileOutputStream);
        } catch (IOException | CertificateEncodingException e) {
            // If we fail to write the latest snapshot, we should delete any older snapshot that
            // happens to be around. Otherwise snapshot syncs might end up going 'back in time'.
            snapshotFile.delete();
            throw e;
        }
    }

    /**
     * Reads the last snapshot for recovery agent {@code uid} from disk.
     *
     * @return The snapshot, or null if none existed.
     * @throws IOException if an IO error occurs reading from disk.
     */
    @Nullable
    private KeyChainSnapshot readFromDisk(int uid)
            throws IOException, KeyChainSnapshotParserException {
        File snapshotFile = getSnapshotFile(uid);

        try (
            FileInputStream fileInputStream = new FileInputStream(snapshotFile)
        ) {
            return KeyChainSnapshotDeserializer.deserialize(fileInputStream);
        } catch (IOException | KeyChainSnapshotParserException e) {
            // If we fail to read the latest snapshot, we should delete it in case it is in some way
            // corrupted. We can regenerate snapshots anyway.
            snapshotFile.delete();
            throw e;
        }
    }

    private File getSnapshotFile(int uid) {
        File folder = getStorageFolder();
        String fileName = getSnapshotFileName(uid);
        return new File(folder, fileName);
    }

    private String getSnapshotFileName(int uid) {
        return String.format(Locale.US, "%d.xml", uid);
    }

    private File getStorageFolder() {
        File folder = new File(rootDirectory, STORAGE_PATH);
        folder.mkdirs();
        return folder;
    }
}
Loading