Loading packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java 0 → 100644 +233 −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.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkState; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.security.keystore.recovery.InternalRecoveryServiceException; import android.security.keystore.recovery.RecoveryController; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.security.KeyStoreException; import java.util.Optional; /** * State about encrypted backups that needs to be remembered. */ public class CryptoSettings { private static final String TAG = "CryptoSettings"; private static final String SHARED_PREFERENCES_NAME = "crypto_settings"; private static final String KEY_IS_INITIALIZED = "isInitialized"; private static final String KEY_ACTIVE_SECONDARY_ALIAS = "activeSecondary"; private static final String KEY_NEXT_SECONDARY_ALIAS = "nextSecondary"; private static final String SECONDARY_KEY_LAST_ROTATED_AT = "secondaryKeyLastRotatedAt"; private static final String[] SETTINGS_FOR_BACKUP = { KEY_IS_INITIALIZED, KEY_ACTIVE_SECONDARY_ALIAS, KEY_NEXT_SECONDARY_ALIAS, SECONDARY_KEY_LAST_ROTATED_AT }; private static final String KEY_ANCESTRAL_SECONDARY_KEY_VERSION = "ancestral_secondary_key_version"; private final SharedPreferences mSharedPreferences; private final Context mContext; /** * A new instance. * * @param context For looking up the {@link SharedPreferences}, for storing state. * @return The instance. */ public static CryptoSettings getInstance(Context context) { // We need single process mode because CryptoSettings may be used from several processes // simultaneously. SharedPreferences sharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); return new CryptoSettings(sharedPreferences, context); } /** * A new instance using {@link SharedPreferences} in the default mode. * * <p>This will not work across multiple processes but will work in tests. */ @VisibleForTesting public static CryptoSettings getInstanceForTesting(Context context) { SharedPreferences sharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); return new CryptoSettings(sharedPreferences, context); } private CryptoSettings(SharedPreferences sharedPreferences, Context context) { mSharedPreferences = checkNotNull(sharedPreferences); mContext = checkNotNull(context); } /** * The alias of the current active secondary key. This should be used to retrieve the key from * AndroidKeyStore. */ public Optional<String> getActiveSecondaryKeyAlias() { return getStringInSharedPrefs(KEY_ACTIVE_SECONDARY_ALIAS); } /** * The alias of the secondary key to which the client is rotating. The rotation is not * immediate, which is why this setting is needed. Once the next key is created, it can take up * to 72 hours potentially (or longer if the user has no network) for the next key to be synced * with the keystore. Only after that has happened does the client attempt to re-wrap all * tertiary keys and commit the rotation. */ public Optional<String> getNextSecondaryKeyAlias() { return getStringInSharedPrefs(KEY_NEXT_SECONDARY_ALIAS); } /** * If the settings have been initialized. */ public boolean getIsInitialized() { return mSharedPreferences.getBoolean(KEY_IS_INITIALIZED, false); } /** * Sets the alias of the currently active secondary key. * * @param activeAlias The alias, as in AndroidKeyStore. * @throws IllegalArgumentException if the alias is not in the user's keystore. */ public void setActiveSecondaryKeyAlias(String activeAlias) throws IllegalArgumentException { assertIsValidAlias(activeAlias); mSharedPreferences.edit().putString(KEY_ACTIVE_SECONDARY_ALIAS, activeAlias).apply(); } /** * Sets the alias of the secondary key to which the client is rotating. * * @param nextAlias The alias, as in AndroidKeyStore. * @throws KeyStoreException if unable to check whether alias is valid in the keystore. * @throws IllegalArgumentException if the alias is not in the user's keystore. */ public void setNextSecondaryAlias(String nextAlias) throws IllegalArgumentException { assertIsValidAlias(nextAlias); mSharedPreferences.edit().putString(KEY_NEXT_SECONDARY_ALIAS, nextAlias).apply(); } /** * Unsets the alias of the key to which the client is rotating. This is generally performed once * a rotation is complete. */ public void removeNextSecondaryKeyAlias() { mSharedPreferences.edit().remove(KEY_NEXT_SECONDARY_ALIAS).apply(); } /** * Sets the timestamp of when the secondary key was last rotated. * * @param timestamp The timestamp to set. */ public void setSecondaryLastRotated(long timestamp) { mSharedPreferences.edit().putLong(SECONDARY_KEY_LAST_ROTATED_AT, timestamp).apply(); } /** * Returns a timestamp of when the secondary key was last rotated. * * @return The timestamp. */ public Optional<Long> getSecondaryLastRotated() { if (!mSharedPreferences.contains(SECONDARY_KEY_LAST_ROTATED_AT)) { return Optional.empty(); } return Optional.of(mSharedPreferences.getLong(SECONDARY_KEY_LAST_ROTATED_AT, -1)); } /** * Sets the settings to have been initialized. (Otherwise loading should try to initialize * again.) */ private void setIsInitialized() { mSharedPreferences.edit().putBoolean(KEY_IS_INITIALIZED, true).apply(); } /** * Initializes with the given key alias. * * @param alias The secondary key alias to be set as active. * @throws IllegalArgumentException if the alias does not reference a valid key. * @throws IllegalStateException if attempting to initialize an already initialized settings. */ public void initializeWithKeyAlias(String alias) throws IllegalArgumentException { checkState( !getIsInitialized(), "Attempting to initialize an already initialized settings."); setActiveSecondaryKeyAlias(alias); setIsInitialized(); } /** Returns the secondary key version of the encrypted backup set to restore from (if set). */ public Optional<String> getAncestralSecondaryKeyVersion() { return Optional.ofNullable( mSharedPreferences.getString(KEY_ANCESTRAL_SECONDARY_KEY_VERSION, null)); } /** Sets the secondary key version of the encrypted backup set to restore from. */ public void setAncestralSecondaryKeyVersion(String ancestralSecondaryKeyVersion) { mSharedPreferences .edit() .putString(KEY_ANCESTRAL_SECONDARY_KEY_VERSION, ancestralSecondaryKeyVersion) .apply(); } /** Deletes all crypto settings related to backup (as opposed to restore). */ public void clearAllSettingsForBackup() { Editor sharedPrefsEditor = mSharedPreferences.edit(); for (String backupSettingKey : SETTINGS_FOR_BACKUP) { sharedPrefsEditor.remove(backupSettingKey); } sharedPrefsEditor.apply(); Slog.d(TAG, "Cleared crypto settings for backup"); } /** * Throws {@link IllegalArgumentException} if the alias does not refer to a key that is in * the {@link RecoveryController}. */ private void assertIsValidAlias(String alias) throws IllegalArgumentException { try { if (!RecoveryController.getInstance(mContext).getAliases().contains(alias)) { throw new IllegalArgumentException(alias + " is not in RecoveryController"); } } catch (InternalRecoveryServiceException e) { throw new IllegalArgumentException("Problem accessing recovery service", e); } } private Optional<String> getStringInSharedPrefs(String key) { return Optional.ofNullable(mSharedPreferences.getString(key, null)); } } packages/BackupEncryption/test/robolectric/Android.bp +5 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,11 @@ android_robolectric_test { "testng", "truth-prebuilt", ], static_libs: [ "androidx.test.core", "androidx.test.runner", "androidx.test.rules", ], instrumentation_for: "BackupEncryption", } Loading packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java 0 → 100644 +212 −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 static org.testng.Assert.assertThrows; import android.app.Application; import android.security.keystore.recovery.RecoveryController; import androidx.test.core.app.ApplicationProvider; import com.android.server.testing.shadows.ShadowRecoveryController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import java.util.Optional; @RunWith(RobolectricTestRunner.class) @Config(shadows = ShadowRecoveryController.class) public class CryptoSettingsTest { private static final String TEST_KEY_ALIAS = "com.android.server.backup.encryption/keystore/08120c326b928ff34c73b9c58581da63"; private CryptoSettings mCryptoSettings; private Application mApplication; @Before public void setUp() { ShadowRecoveryController.reset(); mApplication = ApplicationProvider.getApplicationContext(); mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication); } @Test public void getActiveSecondaryAlias_isInitiallyAbsent() { assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()).isFalse(); } @Test public void getActiveSecondaryAlias_returnsAliasIfKeyIsInRecoveryController() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.setActiveSecondaryKeyAlias(TEST_KEY_ALIAS); assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); } @Test public void getNextSecondaryAlias_isInitiallyAbsent() { assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); } @Test public void getNextSecondaryAlias_returnsAliasIfKeyIsInRecoveryController() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); } @Test public void isInitialized_isInitiallyFalse() { assertThat(mCryptoSettings.getIsInitialized()).isFalse(); } @Test public void setActiveSecondaryAlias_throwsIfKeyIsNotInRecoveryController() { assertThrows( IllegalArgumentException.class, () -> mCryptoSettings.setActiveSecondaryKeyAlias(TEST_KEY_ALIAS)); } @Test public void setNextSecondaryAlias_inRecoveryController_setsAlias() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); } @Test public void setNextSecondaryAlias_throwsIfKeyIsNotInRecoveryController() { assertThrows( IllegalArgumentException.class, () -> mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS)); } @Test public void removeNextSecondaryAlias_removesIt() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS); mCryptoSettings.removeNextSecondaryKeyAlias(); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); } @Test public void initializeWithKeyAlias_setsAsInitialized() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS); assertThat(mCryptoSettings.getIsInitialized()).isTrue(); } @Test public void initializeWithKeyAlias_setsActiveAlias() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS); assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); } @Test public void initializeWithKeyAlias_throwsIfKeyIsNotInRecoveryController() { assertThrows( IllegalArgumentException.class, () -> mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS)); } @Test public void initializeWithKeyAlias_throwsIfAlreadyInitialized() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS); assertThrows( IllegalStateException.class, () -> mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS)); } @Test public void getSecondaryLastRotated_returnsEmptyInitially() { assertThat(mCryptoSettings.getSecondaryLastRotated()).isEqualTo(Optional.empty()); } @Test public void getSecondaryLastRotated_returnsTimestampAfterItIsSet() { long timestamp = 1000001; mCryptoSettings.setSecondaryLastRotated(timestamp); assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(timestamp); } @Test public void getAncestralSecondaryKeyVersion_notSet_returnsOptionalAbsent() { assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().isPresent()).isFalse(); } @Test public void getAncestralSecondaryKeyVersion_isSet_returnsSetValue() { String secondaryKeyVersion = "some_secondary_key"; mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion); assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()) .isEqualTo(secondaryKeyVersion); } @Test public void getAncestralSecondaryKeyVersion_isSetMultipleTimes_returnsLastSetValue() { String secondaryKeyVersion1 = "some_secondary_key"; String secondaryKeyVersion2 = "another_secondary_key"; mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion1); mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion2); assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()) .isEqualTo(secondaryKeyVersion2); } @Test public void clearAllSettingsForBackup_clearsStateForBackup() throws Exception { String key1 = "key1"; String key2 = "key2"; String ancestralKey = "ancestral_key"; setAliasIsInRecoveryController(key1); setAliasIsInRecoveryController(key2); mCryptoSettings.setActiveSecondaryKeyAlias(key1); mCryptoSettings.setNextSecondaryAlias(key2); mCryptoSettings.setSecondaryLastRotated(100001); mCryptoSettings.setAncestralSecondaryKeyVersion(ancestralKey); mCryptoSettings.clearAllSettingsForBackup(); assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()).isFalse(); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); assertThat(mCryptoSettings.getSecondaryLastRotated().isPresent()).isFalse(); assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()).isEqualTo(ancestralKey); } private void setAliasIsInRecoveryController(String alias) throws Exception { RecoveryController recoveryController = RecoveryController.getInstance(mApplication); recoveryController.generateKey(alias); } } Loading
packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java 0 → 100644 +233 −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.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkState; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.security.keystore.recovery.InternalRecoveryServiceException; import android.security.keystore.recovery.RecoveryController; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.security.KeyStoreException; import java.util.Optional; /** * State about encrypted backups that needs to be remembered. */ public class CryptoSettings { private static final String TAG = "CryptoSettings"; private static final String SHARED_PREFERENCES_NAME = "crypto_settings"; private static final String KEY_IS_INITIALIZED = "isInitialized"; private static final String KEY_ACTIVE_SECONDARY_ALIAS = "activeSecondary"; private static final String KEY_NEXT_SECONDARY_ALIAS = "nextSecondary"; private static final String SECONDARY_KEY_LAST_ROTATED_AT = "secondaryKeyLastRotatedAt"; private static final String[] SETTINGS_FOR_BACKUP = { KEY_IS_INITIALIZED, KEY_ACTIVE_SECONDARY_ALIAS, KEY_NEXT_SECONDARY_ALIAS, SECONDARY_KEY_LAST_ROTATED_AT }; private static final String KEY_ANCESTRAL_SECONDARY_KEY_VERSION = "ancestral_secondary_key_version"; private final SharedPreferences mSharedPreferences; private final Context mContext; /** * A new instance. * * @param context For looking up the {@link SharedPreferences}, for storing state. * @return The instance. */ public static CryptoSettings getInstance(Context context) { // We need single process mode because CryptoSettings may be used from several processes // simultaneously. SharedPreferences sharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); return new CryptoSettings(sharedPreferences, context); } /** * A new instance using {@link SharedPreferences} in the default mode. * * <p>This will not work across multiple processes but will work in tests. */ @VisibleForTesting public static CryptoSettings getInstanceForTesting(Context context) { SharedPreferences sharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); return new CryptoSettings(sharedPreferences, context); } private CryptoSettings(SharedPreferences sharedPreferences, Context context) { mSharedPreferences = checkNotNull(sharedPreferences); mContext = checkNotNull(context); } /** * The alias of the current active secondary key. This should be used to retrieve the key from * AndroidKeyStore. */ public Optional<String> getActiveSecondaryKeyAlias() { return getStringInSharedPrefs(KEY_ACTIVE_SECONDARY_ALIAS); } /** * The alias of the secondary key to which the client is rotating. The rotation is not * immediate, which is why this setting is needed. Once the next key is created, it can take up * to 72 hours potentially (or longer if the user has no network) for the next key to be synced * with the keystore. Only after that has happened does the client attempt to re-wrap all * tertiary keys and commit the rotation. */ public Optional<String> getNextSecondaryKeyAlias() { return getStringInSharedPrefs(KEY_NEXT_SECONDARY_ALIAS); } /** * If the settings have been initialized. */ public boolean getIsInitialized() { return mSharedPreferences.getBoolean(KEY_IS_INITIALIZED, false); } /** * Sets the alias of the currently active secondary key. * * @param activeAlias The alias, as in AndroidKeyStore. * @throws IllegalArgumentException if the alias is not in the user's keystore. */ public void setActiveSecondaryKeyAlias(String activeAlias) throws IllegalArgumentException { assertIsValidAlias(activeAlias); mSharedPreferences.edit().putString(KEY_ACTIVE_SECONDARY_ALIAS, activeAlias).apply(); } /** * Sets the alias of the secondary key to which the client is rotating. * * @param nextAlias The alias, as in AndroidKeyStore. * @throws KeyStoreException if unable to check whether alias is valid in the keystore. * @throws IllegalArgumentException if the alias is not in the user's keystore. */ public void setNextSecondaryAlias(String nextAlias) throws IllegalArgumentException { assertIsValidAlias(nextAlias); mSharedPreferences.edit().putString(KEY_NEXT_SECONDARY_ALIAS, nextAlias).apply(); } /** * Unsets the alias of the key to which the client is rotating. This is generally performed once * a rotation is complete. */ public void removeNextSecondaryKeyAlias() { mSharedPreferences.edit().remove(KEY_NEXT_SECONDARY_ALIAS).apply(); } /** * Sets the timestamp of when the secondary key was last rotated. * * @param timestamp The timestamp to set. */ public void setSecondaryLastRotated(long timestamp) { mSharedPreferences.edit().putLong(SECONDARY_KEY_LAST_ROTATED_AT, timestamp).apply(); } /** * Returns a timestamp of when the secondary key was last rotated. * * @return The timestamp. */ public Optional<Long> getSecondaryLastRotated() { if (!mSharedPreferences.contains(SECONDARY_KEY_LAST_ROTATED_AT)) { return Optional.empty(); } return Optional.of(mSharedPreferences.getLong(SECONDARY_KEY_LAST_ROTATED_AT, -1)); } /** * Sets the settings to have been initialized. (Otherwise loading should try to initialize * again.) */ private void setIsInitialized() { mSharedPreferences.edit().putBoolean(KEY_IS_INITIALIZED, true).apply(); } /** * Initializes with the given key alias. * * @param alias The secondary key alias to be set as active. * @throws IllegalArgumentException if the alias does not reference a valid key. * @throws IllegalStateException if attempting to initialize an already initialized settings. */ public void initializeWithKeyAlias(String alias) throws IllegalArgumentException { checkState( !getIsInitialized(), "Attempting to initialize an already initialized settings."); setActiveSecondaryKeyAlias(alias); setIsInitialized(); } /** Returns the secondary key version of the encrypted backup set to restore from (if set). */ public Optional<String> getAncestralSecondaryKeyVersion() { return Optional.ofNullable( mSharedPreferences.getString(KEY_ANCESTRAL_SECONDARY_KEY_VERSION, null)); } /** Sets the secondary key version of the encrypted backup set to restore from. */ public void setAncestralSecondaryKeyVersion(String ancestralSecondaryKeyVersion) { mSharedPreferences .edit() .putString(KEY_ANCESTRAL_SECONDARY_KEY_VERSION, ancestralSecondaryKeyVersion) .apply(); } /** Deletes all crypto settings related to backup (as opposed to restore). */ public void clearAllSettingsForBackup() { Editor sharedPrefsEditor = mSharedPreferences.edit(); for (String backupSettingKey : SETTINGS_FOR_BACKUP) { sharedPrefsEditor.remove(backupSettingKey); } sharedPrefsEditor.apply(); Slog.d(TAG, "Cleared crypto settings for backup"); } /** * Throws {@link IllegalArgumentException} if the alias does not refer to a key that is in * the {@link RecoveryController}. */ private void assertIsValidAlias(String alias) throws IllegalArgumentException { try { if (!RecoveryController.getInstance(mContext).getAliases().contains(alias)) { throw new IllegalArgumentException(alias + " is not in RecoveryController"); } } catch (InternalRecoveryServiceException e) { throw new IllegalArgumentException("Problem accessing recovery service", e); } } private Optional<String> getStringInSharedPrefs(String key) { return Optional.ofNullable(mSharedPreferences.getString(key, null)); } }
packages/BackupEncryption/test/robolectric/Android.bp +5 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,11 @@ android_robolectric_test { "testng", "truth-prebuilt", ], static_libs: [ "androidx.test.core", "androidx.test.runner", "androidx.test.rules", ], instrumentation_for: "BackupEncryption", } Loading
packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java 0 → 100644 +212 −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 static org.testng.Assert.assertThrows; import android.app.Application; import android.security.keystore.recovery.RecoveryController; import androidx.test.core.app.ApplicationProvider; import com.android.server.testing.shadows.ShadowRecoveryController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import java.util.Optional; @RunWith(RobolectricTestRunner.class) @Config(shadows = ShadowRecoveryController.class) public class CryptoSettingsTest { private static final String TEST_KEY_ALIAS = "com.android.server.backup.encryption/keystore/08120c326b928ff34c73b9c58581da63"; private CryptoSettings mCryptoSettings; private Application mApplication; @Before public void setUp() { ShadowRecoveryController.reset(); mApplication = ApplicationProvider.getApplicationContext(); mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication); } @Test public void getActiveSecondaryAlias_isInitiallyAbsent() { assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()).isFalse(); } @Test public void getActiveSecondaryAlias_returnsAliasIfKeyIsInRecoveryController() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.setActiveSecondaryKeyAlias(TEST_KEY_ALIAS); assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); } @Test public void getNextSecondaryAlias_isInitiallyAbsent() { assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); } @Test public void getNextSecondaryAlias_returnsAliasIfKeyIsInRecoveryController() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); } @Test public void isInitialized_isInitiallyFalse() { assertThat(mCryptoSettings.getIsInitialized()).isFalse(); } @Test public void setActiveSecondaryAlias_throwsIfKeyIsNotInRecoveryController() { assertThrows( IllegalArgumentException.class, () -> mCryptoSettings.setActiveSecondaryKeyAlias(TEST_KEY_ALIAS)); } @Test public void setNextSecondaryAlias_inRecoveryController_setsAlias() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); } @Test public void setNextSecondaryAlias_throwsIfKeyIsNotInRecoveryController() { assertThrows( IllegalArgumentException.class, () -> mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS)); } @Test public void removeNextSecondaryAlias_removesIt() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS); mCryptoSettings.removeNextSecondaryKeyAlias(); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); } @Test public void initializeWithKeyAlias_setsAsInitialized() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS); assertThat(mCryptoSettings.getIsInitialized()).isTrue(); } @Test public void initializeWithKeyAlias_setsActiveAlias() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS); assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); } @Test public void initializeWithKeyAlias_throwsIfKeyIsNotInRecoveryController() { assertThrows( IllegalArgumentException.class, () -> mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS)); } @Test public void initializeWithKeyAlias_throwsIfAlreadyInitialized() throws Exception { setAliasIsInRecoveryController(TEST_KEY_ALIAS); mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS); assertThrows( IllegalStateException.class, () -> mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS)); } @Test public void getSecondaryLastRotated_returnsEmptyInitially() { assertThat(mCryptoSettings.getSecondaryLastRotated()).isEqualTo(Optional.empty()); } @Test public void getSecondaryLastRotated_returnsTimestampAfterItIsSet() { long timestamp = 1000001; mCryptoSettings.setSecondaryLastRotated(timestamp); assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(timestamp); } @Test public void getAncestralSecondaryKeyVersion_notSet_returnsOptionalAbsent() { assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().isPresent()).isFalse(); } @Test public void getAncestralSecondaryKeyVersion_isSet_returnsSetValue() { String secondaryKeyVersion = "some_secondary_key"; mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion); assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()) .isEqualTo(secondaryKeyVersion); } @Test public void getAncestralSecondaryKeyVersion_isSetMultipleTimes_returnsLastSetValue() { String secondaryKeyVersion1 = "some_secondary_key"; String secondaryKeyVersion2 = "another_secondary_key"; mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion1); mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion2); assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()) .isEqualTo(secondaryKeyVersion2); } @Test public void clearAllSettingsForBackup_clearsStateForBackup() throws Exception { String key1 = "key1"; String key2 = "key2"; String ancestralKey = "ancestral_key"; setAliasIsInRecoveryController(key1); setAliasIsInRecoveryController(key2); mCryptoSettings.setActiveSecondaryKeyAlias(key1); mCryptoSettings.setNextSecondaryAlias(key2); mCryptoSettings.setSecondaryLastRotated(100001); mCryptoSettings.setAncestralSecondaryKeyVersion(ancestralKey); mCryptoSettings.clearAllSettingsForBackup(); assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()).isFalse(); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); assertThat(mCryptoSettings.getSecondaryLastRotated().isPresent()).isFalse(); assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()).isEqualTo(ancestralKey); } private void setAliasIsInRecoveryController(String alias) throws Exception { RecoveryController recoveryController = RecoveryController.getInstance(mApplication); recoveryController.generateKey(alias); } }