Loading packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/InitializeRecoverableSecondaryKeyTask.java 0 → 100644 +157 −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.tasks; import android.content.Context; import android.security.keystore.recovery.InternalRecoveryServiceException; import android.security.keystore.recovery.LockScreenRequiredException; import android.security.keystore.recovery.RecoveryController; import android.util.Slog; import com.android.server.backup.encryption.CryptoSettings; import com.android.server.backup.encryption.client.CryptoBackupServer; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; import java.security.InvalidKeyException; import java.security.UnrecoverableKeyException; import java.util.Collections; import java.util.Optional; /** * Initializes the device for encrypted backup, through generating a secondary key, and setting its * alias in the settings. * * <p>If the device is already initialized, this is a no-op. */ public class InitializeRecoverableSecondaryKeyTask { private static final String TAG = "InitializeRecoverableSecondaryKeyTask"; private final Context mContext; private final CryptoSettings mCryptoSettings; private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; private final CryptoBackupServer mBackupServer; /** * A new instance. * * @param cryptoSettings Settings to store the active key alias. * @param secondaryKeyManager Key manager to generate the new active secondary key. * @param backupServer Server with which to sync the active key alias. */ public InitializeRecoverableSecondaryKeyTask( Context context, CryptoSettings cryptoSettings, RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager, CryptoBackupServer backupServer) { mContext = context; mCryptoSettings = cryptoSettings; mSecondaryKeyManager = secondaryKeyManager; mBackupServer = backupServer; } /** * Initializes the device for encrypted backup, by generating a recoverable secondary key, then * sending that alias to the backup server and saving it in local settings. * * <p>If there is already an active secondary key then does nothing. If the active secondary key * is destroyed then throws {@link InvalidKeyException}. * * <p>If a key rotation is pending and able to finish (i.e., the new key has synced with the * remote trusted hardware module), then it completes the rotation before returning the key. * * @return The active secondary key. * @throws InvalidKeyException if the secondary key is in a bad state. */ public RecoverableKeyStoreSecondaryKey run() throws InvalidKeyException, LockScreenRequiredException, UnrecoverableKeyException, InternalRecoveryServiceException { // Complete any pending key rotations new RotateSecondaryKeyTask( mContext, mSecondaryKeyManager, mBackupServer, mCryptoSettings, RecoveryController.getInstance(mContext)) .run(); return runInternal(); } private RecoverableKeyStoreSecondaryKey runInternal() throws InvalidKeyException, LockScreenRequiredException, UnrecoverableKeyException, InternalRecoveryServiceException { Optional<RecoverableKeyStoreSecondaryKey> maybeActiveKey = loadFromSetting(); if (maybeActiveKey.isPresent()) { assertKeyNotDestroyed(maybeActiveKey.get()); Slog.d(TAG, "Secondary key already initialized: " + maybeActiveKey.get().getAlias()); return maybeActiveKey.get(); } Slog.v(TAG, "Initializing for crypto: generating a secondary key."); RecoverableKeyStoreSecondaryKey key = mSecondaryKeyManager.generate(); String alias = key.getAlias(); Slog.i(TAG, "Generated new secondary key " + alias); // No tertiary keys yet as we are creating a brand new secondary (without rotation). mBackupServer.setActiveSecondaryKeyAlias(alias, /*tertiaryKeys=*/ Collections.emptyMap()); Slog.v(TAG, "Successfully synced %s " + alias + " with server."); mCryptoSettings.initializeWithKeyAlias(alias); Slog.v(TAG, "Successfully saved " + alias + " as active secondary to disk."); return key; } private void assertKeyNotDestroyed(RecoverableKeyStoreSecondaryKey key) throws InvalidKeyException { if (key.getStatus(mContext) == RecoverableKeyStoreSecondaryKey.Status.DESTROYED) { throw new InvalidKeyException("Key destroyed: " + key.getAlias()); } } private Optional<RecoverableKeyStoreSecondaryKey> loadFromSetting() throws InvalidKeyException, UnrecoverableKeyException, InternalRecoveryServiceException { // TODO: b/141856950. if (!mCryptoSettings.getIsInitialized()) { return Optional.empty(); } Optional<String> maybeAlias = mCryptoSettings.getActiveSecondaryKeyAlias(); if (!maybeAlias.isPresent()) { throw new InvalidKeyException( "Settings said crypto was initialized, but there was no active secondary" + " alias"); } String alias = maybeAlias.get(); Optional<RecoverableKeyStoreSecondaryKey> key; key = mSecondaryKeyManager.get(alias); if (!key.isPresent()) { throw new InvalidKeyException( "Initialized with key but it was not in key store: " + alias); } return key; } } packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/InitializeRecoverableSecondaryKeyTaskTest.java 0 → 100644 +165 −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.tasks; import static android.security.keystore.recovery.RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; 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.backup.encryption.CryptoSettings; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; import com.android.server.testing.fakes.FakeCryptoBackupServer; import com.android.server.testing.shadows.ShadowRecoveryController; import java.security.InvalidKeyException; import java.security.SecureRandom; import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @Config(shadows = {ShadowRecoveryController.class}) @RunWith(RobolectricTestRunner.class) public class InitializeRecoverableSecondaryKeyTaskTest { @Mock private CryptoSettings mMockCryptoSettings; private Application mApplication; private InitializeRecoverableSecondaryKeyTask mTask; private CryptoSettings mCryptoSettings; private FakeCryptoBackupServer mFakeCryptoBackupServer; private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); ShadowRecoveryController.reset(); mApplication = ApplicationProvider.getApplicationContext(); mFakeCryptoBackupServer = new FakeCryptoBackupServer(); mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication); mSecondaryKeyManager = new RecoverableKeyStoreSecondaryKeyManager( RecoveryController.getInstance(mApplication), new SecureRandom()); mTask = new InitializeRecoverableSecondaryKeyTask( mApplication, mCryptoSettings, mSecondaryKeyManager, mFakeCryptoBackupServer); } @Test public void testRun_generatesNewKeyInRecoveryController() throws Exception { RecoverableKeyStoreSecondaryKey key = mTask.run(); assertThat(RecoveryController.getInstance(mApplication).getAliases()) .contains(key.getAlias()); } @Test public void testRun_setsAliasOnServer() throws Exception { RecoverableKeyStoreSecondaryKey key = mTask.run(); assertThat(mFakeCryptoBackupServer.getActiveSecondaryKeyAlias().get()) .isEqualTo(key.getAlias()); } @Test public void testRun_setsAliasInSettings() throws Exception { RecoverableKeyStoreSecondaryKey key = mTask.run(); assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(key.getAlias()); } @Test public void testRun_initializesSettings() throws Exception { mTask.run(); assertThat(mCryptoSettings.getIsInitialized()).isTrue(); } @Test public void testRun_initializeSettingsFails_throws() throws Exception { useMockCryptoSettings(); doThrow(IllegalArgumentException.class) .when(mMockCryptoSettings) .initializeWithKeyAlias(any()); assertThrows(IllegalArgumentException.class, () -> mTask.run()); } @Test public void testRun_doesNotGenerateANewKeyIfOneIsAvailable() throws Exception { RecoverableKeyStoreSecondaryKey key1 = mTask.run(); RecoverableKeyStoreSecondaryKey key2 = mTask.run(); assertThat(key1.getAlias()).isEqualTo(key2.getAlias()); assertThat(key2.getSecretKey()).isEqualTo(key2.getSecretKey()); } @Test public void testRun_existingKeyButDestroyed_throws() throws Exception { RecoverableKeyStoreSecondaryKey key = mTask.run(); ShadowRecoveryController.setRecoveryStatus( key.getAlias(), RECOVERY_STATUS_PERMANENT_FAILURE); assertThrows(InvalidKeyException.class, () -> mTask.run()); } @Test public void testRun_settingsInitializedButNotSecondaryKeyAlias_throws() { useMockCryptoSettings(); when(mMockCryptoSettings.getIsInitialized()).thenReturn(true); when(mMockCryptoSettings.getActiveSecondaryKeyAlias()).thenReturn(Optional.empty()); assertThrows(InvalidKeyException.class, () -> mTask.run()); } @Test public void testRun_keyAliasSetButNotInStore_throws() { useMockCryptoSettings(); when(mMockCryptoSettings.getIsInitialized()).thenReturn(true); when(mMockCryptoSettings.getActiveSecondaryKeyAlias()) .thenReturn(Optional.of("missingAlias")); assertThrows(InvalidKeyException.class, () -> mTask.run()); } private void useMockCryptoSettings() { mTask = new InitializeRecoverableSecondaryKeyTask( mApplication, mMockCryptoSettings, mSecondaryKeyManager, mFakeCryptoBackupServer); } } Loading
packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/InitializeRecoverableSecondaryKeyTask.java 0 → 100644 +157 −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.tasks; import android.content.Context; import android.security.keystore.recovery.InternalRecoveryServiceException; import android.security.keystore.recovery.LockScreenRequiredException; import android.security.keystore.recovery.RecoveryController; import android.util.Slog; import com.android.server.backup.encryption.CryptoSettings; import com.android.server.backup.encryption.client.CryptoBackupServer; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; import java.security.InvalidKeyException; import java.security.UnrecoverableKeyException; import java.util.Collections; import java.util.Optional; /** * Initializes the device for encrypted backup, through generating a secondary key, and setting its * alias in the settings. * * <p>If the device is already initialized, this is a no-op. */ public class InitializeRecoverableSecondaryKeyTask { private static final String TAG = "InitializeRecoverableSecondaryKeyTask"; private final Context mContext; private final CryptoSettings mCryptoSettings; private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; private final CryptoBackupServer mBackupServer; /** * A new instance. * * @param cryptoSettings Settings to store the active key alias. * @param secondaryKeyManager Key manager to generate the new active secondary key. * @param backupServer Server with which to sync the active key alias. */ public InitializeRecoverableSecondaryKeyTask( Context context, CryptoSettings cryptoSettings, RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager, CryptoBackupServer backupServer) { mContext = context; mCryptoSettings = cryptoSettings; mSecondaryKeyManager = secondaryKeyManager; mBackupServer = backupServer; } /** * Initializes the device for encrypted backup, by generating a recoverable secondary key, then * sending that alias to the backup server and saving it in local settings. * * <p>If there is already an active secondary key then does nothing. If the active secondary key * is destroyed then throws {@link InvalidKeyException}. * * <p>If a key rotation is pending and able to finish (i.e., the new key has synced with the * remote trusted hardware module), then it completes the rotation before returning the key. * * @return The active secondary key. * @throws InvalidKeyException if the secondary key is in a bad state. */ public RecoverableKeyStoreSecondaryKey run() throws InvalidKeyException, LockScreenRequiredException, UnrecoverableKeyException, InternalRecoveryServiceException { // Complete any pending key rotations new RotateSecondaryKeyTask( mContext, mSecondaryKeyManager, mBackupServer, mCryptoSettings, RecoveryController.getInstance(mContext)) .run(); return runInternal(); } private RecoverableKeyStoreSecondaryKey runInternal() throws InvalidKeyException, LockScreenRequiredException, UnrecoverableKeyException, InternalRecoveryServiceException { Optional<RecoverableKeyStoreSecondaryKey> maybeActiveKey = loadFromSetting(); if (maybeActiveKey.isPresent()) { assertKeyNotDestroyed(maybeActiveKey.get()); Slog.d(TAG, "Secondary key already initialized: " + maybeActiveKey.get().getAlias()); return maybeActiveKey.get(); } Slog.v(TAG, "Initializing for crypto: generating a secondary key."); RecoverableKeyStoreSecondaryKey key = mSecondaryKeyManager.generate(); String alias = key.getAlias(); Slog.i(TAG, "Generated new secondary key " + alias); // No tertiary keys yet as we are creating a brand new secondary (without rotation). mBackupServer.setActiveSecondaryKeyAlias(alias, /*tertiaryKeys=*/ Collections.emptyMap()); Slog.v(TAG, "Successfully synced %s " + alias + " with server."); mCryptoSettings.initializeWithKeyAlias(alias); Slog.v(TAG, "Successfully saved " + alias + " as active secondary to disk."); return key; } private void assertKeyNotDestroyed(RecoverableKeyStoreSecondaryKey key) throws InvalidKeyException { if (key.getStatus(mContext) == RecoverableKeyStoreSecondaryKey.Status.DESTROYED) { throw new InvalidKeyException("Key destroyed: " + key.getAlias()); } } private Optional<RecoverableKeyStoreSecondaryKey> loadFromSetting() throws InvalidKeyException, UnrecoverableKeyException, InternalRecoveryServiceException { // TODO: b/141856950. if (!mCryptoSettings.getIsInitialized()) { return Optional.empty(); } Optional<String> maybeAlias = mCryptoSettings.getActiveSecondaryKeyAlias(); if (!maybeAlias.isPresent()) { throw new InvalidKeyException( "Settings said crypto was initialized, but there was no active secondary" + " alias"); } String alias = maybeAlias.get(); Optional<RecoverableKeyStoreSecondaryKey> key; key = mSecondaryKeyManager.get(alias); if (!key.isPresent()) { throw new InvalidKeyException( "Initialized with key but it was not in key store: " + alias); } return key; } }
packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/InitializeRecoverableSecondaryKeyTaskTest.java 0 → 100644 +165 −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.tasks; import static android.security.keystore.recovery.RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; 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.backup.encryption.CryptoSettings; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; import com.android.server.testing.fakes.FakeCryptoBackupServer; import com.android.server.testing.shadows.ShadowRecoveryController; import java.security.InvalidKeyException; import java.security.SecureRandom; import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @Config(shadows = {ShadowRecoveryController.class}) @RunWith(RobolectricTestRunner.class) public class InitializeRecoverableSecondaryKeyTaskTest { @Mock private CryptoSettings mMockCryptoSettings; private Application mApplication; private InitializeRecoverableSecondaryKeyTask mTask; private CryptoSettings mCryptoSettings; private FakeCryptoBackupServer mFakeCryptoBackupServer; private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); ShadowRecoveryController.reset(); mApplication = ApplicationProvider.getApplicationContext(); mFakeCryptoBackupServer = new FakeCryptoBackupServer(); mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication); mSecondaryKeyManager = new RecoverableKeyStoreSecondaryKeyManager( RecoveryController.getInstance(mApplication), new SecureRandom()); mTask = new InitializeRecoverableSecondaryKeyTask( mApplication, mCryptoSettings, mSecondaryKeyManager, mFakeCryptoBackupServer); } @Test public void testRun_generatesNewKeyInRecoveryController() throws Exception { RecoverableKeyStoreSecondaryKey key = mTask.run(); assertThat(RecoveryController.getInstance(mApplication).getAliases()) .contains(key.getAlias()); } @Test public void testRun_setsAliasOnServer() throws Exception { RecoverableKeyStoreSecondaryKey key = mTask.run(); assertThat(mFakeCryptoBackupServer.getActiveSecondaryKeyAlias().get()) .isEqualTo(key.getAlias()); } @Test public void testRun_setsAliasInSettings() throws Exception { RecoverableKeyStoreSecondaryKey key = mTask.run(); assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(key.getAlias()); } @Test public void testRun_initializesSettings() throws Exception { mTask.run(); assertThat(mCryptoSettings.getIsInitialized()).isTrue(); } @Test public void testRun_initializeSettingsFails_throws() throws Exception { useMockCryptoSettings(); doThrow(IllegalArgumentException.class) .when(mMockCryptoSettings) .initializeWithKeyAlias(any()); assertThrows(IllegalArgumentException.class, () -> mTask.run()); } @Test public void testRun_doesNotGenerateANewKeyIfOneIsAvailable() throws Exception { RecoverableKeyStoreSecondaryKey key1 = mTask.run(); RecoverableKeyStoreSecondaryKey key2 = mTask.run(); assertThat(key1.getAlias()).isEqualTo(key2.getAlias()); assertThat(key2.getSecretKey()).isEqualTo(key2.getSecretKey()); } @Test public void testRun_existingKeyButDestroyed_throws() throws Exception { RecoverableKeyStoreSecondaryKey key = mTask.run(); ShadowRecoveryController.setRecoveryStatus( key.getAlias(), RECOVERY_STATUS_PERMANENT_FAILURE); assertThrows(InvalidKeyException.class, () -> mTask.run()); } @Test public void testRun_settingsInitializedButNotSecondaryKeyAlias_throws() { useMockCryptoSettings(); when(mMockCryptoSettings.getIsInitialized()).thenReturn(true); when(mMockCryptoSettings.getActiveSecondaryKeyAlias()).thenReturn(Optional.empty()); assertThrows(InvalidKeyException.class, () -> mTask.run()); } @Test public void testRun_keyAliasSetButNotInStore_throws() { useMockCryptoSettings(); when(mMockCryptoSettings.getIsInitialized()).thenReturn(true); when(mMockCryptoSettings.getActiveSecondaryKeyAlias()) .thenReturn(Optional.of("missingAlias")); assertThrows(InvalidKeyException.class, () -> mTask.run()); } private void useMockCryptoSettings() { mTask = new InitializeRecoverableSecondaryKeyTask( mApplication, mMockCryptoSettings, mSecondaryKeyManager, mFakeCryptoBackupServer); } }