blob: cda73174a3e2a80641cbda25354685b53ae60313 [file] [log] [blame]
/*
* 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<String, SecretKey> 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<String, WrappedKeyProto.WrappedKey> 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<String, SecretKey> 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();
}
}