Loading services/core/java/com/android/server/locksettings/LockSettingsService.java +1 −1 Original line number Diff line number Diff line Loading @@ -581,7 +581,7 @@ public class LockSettingsService extends ILockSettings.Stub { public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks, LockSettingsStorage storage) { return new RebootEscrowManager(mContext, callbacks, storage, getHandler(getServiceThread())); getHandler(getServiceThread()), getUserManagerInternal()); } public int binderGetCallingUid() { Loading services/core/java/com/android/server/locksettings/RebootEscrowManager.java +54 −7 Original line number Diff line number Diff line Loading @@ -51,16 +51,20 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.widget.RebootEscrowListener; import com.android.server.pm.UserManagerInternal; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; import javax.crypto.SecretKey; Loading Loading @@ -138,6 +142,7 @@ class RebootEscrowManager { ERROR_KEYSTORE_FAILURE, ERROR_NO_NETWORK, ERROR_TIMEOUT_EXHAUSTED, ERROR_NO_REBOOT_ESCROW_DATA, }) @Retention(RetentionPolicy.SOURCE) @interface RebootEscrowErrorCode { Loading @@ -153,6 +158,7 @@ class RebootEscrowManager { static final int ERROR_KEYSTORE_FAILURE = 7; static final int ERROR_NO_NETWORK = 8; static final int ERROR_TIMEOUT_EXHAUSTED = 9; static final int ERROR_NO_REBOOT_ESCROW_DATA = 10; private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE; Loading Loading @@ -222,11 +228,16 @@ class RebootEscrowManager { private final RebootEscrowKeyStoreManager mKeyStoreManager; private final LockSettingsStorage mStorage; private RebootEscrowProviderInterface mRebootEscrowProvider; private final UserManagerInternal mUserManagerInternal; Injector(Context context, LockSettingsStorage storage) { Injector( Context context, LockSettingsStorage storage, UserManagerInternal userManagerInternal) { mContext = context; mStorage = storage; mKeyStoreManager = new RebootEscrowKeyStoreManager(); mUserManagerInternal = userManagerInternal; } private RebootEscrowProviderInterface createRebootEscrowProvider() { Loading Loading @@ -326,6 +337,10 @@ class RebootEscrowManager { return (UserManager) mContext.getSystemService(Context.USER_SERVICE); } public UserManagerInternal getUserManagerInternal() { return mUserManagerInternal; } public RebootEscrowKeyStoreManager getKeyStoreManager() { return mKeyStoreManager; } Loading Loading @@ -402,8 +417,8 @@ class RebootEscrowManager { } RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage, Handler handler) { this(new Injector(context, storage), callbacks, storage, handler); Handler handler, UserManagerInternal userManagerInternal) { this(new Injector(context, storage, userManagerInternal), callbacks, storage, handler); } @VisibleForTesting Loading Loading @@ -451,18 +466,50 @@ class RebootEscrowManager { onEscrowRestoreComplete(false, attemptCount, retryHandler); } void loadRebootEscrowDataIfAvailable(Handler retryHandler) { List<UserInfo> users = mUserManager.getUsers(); private List<UserInfo> getUsersToUnlock(List<UserInfo> users) { // System user must be unlocked to unlock any other user if (mCallbacks.isUserSecure(USER_SYSTEM) && !mStorage.hasRebootEscrow(USER_SYSTEM)) { Slog.i(TAG, "No reboot escrow data found for system user"); return Collections.emptyList(); } Set<Integer> noEscrowDataUsers = new HashSet<>(); for (UserInfo user : users) { if (mCallbacks.isUserSecure(user.id) && !mStorage.hasRebootEscrow(user.id)) { Slog.d(TAG, "No reboot escrow data found for user " + user); noEscrowDataUsers.add(user.id); } } List<UserInfo> rebootEscrowUsers = new ArrayList<>(); for (UserInfo user : users) { if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) { // No lskf, no need to unlock. if (!mCallbacks.isUserSecure(user.id)) { continue; } // Don't unlock if user or user's parent does not have reboot data int userId = user.id; if (noEscrowDataUsers.contains(userId) || noEscrowDataUsers.contains( mInjector.getUserManagerInternal().getProfileParentId(userId))) { continue; } rebootEscrowUsers.add(user); } return rebootEscrowUsers; } void loadRebootEscrowDataIfAvailable(Handler retryHandler) { List<UserInfo> users = mUserManager.getUsers(); List<UserInfo> rebootEscrowUsers = getUsersToUnlock(users); if (rebootEscrowUsers.isEmpty()) { Slog.i(TAG, "No reboot escrow data found for users," + " skipping loading escrow data"); setLoadEscrowDataErrorCode(ERROR_NO_REBOOT_ESCROW_DATA, retryHandler); reportMetricOnRestoreComplete( /* success= */ false, /* attemptCount= */ 1, retryHandler); clearMetricsStorage(); return; } Loading services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +224 −16 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ 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.content.pm.UserInfo.NO_PROFILE_GROUP_ID; import static android.os.UserHandle.USER_SYSTEM; import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_ESCROW_NOT_READY; Loading @@ -32,6 +33,7 @@ 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.anyByte; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; Loading Loading @@ -65,6 +67,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.widget.RebootEscrowListener; import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection; import com.android.server.pm.UserManagerInternal; import org.junit.Before; import org.junit.Test; Loading Loading @@ -107,6 +110,7 @@ public class RebootEscrowManagerTests { private Context mContext; private UserManager mUserManager; private UserManagerInternal mUserManagerInternal; private RebootEscrowManager.Callbacks mCallbacks; private IRebootEscrow mRebootEscrow; private ResumeOnRebootServiceConnection mServiceConnection; Loading @@ -126,13 +130,15 @@ public class RebootEscrowManagerTests { long getCurrentTimeMillis(); void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete); int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete); } static class MockInjector extends RebootEscrowManager.Injector { private final IRebootEscrow mRebootEscrow; private final RebootEscrowProviderInterface mDefaultRebootEscrowProvider; private final UserManager mUserManager; private final UserManagerInternal mUserManagerInternal; private final MockableRebootEscrowInjected mInjected; private final RebootEscrowKeyStoreManager mKeyStoreManager; private boolean mServerBased; Loading @@ -141,12 +147,16 @@ public class RebootEscrowManagerTests { private Consumer<ConnectivityManager.NetworkCallback> mNetworkConsumer; private boolean mWaitForInternet; MockInjector(Context context, UserManager userManager, MockInjector( Context context, UserManager userManager, UserManagerInternal userManagerInternal, IRebootEscrow rebootEscrow, RebootEscrowKeyStoreManager keyStoreManager, LockSettingsStorageTestable storage, MockableRebootEscrowInjected injected) { super(context, storage); // TODO: change this super(context, storage, userManagerInternal); mRebootEscrow = rebootEscrow; mServerBased = false; mWaitForInternet = false; Loading @@ -159,16 +169,20 @@ public class RebootEscrowManagerTests { }; mDefaultRebootEscrowProvider = new RebootEscrowProviderHalImpl(halInjector); mUserManager = userManager; mUserManagerInternal = userManagerInternal; mKeyStoreManager = keyStoreManager; mInjected = injected; } MockInjector(Context context, UserManager userManager, MockInjector( Context context, UserManager userManager, UserManagerInternal userManagerInternal, ResumeOnRebootServiceConnection serviceConnection, RebootEscrowKeyStoreManager keyStoreManager, LockSettingsStorageTestable storage, MockableRebootEscrowInjected injected) { super(context, storage); super(context, storage, userManagerInternal); mRebootEscrow = null; mServerBased = true; mWaitForInternet = false; Loading @@ -187,6 +201,7 @@ public class RebootEscrowManagerTests { mDefaultRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl( storage, injector); mUserManager = userManager; mUserManagerInternal = userManagerInternal; mKeyStoreManager = keyStoreManager; mInjected = injected; } Loading @@ -201,6 +216,11 @@ public class RebootEscrowManagerTests { return mUserManager; } @Override public UserManagerInternal getUserManagerInternal() { return mUserManagerInternal; } @Override public boolean serverBasedResumeOnReboot() { return mServerBased; Loading Loading @@ -301,6 +321,7 @@ public class RebootEscrowManagerTests { public void setUp_baseServices() throws Exception { mContext = new ContextWrapper(InstrumentationRegistry.getContext()); mUserManager = mock(UserManager.class); mUserManagerInternal = mock(UserManagerInternal.class); mCallbacks = mock(RebootEscrowManager.Callbacks.class); mRebootEscrow = mock(IRebootEscrow.class); mServiceConnection = mock(ResumeOnRebootServiceConnection.class); Loading @@ -314,28 +335,43 @@ public class RebootEscrowManagerTests { new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings")); ArrayList<UserInfo> users = new ArrayList<>(); users.add(new UserInfo(PRIMARY_USER_ID, "primary", FLAG_PRIMARY)); users.add(new UserInfo(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE)); users.add(new UserInfo(NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL)); users.add(new UserInfo(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL)); users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, PRIMARY_USER_ID)); users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, PRIMARY_USER_ID)); users.add( createUser( NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL, NO_PROFILE_GROUP_ID)); users.add(createUser(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, NO_PROFILE_GROUP_ID)); when(mUserManager.getUsers()).thenReturn(users); when(mCallbacks.isUserSecure(PRIMARY_USER_ID)).thenReturn(true); 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); mInjected = mock(MockableRebootEscrowInjected.class); mMockInjector = new MockInjector(mContext, mUserManager, mRebootEscrow, mKeyStoreManager, mStorage, mInjected); mMockInjector = new MockInjector( mContext, mUserManager, mUserManagerInternal, mRebootEscrow, mKeyStoreManager, mStorage, mInjected); HandlerThread thread = new HandlerThread("RebootEscrowManagerTest"); thread.start(); mHandler = new Handler(thread.getLooper()); mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler); } private void setServerBasedRebootEscrowProvider() throws Exception { mMockInjector = new MockInjector(mContext, mUserManager, mServiceConnection, mKeyStoreManager, mStorage, mInjected); mMockInjector = new MockInjector( mContext, mUserManager, mUserManagerInternal, mServiceConnection, mKeyStoreManager, mStorage, mInjected); mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler); } Loading @@ -352,6 +388,12 @@ public class RebootEscrowManagerTests { waitForHandler(); } private UserInfo createUser(int id, String name, int flag, int profileGroupId) { UserInfo user = new UserInfo(id, name, flag); when(mUserManagerInternal.getProfileParentId(eq(id))).thenReturn(profileGroupId); return user; } @Test public void prepareRebootEscrow_Success() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); Loading Loading @@ -558,6 +600,172 @@ public class RebootEscrowManagerTests { -1, USER_SYSTEM), -1); } @Test public void loadRebootEscrowDataIfAvailable_noDataPrimaryUser_Failure() throws Exception { setServerBasedRebootEscrowProvider(); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mServiceConnection); // escrow secondary user, don't escrow primary user callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID); verify(mockListener).onPreparedForReboot(eq(true)); verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertTrue(mStorage.hasRebootEscrowServerBlob()); // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class); doNothing() .when(mInjected) .reportMetric( metricsSuccessCaptor.capture(), metricsErrorCodeCaptor.capture(), eq(2) /* Server based */, eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt()); mService.loadRebootEscrowDataIfAvailable(null); verify(mServiceConnection, never()).unwrap(any(), anyLong()); verify(mCallbacks, never()).onRebootEscrowRestored(anyByte(), any(), anyInt()); assertFalse(metricsSuccessCaptor.getValue()); assertEquals( Integer.valueOf(RebootEscrowManager.ERROR_NO_REBOOT_ESCROW_DATA), metricsErrorCodeCaptor.getValue()); } @Test public void loadRebootEscrowDataIfAvailable_noDataSecondaryUser_Success() throws Exception { setServerBasedRebootEscrowProvider(); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mServiceConnection); // Setup work profile with secondary user as parent. ArrayList<UserInfo> users = new ArrayList<>(); users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, NO_PROFILE_GROUP_ID)); users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, SECURE_SECONDARY_USER_ID)); users.add( createUser( SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, SECURE_SECONDARY_USER_ID)); when(mUserManager.getUsers()).thenReturn(users); // escrow primary user and work profile, don't escrow secondary user callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID); verify(mockListener).onPreparedForReboot(eq(true)); verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); callToRebootEscrowIfNeededAndWait(WORK_PROFILE_USER_ID); verify(mockListener).onPreparedForReboot(eq(true)); verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); assertTrue(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID)); assertTrue(mStorage.hasRebootEscrowServerBlob()); // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing() .when(mInjected) .reportMetric( metricsSuccessCaptor.capture(), eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt()); when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); mService.loadRebootEscrowDataIfAvailable(null); verify(mServiceConnection).unwrap(any(), anyLong()); verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID)); verify(mCallbacks, never()) .onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID)); verify(mCallbacks, never()) .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID)); verify(mCallbacks, never()) .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID)); assertTrue(metricsSuccessCaptor.getValue()); } @Test public void loadRebootEscrowDataIfAvailable_noDataWorkProfile_Success() throws Exception { setServerBasedRebootEscrowProvider(); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mServiceConnection); // escrow primary user and secondary user, don't escrow work profile callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID); verify(mockListener).onPreparedForReboot(eq(true)); verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID); verify(mockListener).onPreparedForReboot(eq(true)); verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID)); assertTrue(mStorage.hasRebootEscrowServerBlob()); // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing() .when(mInjected) .reportMetric( metricsSuccessCaptor.capture(), eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt()); when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); mService.loadRebootEscrowDataIfAvailable(null); verify(mServiceConnection).unwrap(any(), anyLong()); verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID)); verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID)); verify(mCallbacks, never()) .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID)); verify(mCallbacks, never()) .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID)); assertTrue(metricsSuccessCaptor.getValue()); } @Test public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception { setServerBasedRebootEscrowProvider(); Loading Loading
services/core/java/com/android/server/locksettings/LockSettingsService.java +1 −1 Original line number Diff line number Diff line Loading @@ -581,7 +581,7 @@ public class LockSettingsService extends ILockSettings.Stub { public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks, LockSettingsStorage storage) { return new RebootEscrowManager(mContext, callbacks, storage, getHandler(getServiceThread())); getHandler(getServiceThread()), getUserManagerInternal()); } public int binderGetCallingUid() { Loading
services/core/java/com/android/server/locksettings/RebootEscrowManager.java +54 −7 Original line number Diff line number Diff line Loading @@ -51,16 +51,20 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.widget.RebootEscrowListener; import com.android.server.pm.UserManagerInternal; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; import javax.crypto.SecretKey; Loading Loading @@ -138,6 +142,7 @@ class RebootEscrowManager { ERROR_KEYSTORE_FAILURE, ERROR_NO_NETWORK, ERROR_TIMEOUT_EXHAUSTED, ERROR_NO_REBOOT_ESCROW_DATA, }) @Retention(RetentionPolicy.SOURCE) @interface RebootEscrowErrorCode { Loading @@ -153,6 +158,7 @@ class RebootEscrowManager { static final int ERROR_KEYSTORE_FAILURE = 7; static final int ERROR_NO_NETWORK = 8; static final int ERROR_TIMEOUT_EXHAUSTED = 9; static final int ERROR_NO_REBOOT_ESCROW_DATA = 10; private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE; Loading Loading @@ -222,11 +228,16 @@ class RebootEscrowManager { private final RebootEscrowKeyStoreManager mKeyStoreManager; private final LockSettingsStorage mStorage; private RebootEscrowProviderInterface mRebootEscrowProvider; private final UserManagerInternal mUserManagerInternal; Injector(Context context, LockSettingsStorage storage) { Injector( Context context, LockSettingsStorage storage, UserManagerInternal userManagerInternal) { mContext = context; mStorage = storage; mKeyStoreManager = new RebootEscrowKeyStoreManager(); mUserManagerInternal = userManagerInternal; } private RebootEscrowProviderInterface createRebootEscrowProvider() { Loading Loading @@ -326,6 +337,10 @@ class RebootEscrowManager { return (UserManager) mContext.getSystemService(Context.USER_SERVICE); } public UserManagerInternal getUserManagerInternal() { return mUserManagerInternal; } public RebootEscrowKeyStoreManager getKeyStoreManager() { return mKeyStoreManager; } Loading Loading @@ -402,8 +417,8 @@ class RebootEscrowManager { } RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage, Handler handler) { this(new Injector(context, storage), callbacks, storage, handler); Handler handler, UserManagerInternal userManagerInternal) { this(new Injector(context, storage, userManagerInternal), callbacks, storage, handler); } @VisibleForTesting Loading Loading @@ -451,18 +466,50 @@ class RebootEscrowManager { onEscrowRestoreComplete(false, attemptCount, retryHandler); } void loadRebootEscrowDataIfAvailable(Handler retryHandler) { List<UserInfo> users = mUserManager.getUsers(); private List<UserInfo> getUsersToUnlock(List<UserInfo> users) { // System user must be unlocked to unlock any other user if (mCallbacks.isUserSecure(USER_SYSTEM) && !mStorage.hasRebootEscrow(USER_SYSTEM)) { Slog.i(TAG, "No reboot escrow data found for system user"); return Collections.emptyList(); } Set<Integer> noEscrowDataUsers = new HashSet<>(); for (UserInfo user : users) { if (mCallbacks.isUserSecure(user.id) && !mStorage.hasRebootEscrow(user.id)) { Slog.d(TAG, "No reboot escrow data found for user " + user); noEscrowDataUsers.add(user.id); } } List<UserInfo> rebootEscrowUsers = new ArrayList<>(); for (UserInfo user : users) { if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) { // No lskf, no need to unlock. if (!mCallbacks.isUserSecure(user.id)) { continue; } // Don't unlock if user or user's parent does not have reboot data int userId = user.id; if (noEscrowDataUsers.contains(userId) || noEscrowDataUsers.contains( mInjector.getUserManagerInternal().getProfileParentId(userId))) { continue; } rebootEscrowUsers.add(user); } return rebootEscrowUsers; } void loadRebootEscrowDataIfAvailable(Handler retryHandler) { List<UserInfo> users = mUserManager.getUsers(); List<UserInfo> rebootEscrowUsers = getUsersToUnlock(users); if (rebootEscrowUsers.isEmpty()) { Slog.i(TAG, "No reboot escrow data found for users," + " skipping loading escrow data"); setLoadEscrowDataErrorCode(ERROR_NO_REBOOT_ESCROW_DATA, retryHandler); reportMetricOnRestoreComplete( /* success= */ false, /* attemptCount= */ 1, retryHandler); clearMetricsStorage(); return; } Loading
services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +224 −16 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ 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.content.pm.UserInfo.NO_PROFILE_GROUP_ID; import static android.os.UserHandle.USER_SYSTEM; import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_ESCROW_NOT_READY; Loading @@ -32,6 +33,7 @@ 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.anyByte; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; Loading Loading @@ -65,6 +67,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.widget.RebootEscrowListener; import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection; import com.android.server.pm.UserManagerInternal; import org.junit.Before; import org.junit.Test; Loading Loading @@ -107,6 +110,7 @@ public class RebootEscrowManagerTests { private Context mContext; private UserManager mUserManager; private UserManagerInternal mUserManagerInternal; private RebootEscrowManager.Callbacks mCallbacks; private IRebootEscrow mRebootEscrow; private ResumeOnRebootServiceConnection mServiceConnection; Loading @@ -126,13 +130,15 @@ public class RebootEscrowManagerTests { long getCurrentTimeMillis(); void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete); int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete); } static class MockInjector extends RebootEscrowManager.Injector { private final IRebootEscrow mRebootEscrow; private final RebootEscrowProviderInterface mDefaultRebootEscrowProvider; private final UserManager mUserManager; private final UserManagerInternal mUserManagerInternal; private final MockableRebootEscrowInjected mInjected; private final RebootEscrowKeyStoreManager mKeyStoreManager; private boolean mServerBased; Loading @@ -141,12 +147,16 @@ public class RebootEscrowManagerTests { private Consumer<ConnectivityManager.NetworkCallback> mNetworkConsumer; private boolean mWaitForInternet; MockInjector(Context context, UserManager userManager, MockInjector( Context context, UserManager userManager, UserManagerInternal userManagerInternal, IRebootEscrow rebootEscrow, RebootEscrowKeyStoreManager keyStoreManager, LockSettingsStorageTestable storage, MockableRebootEscrowInjected injected) { super(context, storage); // TODO: change this super(context, storage, userManagerInternal); mRebootEscrow = rebootEscrow; mServerBased = false; mWaitForInternet = false; Loading @@ -159,16 +169,20 @@ public class RebootEscrowManagerTests { }; mDefaultRebootEscrowProvider = new RebootEscrowProviderHalImpl(halInjector); mUserManager = userManager; mUserManagerInternal = userManagerInternal; mKeyStoreManager = keyStoreManager; mInjected = injected; } MockInjector(Context context, UserManager userManager, MockInjector( Context context, UserManager userManager, UserManagerInternal userManagerInternal, ResumeOnRebootServiceConnection serviceConnection, RebootEscrowKeyStoreManager keyStoreManager, LockSettingsStorageTestable storage, MockableRebootEscrowInjected injected) { super(context, storage); super(context, storage, userManagerInternal); mRebootEscrow = null; mServerBased = true; mWaitForInternet = false; Loading @@ -187,6 +201,7 @@ public class RebootEscrowManagerTests { mDefaultRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl( storage, injector); mUserManager = userManager; mUserManagerInternal = userManagerInternal; mKeyStoreManager = keyStoreManager; mInjected = injected; } Loading @@ -201,6 +216,11 @@ public class RebootEscrowManagerTests { return mUserManager; } @Override public UserManagerInternal getUserManagerInternal() { return mUserManagerInternal; } @Override public boolean serverBasedResumeOnReboot() { return mServerBased; Loading Loading @@ -301,6 +321,7 @@ public class RebootEscrowManagerTests { public void setUp_baseServices() throws Exception { mContext = new ContextWrapper(InstrumentationRegistry.getContext()); mUserManager = mock(UserManager.class); mUserManagerInternal = mock(UserManagerInternal.class); mCallbacks = mock(RebootEscrowManager.Callbacks.class); mRebootEscrow = mock(IRebootEscrow.class); mServiceConnection = mock(ResumeOnRebootServiceConnection.class); Loading @@ -314,28 +335,43 @@ public class RebootEscrowManagerTests { new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings")); ArrayList<UserInfo> users = new ArrayList<>(); users.add(new UserInfo(PRIMARY_USER_ID, "primary", FLAG_PRIMARY)); users.add(new UserInfo(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE)); users.add(new UserInfo(NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL)); users.add(new UserInfo(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL)); users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, PRIMARY_USER_ID)); users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, PRIMARY_USER_ID)); users.add( createUser( NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL, NO_PROFILE_GROUP_ID)); users.add(createUser(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, NO_PROFILE_GROUP_ID)); when(mUserManager.getUsers()).thenReturn(users); when(mCallbacks.isUserSecure(PRIMARY_USER_ID)).thenReturn(true); 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); mInjected = mock(MockableRebootEscrowInjected.class); mMockInjector = new MockInjector(mContext, mUserManager, mRebootEscrow, mKeyStoreManager, mStorage, mInjected); mMockInjector = new MockInjector( mContext, mUserManager, mUserManagerInternal, mRebootEscrow, mKeyStoreManager, mStorage, mInjected); HandlerThread thread = new HandlerThread("RebootEscrowManagerTest"); thread.start(); mHandler = new Handler(thread.getLooper()); mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler); } private void setServerBasedRebootEscrowProvider() throws Exception { mMockInjector = new MockInjector(mContext, mUserManager, mServiceConnection, mKeyStoreManager, mStorage, mInjected); mMockInjector = new MockInjector( mContext, mUserManager, mUserManagerInternal, mServiceConnection, mKeyStoreManager, mStorage, mInjected); mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler); } Loading @@ -352,6 +388,12 @@ public class RebootEscrowManagerTests { waitForHandler(); } private UserInfo createUser(int id, String name, int flag, int profileGroupId) { UserInfo user = new UserInfo(id, name, flag); when(mUserManagerInternal.getProfileParentId(eq(id))).thenReturn(profileGroupId); return user; } @Test public void prepareRebootEscrow_Success() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); Loading Loading @@ -558,6 +600,172 @@ public class RebootEscrowManagerTests { -1, USER_SYSTEM), -1); } @Test public void loadRebootEscrowDataIfAvailable_noDataPrimaryUser_Failure() throws Exception { setServerBasedRebootEscrowProvider(); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mServiceConnection); // escrow secondary user, don't escrow primary user callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID); verify(mockListener).onPreparedForReboot(eq(true)); verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertTrue(mStorage.hasRebootEscrowServerBlob()); // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class); doNothing() .when(mInjected) .reportMetric( metricsSuccessCaptor.capture(), metricsErrorCodeCaptor.capture(), eq(2) /* Server based */, eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt()); mService.loadRebootEscrowDataIfAvailable(null); verify(mServiceConnection, never()).unwrap(any(), anyLong()); verify(mCallbacks, never()).onRebootEscrowRestored(anyByte(), any(), anyInt()); assertFalse(metricsSuccessCaptor.getValue()); assertEquals( Integer.valueOf(RebootEscrowManager.ERROR_NO_REBOOT_ESCROW_DATA), metricsErrorCodeCaptor.getValue()); } @Test public void loadRebootEscrowDataIfAvailable_noDataSecondaryUser_Success() throws Exception { setServerBasedRebootEscrowProvider(); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mServiceConnection); // Setup work profile with secondary user as parent. ArrayList<UserInfo> users = new ArrayList<>(); users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, NO_PROFILE_GROUP_ID)); users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, SECURE_SECONDARY_USER_ID)); users.add( createUser( SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, SECURE_SECONDARY_USER_ID)); when(mUserManager.getUsers()).thenReturn(users); // escrow primary user and work profile, don't escrow secondary user callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID); verify(mockListener).onPreparedForReboot(eq(true)); verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); callToRebootEscrowIfNeededAndWait(WORK_PROFILE_USER_ID); verify(mockListener).onPreparedForReboot(eq(true)); verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); assertTrue(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID)); assertTrue(mStorage.hasRebootEscrowServerBlob()); // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing() .when(mInjected) .reportMetric( metricsSuccessCaptor.capture(), eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt()); when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); mService.loadRebootEscrowDataIfAvailable(null); verify(mServiceConnection).unwrap(any(), anyLong()); verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID)); verify(mCallbacks, never()) .onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID)); verify(mCallbacks, never()) .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID)); verify(mCallbacks, never()) .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID)); assertTrue(metricsSuccessCaptor.getValue()); } @Test public void loadRebootEscrowDataIfAvailable_noDataWorkProfile_Success() throws Exception { setServerBasedRebootEscrowProvider(); RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); mService.prepareRebootEscrow(); clearInvocations(mServiceConnection); // escrow primary user and secondary user, don't escrow work profile callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID); verify(mockListener).onPreparedForReboot(eq(true)); verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID); verify(mockListener).onPreparedForReboot(eq(true)); verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID)); assertFalse(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID)); assertTrue(mStorage.hasRebootEscrowServerBlob()); // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing() .when(mInjected) .reportMetric( metricsSuccessCaptor.capture(), eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt()); when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); mService.loadRebootEscrowDataIfAvailable(null); verify(mServiceConnection).unwrap(any(), anyLong()); verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID)); verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID)); verify(mCallbacks, never()) .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID)); verify(mCallbacks, never()) .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID)); assertTrue(metricsSuccessCaptor.getValue()); } @Test public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception { setServerBasedRebootEscrowProvider(); Loading