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

src.com.android.server.backup.encryption.tasks.RotateSecondaryKeyTaskTest 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.android.server.backup.testing.CryptoTestUtils.generateAesKey;

import static com.google.common.truth.Truth.assertThat;

import static org.testng.Assert.assertFalse;

import android.app.Application;
import android.platform.test.annotations.Presubmit;
import android.security.keystore.recovery.RecoveryController;

import androidx.test.core.app.ApplicationProvider;

import com.android.server.backup.encryption.CryptoSettings;
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.TertiaryKeyStore;
import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
import com.android.server.testing.fakes.FakeCryptoBackupServer;
import com.android.server.testing.shadows.ShadowRecoveryController;

import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.SecretKey;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Presubmit
@Config(shadows = {ShadowRecoveryController.class, ShadowRecoveryController.class})
public class RotateSecondaryKeyTaskTest {
    private static final String APP_1 = "app1";
    private static final String APP_2 = "app2";
    private static final String APP_3 = "app3";

    private static final String CURRENT_SECONDARY_KEY_ALIAS =
            "recoverablekey.alias/d524796bd07de3c2225c63d434eff698";
    private static final String NEXT_SECONDARY_KEY_ALIAS =
            "recoverablekey.alias/6c6d198a7f12e662b6bc45f4849db170";

    private Application mApplication;
    private RotateSecondaryKeyTask mTask;
    private RecoveryController mRecoveryController;
    private FakeCryptoBackupServer mBackupServer;
    private CryptoSettings mCryptoSettings;
    private Map mTertiaryKeysByPackageName;
    private RecoverableKeyStoreSecondaryKeyManager mRecoverableSecondaryKeyManager;

    @Before
    public void setUp() throws Exception {
        mApplication = ApplicationProvider.getApplicationContext();

        mTertiaryKeysByPackageName = new HashMap<>();
        mTertiaryKeysByPackageName.put(APP_1, generateAesKey());
        mTertiaryKeysByPackageName.put(APP_2, generateAesKey());
        mTertiaryKeysByPackageName.put(APP_3, generateAesKey());

        mRecoveryController = RecoveryController.getInstance(mApplication);
        mRecoverableSecondaryKeyManager =
                new RecoverableKeyStoreSecondaryKeyManager(
                        RecoveryController.getInstance(mApplication), new SecureRandom());
        mBackupServer = new FakeCryptoBackupServer();
        mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication);
        addNextSecondaryKeyToRecoveryController();
        mCryptoSettings.setNextSecondaryAlias(NEXT_SECONDARY_KEY_ALIAS);

        mTask =
                new RotateSecondaryKeyTask(
                        mApplication,
                        mRecoverableSecondaryKeyManager,
                        mBackupServer,
                        mCryptoSettings,
                        mRecoveryController);

