diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index 8a79e4c680d2a..77d7c3cfdbf2f 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -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); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java index dcaa0b4785cc4..f789155cee525 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java @@ -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; diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java index ee8b2cfd18f54..ff30ecd7d3a73 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java @@ -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"; diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java index f817a8fb02e4a..17a16bf5906b2 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java @@ -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( diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java index 3f93cc6f5f88b..c02b103f1d338 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java @@ -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. * *
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 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;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index 9ae45ea15ca2e..81a73efd94add 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.os.FileUtils;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
@@ -49,7 +50,6 @@ import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -72,6 +72,9 @@ import javax.crypto.SecretKey;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KeySyncTaskTest {
+
+ private static final String SNAPSHOT_TOP_LEVEL_DIRECTORY = "recoverablekeystore";
+
private static final String KEY_ALGORITHM = "AES";
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String TEST_ROOT_CERT_ALIAS = "trusted_root";
@@ -117,7 +120,7 @@ public class KeySyncTaskTest {
TEST_ROOT_CERT_ALIAS);
mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2,
TEST_ROOT_CERT_ALIAS);
- mRecoverySnapshotStorage = new RecoverySnapshotStorage();
+ mRecoverySnapshotStorage = new RecoverySnapshotStorage(context.getFilesDir());
mKeySyncTask = new KeySyncTask(
mRecoverableKeyStoreDb,
@@ -139,6 +142,10 @@ public class KeySyncTaskTest {
public void tearDown() {
mRecoverableKeyStoreDb.close();
mDatabaseFile.delete();
+
+ File file = new File(InstrumentationRegistry.getTargetContext().getFilesDir(),
+ SNAPSHOT_TOP_LEVEL_DIRECTORY);
+ FileUtils.deleteContentsAndDir(file);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java
index 6c2958e4f456c..2f4da86bf38f5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java
@@ -45,6 +45,7 @@ public class KeyChainSnapshotSerializerTest {
private static final int MAX_ATTEMPTS = 21;
private static final byte[] SERVER_PARAMS = new byte[] { 8, 2, 4 };
private static final byte[] KEY_BLOB = new byte[] { 124, 53, 53, 53 };
+ private static final byte[] PUBLIC_KEY_BLOB = new byte[] { 6, 6, 6, 6, 6, 6, 7 };
private static final CertPath CERT_PATH = TestData.CERT_PATH_1;
private static final int SECRET_TYPE = KeyChainProtectionParams.TYPE_LOCKSCREEN;
private static final int LOCK_SCREEN_UI = KeyChainProtectionParams.UI_FORMAT_PASSWORD;
@@ -92,6 +93,11 @@ public class KeyChainSnapshotSerializerTest {
assertThat(roundTrip().getTrustedHardwareCertPath()).isEqualTo(CERT_PATH);
}
+ @Test
+ public void roundTrip_persistsBackendPublicKey() throws Exception {
+ assertThat(roundTrip().getTrustedHardwarePublicKey()).isEqualTo(PUBLIC_KEY_BLOB);
+ }
+
@Test
public void roundTrip_persistsParamsList() throws Exception {
assertThat(roundTrip().getKeyChainProtectionParams()).hasSize(1);
@@ -163,6 +169,12 @@ public class KeyChainSnapshotSerializerTest {
assertThat(roundTripKeys().get(2).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_3_BYTES);
}
+ @Test
+ public void serialize_doesNotThrowForNullPublicKey() throws Exception {
+ KeyChainSnapshotSerializer.serialize(
+ createTestKeyChainSnapshotNoPublicKey(), new ByteArrayOutputStream());
+ }
+
private static List