Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fde3621d authored by Al Sutton's avatar Al Sutton
Browse files

Add backup data round trip test

Bug: 111386661
Test: make RunBackupEncryptionRoboIntegTests
Change-Id: Idbfec4ff7bcb0d0b44931884d1c11af7a937bd30
parent 836548b7
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
// 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",
}
+24 −0
Original line number Diff line number Diff line
<?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>
+17 −0
Original line number Diff line number Diff line
#
# 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
+217 −0
Original line number Diff line number Diff line
/*
 * 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.
        }
    }
}