        ShadowRecoveryController.reset();
    }

    @Test
    public void run_failsIfThereIsNoActiveSecondaryKey() throws Exception {
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
        addCurrentSecondaryKeyToRecoveryController();
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());

        mTask.run();

        assertFalse(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent());
    }

    @Test
    public void run_failsIfActiveSecondaryIsNotInRecoveryController() throws Exception {
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
        // Have to add it first as otherwise CryptoSettings throws an exception when trying to set
        // it
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());

        mTask.run();

        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
                .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS);
    }

    @Test
    public void run_doesNothingIfFlagIsDisabled() throws Exception {
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
        addWrappedTertiaries();

        mTask.run();

        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
                .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS);
    }

    @Test
    public void run_setsActiveSecondary() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
        addWrappedTertiaries();

        mTask.run();

        assertThat(mBackupServer.getActiveSecondaryKeyAlias().get())
                .isEqualTo(NEXT_SECONDARY_KEY_ALIAS);
    }

    @Test
    public void run_rewrapsExistingTertiaryKeys() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
        addWrappedTertiaries();

        mTask.run();

        Map rewrappedKeys =
                mBackupServer.getAllTertiaryKeys(NEXT_SECONDARY_KEY_ALIAS);
        SecretKey secondaryKey = (SecretKey) mRecoveryController.getKey(NEXT_SECONDARY_KEY_ALIAS);
        for (String packageName : mTertiaryKeysByPackageName.keySet()) {
            WrappedKeyProto.WrappedKey rewrappedKey = rewrappedKeys.get(packageName);
            assertThat(KeyWrapUtils.unwrap(secondaryKey, rewrappedKey))
                    .isEqualTo(mTertiaryKeysByPackageName.get(packageName));
        }
    }

    @Test
    public void run_persistsRewrappedKeysToDisk() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
        addWrappedTertiaries();

        mTask.run();

        RecoverableKeyStoreSecondaryKey secondaryKey = getRecoverableKey(NEXT_SECONDARY_KEY_ALIAS);
        Map keys =
                TertiaryKeyStore.newInstance(mApplication, secondaryKey).getAll();
        for (String packageName : mTertiaryKeysByPackageName.keySet()) {
            SecretKey tertiaryKey = mTertiaryKeysByPackageName.get(packageName);
            SecretKey newlyWrappedKey = keys.get(packageName);
            assertThat(tertiaryKey.getEncoded()).isEqualTo(newlyWrappedKey.getEncoded());
        }
    }

    @Test
    public void run_stillSetsActiveSecondaryIfNoTertiaries() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());

        mTask.run();

        assertThat(mBackupServer.getActiveSecondaryKeyAlias().get())
                .isEqualTo(NEXT_SECONDARY_KEY_ALIAS);
    }

    @Test
    public void run_setsActiveSecondaryKeyAliasInSettings() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());

        mTask.run();

        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
                .isEqualTo(NEXT_SECONDARY_KEY_ALIAS);
    }

    @Test
    public void run_removesNextSecondaryKeyAliasInSettings() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());

        mTask.run();

        assertFalse(mCryptoSettings.getNextSecondaryKeyAlias().isPresent());
    }

    @Test
    public void run_deletesOldKeyFromRecoverableKeyStoreLoader() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());

        mTask.run();

        assertThat(mRecoveryController.getKey(CURRENT_SECONDARY_KEY_ALIAS)).isNull();
    }

    @Test
    public void run_doesNotRotateIfNoNextAlias() throws Exception {
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
        mCryptoSettings.removeNextSecondaryKeyAlias();

        mTask.run();

        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
                .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS);
        assertFalse(mCryptoSettings.getNextSecondaryKeyAlias().isPresent());
    }

    @Test
    public void run_doesNotRotateIfKeyIsNotSyncedYet() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());

        mTask.run();

        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
                .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS);
    }

    @Test
    public void run_doesNotClearNextKeyIfSyncIsJustPending() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());

        mTask.run();

        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get())
                .isEqualTo(NEXT_SECONDARY_KEY_ALIAS);
    }

    @Test
    public void run_doesNotRotateIfPermanentFailure() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());

        mTask.run();

        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
                .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS);
    }

    @Test
    public void run_removesNextKeyIfPermanentFailure() throws Exception {
        addNextSecondaryKeyToRecoveryController();
        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
        addCurrentSecondaryKeyToRecoveryController();
        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
        mBackupServer.setActiveSecondaryKeyAlias(
                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());

        mTask.run();

        assertFalse(mCryptoSettings.getNextSecondaryKeyAlias().isPresent());
    }

    private void setNextKeyRecoveryStatus(int status) throws Exception {
        mRecoveryController.setRecoveryStatus(NEXT_SECONDARY_KEY_ALIAS, status);
    }

    private void addCurrentSecondaryKeyToRecoveryController() throws Exception {
        mRecoveryController.generateKey(CURRENT_SECONDARY_KEY_ALIAS);
    }

    private void addNextSecondaryKeyToRecoveryController() throws Exception {
        mRecoveryController.generateKey(NEXT_SECONDARY_KEY_ALIAS);
    }

    private void addWrappedTertiaries() throws Exception {
        TertiaryKeyStore tertiaryKeyStore =
                TertiaryKeyStore.newInstance(
                        mApplication, getRecoverableKey(CURRENT_SECONDARY_KEY_ALIAS));

        for (String packageName : mTertiaryKeysByPackageName.keySet()) {
            tertiaryKeyStore.save(packageName, mTertiaryKeysByPackageName.get(packageName));
        }
    }

    private RecoverableKeyStoreSecondaryKey getRecoverableKey(String alias) throws Exception {
        return mRecoverableSecondaryKeyManager.get(alias).get();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy