diff options
author | Al Sutton <alsutton@google.com> | 2019-09-26 15:55:04 +0100 |
---|---|---|
committer | Al Sutton <alsutton@google.com> | 2019-09-27 14:01:32 +0100 |
commit | fde3621d31cd9f5f2d189e857ec9be795abaf624 (patch) | |
tree | 4be6a63ec46aef3e5e277fb95e1ee5ad24c33f57 /packages/BackupEncryption | |
parent | 836548b7b95323e73510f6f0e0530d527b77a73e (diff) |
Add backup data round trip test
Bug: 111386661
Test: make RunBackupEncryptionRoboIntegTests
Change-Id: Idbfec4ff7bcb0d0b44931884d1c11af7a937bd30
Diffstat (limited to 'packages/BackupEncryption')
4 files changed, 291 insertions, 0 deletions
diff --git a/packages/BackupEncryption/test/robolectric-integration/Android.bp b/packages/BackupEncryption/test/robolectric-integration/Android.bp new file mode 100644 index 000000000000..f696278ab967 --- /dev/null +++ b/packages/BackupEncryption/test/robolectric-integration/Android.bp @@ -0,0 +1,33 @@ +// Copyright (C) 2019 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. + +android_robolectric_test { + name: "BackupEncryptionRoboIntegTests", + srcs: [ + "src/**/*.java", + ], + java_resource_dirs: ["config"], + libs: [ + "backup-encryption-protos", + "platform-test-annotations", + "testng", + "truth-prebuilt", + ], + static_libs: [ + "androidx.test.core", + "androidx.test.runner", + "androidx.test.rules", + ], + instrumentation_for: "BackupEncryption", +} diff --git a/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml b/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml new file mode 100644 index 000000000000..c3930cc7c4f1 --- /dev/null +++ b/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + coreApp="true" + package="com.android.server.backup.encryption.robointeg"> + + <application/> + +</manifest> diff --git a/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties b/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties new file mode 100644 index 000000000000..26fceb3f84a4 --- /dev/null +++ b/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties @@ -0,0 +1,17 @@ +# +# Copyright (C) 2019 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. +# + +sdk=NEWEST_SDK 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 new file mode 100644 index 000000000000..8ec68fdf822d --- /dev/null +++ b/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2019 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.backup.encryption; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.server.backup.encryption.client.CryptoBackupServer; +import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; +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 org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Map; + +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; + +@RunWith(RobolectricTestRunner.class) +public class RoundTripTest { + /** Amount of data we want to round trip in this test */ + private static final int TEST_DATA_SIZE = 1024 * 1024; // 1MB + + /** Buffer size used when reading data from the restore task */ + private static final int READ_BUFFER_SIZE = 1024; // 1024 byte buffer. + + /** 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 */ + private static final String TEST_PACKAGE_NAME = "com.android.backup.test"; + + /** The name we use to refer to our secondary key */ + private static final String TEST_KEY_ALIAS = "test/backup/KEY_ALIAS"; + + /** Original data used for comparison after round trip */ + private final byte[] mOriginalData = new byte[TEST_DATA_SIZE]; + + /** App context, used to store the key data and chunk listings */ + private Context mContext; + + /** The secondary key we're using for the test */ + private RecoverableKeyStoreSecondaryKey mSecondaryKey; + + /** Source of random material which is considered non-predictable in its' generation */ + private SecureRandom mSecureRandom = new SecureRandom(); + + @Before + public void setUp() throws NoSuchAlgorithmException { + mContext = ApplicationProvider.getApplicationContext(); + mSecondaryKey = new RecoverableKeyStoreSecondaryKey(TEST_KEY_ALIAS, generateAesKey()); + fillBuffer(mOriginalData); + } + + @Test + public void testRoundTrip() throws Exception { + byte[] backupData = performBackup(mOriginalData); + assertThat(backupData).isNotEqualTo(mOriginalData); + byte[] restoredData = performRestore(backupData); + assertThat(restoredData).isEqualTo(mOriginalData); + } + + /** Perform a backup and return the backed-up representation of the data */ + private byte[] performBackup(byte[] backupData) throws Exception { + DummyServer dummyServer = new DummyServer(); + EncryptedFullBackupTask backupTask = + EncryptedFullBackupTask.newInstance( + mContext, + dummyServer, + mSecureRandom, + mSecondaryKey, + TEST_PACKAGE_NAME, + new ByteArrayInputStream(backupData)); + backupTask.call(); + return dummyServer.mStoredData; + } + + /** Perform a restore and resturn the bytes obtained from the restore process */ + private byte[] performRestore(byte[] backupData) + throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, + InvalidAlgorithmParameterException, InvalidKeyException, + IllegalBlockSizeException { + ByteArrayOutputStream decryptedOutput = new ByteArrayOutputStream(); + + EncryptedFullRestoreTask restoreTask = + EncryptedFullRestoreTask.newInstance( + mContext, new FakeFullRestoreDownloader(backupData), getTertiaryKey()); + + byte[] buffer = new byte[READ_BUFFER_SIZE]; + int bytesRead = restoreTask.readNextChunk(buffer); + while (bytesRead != -1) { + decryptedOutput.write(buffer, 0, bytesRead); + bytesRead = restoreTask.readNextChunk(buffer); + } + + return decryptedOutput.toByteArray(); + } + + /** Get the tertiary key for our test package from the key manager */ + private SecretKey getTertiaryKey() + throws IllegalBlockSizeException, InvalidAlgorithmParameterException, + NoSuchAlgorithmException, IOException, NoSuchPaddingException, + InvalidKeyException { + TertiaryKeyManager tertiaryKeyManager = + new TertiaryKeyManager( + mContext, + mSecureRandom, + TertiaryKeyRotationScheduler.getInstance(mContext), + mSecondaryKey, + TEST_PACKAGE_NAME); + return tertiaryKeyManager.getKey(); + } + + /** Fill a buffer with data in a predictable way */ + private void fillBuffer(byte[] buffer) { + byte loopingCounter = 0; + for (int i = 0; i < buffer.length; i++) { + buffer[i] = loopingCounter; + loopingCounter++; + } + } + + /** Generate a new, random, AES key */ + public static SecretKey generateAesKey() throws NoSuchAlgorithmException { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); + keyGenerator.init(KEY_SIZE_BITS); + return keyGenerator.generateKey(); + } + + /** + * 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; + + @Override + public String uploadIncrementalBackup( + String packageName, + String oldDocId, + byte[] diffScript, + WrappedKeyProto.WrappedKey tertiaryKey) { + throw new RuntimeException("Not Implemented"); + } + + @Override + public String uploadNonIncrementalBackup( + String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey) { + assertThat(packageName).isEqualTo(TEST_PACKAGE_NAME); + mStoredData = data; + return DUMMY_DOC_ID; + } + + @Override + public void setActiveSecondaryKeyAlias( + String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) { + throw new RuntimeException("Not Implemented"); + } + } + + /** Fake package wrapper which returns data from a byte array. */ + private static class FakeFullRestoreDownloader extends FullRestoreDownloader { + private final ByteArrayInputStream mData; + + FakeFullRestoreDownloader(byte[] data) { + // We override all methods of the superclass, so it does not require any collaborators. + super(); + mData = new ByteArrayInputStream(data); + } + + @Override + public int readNextChunk(byte[] buffer) throws IOException { + return mData.read(buffer); + } + + @Override + public void finish(FinishType finishType) { + // Do nothing. + } + } +} |