Evict CE key on request and when work mode is turned off.
DPMS.lockNow takes a flag which can request the managed profile CE key to
be evicted.
Test: com.android.cts.devicepolicy.ManagedProfileTest#testLockNowWithKeyEviction*
Bug: 31000719
Change-Id: I68f4d6eed4b041c39fd13375f7f284f5d6ac33da
diff --git a/api/current.txt b/api/current.txt
index 395d7aa..e703823 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6129,6 +6129,7 @@
method public boolean isSecurityLoggingEnabled(android.content.ComponentName);
method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String);
method public void lockNow();
+ method public void lockNow(int);
method public void reboot(android.content.ComponentName);
method public void removeActiveAdmin(android.content.ComponentName);
method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -6245,6 +6246,7 @@
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PROXY_PORT = "android.app.extra.PROVISIONING_WIFI_PROXY_PORT";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID";
+ field public static final int FLAG_EVICT_CE_KEY = 1; // 0x1
field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
diff --git a/api/system-current.txt b/api/system-current.txt
index f38b02e..3f20037 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6315,6 +6315,7 @@
method public boolean isSecurityLoggingEnabled(android.content.ComponentName);
method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String);
method public void lockNow();
+ method public void lockNow(int);
method public void notifyPendingSystemUpdate(long);
method public void reboot(android.content.ComponentName);
method public void removeActiveAdmin(android.content.ComponentName);
@@ -6438,6 +6439,7 @@
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PROXY_PORT = "android.app.extra.PROVISIONING_WIFI_PROXY_PORT";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID";
+ field public static final int FLAG_EVICT_CE_KEY = 1; // 0x1
field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
diff --git a/api/test-current.txt b/api/test-current.txt
index e43d1b5..85b2471c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6151,6 +6151,7 @@
method public boolean isSecurityLoggingEnabled(android.content.ComponentName);
method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String);
method public void lockNow();
+ method public void lockNow(int);
method public void reboot(android.content.ComponentName);
method public void removeActiveAdmin(android.content.ComponentName);
method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -6267,6 +6268,7 @@
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PROXY_PORT = "android.app.extra.PROVISIONING_WIFI_PROXY_PORT";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID";
+ field public static final int FLAG_EVICT_CE_KEY = 1; // 0x1
field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index fcc6e3d..533f60c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -578,6 +578,7 @@
boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras,
in IBinder activityToken, int flags);
void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
+ int restartUserInBackground(int userId);
// WARNING: when these transactions are updated, check if they are any callers on the native
// side. If so, make sure they are using the correct transaction ids and arguments.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6172884..a6226b9 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2715,6 +2715,43 @@
}
/**
+ * Flag for {@link #lockNow(int)}: also evict the user's credential encryption key from the
+ * keyring. The user's credential will need to be entered again in order to derive the
+ * credential encryption key that will be stored back in the keyring for future use.
+ * <p>
+ * This flag can only be used by a profile owner when locking a managed profile on an FBE
+ * device.
+ * <p>
+ * In order to secure user data, the user will be stopped and restarted so apps should wait
+ * until they are next run to perform further actions.
+ */
+ public static final int FLAG_EVICT_CE_KEY = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag=true, value={FLAG_EVICT_CE_KEY})
+ public @interface LockNowFlag {}
+
+ /**
+ * Make the device lock immediately, as if the lock screen timeout has expired at the point of
+ * this call.
+ * <p>
+ * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
+ * to be able to call this method; if it has not, a security exception will be thrown.
+ * <p>
+ * This method can be called on the {@link DevicePolicyManager} instance returned by
+ * {@link #getParentProfileInstance(ComponentName)} in order to lock the parent profile.
+ * <p>
+ * Equivalent to calling {@link #lockNow(int)} with no flags.
+ *
+ * @throws SecurityException if the calling application does not own an active administrator
+ * that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
+ */
+ public void lockNow() {
+ lockNow(0);
+ }
+
+ /**
* Make the device lock immediately, as if the lock screen timeout has expired at the point of
* this call.
* <p>
@@ -2724,13 +2761,20 @@
* This method can be called on the {@link DevicePolicyManager} instance returned by
* {@link #getParentProfileInstance(ComponentName)} in order to lock the parent profile.
*
+ * @param flags May be 0 or {@link #FLAG_EVICT_CE_KEY}.
* @throws SecurityException if the calling application does not own an active administrator
- * that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
+ * that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} or the
+ * {@link #FLAG_EVICT_CE_KEY} flag is passed by an application that is not a profile
+ * owner of a managed profile.
+ * @throws IllegalArgumentException if the {@link #FLAG_EVICT_CE_KEY} flag is passed when
+ * locking the parent profile.
+ * @throws UnsupportedOperationException if the {@link #FLAG_EVICT_CE_KEY} flag is passed on a
+ * non-FBE device.
*/
- public void lockNow() {
+ public void lockNow(@LockNowFlag int flags) {
if (mService != null) {
try {
- mService.lockNow(mParentInstance);
+ mService.lockNow(flags, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9be694e..66185d5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -90,7 +90,7 @@
void setRequiredStrongAuthTimeout(in ComponentName who, long timeMs, boolean parent);
long getRequiredStrongAuthTimeout(in ComponentName who, int userId, boolean parent);
- void lockNow(boolean parent);
+ void lockNow(int flags, boolean parent);
void wipeData(int flags);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index d443b66..9513854 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -40,6 +40,7 @@
in String[] disallowedPackages);
UserInfo createRestrictedProfile(String name, int parentUserHandle);
void setUserEnabled(int userHandle);
+ void evictCredentialEncryptionKey(int userHandle);
boolean removeUser(int userHandle);
void setUserName(int userHandle, String name);
void setUserIcon(int userHandle, in Bitmap icon);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0a32f0d..e5cd3d6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1641,6 +1641,19 @@
}
/**
+ * Evicts the user's credential encryption key from memory by stopping and restarting the user.
+ *
+ * @hide
+ */
+ public void evictCredentialEncryptionKey(@UserIdInt int userHandle) {
+ try {
+ mService.evictCredentialEncryptionKey(userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return the number of users currently created on the device.
*/
public int getUserCount() {
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 4d6ffe6..a5552b8 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -176,12 +176,8 @@
}
@Override
- public void onBootPhase(int phase) {
- if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
- mLockSettingsService.maybeShowEncryptionNotifications();
- } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
- // TODO
- }
+ public void onStartUser(int userHandle) {
+ mLockSettingsService.onStartUser(userHandle);
}
@Override
@@ -313,27 +309,25 @@
* If the account is credential-encrypted, show notification requesting the user to unlock
* the device.
*/
- private void maybeShowEncryptionNotifications() {
- final List<UserInfo> users = mUserManager.getUsers();
- for (int i = 0; i < users.size(); i++) {
- UserInfo user = users.get(i);
- UserHandle userHandle = user.getUserHandle();
- final boolean isSecure = mStorage.hasPassword(user.id) || mStorage.hasPattern(user.id);
- if (isSecure && !mUserManager.isUserUnlockingOrUnlocked(userHandle)) {
- if (!user.isManagedProfile()) {
- // When the user is locked, we communicate it loud-and-clear
- // on the lockscreen; we only show a notification below for
- // locked managed profiles.
- } else {
- UserInfo parent = mUserManager.getProfileParent(user.id);
- if (parent != null &&
- mUserManager.isUserUnlockingOrUnlocked(parent.getUserHandle()) &&
- !mUserManager.isQuietModeEnabled(userHandle)) {
- // Only show notifications for managed profiles once their parent
- // user is unlocked.
- showEncryptionNotificationForProfile(userHandle);
- }
- }
+ private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId) {
+ final UserInfo user = mUserManager.getUserInfo(userId);
+ if (!user.isManagedProfile()) {
+ // When the user is locked, we communicate it loud-and-clear
+ // on the lockscreen; we only show a notification below for
+ // locked managed profiles.
+ return;
+ }
+
+ final UserHandle userHandle = user.getUserHandle();
+ final boolean isSecure = mStorage.hasPassword(userId) || mStorage.hasPattern(userId);
+ if (isSecure && !mUserManager.isUserUnlockingOrUnlocked(userHandle)) {
+ UserInfo parent = mUserManager.getProfileParent(userId);
+ if (parent != null &&
+ mUserManager.isUserUnlockingOrUnlocked(parent.getUserHandle()) &&
+ !mUserManager.isQuietModeEnabled(userHandle)) {
+ // Only show notifications for managed profiles once their parent
+ // user is unlocked.
+ showEncryptionNotificationForProfile(userHandle);
}
}
}
@@ -384,7 +378,7 @@
mNotificationManager.notifyAsUser(null, FBE_ENCRYPTED_NOTIFICATION, notification, user);
}
- public void hideEncryptionNotification(UserHandle userHandle) {
+ private void hideEncryptionNotification(UserHandle userHandle) {
if (DEBUG) Slog.v(TAG, "hide encryption notification, user: "+ userHandle.getIdentifier());
mNotificationManager.cancelAsUser(null, FBE_ENCRYPTED_NOTIFICATION, userHandle);
}
@@ -393,6 +387,10 @@
hideEncryptionNotification(new UserHandle(userId));
}
+ public void onStartUser(final int userId) {
+ maybeShowEncryptionNotificationForUser(userId);
+ }
+
public void onUnlockUser(final int userId) {
// Hide notification first, as tie managed profile lock takes time
hideEncryptionNotification(new UserHandle(userId));
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index eae4905..f7c0275 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22566,6 +22566,11 @@
}
}
+ @Override
+ public int restartUserInBackground(final int userId) {
+ return mUserController.restartUser(userId, /* foreground */ false);
+ }
+
/**
* Attach an agent to the specified process (proces name or PID)
*/
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a0a04bb..45e06b0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -442,6 +442,19 @@
}
}
+ int restartUser(final int userId, final boolean foreground) {
+ return stopUser(userId, /* force */ true, new IStopUserCallback.Stub() {
+ @Override
+ public void userStopped(final int userId) {
+ // Post to the same handler that this callback is called from to ensure the user
+ // cleanup is complete before restarting.
+ mHandler.post(() -> startUser(userId, foreground));
+ }
+ @Override
+ public void userStopAborted(final int userId) {}
+ });
+ }
+
int stopUser(final int userId, final boolean force, final IStopUserCallback callback) {
if (mInjector.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED) {
@@ -634,6 +647,12 @@
}
if (stopped) {
+ // Evict the user's credential encryption key
+ try {
+ getStorageManager().lockUserKey(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowAsRuntimeException();
+ }
mInjector.systemServiceManagerCleanupUser(userId);
synchronized (mLock) {
mInjector.stackSupervisorRemoveUserLocked(userId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 05228ec..9b47beb 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -26,6 +26,7 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
@@ -857,6 +858,25 @@
}
}
+ /**
+ * Evicts a user's CE key by stopping and restarting the user.
+ *
+ * The key is evicted automatically by the user controller when the user has stopped.
+ */
+ @Override
+ public void evictCredentialEncryptionKey(@UserIdInt int userId) {
+ checkManageUsersPermission("evict CE key");
+ final IActivityManager am = ActivityManagerNative.getDefault();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ am.restartUserInBackground(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowAsRuntimeException();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
public UserInfo getUserInfo(int userId) {
checkManageOrCreateUsersPermission("query user");
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index cca8cc8..c69b87c 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -25,6 +25,7 @@
import org.xmlpull.v1.XmlPullParserException;
import android.Manifest;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustListener;
@@ -103,6 +104,7 @@
private static final int MSG_SWITCH_USER = 9;
private static final int MSG_FLUSH_TRUST_USUALLY_MANAGED = 10;
private static final int MSG_UNLOCK_USER = 11;
+ private static final int MSG_STOP_USER = 12;
private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
@@ -414,15 +416,18 @@
}
}
boolean deviceLocked = secure && showingKeyguard && !trusted;
+ setDeviceLockedForUser(id, deviceLocked);
+ }
+ }
- boolean changed;
- synchronized (mDeviceLockedForUser) {
- changed = isDeviceLockedInner(id) != deviceLocked;
- mDeviceLockedForUser.put(id, deviceLocked);
- }
- if (changed) {
- dispatchDeviceLocked(id, deviceLocked);
- }
+ private void setDeviceLockedForUser(@UserIdInt int userId, boolean locked) {
+ final boolean changed;
+ synchronized (mDeviceLockedForUser) {
+ changed = isDeviceLockedInner(userId) != locked;
+ mDeviceLockedForUser.put(userId, locked);
+ }
+ if (changed) {
+ dispatchDeviceLocked(userId, locked);
}
}
@@ -724,6 +729,11 @@
mHandler.obtainMessage(MSG_UNLOCK_USER, userId, 0, null).sendToTarget();
}
+ @Override
+ public void onStopUser(@UserIdInt int userId) {
+ mHandler.obtainMessage(MSG_STOP_USER, userId, 0, null).sendToTarget();
+ }
+
// Plumbing
private final IBinder mService = new ITrustManager.Stub() {
@@ -982,6 +992,9 @@
mCurrentUser = msg.arg1;
refreshDeviceLockedForUser(UserHandle.USER_ALL);
break;
+ case MSG_STOP_USER:
+ setDeviceLockedForUser(msg.arg1, true);
+ break;
case MSG_FLUSH_TRUST_USUALLY_MANAGED:
SparseBooleanArray usuallyManaged;
synchronized (mTrustUsuallyManagedForUser) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 050f25d..6233d08 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -75,6 +75,7 @@
import android.app.admin.SecurityLog.SecurityEvent;
import android.app.admin.SystemUpdatePolicy;
import android.app.backup.IBackupManager;
+import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -1496,6 +1497,10 @@
return TelephonyManager.from(mContext);
}
+ TrustManager getTrustManager() {
+ return (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
+ }
+
IWindowManager getIWindowManager() {
return IWindowManager.Stub
.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
@@ -4377,31 +4382,52 @@
}
@Override
- public void lockNow(boolean parent) {
+ public void lockNow(int flags, boolean parent) {
if (!mHasFeature) {
return;
}
+
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
synchronized (this) {
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(
+ final ActiveAdmin admin = getActiveAdminForCallerLocked(
null, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent);
- int userToLock = mInjector.userHandleGetCallingUserId();
-
- // Unless this is a managed profile with work challenge enabled, lock all users.
- if (parent || !isSeparateProfileChallengeEnabled(userToLock)) {
- userToLock = UserHandle.USER_ALL;
- }
final long ident = mInjector.binderClearCallingIdentity();
try {
+ // Evict key
+ if ((flags & DevicePolicyManager.FLAG_EVICT_CE_KEY) != 0) {
+ enforceManagedProfile(callingUserId, "set FLAG_EVICT_CE_KEY");
+ if (!isProfileOwner(admin.info.getComponent(), callingUserId)) {
+ throw new SecurityException(
+ "Only profile owner admins can set FLAG_EVICT_CE_KEY");
+ }
+ if (parent) {
+ throw new IllegalArgumentException(
+ "Cannot set FLAG_EVICT_CE_KEY for the parent");
+ }
+ if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
+ throw new UnsupportedOperationException(
+ "FLAG_EVICT_CE_KEY only applies to FBE devices");
+ }
+ mUserManager.evictCredentialEncryptionKey(callingUserId);
+ }
+
+ // Lock all users unless this is a managed profile with a separate challenge
+ final int userToLock = (parent || !isSeparateProfileChallengeEnabled(callingUserId)
+ ? UserHandle.USER_ALL : callingUserId);
mLockPatternUtils.requireStrongAuth(
STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW, userToLock);
+
+ // Require authentication for the device or profile
if (userToLock == UserHandle.USER_ALL) {
// Power off the display
mInjector.powerManagerGoToSleep(SystemClock.uptimeMillis(),
PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN, 0);
mInjector.getIWindowManager().lockNow(null);
+ } else {
+ mInjector.getTrustManager().setDeviceLockedForUser(userToLock, true);
}
} catch (RemoteException e) {
} finally {