All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.backup.encryption.tasks.EncryptedFullBackupTaskTest Maven / Gradle / Ivy

/*
 * 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 org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;

import com.android.server.backup.encryption.chunk.ChunkHash;
import com.android.server.backup.encryption.chunking.ProtoStore;
import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer;
import com.android.server.backup.encryption.keys.TertiaryKeyManager;
import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkListing;
import com.android.server.backup.encryption.protos.nano.WrappedKeyProto.WrappedKey;
import com.android.server.backup.testing.CryptoTestUtils;

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.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Optional;

import javax.crypto.SecretKey;

@Config(shadows = {EncryptedBackupTaskTest.ShadowBackupFileBuilder.class})
@RunWith(RobolectricTestRunner.class)
public class EncryptedFullBackupTaskTest {
    private static final String TEST_PACKAGE_NAME = "com.example.package";
    private static final byte[] TEST_EXISTING_FINGERPRINT_MIXER_SALT =
            Arrays.copyOf(new byte[] {11}, ChunkHash.HASH_LENGTH_BYTES);
    private static final byte[] TEST_GENERATED_FINGERPRINT_MIXER_SALT =
            Arrays.copyOf(new byte[] {22}, ChunkHash.HASH_LENGTH_BYTES);
    private static final ChunkHash TEST_CHUNK_HASH_1 =
            new ChunkHash(Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES));
    private static final ChunkHash TEST_CHUNK_HASH_2 =
            new ChunkHash(Arrays.copyOf(new byte[] {2}, ChunkHash.HASH_LENGTH_BYTES));
    private static final int TEST_CHUNK_LENGTH_1 = 20;
    private static final int TEST_CHUNK_LENGTH_2 = 40;

    @Mock private ProtoStore mChunkListingStore;
    @Mock private TertiaryKeyManager mTertiaryKeyManager;
    @Mock private InputStream mInputStream;
    @Mock private EncryptedBackupTask mEncryptedBackupTask;
    @Mock private SecretKey mTertiaryKey;
    @Mock private SecureRandom mSecureRandom;

    private EncryptedFullBackupTask mTask;
    private ChunkListing mOldChunkListing;
    private ChunkListing mNewChunkListing;
    private WrappedKey mWrappedTertiaryKey;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mWrappedTertiaryKey = new WrappedKey();
        when(mTertiaryKeyManager.getKey()).thenReturn(mTertiaryKey);
        when(mTertiaryKeyManager.getWrappedKey()).thenReturn(mWrappedTertiaryKey);

        mOldChunkListing =
                CryptoTestUtils.newChunkListing(
                        /* docId */ null,
                        TEST_EXISTING_FINGERPRINT_MIXER_SALT,
                        ChunksMetadataProto.AES_256_GCM,
                        ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED,
                        CryptoTestUtils.newChunk(TEST_CHUNK_HASH_1.getHash(), TEST_CHUNK_LENGTH_1));
        mNewChunkListing =
                CryptoTestUtils.newChunkListing(
                        /* docId */ null,
                        /* fingerprintSalt */ null,
                        ChunksMetadataProto.AES_256_GCM,
                        ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED,
                        CryptoTestUtils.newChunk(TEST_CHUNK_HASH_1.getHash(), TEST_CHUNK_LENGTH_1),
                        CryptoTestUtils.newChunk(TEST_CHUNK_HASH_2.getHash(), TEST_CHUNK_LENGTH_2));
        when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any()))
                .thenReturn(mNewChunkListing);
        when(mEncryptedBackupTask.performIncrementalBackup(any(), any(), any()))
                .thenReturn(mNewChunkListing);
        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());

        doAnswer(invocation -> {
            byte[] byteArray = (byte[]) invocation.getArguments()[0];
            System.arraycopy(
                    TEST_GENERATED_FINGERPRINT_MIXER_SALT,
                    /* srcPos */ 0,
                    byteArray,
                    /* destPos */ 0,
                    FingerprintMixer.SALT_LENGTH_BYTES);
            return null;
        })
                .when(mSecureRandom)
                .nextBytes(any(byte[].class));

        mTask =
                new EncryptedFullBackupTask(
                        mChunkListingStore,
                        mTertiaryKeyManager,
                        mEncryptedBackupTask,
                        mInputStream,
                        TEST_PACKAGE_NAME,
                        mSecureRandom);
    }

    @Test
    public void call_existingChunkListingButTertiaryKeyRotated_performsNonIncrementalBackup()
            throws Exception {
        when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(true);
        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
                .thenReturn(Optional.of(mOldChunkListing));

        mTask.call();

        verify(mEncryptedBackupTask)
                .performNonIncrementalBackup(
                        eq(mTertiaryKey),
                        eq(mWrappedTertiaryKey),
                        eq(TEST_GENERATED_FINGERPRINT_MIXER_SALT));
    }

    @Test
    public void call_noExistingChunkListing_performsNonIncrementalBackup() throws Exception {
        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
        mTask.call();
        verify(mEncryptedBackupTask)
                .performNonIncrementalBackup(
                        eq(mTertiaryKey),
                        eq(mWrappedTertiaryKey),
                        eq(TEST_GENERATED_FINGERPRINT_MIXER_SALT));
    }

    @Test
    public void call_existingChunkListing_performsIncrementalBackup() throws Exception {
        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
                .thenReturn(Optional.of(mOldChunkListing));
        mTask.call();
        verify(mEncryptedBackupTask)
                .performIncrementalBackup(
                        eq(mTertiaryKey), eq(mWrappedTertiaryKey), eq(mOldChunkListing));
    }

    @Test
    public void
            call_existingChunkListingWithNoFingerprintMixerSalt_doesntSetSaltBeforeIncBackup()
                    throws Exception {
        mOldChunkListing.fingerprintMixerSalt = new byte[0];
        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
                .thenReturn(Optional.of(mOldChunkListing));

        mTask.call();

        verify(mEncryptedBackupTask)
                .performIncrementalBackup(
                        eq(mTertiaryKey), eq(mWrappedTertiaryKey), eq(mOldChunkListing));
    }

    @Test
    public void call_noExistingChunkListing_storesNewChunkListing() throws Exception {
        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
        mTask.call();
        verify(mChunkListingStore).saveProto(TEST_PACKAGE_NAME, mNewChunkListing);
    }

    @Test
    public void call_existingChunkListing_storesNewChunkListing() throws Exception {
        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
                .thenReturn(Optional.of(mOldChunkListing));
        mTask.call();
        verify(mChunkListingStore).saveProto(TEST_PACKAGE_NAME, mNewChunkListing);
    }

    @Test
    public void call_exceptionDuringBackup_doesNotSaveNewChunkListing() throws Exception {
        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
        when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any()))
                .thenThrow(GeneralSecurityException.class);

        assertThrows(Exception.class, () -> mTask.call());

        assertThat(mChunkListingStore.loadProto(TEST_PACKAGE_NAME).isPresent()).isFalse();
    }

    @Test
    public void call_incrementalThrowsPermanentException_clearsState() throws Exception {
        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
                .thenReturn(Optional.of(mOldChunkListing));
        when(mEncryptedBackupTask.performIncrementalBackup(any(), any(), any()))
                .thenThrow(IOException.class);

        assertThrows(IOException.class, () -> mTask.call());

        verify(mChunkListingStore).deleteProto(TEST_PACKAGE_NAME);
    }

    @Test
    public void call_closesInputStream() throws Exception {
        mTask.call();
        verify(mInputStream).close();
    }

    @Test
    public void cancel_cancelsTask() throws Exception {
        mTask.cancel();
        verify(mEncryptedBackupTask).cancel();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy