Loading services/core/java/com/android/server/locksettings/RebootEscrowManager.java +56 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.locksettings; import static android.os.UserHandle.USER_SYSTEM; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; Loading @@ -24,6 +26,7 @@ import android.hardware.rebootescrow.IRebootEscrow; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserManager; import android.provider.Settings; import android.util.Slog; import com.android.internal.annotations.GuardedBy; Loading @@ -39,6 +42,26 @@ import java.util.NoSuchElementException; class RebootEscrowManager { private static final String TAG = "RebootEscrowManager"; /** * Used in the database storage to indicate the boot count at which the reboot escrow was * previously armed. */ @VisibleForTesting public static final String REBOOT_ESCROW_ARMED_KEY = "reboot_escrow_armed_count"; /** * Number of boots until we consider the escrow data to be stale for the purposes of metrics. * <p> * If the delta between the current boot number and the boot number stored when the mechanism * was armed is under this number and the escrow mechanism fails, we report it as a failure of * the mechanism. * <p> * If the delta over this number and escrow fails, we will not report the metric as failed * since there most likely was some other issue if the device rebooted several times before * getting to the escrow restore code. */ private static final int BOOT_COUNT_TOLERANCE = 5; /** * Used to track when the reboot escrow is wanted. Should stay true once escrow is requested * unless clearRebootEscrow is called. This will allow all the active users to be unlocked Loading Loading @@ -74,6 +97,7 @@ class RebootEscrowManager { interface Callbacks { boolean isUserSecure(int userId); void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId); } Loading @@ -92,7 +116,8 @@ class RebootEscrowManager { return (UserManager) mContext.getSystemService(Context.USER_SERVICE); } public @Nullable IRebootEscrow getRebootEscrow() { @Nullable public IRebootEscrow getRebootEscrow() { try { return IRebootEscrow.Stub.asInterface(ServiceManager.getService( "android.hardware.rebootescrow.IRebootEscrow/default")); Loading @@ -101,6 +126,15 @@ class RebootEscrowManager { } return null; } public int getBootCount() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0); } public void reportMetric(boolean success) { FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, success); } } RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) { Loading Loading @@ -135,7 +169,7 @@ class RebootEscrowManager { for (UserInfo user : users) { mStorage.removeRebootEscrow(user.id); } FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false); onEscrowRestoreComplete(false); return; } Loading @@ -143,8 +177,19 @@ class RebootEscrowManager { for (UserInfo user : rebootEscrowUsers) { allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey); } FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, allUsersUnlocked); onEscrowRestoreComplete(allUsersUnlocked); } private void onEscrowRestoreComplete(boolean success) { int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, 0, USER_SYSTEM); mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); int bootCountDelta = mInjector.getBootCount() - previousBootCount; if (bootCountDelta > BOOT_COUNT_TOLERANCE) { return; } mInjector.reportMetric(success); } private RebootEscrowKey getAndClearRebootEscrowKey() { Loading Loading @@ -267,6 +312,8 @@ class RebootEscrowManager { return; } mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); try { rebootEscrow.storeKey(new byte[32]); } catch (RemoteException e) { Loading Loading @@ -308,6 +355,11 @@ class RebootEscrowManager { } catch (RemoteException e) { Slog.e(TAG, "Failed escrow secret to RebootEscrow HAL", e); } if (armedRebootEscrow) { mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM); } return armedRebootEscrow; } Loading services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +145 −4 Original line number Diff line number Diff line Loading @@ -19,12 +19,16 @@ package com.android.server.locksettings; import static android.content.pm.UserInfo.FLAG_FULL; import static android.content.pm.UserInfo.FLAG_PRIMARY; import static android.content.pm.UserInfo.FLAG_PROFILE; import static android.os.UserHandle.USER_SYSTEM; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; Loading @@ -32,8 +36,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertNull; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; import android.hardware.rebootescrow.IRebootEscrow; import android.os.RemoteException; Loading @@ -49,6 +55,7 @@ import com.android.internal.widget.RebootEscrowListener; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.io.File; import java.util.ArrayList; Loading Loading @@ -76,16 +83,26 @@ public class RebootEscrowManagerTests { LockSettingsStorageTestable mStorage; private MockableRebootEscrowInjected mInjected; private RebootEscrowManager mService; public interface MockableRebootEscrowInjected { int getBootCount(); void reportMetric(boolean success); } static class MockInjector extends RebootEscrowManager.Injector { private final IRebootEscrow mRebootEscrow; private final UserManager mUserManager; private final MockableRebootEscrowInjected mInjected; MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow) { MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow, MockableRebootEscrowInjected injected) { super(context); mRebootEscrow = rebootEscrow; mUserManager = userManager; mInjected = injected; } @Override Loading @@ -97,11 +114,21 @@ public class RebootEscrowManagerTests { public IRebootEscrow getRebootEscrow() { return mRebootEscrow; } @Override public int getBootCount() { return mInjected.getBootCount(); } @Override public void reportMetric(boolean success) { mInjected.reportMetric(success); } } @Before public void setUp_baseServices() throws Exception { mContext = mock(Context.class); mContext = new ContextWrapper(InstrumentationRegistry.getContext()); mUserManager = mock(UserManager.class); mCallbacks = mock(RebootEscrowManager.Callbacks.class); mRebootEscrow = mock(IRebootEscrow.class); Loading @@ -119,8 +146,9 @@ public class RebootEscrowManagerTests { when(mCallbacks.isUserSecure(WORK_PROFILE_USER_ID)).thenReturn(true); when(mCallbacks.isUserSecure(NONSECURE_SECONDARY_USER_ID)).thenReturn(false); when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true); mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow), mCallbacks, mStorage); mInjected = mock(MockableRebootEscrowInjected.class); mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow, mInjected), mCallbacks, mStorage); } @Test Loading Loading @@ -160,7 +188,11 @@ public class RebootEscrowManagerTests { verify(mockListener).onPreparedForReboot(eq(true)); verify(mRebootEscrow, never()).storeKey(any()); assertNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); assertTrue(mService.armRebootEscrowIfNeeded()); assertNotNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); verify(mRebootEscrow).storeKey(any()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); Loading @@ -180,7 +212,15 @@ public class RebootEscrowManagerTests { FAKE_AUTH_TOKEN); verify(mRebootEscrow, never()).storeKey(any()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID)); assertNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); assertTrue(mService.armRebootEscrowIfNeeded()); assertNotNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); verify(mRebootEscrow, times(1)).storeKey(any()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); Loading @@ -200,4 +240,105 @@ public class RebootEscrowManagerTests { assertFalse(mService.armRebootEscrowIfNeeded()); verifyNoMoreInteractions(mRebootEscrow); } @Test public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception { mService.loadRebootEscrowDataIfAvailable(); } @Test public void loadRebootEscrowDataIfAvailable_Success() throws Exception { when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mRebootEscrow); mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); verify(mockListener).onPreparedForReboot(eq(true)); verify(mRebootEscrow, never()).storeKey(any()); ArgumentCaptor<byte[]> keyByteCaptor = ArgumentCaptor.forClass(byte[].class); assertTrue(mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(keyByteCaptor.capture()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID)); // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); mService.loadRebootEscrowDataIfAvailable(); verify(mRebootEscrow).retrieveKey(); assertTrue(metricsSuccessCaptor.getValue()); } @Test public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception { when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mRebootEscrow); mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); verify(mockListener).onPreparedForReboot(eq(true)); verify(mRebootEscrow, never()).storeKey(any()); ArgumentCaptor<byte[]> keyByteCaptor = ArgumentCaptor.forClass(byte[].class); assertTrue(mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(keyByteCaptor.capture()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID)); // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); mService.loadRebootEscrowDataIfAvailable(); verify(mRebootEscrow).retrieveKey(); verify(mInjected, never()).reportMetric(anyBoolean()); } @Test public void loadRebootEscrowDataIfAvailable_RestoreUnsuccessful_Failure() throws Exception { when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mRebootEscrow); mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); verify(mockListener).onPreparedForReboot(eq(true)); verify(mRebootEscrow, never()).storeKey(any()); assertTrue(mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(any()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID)); // pretend reboot happens here. when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]); mService.loadRebootEscrowDataIfAvailable(); verify(mRebootEscrow).retrieveKey(); assertFalse(metricsSuccessCaptor.getValue()); } } Loading
services/core/java/com/android/server/locksettings/RebootEscrowManager.java +56 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.locksettings; import static android.os.UserHandle.USER_SYSTEM; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; Loading @@ -24,6 +26,7 @@ import android.hardware.rebootescrow.IRebootEscrow; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserManager; import android.provider.Settings; import android.util.Slog; import com.android.internal.annotations.GuardedBy; Loading @@ -39,6 +42,26 @@ import java.util.NoSuchElementException; class RebootEscrowManager { private static final String TAG = "RebootEscrowManager"; /** * Used in the database storage to indicate the boot count at which the reboot escrow was * previously armed. */ @VisibleForTesting public static final String REBOOT_ESCROW_ARMED_KEY = "reboot_escrow_armed_count"; /** * Number of boots until we consider the escrow data to be stale for the purposes of metrics. * <p> * If the delta between the current boot number and the boot number stored when the mechanism * was armed is under this number and the escrow mechanism fails, we report it as a failure of * the mechanism. * <p> * If the delta over this number and escrow fails, we will not report the metric as failed * since there most likely was some other issue if the device rebooted several times before * getting to the escrow restore code. */ private static final int BOOT_COUNT_TOLERANCE = 5; /** * Used to track when the reboot escrow is wanted. Should stay true once escrow is requested * unless clearRebootEscrow is called. This will allow all the active users to be unlocked Loading Loading @@ -74,6 +97,7 @@ class RebootEscrowManager { interface Callbacks { boolean isUserSecure(int userId); void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId); } Loading @@ -92,7 +116,8 @@ class RebootEscrowManager { return (UserManager) mContext.getSystemService(Context.USER_SERVICE); } public @Nullable IRebootEscrow getRebootEscrow() { @Nullable public IRebootEscrow getRebootEscrow() { try { return IRebootEscrow.Stub.asInterface(ServiceManager.getService( "android.hardware.rebootescrow.IRebootEscrow/default")); Loading @@ -101,6 +126,15 @@ class RebootEscrowManager { } return null; } public int getBootCount() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0); } public void reportMetric(boolean success) { FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, success); } } RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) { Loading Loading @@ -135,7 +169,7 @@ class RebootEscrowManager { for (UserInfo user : users) { mStorage.removeRebootEscrow(user.id); } FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false); onEscrowRestoreComplete(false); return; } Loading @@ -143,8 +177,19 @@ class RebootEscrowManager { for (UserInfo user : rebootEscrowUsers) { allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey); } FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, allUsersUnlocked); onEscrowRestoreComplete(allUsersUnlocked); } private void onEscrowRestoreComplete(boolean success) { int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, 0, USER_SYSTEM); mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); int bootCountDelta = mInjector.getBootCount() - previousBootCount; if (bootCountDelta > BOOT_COUNT_TOLERANCE) { return; } mInjector.reportMetric(success); } private RebootEscrowKey getAndClearRebootEscrowKey() { Loading Loading @@ -267,6 +312,8 @@ class RebootEscrowManager { return; } mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); try { rebootEscrow.storeKey(new byte[32]); } catch (RemoteException e) { Loading Loading @@ -308,6 +355,11 @@ class RebootEscrowManager { } catch (RemoteException e) { Slog.e(TAG, "Failed escrow secret to RebootEscrow HAL", e); } if (armedRebootEscrow) { mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM); } return armedRebootEscrow; } Loading
services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +145 −4 Original line number Diff line number Diff line Loading @@ -19,12 +19,16 @@ package com.android.server.locksettings; import static android.content.pm.UserInfo.FLAG_FULL; import static android.content.pm.UserInfo.FLAG_PRIMARY; import static android.content.pm.UserInfo.FLAG_PROFILE; import static android.os.UserHandle.USER_SYSTEM; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; Loading @@ -32,8 +36,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertNull; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; import android.hardware.rebootescrow.IRebootEscrow; import android.os.RemoteException; Loading @@ -49,6 +55,7 @@ import com.android.internal.widget.RebootEscrowListener; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.io.File; import java.util.ArrayList; Loading Loading @@ -76,16 +83,26 @@ public class RebootEscrowManagerTests { LockSettingsStorageTestable mStorage; private MockableRebootEscrowInjected mInjected; private RebootEscrowManager mService; public interface MockableRebootEscrowInjected { int getBootCount(); void reportMetric(boolean success); } static class MockInjector extends RebootEscrowManager.Injector { private final IRebootEscrow mRebootEscrow; private final UserManager mUserManager; private final MockableRebootEscrowInjected mInjected; MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow) { MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow, MockableRebootEscrowInjected injected) { super(context); mRebootEscrow = rebootEscrow; mUserManager = userManager; mInjected = injected; } @Override Loading @@ -97,11 +114,21 @@ public class RebootEscrowManagerTests { public IRebootEscrow getRebootEscrow() { return mRebootEscrow; } @Override public int getBootCount() { return mInjected.getBootCount(); } @Override public void reportMetric(boolean success) { mInjected.reportMetric(success); } } @Before public void setUp_baseServices() throws Exception { mContext = mock(Context.class); mContext = new ContextWrapper(InstrumentationRegistry.getContext()); mUserManager = mock(UserManager.class); mCallbacks = mock(RebootEscrowManager.Callbacks.class); mRebootEscrow = mock(IRebootEscrow.class); Loading @@ -119,8 +146,9 @@ public class RebootEscrowManagerTests { when(mCallbacks.isUserSecure(WORK_PROFILE_USER_ID)).thenReturn(true); when(mCallbacks.isUserSecure(NONSECURE_SECONDARY_USER_ID)).thenReturn(false); when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true); mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow), mCallbacks, mStorage); mInjected = mock(MockableRebootEscrowInjected.class); mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow, mInjected), mCallbacks, mStorage); } @Test Loading Loading @@ -160,7 +188,11 @@ public class RebootEscrowManagerTests { verify(mockListener).onPreparedForReboot(eq(true)); verify(mRebootEscrow, never()).storeKey(any()); assertNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); assertTrue(mService.armRebootEscrowIfNeeded()); assertNotNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); verify(mRebootEscrow).storeKey(any()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); Loading @@ -180,7 +212,15 @@ public class RebootEscrowManagerTests { FAKE_AUTH_TOKEN); verify(mRebootEscrow, never()).storeKey(any()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID)); assertNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); assertTrue(mService.armRebootEscrowIfNeeded()); assertNotNull( mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); verify(mRebootEscrow, times(1)).storeKey(any()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); Loading @@ -200,4 +240,105 @@ public class RebootEscrowManagerTests { assertFalse(mService.armRebootEscrowIfNeeded()); verifyNoMoreInteractions(mRebootEscrow); } @Test public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception { mService.loadRebootEscrowDataIfAvailable(); } @Test public void loadRebootEscrowDataIfAvailable_Success() throws Exception { when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mRebootEscrow); mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); verify(mockListener).onPreparedForReboot(eq(true)); verify(mRebootEscrow, never()).storeKey(any()); ArgumentCaptor<byte[]> keyByteCaptor = ArgumentCaptor.forClass(byte[].class); assertTrue(mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(keyByteCaptor.capture()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID)); // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); mService.loadRebootEscrowDataIfAvailable(); verify(mRebootEscrow).retrieveKey(); assertTrue(metricsSuccessCaptor.getValue()); } @Test public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception { when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mRebootEscrow); mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); verify(mockListener).onPreparedForReboot(eq(true)); verify(mRebootEscrow, never()).storeKey(any()); ArgumentCaptor<byte[]> keyByteCaptor = ArgumentCaptor.forClass(byte[].class); assertTrue(mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(keyByteCaptor.capture()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID)); // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); mService.loadRebootEscrowDataIfAvailable(); verify(mRebootEscrow).retrieveKey(); verify(mInjected, never()).reportMetric(anyBoolean()); } @Test public void loadRebootEscrowDataIfAvailable_RestoreUnsuccessful_Failure() throws Exception { when(mInjected.getBootCount()).thenReturn(0); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mRebootEscrow); mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); verify(mockListener).onPreparedForReboot(eq(true)); verify(mRebootEscrow, never()).storeKey(any()); assertTrue(mService.armRebootEscrowIfNeeded()); verify(mRebootEscrow).storeKey(any()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID)); // pretend reboot happens here. when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]); mService.loadRebootEscrowDataIfAvailable(); verify(mRebootEscrow).retrieveKey(); assertFalse(metricsSuccessCaptor.getValue()); } }