blob: ccc5f32dad3681adecb66f9405a5f6c978f00b7a [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.keys;
import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
import android.content.Context;
import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.security.InvalidKeyException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.crypto.SecretKey;
/** Tests for the tertiary key store */
@RunWith(RobolectricTestRunner.class)
public class TertiaryKeyStoreTest {
private static final String SECONDARY_KEY_ALIAS = "Robbo/Ranx";
private Context mApplication;
private TertiaryKeyStore mTertiaryKeyStore;
private SecretKey mSecretKey;
/** Initialise the keystore for testing */
@Before
public void setUp() throws Exception {
mApplication = RuntimeEnvironment.application;
mSecretKey = generateAesKey();
mTertiaryKeyStore =
TertiaryKeyStore.newInstance(
mApplication,
new RecoverableKeyStoreSecondaryKey(SECONDARY_KEY_ALIAS, mSecretKey));
}
/** Test a reound trip for a key */
@Test
public void load_loadsAKeyThatWasSaved() throws Exception {
String packageName = "com.android.example";
SecretKey packageKey = generateAesKey();
mTertiaryKeyStore.save(packageName, packageKey);
Optional<SecretKey> maybeLoadedKey = mTertiaryKeyStore.load(packageName);
assertTrue(maybeLoadedKey.isPresent());
assertEquals(packageKey, maybeLoadedKey.get());
}
/** Test isolation between packages */
@Test
public void load_doesNotLoadAKeyForAnotherSecondary() throws Exception {
String packageName = "com.android.example";
SecretKey packageKey = generateAesKey();
mTertiaryKeyStore.save(packageName, packageKey);
TertiaryKeyStore managerWithOtherSecondaryKey =
TertiaryKeyStore.newInstance(
mApplication,
new RecoverableKeyStoreSecondaryKey(
"myNewSecondaryKeyAlias", generateAesKey()));
assertFalse(managerWithOtherSecondaryKey.load(packageName).isPresent());
}
/** Test non-existent key handling */
@Test
public void load_returnsAbsentForANonExistentKey() throws Exception {
assertFalse(mTertiaryKeyStore.load("mystery.package").isPresent());
}
/** Test handling incorrect keys */
@Test
public void load_throwsIfHasWrongBackupKey() throws Exception {
String packageName = "com.android.example";
SecretKey packageKey = generateAesKey();
mTertiaryKeyStore.save(packageName, packageKey);
TertiaryKeyStore managerWithBadKey =
TertiaryKeyStore.newInstance(
mApplication,
new RecoverableKeyStoreSecondaryKey(SECONDARY_KEY_ALIAS, generateAesKey()));
assertThrows(InvalidKeyException.class, () -> managerWithBadKey.load(packageName));
}
/** Test handling of empty app name */
@Test
public void load_throwsForEmptyApplicationName() throws Exception {
assertThrows(IllegalArgumentException.class, () -> mTertiaryKeyStore.load(""));
}
/** Test handling of an invalid app name */
@Test
public void load_throwsForBadApplicationName() throws Exception {
assertThrows(
IllegalArgumentException.class,
() -> mTertiaryKeyStore.load("com/android/example"));
}
/** Test key replacement */
@Test
public void save_overwritesPreviousKey() throws Exception {
String packageName = "com.android.example";
SecretKey oldKey = generateAesKey();
mTertiaryKeyStore.save(packageName, oldKey);
SecretKey newKey = generateAesKey();
mTertiaryKeyStore.save(packageName, newKey);
Optional<SecretKey> maybeLoadedKey = mTertiaryKeyStore.load(packageName);
assertTrue(maybeLoadedKey.isPresent());
SecretKey loadedKey = maybeLoadedKey.get();
assertThat(loadedKey).isNotEqualTo(oldKey);
assertThat(loadedKey).isEqualTo(newKey);
}
/** Test saving with an empty application name fails */
@Test
public void save_throwsForEmptyApplicationName() throws Exception {
assertThrows(
IllegalArgumentException.class, () -> mTertiaryKeyStore.save("", generateAesKey()));
}
/** Test saving an invalid application name fails */
@Test
public void save_throwsForBadApplicationName() throws Exception {
assertThrows(
IllegalArgumentException.class,
() -> mTertiaryKeyStore.save("com/android/example", generateAesKey()));
}
/** Test handling an empty database */
@Test
public void getAll_returnsEmptyMapForEmptyDb() throws Exception {
assertThat(mTertiaryKeyStore.getAll()).isEmpty();
}
/** Test loading all available keys works as expected */
@Test
public void getAll_returnsAllKeysSaved() throws Exception {
String package1 = "com.android.example";
SecretKey key1 = generateAesKey();
String package2 = "com.anndroid.example1";
SecretKey key2 = generateAesKey();
String package3 = "com.android.example2";
SecretKey key3 = generateAesKey();
mTertiaryKeyStore.save(package1, key1);
mTertiaryKeyStore.save(package2, key2);
mTertiaryKeyStore.save(package3, key3);
Map<String, SecretKey> keys = mTertiaryKeyStore.getAll();
assertThat(keys).containsExactly(package1, key1, package2, key2, package3, key3);
}
/** Test cross-secondary isolation */
@Test
public void getAll_doesNotReturnKeysForOtherSecondary() throws Exception {
String packageName = "com.android.example";
TertiaryKeyStore managerWithOtherSecondaryKey =
TertiaryKeyStore.newInstance(
mApplication,
new RecoverableKeyStoreSecondaryKey(
"myNewSecondaryKeyAlias", generateAesKey()));
managerWithOtherSecondaryKey.save(packageName, generateAesKey());
assertThat(mTertiaryKeyStore.getAll()).isEmpty();
}
/** Test mass put into the keystore */
@Test
public void putAll_putsAllWrappedKeysInTheStore() throws Exception {
String packageName = "com.android.example";
SecretKey key = generateAesKey();
WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(mSecretKey, key);
Map<String, WrappedKeyProto.WrappedKey> testElements = new HashMap<>();
testElements.put(packageName, wrappedKey);
mTertiaryKeyStore.putAll(testElements);
assertThat(mTertiaryKeyStore.getAll()).containsKey(packageName);
assertThat(mTertiaryKeyStore.getAll().get(packageName).getEncoded())
.isEqualTo(key.getEncoded());
}
}