Add integration test for encrypted KV B&R
Bug: 11386661 Test: RoundTripTest Change-Id: Ifd18961347d8ebaf00ddc12196f2ee6ec543b457
This commit is contained in:
@@ -23,6 +23,7 @@ android_robolectric_test {
|
||||
"platform-test-annotations",
|
||||
"testng",
|
||||
"truth-prebuilt",
|
||||
"BackupEncryptionRoboTests",
|
||||
],
|
||||
static_libs: [
|
||||
"androidx.test.core",
|
||||
|
||||
@@ -19,21 +19,35 @@ package com.android.server.backup.encryption;
|
||||
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.InvalidAlgorithmParameterException;
|
||||
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 @@ public class RoundTripTest {
|
||||
|
||||
/** 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 @@ public class RoundTripTest {
|
||||
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 @@ public class RoundTripTest {
|
||||
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 @@ public class RoundTripTest {
|
||||
|
||||
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 @@ public class RoundTripTest {
|
||||
}
|
||||
|
||||
/** 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 @@ public class RoundTripTest {
|
||||
mContext,
|
||||
mSecureRandom,
|
||||
TertiaryKeyRotationScheduler.getInstance(mContext),
|
||||
mSecondaryKey,
|
||||
secondaryKey,
|
||||
TEST_PACKAGE_NAME);
|
||||
return tertiaryKeyManager.getKey();
|
||||
}
|
||||
@@ -162,13 +266,13 @@ public class RoundTripTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public class RoundTripTest {
|
||||
@Override
|
||||
public void setActiveSecondaryKeyAlias(
|
||||
String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) {
|
||||
throw new RuntimeException("Not Implemented");
|
||||
mSecondaryKeyAlias = keyAlias;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user