Loading packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java +9 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.security.KeyStoreException; import java.util.Optional; import java.util.concurrent.TimeUnit; /** * State about encrypted backups that needs to be remembered. Loading @@ -51,6 +52,9 @@ public class CryptoSettings { SECONDARY_KEY_LAST_ROTATED_AT }; private static final long DEFAULT_SECONDARY_KEY_ROTATION_PERIOD = TimeUnit.MILLISECONDS.convert(31, TimeUnit.DAYS); private static final String KEY_ANCESTRAL_SECONDARY_KEY_VERSION = "ancestral_secondary_key_version"; Loading Loading @@ -202,6 +206,11 @@ public class CryptoSettings { .apply(); } /** The number of milliseconds between secondary key rotation */ public long backupSecondaryKeyRotationIntervalMs() { return DEFAULT_SECONDARY_KEY_ROTATION_PERIOD; } /** Deletes all crypto settings related to backup (as opposed to restore). */ public void clearAllSettingsForBackup() { Editor sharedPrefsEditor = mSharedPreferences.edit(); Loading packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java 0 → 100644 +116 −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.keys; import android.content.Context; import android.util.Slog; import com.android.server.backup.encryption.CryptoSettings; import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask; import java.io.File; import java.time.Clock; import java.util.Optional; /** * Helps schedule rotations of secondary keys. * * <p>TODO(b/72028016) Replace with a job. */ public class SecondaryKeyRotationScheduler { private static final String TAG = "SecondaryKeyRotationScheduler"; private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation"; private final Context mContext; private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; private final CryptoSettings mCryptoSettings; private final Clock mClock; public SecondaryKeyRotationScheduler( Context context, RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager, CryptoSettings cryptoSettings, Clock clock) { mContext = context; mCryptoSettings = cryptoSettings; mClock = clock; mSecondaryKeyManager = secondaryKeyManager; } /** * Returns {@code true} if a sentinel file for forcing secondary key rotation is present. This * is only for testing purposes. */ private boolean isForceRotationTestSentinelPresent() { File file = new File(mContext.getFilesDir(), SENTINEL_FILE_PATH); if (file.exists()) { file.delete(); return true; } return false; } /** Start the key rotation task if it's time to do so */ public void startRotationIfScheduled() { if (isForceRotationTestSentinelPresent()) { Slog.i(TAG, "Found force flag for secondary rotation. Starting now."); startRotation(); return; } Optional<Long> maybeLastRotated = mCryptoSettings.getSecondaryLastRotated(); if (!maybeLastRotated.isPresent()) { Slog.v(TAG, "No previous rotation, scheduling from now."); scheduleRotationFromNow(); return; } long lastRotated = maybeLastRotated.get(); long now = mClock.millis(); if (lastRotated > now) { Slog.i(TAG, "Last rotation was in the future. Clock must have changed. Rotate now."); startRotation(); return; } long millisSinceLastRotation = now - lastRotated; long rotationInterval = mCryptoSettings.backupSecondaryKeyRotationIntervalMs(); if (millisSinceLastRotation >= rotationInterval) { Slog.i( TAG, "Last rotation was more than " + rotationInterval + "ms (" + millisSinceLastRotation + "ms) in the past. Rotate now."); startRotation(); } Slog.v(TAG, "No rotation required, last " + lastRotated + "."); } private void startRotation() { scheduleRotationFromNow(); new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager).run(); } private void scheduleRotationFromNow() { mCryptoSettings.setSecondaryLastRotated(mClock.millis()); } } packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java 0 → 100644 +104 −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.security.keystore.recovery.InternalRecoveryServiceException; import android.security.keystore.recovery.LockScreenRequiredException; import android.util.Slog; import com.android.internal.util.Preconditions; import com.android.server.backup.encryption.CryptoSettings; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; import java.security.UnrecoverableKeyException; import java.util.Optional; /** * Starts rotating to a new secondary key. Cannot complete until the screen is unlocked and the new * key is synced. */ public class StartSecondaryKeyRotationTask { private static final String TAG = "BE-StSecondaryKeyRotTsk"; private final CryptoSettings mCryptoSettings; private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; public StartSecondaryKeyRotationTask( CryptoSettings cryptoSettings, RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager) { mCryptoSettings = Preconditions.checkNotNull(cryptoSettings); mSecondaryKeyManager = Preconditions.checkNotNull(secondaryKeyManager); } /** Begin the key rotation */ public void run() { Slog.i(TAG, "Attempting to initiate a secondary key rotation."); Optional<String> maybeCurrentAlias = mCryptoSettings.getActiveSecondaryKeyAlias(); if (!maybeCurrentAlias.isPresent()) { Slog.w(TAG, "No active current alias. Cannot trigger a secondary rotation."); return; } String currentAlias = maybeCurrentAlias.get(); Optional<String> maybeNextAlias = mCryptoSettings.getNextSecondaryKeyAlias(); if (maybeNextAlias.isPresent()) { String nextAlias = maybeNextAlias.get(); if (nextAlias.equals(currentAlias)) { // Shouldn't be possible, but guard against accidentally deleting the active key. Slog.e(TAG, "Was already trying to rotate to what is already the active key."); } else { Slog.w(TAG, "Was already rotating to another key. Cancelling that."); try { mSecondaryKeyManager.remove(nextAlias); } catch (Exception e) { Slog.wtf(TAG, "Could not remove old key", e); } } mCryptoSettings.removeNextSecondaryKeyAlias(); } RecoverableKeyStoreSecondaryKey newSecondaryKey; try { newSecondaryKey = mSecondaryKeyManager.generate(); } catch (LockScreenRequiredException e) { Slog.e(TAG, "No lock screen is set - cannot generate a new key to rotate to.", e); return; } catch (InternalRecoveryServiceException e) { Slog.e(TAG, "Internal error in Recovery Controller, failed to rotate key.", e); return; } catch (UnrecoverableKeyException e) { Slog.e(TAG, "Failed to get key after generating, failed to rotate", e); return; } String alias = newSecondaryKey.getAlias(); Slog.i(TAG, "Generated a new secondary key with alias '" + alias + "'."); try { mCryptoSettings.setNextSecondaryAlias(alias); Slog.i(TAG, "Successfully set '" + alias + "' as next key to rotate to"); } catch (IllegalArgumentException e) { Slog.e(TAG, "Unexpected error setting next alias", e); try { mSecondaryKeyManager.remove(alias); } catch (Exception err) { Slog.wtf(TAG, "Failed to remove generated key after encountering error", err); } } } } packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java 0 → 100644 +179 −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.keys; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; import android.content.Context; import android.platform.test.annotations.Presubmit; import androidx.test.core.app.ApplicationProvider; import com.android.server.backup.encryption.CryptoSettings; import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask; 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.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; import java.io.File; import java.time.Clock; @Config(shadows = SecondaryKeyRotationSchedulerTest.ShadowStartSecondaryKeyRotationTask.class) @RunWith(RobolectricTestRunner.class) @Presubmit public class SecondaryKeyRotationSchedulerTest { private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation"; @Mock private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; @Mock private Clock mClock; private CryptoSettings mCryptoSettings; private SecondaryKeyRotationScheduler mScheduler; private long mRotationIntervalMillis; @Before public void setUp() { MockitoAnnotations.initMocks(this); Context application = ApplicationProvider.getApplicationContext(); mCryptoSettings = CryptoSettings.getInstanceForTesting(application); mRotationIntervalMillis = mCryptoSettings.backupSecondaryKeyRotationIntervalMs(); mScheduler = new SecondaryKeyRotationScheduler( application, mSecondaryKeyManager, mCryptoSettings, mClock); ShadowStartSecondaryKeyRotationTask.reset(); } @Test public void startRotationIfScheduled_rotatesIfRotationWasFarEnoughInThePast() { long lastRotated = 100009; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(lastRotated + mRotationIntervalMillis); mScheduler.startRotationIfScheduled(); assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); } @Test public void startRotationIfScheduled_setsNewRotationTimeIfRotationWasFarEnoughInThePast() { long lastRotated = 100009; long now = lastRotated + mRotationIntervalMillis; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(now); mScheduler.startRotationIfScheduled(); assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); } @Test public void startRotationIfScheduled_rotatesIfClockHasChanged() { long lastRotated = 100009; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(lastRotated - 1); mScheduler.startRotationIfScheduled(); assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); } @Test public void startRotationIfScheduled_rotatesIfSentinelFileIsPresent() throws Exception { File file = new File(RuntimeEnvironment.application.getFilesDir(), SENTINEL_FILE_PATH); file.createNewFile(); mScheduler.startRotationIfScheduled(); assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); } @Test public void startRotationIfScheduled_setsNextRotationIfClockHasChanged() { long lastRotated = 100009; long now = lastRotated - 1; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(now); mScheduler.startRotationIfScheduled(); assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); } @Test public void startRotationIfScheduled_doesNothingIfRotationWasRecentEnough() { long lastRotated = 100009; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(lastRotated + mRotationIntervalMillis - 1); mScheduler.startRotationIfScheduled(); assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isFalse(); } @Test public void startRotationIfScheduled_doesNotSetRotationTimeIfRotationWasRecentEnough() { long lastRotated = 100009; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(lastRotated + mRotationIntervalMillis - 1); mScheduler.startRotationIfScheduled(); assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(lastRotated); } @Test public void startRotationIfScheduled_setsLastRotatedToNowIfNeverRotated() { long now = 13295436; setNow(now); mScheduler.startRotationIfScheduled(); assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); } private void setNow(long timestamp) { when(mClock.millis()).thenReturn(timestamp); } @Implements(StartSecondaryKeyRotationTask.class) public static class ShadowStartSecondaryKeyRotationTask { private static boolean sRan = false; @Implementation public void run() { sRan = true; } @Resetter public static void reset() { sRan = false; } } } packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java 0 → 100644 +113 −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 com.google.common.truth.Truth.assertThat; import android.platform.test.annotations.Presubmit; import android.security.keystore.recovery.RecoveryController; 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.shadows.ShadowRecoveryController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.security.SecureRandom; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowRecoveryController.class}) @Presubmit public class StartSecondaryKeyRotationTaskTest { private CryptoSettings mCryptoSettings; private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; private StartSecondaryKeyRotationTask mStartSecondaryKeyRotationTask; @Before public void setUp() throws Exception { mSecondaryKeyManager = new RecoverableKeyStoreSecondaryKeyManager( RecoveryController.getInstance(RuntimeEnvironment.application), new SecureRandom()); mCryptoSettings = CryptoSettings.getInstanceForTesting(RuntimeEnvironment.application); mStartSecondaryKeyRotationTask = new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager); ShadowRecoveryController.reset(); } @Test public void run_doesNothingIfNoActiveSecondaryExists() { mStartSecondaryKeyRotationTask.run(); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); } @Test public void run_doesNotRemoveExistingNextSecondaryKeyIfItIsAlreadyActive() throws Exception { generateAnActiveKey(); String activeAlias = mCryptoSettings.getActiveSecondaryKeyAlias().get(); mCryptoSettings.setNextSecondaryAlias(activeAlias); mStartSecondaryKeyRotationTask.run(); assertThat(mSecondaryKeyManager.get(activeAlias).isPresent()).isTrue(); } @Test public void run_doesRemoveExistingNextSecondaryKeyIfItIsNotYetActive() throws Exception { generateAnActiveKey(); RecoverableKeyStoreSecondaryKey nextKey = mSecondaryKeyManager.generate(); String nextAlias = nextKey.getAlias(); mCryptoSettings.setNextSecondaryAlias(nextAlias); mStartSecondaryKeyRotationTask.run(); assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isFalse(); } @Test public void run_generatesANewNextSecondaryKey() throws Exception { generateAnActiveKey(); mStartSecondaryKeyRotationTask.run(); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isTrue(); } @Test public void run_generatesANewKeyThatExistsInKeyStore() throws Exception { generateAnActiveKey(); mStartSecondaryKeyRotationTask.run(); String nextAlias = mCryptoSettings.getNextSecondaryKeyAlias().get(); assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isTrue(); } private void generateAnActiveKey() throws Exception { RecoverableKeyStoreSecondaryKey secondaryKey = mSecondaryKeyManager.generate(); mCryptoSettings.setActiveSecondaryKeyAlias(secondaryKey.getAlias()); } } Loading
packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java +9 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.security.KeyStoreException; import java.util.Optional; import java.util.concurrent.TimeUnit; /** * State about encrypted backups that needs to be remembered. Loading @@ -51,6 +52,9 @@ public class CryptoSettings { SECONDARY_KEY_LAST_ROTATED_AT }; private static final long DEFAULT_SECONDARY_KEY_ROTATION_PERIOD = TimeUnit.MILLISECONDS.convert(31, TimeUnit.DAYS); private static final String KEY_ANCESTRAL_SECONDARY_KEY_VERSION = "ancestral_secondary_key_version"; Loading Loading @@ -202,6 +206,11 @@ public class CryptoSettings { .apply(); } /** The number of milliseconds between secondary key rotation */ public long backupSecondaryKeyRotationIntervalMs() { return DEFAULT_SECONDARY_KEY_ROTATION_PERIOD; } /** Deletes all crypto settings related to backup (as opposed to restore). */ public void clearAllSettingsForBackup() { Editor sharedPrefsEditor = mSharedPreferences.edit(); Loading
packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java 0 → 100644 +116 −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.keys; import android.content.Context; import android.util.Slog; import com.android.server.backup.encryption.CryptoSettings; import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask; import java.io.File; import java.time.Clock; import java.util.Optional; /** * Helps schedule rotations of secondary keys. * * <p>TODO(b/72028016) Replace with a job. */ public class SecondaryKeyRotationScheduler { private static final String TAG = "SecondaryKeyRotationScheduler"; private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation"; private final Context mContext; private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; private final CryptoSettings mCryptoSettings; private final Clock mClock; public SecondaryKeyRotationScheduler( Context context, RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager, CryptoSettings cryptoSettings, Clock clock) { mContext = context; mCryptoSettings = cryptoSettings; mClock = clock; mSecondaryKeyManager = secondaryKeyManager; } /** * Returns {@code true} if a sentinel file for forcing secondary key rotation is present. This * is only for testing purposes. */ private boolean isForceRotationTestSentinelPresent() { File file = new File(mContext.getFilesDir(), SENTINEL_FILE_PATH); if (file.exists()) { file.delete(); return true; } return false; } /** Start the key rotation task if it's time to do so */ public void startRotationIfScheduled() { if (isForceRotationTestSentinelPresent()) { Slog.i(TAG, "Found force flag for secondary rotation. Starting now."); startRotation(); return; } Optional<Long> maybeLastRotated = mCryptoSettings.getSecondaryLastRotated(); if (!maybeLastRotated.isPresent()) { Slog.v(TAG, "No previous rotation, scheduling from now."); scheduleRotationFromNow(); return; } long lastRotated = maybeLastRotated.get(); long now = mClock.millis(); if (lastRotated > now) { Slog.i(TAG, "Last rotation was in the future. Clock must have changed. Rotate now."); startRotation(); return; } long millisSinceLastRotation = now - lastRotated; long rotationInterval = mCryptoSettings.backupSecondaryKeyRotationIntervalMs(); if (millisSinceLastRotation >= rotationInterval) { Slog.i( TAG, "Last rotation was more than " + rotationInterval + "ms (" + millisSinceLastRotation + "ms) in the past. Rotate now."); startRotation(); } Slog.v(TAG, "No rotation required, last " + lastRotated + "."); } private void startRotation() { scheduleRotationFromNow(); new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager).run(); } private void scheduleRotationFromNow() { mCryptoSettings.setSecondaryLastRotated(mClock.millis()); } }
packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java 0 → 100644 +104 −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.security.keystore.recovery.InternalRecoveryServiceException; import android.security.keystore.recovery.LockScreenRequiredException; import android.util.Slog; import com.android.internal.util.Preconditions; import com.android.server.backup.encryption.CryptoSettings; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; import java.security.UnrecoverableKeyException; import java.util.Optional; /** * Starts rotating to a new secondary key. Cannot complete until the screen is unlocked and the new * key is synced. */ public class StartSecondaryKeyRotationTask { private static final String TAG = "BE-StSecondaryKeyRotTsk"; private final CryptoSettings mCryptoSettings; private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; public StartSecondaryKeyRotationTask( CryptoSettings cryptoSettings, RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager) { mCryptoSettings = Preconditions.checkNotNull(cryptoSettings); mSecondaryKeyManager = Preconditions.checkNotNull(secondaryKeyManager); } /** Begin the key rotation */ public void run() { Slog.i(TAG, "Attempting to initiate a secondary key rotation."); Optional<String> maybeCurrentAlias = mCryptoSettings.getActiveSecondaryKeyAlias(); if (!maybeCurrentAlias.isPresent()) { Slog.w(TAG, "No active current alias. Cannot trigger a secondary rotation."); return; } String currentAlias = maybeCurrentAlias.get(); Optional<String> maybeNextAlias = mCryptoSettings.getNextSecondaryKeyAlias(); if (maybeNextAlias.isPresent()) { String nextAlias = maybeNextAlias.get(); if (nextAlias.equals(currentAlias)) { // Shouldn't be possible, but guard against accidentally deleting the active key. Slog.e(TAG, "Was already trying to rotate to what is already the active key."); } else { Slog.w(TAG, "Was already rotating to another key. Cancelling that."); try { mSecondaryKeyManager.remove(nextAlias); } catch (Exception e) { Slog.wtf(TAG, "Could not remove old key", e); } } mCryptoSettings.removeNextSecondaryKeyAlias(); } RecoverableKeyStoreSecondaryKey newSecondaryKey; try { newSecondaryKey = mSecondaryKeyManager.generate(); } catch (LockScreenRequiredException e) { Slog.e(TAG, "No lock screen is set - cannot generate a new key to rotate to.", e); return; } catch (InternalRecoveryServiceException e) { Slog.e(TAG, "Internal error in Recovery Controller, failed to rotate key.", e); return; } catch (UnrecoverableKeyException e) { Slog.e(TAG, "Failed to get key after generating, failed to rotate", e); return; } String alias = newSecondaryKey.getAlias(); Slog.i(TAG, "Generated a new secondary key with alias '" + alias + "'."); try { mCryptoSettings.setNextSecondaryAlias(alias); Slog.i(TAG, "Successfully set '" + alias + "' as next key to rotate to"); } catch (IllegalArgumentException e) { Slog.e(TAG, "Unexpected error setting next alias", e); try { mSecondaryKeyManager.remove(alias); } catch (Exception err) { Slog.wtf(TAG, "Failed to remove generated key after encountering error", err); } } } }
packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java 0 → 100644 +179 −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.keys; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; import android.content.Context; import android.platform.test.annotations.Presubmit; import androidx.test.core.app.ApplicationProvider; import com.android.server.backup.encryption.CryptoSettings; import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask; 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.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; import java.io.File; import java.time.Clock; @Config(shadows = SecondaryKeyRotationSchedulerTest.ShadowStartSecondaryKeyRotationTask.class) @RunWith(RobolectricTestRunner.class) @Presubmit public class SecondaryKeyRotationSchedulerTest { private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation"; @Mock private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; @Mock private Clock mClock; private CryptoSettings mCryptoSettings; private SecondaryKeyRotationScheduler mScheduler; private long mRotationIntervalMillis; @Before public void setUp() { MockitoAnnotations.initMocks(this); Context application = ApplicationProvider.getApplicationContext(); mCryptoSettings = CryptoSettings.getInstanceForTesting(application); mRotationIntervalMillis = mCryptoSettings.backupSecondaryKeyRotationIntervalMs(); mScheduler = new SecondaryKeyRotationScheduler( application, mSecondaryKeyManager, mCryptoSettings, mClock); ShadowStartSecondaryKeyRotationTask.reset(); } @Test public void startRotationIfScheduled_rotatesIfRotationWasFarEnoughInThePast() { long lastRotated = 100009; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(lastRotated + mRotationIntervalMillis); mScheduler.startRotationIfScheduled(); assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); } @Test public void startRotationIfScheduled_setsNewRotationTimeIfRotationWasFarEnoughInThePast() { long lastRotated = 100009; long now = lastRotated + mRotationIntervalMillis; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(now); mScheduler.startRotationIfScheduled(); assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); } @Test public void startRotationIfScheduled_rotatesIfClockHasChanged() { long lastRotated = 100009; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(lastRotated - 1); mScheduler.startRotationIfScheduled(); assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); } @Test public void startRotationIfScheduled_rotatesIfSentinelFileIsPresent() throws Exception { File file = new File(RuntimeEnvironment.application.getFilesDir(), SENTINEL_FILE_PATH); file.createNewFile(); mScheduler.startRotationIfScheduled(); assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); } @Test public void startRotationIfScheduled_setsNextRotationIfClockHasChanged() { long lastRotated = 100009; long now = lastRotated - 1; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(now); mScheduler.startRotationIfScheduled(); assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); } @Test public void startRotationIfScheduled_doesNothingIfRotationWasRecentEnough() { long lastRotated = 100009; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(lastRotated + mRotationIntervalMillis - 1); mScheduler.startRotationIfScheduled(); assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isFalse(); } @Test public void startRotationIfScheduled_doesNotSetRotationTimeIfRotationWasRecentEnough() { long lastRotated = 100009; mCryptoSettings.setSecondaryLastRotated(lastRotated); setNow(lastRotated + mRotationIntervalMillis - 1); mScheduler.startRotationIfScheduled(); assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(lastRotated); } @Test public void startRotationIfScheduled_setsLastRotatedToNowIfNeverRotated() { long now = 13295436; setNow(now); mScheduler.startRotationIfScheduled(); assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); } private void setNow(long timestamp) { when(mClock.millis()).thenReturn(timestamp); } @Implements(StartSecondaryKeyRotationTask.class) public static class ShadowStartSecondaryKeyRotationTask { private static boolean sRan = false; @Implementation public void run() { sRan = true; } @Resetter public static void reset() { sRan = false; } } }
packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java 0 → 100644 +113 −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 com.google.common.truth.Truth.assertThat; import android.platform.test.annotations.Presubmit; import android.security.keystore.recovery.RecoveryController; 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.shadows.ShadowRecoveryController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.security.SecureRandom; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowRecoveryController.class}) @Presubmit public class StartSecondaryKeyRotationTaskTest { private CryptoSettings mCryptoSettings; private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; private StartSecondaryKeyRotationTask mStartSecondaryKeyRotationTask; @Before public void setUp() throws Exception { mSecondaryKeyManager = new RecoverableKeyStoreSecondaryKeyManager( RecoveryController.getInstance(RuntimeEnvironment.application), new SecureRandom()); mCryptoSettings = CryptoSettings.getInstanceForTesting(RuntimeEnvironment.application); mStartSecondaryKeyRotationTask = new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager); ShadowRecoveryController.reset(); } @Test public void run_doesNothingIfNoActiveSecondaryExists() { mStartSecondaryKeyRotationTask.run(); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); } @Test public void run_doesNotRemoveExistingNextSecondaryKeyIfItIsAlreadyActive() throws Exception { generateAnActiveKey(); String activeAlias = mCryptoSettings.getActiveSecondaryKeyAlias().get(); mCryptoSettings.setNextSecondaryAlias(activeAlias); mStartSecondaryKeyRotationTask.run(); assertThat(mSecondaryKeyManager.get(activeAlias).isPresent()).isTrue(); } @Test public void run_doesRemoveExistingNextSecondaryKeyIfItIsNotYetActive() throws Exception { generateAnActiveKey(); RecoverableKeyStoreSecondaryKey nextKey = mSecondaryKeyManager.generate(); String nextAlias = nextKey.getAlias(); mCryptoSettings.setNextSecondaryAlias(nextAlias); mStartSecondaryKeyRotationTask.run(); assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isFalse(); } @Test public void run_generatesANewNextSecondaryKey() throws Exception { generateAnActiveKey(); mStartSecondaryKeyRotationTask.run(); assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isTrue(); } @Test public void run_generatesANewKeyThatExistsInKeyStore() throws Exception { generateAnActiveKey(); mStartSecondaryKeyRotationTask.run(); String nextAlias = mCryptoSettings.getNextSecondaryKeyAlias().get(); assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isTrue(); } private void generateAnActiveKey() throws Exception { RecoverableKeyStoreSecondaryKey secondaryKey = mSecondaryKeyManager.generate(); mCryptoSettings.setActiveSecondaryKeyAlias(secondaryKey.getAlias()); } }