diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshot.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshot.java
new file mode 100644
index 0000000000000..52381b8f87d12
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshot.java
@@ -0,0 +1,298 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.security.keystore.recovery.KeyChainProtectionParams;
+import android.security.keystore.recovery.KeyChainSnapshot;
+import android.security.keystore.recovery.KeyDerivationParams;
+import android.security.keystore.recovery.WrappedApplicationKey;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides helper methods serialize and deserialize {@link KeyChainSnapshot}.
+ *
+ *
It is necessary since {@link android.os.Parcelable} is not designed for persistent storage.
+ *
+ *
For every list, length is stored before the elements.
+ *
+ */
+public class PersistentKeyChainSnapshot {
+ private static final int VERSION = 1;
+ private static final int NULL_LIST_LENGTH = -1;
+
+ private DataInputStream mInput;
+ private DataOutputStream mOut;
+ private ByteArrayOutputStream mOutStream;
+
+ @VisibleForTesting
+ PersistentKeyChainSnapshot() {
+ }
+
+ @VisibleForTesting
+ void initReader(byte[] input) {
+ mInput = new DataInputStream(new ByteArrayInputStream(input));
+ }
+
+ @VisibleForTesting
+ void initWriter() {
+ mOutStream = new ByteArrayOutputStream();
+ mOut = new DataOutputStream(mOutStream);
+ }
+
+ @VisibleForTesting
+ byte[] getOutput() {
+ return mOutStream.toByteArray();
+ }
+
+ /**
+ * Converts {@link KeyChainSnapshot} to its binary representation.
+ *
+ * @param snapshot The snapshot.
+ *
+ * @throws IOException if serialization failed.
+ */
+ public static byte[] serialize(@NonNull KeyChainSnapshot snapshot) throws IOException {
+ PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot();
+ writer.initWriter();
+ writer.writeInt(VERSION);
+ writer.writeKeyChainSnapshot(snapshot);
+ return writer.getOutput();
+ }
+
+ /**
+ * deserializes {@link KeyChainSnapshot}.
+ *
+ * @input input - byte array produced by {@link serialize} method.
+ * @throws IOException if parsing failed.
+ */
+ public static @NonNull KeyChainSnapshot deserialize(@NonNull byte[] input)
+ throws IOException {
+ PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot();
+ reader.initReader(input);
+ try {
+ int version = reader.readInt();
+ if (version != VERSION) {
+ throw new IOException("Unsupported version " + version);
+ }
+ return reader.readKeyChainSnapshot();
+ } catch (IOException e) {
+ throw new IOException("Malformed KeyChainSnapshot", e);
+ }
+ }
+
+ /**
+ * Must be in sync with {@link KeyChainSnapshot.writeToParcel}
+ */
+ @VisibleForTesting
+ void writeKeyChainSnapshot(KeyChainSnapshot snapshot) throws IOException {
+ writeInt(snapshot.getSnapshotVersion());
+ writeProtectionParamsList(snapshot.getKeyChainProtectionParams());
+ writeBytes(snapshot.getEncryptedRecoveryKeyBlob());
+ writeKeysList(snapshot.getWrappedApplicationKeys());
+
+ writeInt(snapshot.getMaxAttempts());
+ writeLong(snapshot.getCounterId());
+ writeBytes(snapshot.getServerParams());
+ writeBytes(snapshot.getTrustedHardwarePublicKey());
+ }
+
+ @VisibleForTesting
+ KeyChainSnapshot readKeyChainSnapshot() throws IOException {
+ int snapshotVersion = readInt();
+ List protectionParams = readProtectionParamsList();
+ byte[] encryptedRecoveryKey = readBytes();
+ List keysList = readKeysList();
+
+ int maxAttempts = readInt();
+ long conterId = readLong();
+ byte[] serverParams = readBytes();
+ byte[] trustedHardwarePublicKey = readBytes();
+
+ return new KeyChainSnapshot.Builder()
+ .setSnapshotVersion(snapshotVersion)
+ .setKeyChainProtectionParams(protectionParams)
+ .setEncryptedRecoveryKeyBlob(encryptedRecoveryKey)
+ .setWrappedApplicationKeys(keysList)
+ .setMaxAttempts(maxAttempts)
+ .setCounterId(conterId)
+ .setServerParams(serverParams)
+ .setTrustedHardwarePublicKey(trustedHardwarePublicKey)
+ .build();
+ }
+
+ @VisibleForTesting
+ void writeProtectionParamsList(
+ @NonNull List ProtectionParamsList) throws IOException {
+ writeInt(ProtectionParamsList.size());
+ for (KeyChainProtectionParams protectionParams : ProtectionParamsList) {
+ writeProtectionParams(protectionParams);
+ }
+ }
+
+ @VisibleForTesting
+ List readProtectionParamsList() throws IOException {
+ int length = readInt();
+ List result = new ArrayList<>(length);
+ for (int i = 0; i < length; i++) {
+ result.add(readProtectionParams());
+ }
+ return result;
+ }
+
+ /**
+ * Must be in sync with {@link KeyChainProtectionParams.writeToParcel}
+ */
+ @VisibleForTesting
+ void writeProtectionParams(@NonNull KeyChainProtectionParams protectionParams)
+ throws IOException {
+ if (!ArrayUtils.isEmpty(protectionParams.getSecret())) {
+ // Extra security check.
+ throw new RuntimeException("User generated secret should not be stored");
+ }
+ writeInt(protectionParams.getUserSecretType());
+ writeInt(protectionParams.getLockScreenUiFormat());
+ writeKeyDerivationParams(protectionParams.getKeyDerivationParams());
+ writeBytes(protectionParams.getSecret());
+ }
+
+ @VisibleForTesting
+ KeyChainProtectionParams readProtectionParams() throws IOException {
+ int userSecretType = readInt();
+ int lockScreenUiFormat = readInt();
+ KeyDerivationParams derivationParams = readKeyDerivationParams();
+ byte[] secret = readBytes();
+ return new KeyChainProtectionParams.Builder()
+ .setUserSecretType(userSecretType)
+ .setLockScreenUiFormat(lockScreenUiFormat)
+ .setKeyDerivationParams(derivationParams)
+ .setSecret(secret)
+ .build();
+ }
+
+ /**
+ * Must be in sync with {@link KeyDerivationParams.writeToParcel}
+ */
+ @VisibleForTesting
+ void writeKeyDerivationParams(@NonNull KeyDerivationParams Params) throws IOException {
+ writeInt(Params.getAlgorithm());
+ writeBytes(Params.getSalt());
+ }
+
+ @VisibleForTesting
+ KeyDerivationParams readKeyDerivationParams() throws IOException {
+ int algorithm = readInt();
+ byte[] salt = readBytes();
+ return KeyDerivationParams.createSha256Params(salt);
+ }
+
+ @VisibleForTesting
+ void writeKeysList(@NonNull List applicationKeys) throws IOException {
+ writeInt(applicationKeys.size());
+ for (WrappedApplicationKey keyEntry : applicationKeys) {
+ writeKeyEntry(keyEntry);
+ }
+ }
+
+ @VisibleForTesting
+ List readKeysList() throws IOException {
+ int length = readInt();
+ List result = new ArrayList<>(length);
+ for (int i = 0; i < length; i++) {
+ result.add(readKeyEntry());
+ }
+ return result;
+ }
+
+ /**
+ * Must be in sync with {@link WrappedApplicationKey.writeToParcel}
+ */
+ @VisibleForTesting
+ void writeKeyEntry(@NonNull WrappedApplicationKey keyEntry) throws IOException {
+ mOut.writeUTF(keyEntry.getAlias());
+ writeBytes(keyEntry.getEncryptedKeyMaterial());
+ writeBytes(keyEntry.getAccount());
+ }
+
+ @VisibleForTesting
+ WrappedApplicationKey readKeyEntry() throws IOException {
+ String alias = mInput.readUTF();
+ byte[] keyMaterial = readBytes();
+ byte[] account = readBytes();
+ return new WrappedApplicationKey.Builder()
+ .setAlias(alias)
+ .setEncryptedKeyMaterial(keyMaterial)
+ .setAccount(account)
+ .build();
+ }
+
+ @VisibleForTesting
+ void writeInt(int value) throws IOException {
+ mOut.writeInt(value);
+ }
+
+ @VisibleForTesting
+ int readInt() throws IOException {
+ return mInput.readInt();
+ }
+
+ @VisibleForTesting
+ void writeLong(long value) throws IOException {
+ mOut.writeLong(value);
+ }
+
+ @VisibleForTesting
+ long readLong() throws IOException {
+ return mInput.readLong();
+ }
+
+ @VisibleForTesting
+ void writeBytes(@Nullable byte[] value) throws IOException {
+ if (value == null) {
+ writeInt(NULL_LIST_LENGTH);
+ return;
+ }
+ writeInt(value.length);
+ mOut.write(value, 0, value.length);
+ }
+
+ /**
+ * Reads @code{byte[]} from current position. Converts {@code null} to an empty array.
+ */
+ @VisibleForTesting
+ @NonNull byte[] readBytes() throws IOException {
+ int length = readInt();
+ if (length == NULL_LIST_LENGTH) {
+ return new byte[]{};
+ }
+ byte[] result = new byte[length];
+ mInput.read(result, 0, result.length);
+ return result;
+ }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshotTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshotTest.java
new file mode 100644
index 0000000000000..aad5295abd75b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshotTest.java
@@ -0,0 +1,335 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+
+import android.security.keystore.recovery.KeyDerivationParams;
+import android.security.keystore.recovery.WrappedApplicationKey;
+import android.security.keystore.recovery.KeyChainSnapshot;
+import android.security.keystore.recovery.KeyChainProtectionParams;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PersistentKeyChainSnapshotTest {
+
+ private static final String ALIAS = "some_key";
+ private static final String ALIAS2 = "another_key";
+ private static final byte[] RECOVERY_KEY_MATERIAL = "recovery_key_data"
+ .getBytes(StandardCharsets.UTF_8);
+ private static final byte[] KEY_MATERIAL = "app_key_data".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] PUBLIC_KEY = "public_key_data".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] ACCOUNT = "test_account".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] SALT = "salt".getBytes(StandardCharsets.UTF_8);
+ private static final int SNAPSHOT_VERSION = 2;
+ private static final int MAX_ATTEMPTS = 10;
+ private static final long COUNTER_ID = 123456789L;
+ private static final byte[] SERVER_PARAMS = "server_params".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] ZERO_BYTES = new byte[0];
+ private static final byte[] ONE_BYTE = new byte[]{(byte) 11};
+ private static final byte[] TWO_BYTES = new byte[]{(byte) 222,(byte) 222};
+
+ @Test
+ public void testWriteInt() throws Exception {
+ PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot();
+ writer.initWriter();
+ writer.writeInt(Integer.MIN_VALUE);
+ writer.writeInt(Integer.MAX_VALUE);
+ byte[] result = writer.getOutput();
+
+ PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot();
+ reader.initReader(result);
+ assertThat(reader.readInt()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(reader.readInt()).isEqualTo(Integer.MAX_VALUE);
+
+ assertThrows(
+ IOException.class,
+ () -> reader.readInt());
+ }
+
+ @Test
+ public void testWriteLong() throws Exception {
+ PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot();
+ writer.initWriter();
+ writer.writeLong(Long.MIN_VALUE);
+ writer.writeLong(Long.MAX_VALUE);
+ byte[] result = writer.getOutput();
+
+ PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot();
+ reader.initReader(result);
+ assertThat(reader.readLong()).isEqualTo(Long.MIN_VALUE);
+ assertThat(reader.readLong()).isEqualTo(Long.MAX_VALUE);
+
+ assertThrows(
+ IOException.class,
+ () -> reader.readLong());
+ }
+
+ @Test
+ public void testWriteBytes() throws Exception {
+ PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot();
+ writer.initWriter();
+ writer.writeBytes(ZERO_BYTES);
+ writer.writeBytes(ONE_BYTE);
+ writer.writeBytes(TWO_BYTES);
+ byte[] result = writer.getOutput();
+
+ PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot();
+ reader.initReader(result);
+ assertThat(reader.readBytes()).isEqualTo(ZERO_BYTES);
+ assertThat(reader.readBytes()).isEqualTo(ONE_BYTE);
+ assertThat(reader.readBytes()).isEqualTo(TWO_BYTES);
+
+ assertThrows(
+ IOException.class,
+ () -> reader.readBytes());
+ }
+
+ @Test
+ public void testReadBytes_returnsNullArrayAsEmpty() throws Exception {
+ PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot();
+ writer.initWriter();
+ writer.writeBytes(null);
+ byte[] result = writer.getOutput();
+
+ PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot();
+ reader.initReader(result);
+ assertThat(reader.readBytes()).isEqualTo(new byte[]{}); // null -> empty array
+ }
+
+ @Test
+ public void testWriteKeyEntry() throws Exception {
+ PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot();
+ writer.initWriter();
+ WrappedApplicationKey entry = new WrappedApplicationKey.Builder()
+ .setAlias(ALIAS)
+ .setEncryptedKeyMaterial(KEY_MATERIAL)
+ .setAccount(ACCOUNT)
+ .build();
+ writer.writeKeyEntry(entry);
+
+ byte[] result = writer.getOutput();
+
+ PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot();
+ reader.initReader(result);
+
+ WrappedApplicationKey copy = reader.readKeyEntry();
+ assertThat(copy.getAlias()).isEqualTo(ALIAS);
+ assertThat(copy.getEncryptedKeyMaterial()).isEqualTo(KEY_MATERIAL);
+ assertThat(copy.getAccount()).isEqualTo(ACCOUNT);
+
+ assertThrows(
+ IOException.class,
+ () -> reader.readKeyEntry());
+ }
+
+ public void testWriteProtectionParams() throws Exception {
+ PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot();
+ writer.initWriter();
+ KeyDerivationParams derivationParams = KeyDerivationParams.createSha256Params(SALT);
+ KeyChainProtectionParams protectionParams = new KeyChainProtectionParams.Builder()
+ .setUserSecretType(1)
+ .setLockScreenUiFormat(2)
+ .setKeyDerivationParams(derivationParams)
+ .build();
+ writer.writeProtectionParams(protectionParams);
+
+ byte[] result = writer.getOutput();
+
+ PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot();
+ reader.initReader(result);
+
+ KeyChainProtectionParams copy = reader.readProtectionParams();
+ assertThat(copy.getUserSecretType()).isEqualTo(1);
+ assertThat(copy.getLockScreenUiFormat()).isEqualTo(2);
+ assertThat(copy.getKeyDerivationParams().getSalt()).isEqualTo(SALT);
+
+ assertThrows(
+ IOException.class,
+ () -> reader.readProtectionParams());
+ }
+
+ public void testKeyChainSnapshot() throws Exception {
+ PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot();
+ writer.initWriter();
+
+ KeyDerivationParams derivationParams = KeyDerivationParams.createSha256Params(SALT);
+
+ ArrayList protectionParamsList = new ArrayList<>();
+ protectionParamsList.add(new KeyChainProtectionParams.Builder()
+ .setUserSecretType(1)
+ .setLockScreenUiFormat(2)
+ .setKeyDerivationParams(derivationParams)
+ .build());
+
+ ArrayList appKeysList = new ArrayList<>();
+ appKeysList.add(new WrappedApplicationKey.Builder()
+ .setAlias(ALIAS)
+ .setEncryptedKeyMaterial(KEY_MATERIAL)
+ .setAccount(ACCOUNT)
+ .build());
+
+ KeyChainSnapshot snapshot = new KeyChainSnapshot.Builder()
+ .setSnapshotVersion(SNAPSHOT_VERSION)
+ .setKeyChainProtectionParams(protectionParamsList)
+ .setEncryptedRecoveryKeyBlob(KEY_MATERIAL)
+ .setWrappedApplicationKeys(appKeysList)
+ .setMaxAttempts(MAX_ATTEMPTS)
+ .setCounterId(COUNTER_ID)
+ .setServerParams(SERVER_PARAMS)
+ .setTrustedHardwarePublicKey(PUBLIC_KEY)
+ .build();
+
+ writer.writeKeyChainSnapshot(snapshot);
+
+ byte[] result = writer.getOutput();
+
+ PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot();
+ reader.initReader(result);
+
+ KeyChainSnapshot copy = reader.readKeyChainSnapshot();
+ assertThat(copy.getSnapshotVersion()).isEqualTo(SNAPSHOT_VERSION);
+ assertThat(copy.getKeyChainProtectionParams()).hasSize(2);
+ assertThat(copy.getKeyChainProtectionParams().get(0).getUserSecretType()).isEqualTo(1);
+ assertThat(copy.getKeyChainProtectionParams().get(1).getUserSecretType()).isEqualTo(2);
+ assertThat(copy.getEncryptedRecoveryKeyBlob()).isEqualTo(RECOVERY_KEY_MATERIAL);
+ assertThat(copy.getWrappedApplicationKeys()).hasSize(2);
+ assertThat(copy.getWrappedApplicationKeys().get(0).getAlias()).isEqualTo(ALIAS);
+ assertThat(copy.getWrappedApplicationKeys().get(1).getAlias()).isEqualTo(ALIAS2);
+ assertThat(copy.getMaxAttempts()).isEqualTo(MAX_ATTEMPTS);
+ assertThat(copy.getCounterId()).isEqualTo(COUNTER_ID);
+ assertThat(copy.getServerParams()).isEqualTo(SERVER_PARAMS);
+ assertThat(copy.getTrustedHardwarePublicKey()).isEqualTo(PUBLIC_KEY);
+
+ assertThrows(
+ IOException.class,
+ () -> reader.readKeyChainSnapshot());
+
+ verifyDeserialize(snapshot);
+ }
+
+ public void testKeyChainSnapshot_withManyKeysAndProtectionParams() throws Exception {
+ PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot();
+ writer.initWriter();
+
+ KeyDerivationParams derivationParams = KeyDerivationParams.createSha256Params(SALT);
+
+ ArrayList protectionParamsList = new ArrayList<>();
+ protectionParamsList.add(new KeyChainProtectionParams.Builder()
+ .setUserSecretType(1)
+ .setLockScreenUiFormat(2)
+ .setKeyDerivationParams(derivationParams)
+ .build());
+ protectionParamsList.add(new KeyChainProtectionParams.Builder()
+ .setUserSecretType(2)
+ .setLockScreenUiFormat(3)
+ .setKeyDerivationParams(derivationParams)
+ .build());
+ ArrayList appKeysList = new ArrayList<>();
+ appKeysList.add(new WrappedApplicationKey.Builder()
+ .setAlias(ALIAS)
+ .setEncryptedKeyMaterial(KEY_MATERIAL)
+ .setAccount(ACCOUNT)
+ .build());
+ appKeysList.add(new WrappedApplicationKey.Builder()
+ .setAlias(ALIAS2)
+ .setEncryptedKeyMaterial(KEY_MATERIAL)
+ .setAccount(ACCOUNT)
+ .build());
+
+
+ KeyChainSnapshot snapshot = new KeyChainSnapshot.Builder()
+ .setSnapshotVersion(SNAPSHOT_VERSION)
+ .setKeyChainProtectionParams(protectionParamsList)
+ .setEncryptedRecoveryKeyBlob(KEY_MATERIAL)
+ .setWrappedApplicationKeys(appKeysList)
+ .setMaxAttempts(MAX_ATTEMPTS)
+ .setCounterId(COUNTER_ID)
+ .setServerParams(SERVER_PARAMS)
+ .setTrustedHardwarePublicKey(PUBLIC_KEY)
+ .build();
+
+ writer.writeKeyChainSnapshot(snapshot);
+
+ byte[] result = writer.getOutput();
+
+ PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot();
+ reader.initReader(result);
+
+ KeyChainSnapshot copy = reader.readKeyChainSnapshot();
+ assertThat(copy.getSnapshotVersion()).isEqualTo(SNAPSHOT_VERSION);
+ assertThat(copy.getKeyChainProtectionParams().get(0).getUserSecretType()).isEqualTo(1);
+ assertThat(copy.getEncryptedRecoveryKeyBlob()).isEqualTo(RECOVERY_KEY_MATERIAL);
+ assertThat(copy.getWrappedApplicationKeys().get(0).getAlias()).isEqualTo(ALIAS);
+ assertThat(copy.getMaxAttempts()).isEqualTo(MAX_ATTEMPTS);
+ assertThat(copy.getCounterId()).isEqualTo(COUNTER_ID);
+ assertThat(copy.getServerParams()).isEqualTo(SERVER_PARAMS);
+ assertThat(copy.getTrustedHardwarePublicKey()).isEqualTo(PUBLIC_KEY);
+
+ assertThrows(
+ IOException.class,
+ () -> reader.readKeyChainSnapshot());
+
+ verifyDeserialize(snapshot);
+ }
+
+ private void verifyDeserialize(KeyChainSnapshot snapshot) throws Exception {
+ byte[] serialized = PersistentKeyChainSnapshot.serialize(snapshot);
+ KeyChainSnapshot copy = PersistentKeyChainSnapshot.deserialize(serialized);
+ assertThat(copy.getSnapshotVersion())
+ .isEqualTo(snapshot.getSnapshotVersion());
+ assertThat(copy.getKeyChainProtectionParams().size())
+ .isEqualTo(copy.getKeyChainProtectionParams().size());
+ assertThat(copy.getEncryptedRecoveryKeyBlob())
+ .isEqualTo(snapshot.getEncryptedRecoveryKeyBlob());
+ assertThat(copy.getWrappedApplicationKeys().size())
+ .isEqualTo(snapshot.getWrappedApplicationKeys().size());
+ assertThat(copy.getMaxAttempts()).isEqualTo(snapshot.getMaxAttempts());
+ assertThat(copy.getCounterId()).isEqualTo(snapshot.getCounterId());
+ assertThat(copy.getServerParams()).isEqualTo(snapshot.getServerParams());
+ assertThat(copy.getTrustedHardwarePublicKey())
+ .isEqualTo(snapshot.getTrustedHardwarePublicKey());
+ }
+
+
+ public void testDeserialize_failsForNewerVersion() throws Exception {
+ byte[] newVersion = new byte[]{(byte) 2, (byte) 0, (byte) 0, (byte) 0};
+ assertThrows(
+ IOException.class,
+ () -> PersistentKeyChainSnapshot.deserialize(newVersion));
+ }
+
+ public void testDeserialize_failsForEmptyData() throws Exception {
+ byte[] empty = new byte[]{};
+ assertThrows(
+ IOException.class,
+ () -> PersistentKeyChainSnapshot.deserialize(empty));
+ }
+
+}
+