Import EncryptedKvBackupTask

Bug: 111386661
Test: atest EncryptedKvBackupTaskTest
Change-Id: Id9cd0a57c3ed33f18d68e7fad60035e8a956a1de
This commit is contained in:
Ruslan Tkhakokhov
2019-09-30 17:36:12 +01:00
parent c087fbc6f4
commit f45db5e35a
4 changed files with 637 additions and 0 deletions

View File

@@ -0,0 +1,244 @@
/*
* 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.tasks;
import android.annotation.Nullable;
import android.app.backup.BackupDataInput;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.security.keystore.recovery.InternalRecoveryServiceException;
import android.security.keystore.recovery.LockScreenRequiredException;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.backup.encryption.CryptoSettings;
import com.android.server.backup.encryption.chunking.ProtoStore;
import com.android.server.backup.encryption.client.CryptoBackupServer;
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.ChunksMetadataProto;
import com.android.server.backup.encryption.protos.nano.KeyValueListingProto;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.util.Optional;
// TODO(b/141975695): Create a base class for EncryptedKvBackupTask and EncryptedFullBackupTask.
/** Performs encrypted key value backup, handling rotating the tertiary key as necessary. */
public class EncryptedKvBackupTask {
private static final String TAG = "EncryptedKvBackupTask";
private final TertiaryKeyManager mTertiaryKeyManager;
private final RecoverableKeyStoreSecondaryKey mSecondaryKey;
private final ProtoStore<KeyValueListingProto.KeyValueListing> mKeyValueListingStore;
private final ProtoStore<ChunksMetadataProto.ChunkListing> mChunkListingStore;
private final KvBackupEncrypter mKvBackupEncrypter;
private final EncryptedBackupTask mEncryptedBackupTask;
private final String mPackageName;
/** Constructs new instances of {@link EncryptedKvBackupTask}. */
public static class EncryptedKvBackupTaskFactory {
/**
* Creates a new instance.
*
* <p>Either initializes encrypted backup or loads an existing secondary key as necessary.
*
* @param cryptoSettings to load secondary key state from
* @param fileDescriptor to read the backup data from
*/
public EncryptedKvBackupTask newInstance(
Context context,
SecureRandom secureRandom,
CryptoBackupServer cryptoBackupServer,
CryptoSettings cryptoSettings,
RecoverableKeyStoreSecondaryKeyManager
.RecoverableKeyStoreSecondaryKeyManagerProvider
recoverableSecondaryKeyManagerProvider,
ParcelFileDescriptor fileDescriptor,
String packageName)
throws IOException, UnrecoverableKeyException, LockScreenRequiredException,
InternalRecoveryServiceException, InvalidKeyException {
RecoverableKeyStoreSecondaryKey secondaryKey =
new InitializeRecoverableSecondaryKeyTask(
context,
cryptoSettings,
recoverableSecondaryKeyManagerProvider.get(),
cryptoBackupServer)
.run();
KvBackupEncrypter backupEncrypter =
new KvBackupEncrypter(new BackupDataInput(fileDescriptor.getFileDescriptor()));
TertiaryKeyManager tertiaryKeyManager =
new TertiaryKeyManager(
context,
secureRandom,
TertiaryKeyRotationScheduler.getInstance(context),
secondaryKey,
packageName);
return new EncryptedKvBackupTask(
tertiaryKeyManager,
ProtoStore.createKeyValueListingStore(context),
secondaryKey,
ProtoStore.createChunkListingStore(context),
backupEncrypter,
new EncryptedBackupTask(
cryptoBackupServer, secureRandom, packageName, backupEncrypter),
packageName);
}
}
@VisibleForTesting
EncryptedKvBackupTask(
TertiaryKeyManager tertiaryKeyManager,
ProtoStore<KeyValueListingProto.KeyValueListing> keyValueListingStore,
RecoverableKeyStoreSecondaryKey secondaryKey,
ProtoStore<ChunksMetadataProto.ChunkListing> chunkListingStore,
KvBackupEncrypter kvBackupEncrypter,
EncryptedBackupTask encryptedBackupTask,
String packageName) {
mTertiaryKeyManager = tertiaryKeyManager;
mSecondaryKey = secondaryKey;
mKeyValueListingStore = keyValueListingStore;
mChunkListingStore = chunkListingStore;
mKvBackupEncrypter = kvBackupEncrypter;
mEncryptedBackupTask = encryptedBackupTask;
mPackageName = packageName;
}
/**
* Reads backup data from the file descriptor provided in the construtor, encrypts it and
* uploads it to the server.
*
* <p>The {@code incremental} flag indicates if the backup data provided is incremental or a
* complete set. Incremental backup is not possible if no previous crypto state exists, or the
* tertiary key must be rotated in the next backup. If the caller requests incremental backup
* but it is not possible, then the backup will not start and this method will throw {@link
* NonIncrementalBackupRequiredException}.
*
* <p>TODO(b/70704456): Update return code to indicate that we require non-incremental backup.
*
* @param incremental {@code true} if the data provided is a diff from the previous backup,
* {@code false} if it is a complete set
* @throws NonIncrementalBackupRequiredException if the caller provides an incremental backup but the task
* requires non-incremental backup
*/
public void performBackup(boolean incremental)
throws GeneralSecurityException, IOException, NoSuchMethodException,
InstantiationException, IllegalAccessException, InvocationTargetException,
NonIncrementalBackupRequiredException {
if (mTertiaryKeyManager.wasKeyRotated()) {
Slog.d(TAG, "Tertiary key is new so clearing package state.");
deleteListings(mPackageName);
}
Optional<Pair<KeyValueListingProto.KeyValueListing, ChunksMetadataProto.ChunkListing>>
oldListings = getListingsAndEnsureConsistency(mPackageName);
if (oldListings.isPresent() && !incremental) {
Slog.d(
TAG,
"Non-incremental backup requested but incremental state existed, clearing it");
deleteListings(mPackageName);
oldListings = Optional.empty();
}
if (!oldListings.isPresent() && incremental) {
// If we don't have any state then we require a non-incremental backup, but this backup
// is incremental.
throw new NonIncrementalBackupRequiredException();
}
if (oldListings.isPresent()) {
mKvBackupEncrypter.setOldKeyValueListing(oldListings.get().first);
}
ChunksMetadataProto.ChunkListing newChunkListing;
if (oldListings.isPresent()) {
Slog.v(TAG, "Old listings existed, performing incremental backup");
newChunkListing =
mEncryptedBackupTask.performIncrementalBackup(
mTertiaryKeyManager.getKey(),
mTertiaryKeyManager.getWrappedKey(),
oldListings.get().second);
} else {
Slog.v(TAG, "Old listings did not exist, performing non-incremental backup");
// kv backups don't use this salt because they don't involve content-defined chunking.
byte[] fingerprintMixerSalt = null;
newChunkListing =
mEncryptedBackupTask.performNonIncrementalBackup(
mTertiaryKeyManager.getKey(),
mTertiaryKeyManager.getWrappedKey(),
fingerprintMixerSalt);
}
Slog.v(TAG, "Backup and upload succeeded, saving new listings");
saveListings(mPackageName, mKvBackupEncrypter.getNewKeyValueListing(), newChunkListing);
}
private Optional<Pair<KeyValueListingProto.KeyValueListing, ChunksMetadataProto.ChunkListing>>
getListingsAndEnsureConsistency(String packageName)
throws IOException, InvocationTargetException, NoSuchMethodException,
InstantiationException, IllegalAccessException {
Optional<KeyValueListingProto.KeyValueListing> keyValueListing =
mKeyValueListingStore.loadProto(packageName);
Optional<ChunksMetadataProto.ChunkListing> chunkListing =
mChunkListingStore.loadProto(packageName);
// Normally either both protos exist or neither exist, but we correct this just in case.
boolean bothPresent = keyValueListing.isPresent() && chunkListing.isPresent();
if (!bothPresent) {
Slog.d(
TAG,
"Both listing were not present, clearing state, key value="
+ keyValueListing.isPresent()
+ ", chunk="
+ chunkListing.isPresent());
deleteListings(packageName);
return Optional.empty();
}
return Optional.of(Pair.create(keyValueListing.get(), chunkListing.get()));
}
private void saveListings(
String packageName,
KeyValueListingProto.KeyValueListing keyValueListing,
ChunksMetadataProto.ChunkListing chunkListing) {
try {
mKeyValueListingStore.saveProto(packageName, keyValueListing);
mChunkListingStore.saveProto(packageName, chunkListing);
} catch (IOException e) {
// If a problem occurred while saving either listing then they may be inconsistent, so
// delete
// both.
Slog.w(TAG, "Unable to save listings, deleting both for consistency", e);
deleteListings(packageName);
}
}
private void deleteListings(String packageName) {
mKeyValueListingStore.deleteProto(packageName);
mChunkListingStore.deleteProto(packageName);
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.tasks;
// TODO(141840878): Update documentation.
/**
* Exception thrown when the framework provides an incremental backup but the transport requires a
* non-incremental backup.
*/
public class NonIncrementalBackupRequiredException extends Exception {}

View File

@@ -0,0 +1,356 @@
/*
* 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.tasks;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertThrows;
import android.app.Application;
import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import com.android.server.backup.encryption.chunk.ChunkHash;
import com.android.server.backup.encryption.chunking.ProtoStore;
import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
import com.android.server.backup.encryption.keys.TertiaryKeyManager;
import com.android.server.backup.encryption.kv.KeyValueListingBuilder;
import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
import com.android.server.backup.encryption.protos.nano.KeyValueListingProto;
import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
import com.android.server.backup.testing.CryptoTestUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import javax.crypto.SecretKey;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class EncryptedKvBackupTaskTest {
private static final boolean INCREMENTAL = true;
private static final boolean NON_INCREMENTAL = false;
private static final String TEST_PACKAGE_1 = "com.example.app1";
private static final String TEST_KEY_1 = "key_1";
private static final String TEST_KEY_2 = "key_2";
private static final ChunkHash TEST_HASH_1 =
new ChunkHash(Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES));
private static final ChunkHash TEST_HASH_2 =
new ChunkHash(Arrays.copyOf(new byte[] {2}, ChunkHash.HASH_LENGTH_BYTES));
private static final int TEST_LENGTH_1 = 200;
private static final int TEST_LENGTH_2 = 300;
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@Captor private ArgumentCaptor<ChunksMetadataProto.ChunkListing> mChunkListingCaptor;
@Mock private TertiaryKeyManager mTertiaryKeyManager;
@Mock private RecoverableKeyStoreSecondaryKey mSecondaryKey;
@Mock private ProtoStore<KeyValueListingProto.KeyValueListing> mKeyValueListingStore;
@Mock private ProtoStore<ChunksMetadataProto.ChunkListing> mChunkListingStore;
@Mock private KvBackupEncrypter mKvBackupEncrypter;
@Mock private EncryptedBackupTask mEncryptedBackupTask;
@Mock private SecretKey mTertiaryKey;
private WrappedKeyProto.WrappedKey mWrappedTertiaryKey;
private KeyValueListingProto.KeyValueListing mNewKeyValueListing;
private ChunksMetadataProto.ChunkListing mNewChunkListing;
private EncryptedKvBackupTask mTask;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
Application application = ApplicationProvider.getApplicationContext();
mKeyValueListingStore = ProtoStore.createKeyValueListingStore(application);
mChunkListingStore = ProtoStore.createChunkListingStore(application);
mWrappedTertiaryKey = new WrappedKeyProto.WrappedKey();
when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(false);
when(mTertiaryKeyManager.getKey()).thenReturn(mTertiaryKey);
when(mTertiaryKeyManager.getWrappedKey()).thenReturn(mWrappedTertiaryKey);
mNewKeyValueListing =
createKeyValueListing(
CryptoTestUtils.mapOf(
new Pair<>(TEST_KEY_1, TEST_HASH_1),
new Pair<>(TEST_KEY_2, TEST_HASH_2)));
mNewChunkListing =
createChunkListing(
CryptoTestUtils.mapOf(
new Pair<>(TEST_HASH_1, TEST_LENGTH_1),
new Pair<>(TEST_HASH_2, TEST_LENGTH_2)));
when(mKvBackupEncrypter.getNewKeyValueListing()).thenReturn(mNewKeyValueListing);
when(mEncryptedBackupTask.performIncrementalBackup(
eq(mTertiaryKey), eq(mWrappedTertiaryKey), any()))
.thenReturn(mNewChunkListing);
when(mEncryptedBackupTask.performNonIncrementalBackup(
eq(mTertiaryKey), eq(mWrappedTertiaryKey), any()))
.thenReturn(mNewChunkListing);
mTask =
new EncryptedKvBackupTask(
mTertiaryKeyManager,
mKeyValueListingStore,
mSecondaryKey,
mChunkListingStore,
mKvBackupEncrypter,
mEncryptedBackupTask,
TEST_PACKAGE_1);
}
@Test
public void testPerformBackup_rotationRequired_deletesListings() throws Exception {
mKeyValueListingStore.saveProto(
TEST_PACKAGE_1,
createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1))));
mChunkListingStore.saveProto(
TEST_PACKAGE_1,
createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1))));
when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(true);
// Throw an IOException so it aborts before saving the new listings.
when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any()))
.thenThrow(IOException.class);
assertThrows(IOException.class, () -> mTask.performBackup(NON_INCREMENTAL));
assertFalse(mKeyValueListingStore.loadProto(TEST_PACKAGE_1).isPresent());
assertFalse(mChunkListingStore.loadProto(TEST_PACKAGE_1).isPresent());
}
@Test
public void testPerformBackup_rotationRequiredButIncremental_throws() throws Exception {
mKeyValueListingStore.saveProto(
TEST_PACKAGE_1,
createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1))));
mChunkListingStore.saveProto(
TEST_PACKAGE_1,
createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1))));
when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(true);
assertThrows(NonIncrementalBackupRequiredException.class,
() -> mTask.performBackup(INCREMENTAL));
}
@Test
public void testPerformBackup_rotationRequiredAndNonIncremental_performsNonIncrementalBackup()
throws Exception {
mKeyValueListingStore.saveProto(
TEST_PACKAGE_1,
createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1))));
mChunkListingStore.saveProto(
TEST_PACKAGE_1,
createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1))));
when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(true);
mTask.performBackup(NON_INCREMENTAL);
verify(mEncryptedBackupTask)
.performNonIncrementalBackup(eq(mTertiaryKey), eq(mWrappedTertiaryKey), any());
}
@Test
public void testPerformBackup_existingStateButNonIncremental_deletesListings() throws Exception {
mKeyValueListingStore.saveProto(
TEST_PACKAGE_1,
createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1))));
mChunkListingStore.saveProto(
TEST_PACKAGE_1,
createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1))));
// Throw an IOException so it aborts before saving the new listings.
when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any()))
.thenThrow(IOException.class);
assertThrows(IOException.class, () -> mTask.performBackup(NON_INCREMENTAL));
assertFalse(mKeyValueListingStore.loadProto(TEST_PACKAGE_1).isPresent());
assertFalse(mChunkListingStore.loadProto(TEST_PACKAGE_1).isPresent());
}
@Test
public void testPerformBackup_keyValueListingMissing_deletesChunkListingAndPerformsNonIncremental()
throws Exception {
mChunkListingStore.saveProto(
TEST_PACKAGE_1,
createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1))));
// Throw an IOException so it aborts before saving the new listings.
when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any()))
.thenThrow(IOException.class);
assertThrows(IOException.class, () -> mTask.performBackup(NON_INCREMENTAL));
verify(mEncryptedBackupTask).performNonIncrementalBackup(any(), any(), any());
assertFalse(mKeyValueListingStore.loadProto(TEST_PACKAGE_1).isPresent());
assertFalse(mChunkListingStore.loadProto(TEST_PACKAGE_1).isPresent());
}
@Test
public void testPerformBackup_chunkListingMissing_deletesKeyValueListingAndPerformsNonIncremental()
throws Exception {
mKeyValueListingStore.saveProto(
TEST_PACKAGE_1,
createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1))));
// Throw an IOException so it aborts before saving the new listings.
when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any()))
.thenThrow(IOException.class);
assertThrows(IOException.class, () -> mTask.performBackup(NON_INCREMENTAL));
verify(mEncryptedBackupTask).performNonIncrementalBackup(any(), any(), any());
assertFalse(mKeyValueListingStore.loadProto(TEST_PACKAGE_1).isPresent());
assertFalse(mChunkListingStore.loadProto(TEST_PACKAGE_1).isPresent());
}
@Test
public void testPerformBackup_existingStateAndIncremental_performsIncrementalBackup()
throws Exception {
mKeyValueListingStore.saveProto(
TEST_PACKAGE_1,
createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1))));
ChunksMetadataProto.ChunkListing oldChunkListing =
createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1)));
mChunkListingStore.saveProto(TEST_PACKAGE_1, oldChunkListing);
mTask.performBackup(INCREMENTAL);
verify(mEncryptedBackupTask)
.performIncrementalBackup(
eq(mTertiaryKey), eq(mWrappedTertiaryKey), mChunkListingCaptor.capture());
assertChunkListingsEqual(mChunkListingCaptor.getValue(), oldChunkListing);
}
@Test
public void testPerformBackup_noExistingStateAndNonIncremental_performsNonIncrementalBackup()
throws Exception {
mTask.performBackup(NON_INCREMENTAL);
verify(mEncryptedBackupTask)
.performNonIncrementalBackup(eq(mTertiaryKey), eq(mWrappedTertiaryKey), eq(null));
}
@Test
public void testPerformBackup_incremental_savesNewListings() throws Exception {
mKeyValueListingStore.saveProto(
TEST_PACKAGE_1,
createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1))));
mChunkListingStore.saveProto(
TEST_PACKAGE_1,
createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1))));
mTask.performBackup(INCREMENTAL);
KeyValueListingProto.KeyValueListing actualKeyValueListing =
mKeyValueListingStore.loadProto(TEST_PACKAGE_1).get();
ChunksMetadataProto.ChunkListing actualChunkListing =
mChunkListingStore.loadProto(TEST_PACKAGE_1).get();
assertKeyValueListingsEqual(actualKeyValueListing, mNewKeyValueListing);
assertChunkListingsEqual(actualChunkListing, mNewChunkListing);
}
@Test
public void testPerformBackup_nonIncremental_savesNewListings() throws Exception {
mTask.performBackup(NON_INCREMENTAL);
KeyValueListingProto.KeyValueListing actualKeyValueListing =
mKeyValueListingStore.loadProto(TEST_PACKAGE_1).get();
ChunksMetadataProto.ChunkListing actualChunkListing =
mChunkListingStore.loadProto(TEST_PACKAGE_1).get();
assertKeyValueListingsEqual(actualKeyValueListing, mNewKeyValueListing);
assertChunkListingsEqual(actualChunkListing, mNewChunkListing);
}
private static KeyValueListingProto.KeyValueListing createKeyValueListing(
Map<String, ChunkHash> pairs) {
return new KeyValueListingBuilder().addAll(pairs).build();
}
private static ChunksMetadataProto.ChunkListing createChunkListing(
Map<ChunkHash, Integer> chunks) {
ChunksMetadataProto.Chunk[] listingChunks = new ChunksMetadataProto.Chunk[chunks.size()];
int chunksAdded = 0;
for (Entry<ChunkHash, Integer> entry : chunks.entrySet()) {
listingChunks[chunksAdded] = CryptoTestUtils.newChunk(entry.getKey(), entry.getValue());
chunksAdded++;
}
return CryptoTestUtils.newChunkListingWithoutDocId(
/* fingerprintSalt */ new byte[0],
ChunksMetadataProto.AES_256_GCM,
ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED,
listingChunks);
}
private static void assertKeyValueListingsEqual(
KeyValueListingProto.KeyValueListing actual,
KeyValueListingProto.KeyValueListing expected) {
KeyValueListingProto.KeyValueEntry[] actualEntries = actual.entries;
KeyValueListingProto.KeyValueEntry[] expectedEntries = expected.entries;
assertThat(actualEntries.length).isEqualTo(expectedEntries.length);
for (int i = 0; i < actualEntries.length; i++) {
assertWithMessage("entry " + i)
.that(actualEntries[i].key)
.isEqualTo(expectedEntries[i].key);
assertWithMessage("entry " + i)
.that(actualEntries[i].hash)
.isEqualTo(expectedEntries[i].hash);
}
}
private static void assertChunkListingsEqual(
ChunksMetadataProto.ChunkListing actual, ChunksMetadataProto.ChunkListing expected) {
ChunksMetadataProto.Chunk[] actualChunks = actual.chunks;
ChunksMetadataProto.Chunk[] expectedChunks = expected.chunks;
assertThat(actualChunks.length).isEqualTo(expectedChunks.length);
for (int i = 0; i < actualChunks.length; i++) {
assertWithMessage("chunk " + i)
.that(actualChunks[i].hash)
.isEqualTo(expectedChunks[i].hash);
assertWithMessage("chunk " + i)
.that(actualChunks[i].length)
.isEqualTo(expectedChunks[i].length);
}
assertThat(actual.cipherType).isEqualTo(expected.cipherType);
assertThat(actual.documentId)
.isEqualTo(expected.documentId == null ? "" : expected.documentId);
}
}

View File

@@ -16,6 +16,8 @@
package com.android.server.backup.testing;
import android.util.Pair;
import com.android.server.backup.encryption.chunk.ChunkHash;
import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
import com.android.server.backup.encryption.protos.nano.KeyValuePairProto;
@@ -23,6 +25,8 @@ import com.android.server.backup.encryption.protos.nano.KeyValuePairProto;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.crypto.KeyGenerator;
@@ -162,4 +166,12 @@ public class CryptoTestUtils {
clone.checksum = Arrays.copyOf(original.checksum, original.checksum.length);
return clone;
}
public static <K, V> Map<K, V> mapOf(Pair<K, V>... pairs) {
Map<K, V> map = new HashMap<>();
for (Pair<K, V> pair : pairs) {
map.put(pair.first, pair.second);
}
return map;
}
}