Add integration test for encrypted KV B&R
Bug: 11386661
Test: RoundTripTest
Change-Id: Ifd18961347d8ebaf00ddc12196f2ee6ec543b457
diff --git a/packages/BackupEncryption/test/robolectric-integration/Android.bp b/packages/BackupEncryption/test/robolectric-integration/Android.bp
index f696278..67365df 100644
--- a/packages/BackupEncryption/test/robolectric-integration/Android.bp
+++ b/packages/BackupEncryption/test/robolectric-integration/Android.bp
@@ -23,6 +23,7 @@
"platform-test-annotations",
"testng",
"truth-prebuilt",
+ "BackupEncryptionRoboTests",
],
static_libs: [
"androidx.test.core",
diff --git a/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java b/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java
index 8ec68fd..a432d91 100644
--- a/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java
+++ b/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java
@@ -19,21 +19,35 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.RecoveryController;
import androidx.test.core.app.ApplicationProvider;
import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.keys.KeyWrapUtils;
import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
import com.android.server.backup.encryption.keys.TertiaryKeyManager;
import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler;
import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
import com.android.server.backup.encryption.tasks.EncryptedFullBackupTask;
import com.android.server.backup.encryption.tasks.EncryptedFullRestoreTask;
+import com.android.server.backup.encryption.tasks.EncryptedKvBackupTask;
+import com.android.server.backup.encryption.tasks.EncryptedKvRestoreTask;
+import com.android.server.testing.shadows.DataEntity;
+import com.android.server.testing.shadows.ShadowBackupDataInput;
+import com.android.server.testing.shadows.ShadowBackupDataOutput;
+import com.android.server.testing.shadows.ShadowRecoveryController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -42,15 +56,29 @@
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
+import java.util.Optional;
import java.util.Map;
+import java.util.Set;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
+@Config(
+ shadows = {
+ ShadowBackupDataInput.class,
+ ShadowBackupDataOutput.class,
+ ShadowRecoveryController.class
+ })
@RunWith(RobolectricTestRunner.class)
public class RoundTripTest {
+ private static final DataEntity[] KEY_VALUE_DATA = {
+ new DataEntity("test_key_1", "test_value_1"),
+ new DataEntity("test_key_2", "test_value_2"),
+ new DataEntity("test_key_3", "test_value_3")
+ };
+
/** Amount of data we want to round trip in this test */
private static final int TEST_DATA_SIZE = 1024 * 1024; // 1MB
@@ -59,6 +87,7 @@
/** Key parameters used for the secondary encryption key */
private static final String KEY_ALGORITHM = "AES";
+
private static final int KEY_SIZE_BITS = 256;
/** Package name for our test package */
@@ -77,25 +106,82 @@
private RecoverableKeyStoreSecondaryKey mSecondaryKey;
/** Source of random material which is considered non-predictable in its' generation */
- private SecureRandom mSecureRandom = new SecureRandom();
+ private final SecureRandom mSecureRandom = new SecureRandom();
+
+ private RecoverableKeyStoreSecondaryKeyManager.RecoverableKeyStoreSecondaryKeyManagerProvider
+ mSecondaryKeyManagerProvider;
+ private DummyServer mDummyServer;
+ private RecoveryController mRecoveryController;
+
+ @Mock private ParcelFileDescriptor mParcelFileDescriptor;
@Before
- public void setUp() throws NoSuchAlgorithmException {
+ public void setUp() throws NoSuchAlgorithmException, InternalRecoveryServiceException {
+ MockitoAnnotations.initMocks(this);
+
+ ShadowBackupDataInput.reset();
+ ShadowBackupDataOutput.reset();
+
mContext = ApplicationProvider.getApplicationContext();
mSecondaryKey = new RecoverableKeyStoreSecondaryKey(TEST_KEY_ALIAS, generateAesKey());
+ mDummyServer = new DummyServer();
+ mSecondaryKeyManagerProvider =
+ () ->
+ new RecoverableKeyStoreSecondaryKeyManager(
+ RecoveryController.getInstance(mContext), mSecureRandom);
+
fillBuffer(mOriginalData);
}
@Test
- public void testRoundTrip() throws Exception {
- byte[] backupData = performBackup(mOriginalData);
+ public void testFull_nonIncrementalBackupAndRestoreAreSuccessful() throws Exception {
+ byte[] backupData = performFullBackup(mOriginalData);
assertThat(backupData).isNotEqualTo(mOriginalData);
- byte[] restoredData = performRestore(backupData);
+ byte[] restoredData = performFullRestore(backupData);
assertThat(restoredData).isEqualTo(mOriginalData);
}
- /** Perform a backup and return the backed-up representation of the data */
- private byte[] performBackup(byte[] backupData) throws Exception {
+ @Test
+ public void testKeyValue_nonIncrementalBackupAndRestoreAreSuccessful() throws Exception {
+ byte[] backupData = performNonIncrementalKeyValueBackup(KEY_VALUE_DATA);
+
+ // Get the secondary key used to do backup.
+ Optional<RecoverableKeyStoreSecondaryKey> secondaryKey =
+ mSecondaryKeyManagerProvider.get().get(mDummyServer.mSecondaryKeyAlias);
+ assertThat(secondaryKey.isPresent()).isTrue();
+
+ Set<DataEntity> restoredData = performKeyValueRestore(backupData, secondaryKey.get());
+
+ assertThat(restoredData).containsExactly(KEY_VALUE_DATA).inOrder();
+ }
+
+ /** Perform a key/value backup and return the backed-up representation of the data */
+ private byte[] performNonIncrementalKeyValueBackup(DataEntity[] backupData)
+ throws Exception {
+ // Populate test key/value data.
+ for (DataEntity entity : backupData) {
+ ShadowBackupDataInput.addEntity(entity);
+ }
+
+ EncryptedKvBackupTask.EncryptedKvBackupTaskFactory backupTaskFactory =
+ new EncryptedKvBackupTask.EncryptedKvBackupTaskFactory();
+ EncryptedKvBackupTask backupTask =
+ backupTaskFactory.newInstance(
+ mContext,
+ mSecureRandom,
+ mDummyServer,
+ CryptoSettings.getInstance(mContext),
+ mSecondaryKeyManagerProvider,
+ mParcelFileDescriptor,
+ TEST_PACKAGE_NAME);
+
+ backupTask.performBackup(/* incremental */ false);
+
+ return mDummyServer.mStoredData;
+ }
+
+ /** Perform a full backup and return the backed-up representation of the data */
+ private byte[] performFullBackup(byte[] backupData) throws Exception {
DummyServer dummyServer = new DummyServer();
EncryptedFullBackupTask backupTask =
EncryptedFullBackupTask.newInstance(
@@ -109,8 +195,24 @@
return dummyServer.mStoredData;
}
- /** Perform a restore and resturn the bytes obtained from the restore process */
- private byte[] performRestore(byte[] backupData)
+ private Set<DataEntity> performKeyValueRestore(
+ byte[] backupData, RecoverableKeyStoreSecondaryKey secondaryKey) throws Exception {
+ EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory restoreTaskFactory =
+ new EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory();
+ EncryptedKvRestoreTask restoreTask =
+ restoreTaskFactory.newInstance(
+ mContext,
+ mSecondaryKeyManagerProvider,
+ new FakeFullRestoreDownloader(backupData),
+ secondaryKey.getAlias(),
+ KeyWrapUtils.wrap(
+ secondaryKey.getSecretKey(), getTertiaryKey(secondaryKey)));
+ restoreTask.getRestoreData(mParcelFileDescriptor);
+ return ShadowBackupDataOutput.getEntities();
+ }
+
+ /** Perform a full restore and return the bytes obtained from the restore process */
+ private byte[] performFullRestore(byte[] backupData)
throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidAlgorithmParameterException, InvalidKeyException,
IllegalBlockSizeException {
@@ -118,7 +220,9 @@
EncryptedFullRestoreTask restoreTask =
EncryptedFullRestoreTask.newInstance(
- mContext, new FakeFullRestoreDownloader(backupData), getTertiaryKey());
+ mContext,
+ new FakeFullRestoreDownloader(backupData),
+ getTertiaryKey(mSecondaryKey));
byte[] buffer = new byte[READ_BUFFER_SIZE];
int bytesRead = restoreTask.readNextChunk(buffer);
@@ -131,7 +235,7 @@
}
/** Get the tertiary key for our test package from the key manager */
- private SecretKey getTertiaryKey()
+ private SecretKey getTertiaryKey(RecoverableKeyStoreSecondaryKey secondaryKey)
throws IllegalBlockSizeException, InvalidAlgorithmParameterException,
NoSuchAlgorithmException, IOException, NoSuchPaddingException,
InvalidKeyException {
@@ -140,7 +244,7 @@
mContext,
mSecureRandom,
TertiaryKeyRotationScheduler.getInstance(mContext),
- mSecondaryKey,
+ secondaryKey,
TEST_PACKAGE_NAME);
return tertiaryKeyManager.getKey();
}
@@ -162,13 +266,13 @@
}
/**
- * Dummy backup data endpoint. This stores the data so we can use it
- * in subsequent test steps.
+ * Dummy backup data endpoint. This stores the data so we can use it in subsequent test steps.
*/
private static class DummyServer implements CryptoBackupServer {
private static final String DUMMY_DOC_ID = "DummyDoc";
byte[] mStoredData = null;
+ String mSecondaryKeyAlias;
@Override
public String uploadIncrementalBackup(
@@ -190,7 +294,7 @@
@Override
public void setActiveSecondaryKeyAlias(
String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) {
- throw new RuntimeException("Not Implemented");
+ mSecondaryKeyAlias = keyAlias;
}
}