summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/system-current.txt2
-rw-r--r--cmds/dpm/src/com/android/commands/dpm/Dpm.java17
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java28
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl2
-rw-r--r--core/res/AndroidManifest.xml8
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java3
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java143
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java70
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java170
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java17
12 files changed, 436 insertions, 40 deletions
diff --git a/api/system-current.txt b/api/system-current.txt
index fa49f07abf52..e8465fade67f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -74,6 +74,7 @@ package android {
field public static final java.lang.String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
field public static final java.lang.String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO";
field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
+ field public static final java.lang.String GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS = "android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS";
field public static final java.lang.String GRANT_RUNTIME_PERMISSIONS = "android.permission.GRANT_RUNTIME_PERMISSIONS";
field public static final java.lang.String HARDWARE_TEST = "android.permission.HARDWARE_TEST";
field public static final java.lang.String HDMI_CEC = "android.permission.HDMI_CEC";
@@ -581,6 +582,7 @@ package android.app.admin {
method public boolean packageHasActiveAdmins(java.lang.String);
method public deprecated boolean setActiveProfileOwner(android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException;
method public void setDeviceProvisioningConfigApplied();
+ method public void setProfileOwnerCanAccessDeviceIdsForUser(android.content.ComponentName, android.os.UserHandle);
field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
field public static final java.lang.String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
index 376b13cd371e..6c6797a328c9 100644
--- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java
+++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
@@ -48,6 +48,8 @@ public final class Dpm extends BaseCommand {
private static final String COMMAND_CLEAR_FREEZE_PERIOD_RECORD = "clear-freeze-period-record";
private static final String COMMAND_FORCE_NETWORK_LOGS = "force-network-logs";
private static final String COMMAND_FORCE_SECURITY_LOGS = "force-security-logs";
+ private static final String COMMAND_GRANT_PO_DEVICE_ID_ACCESS =
+ "grant-profile-owner-device-ids-access";
private IDevicePolicyManager mDevicePolicyManager;
private int mUserId = UserHandle.USER_SYSTEM;
@@ -89,7 +91,10 @@ public final class Dpm extends BaseCommand {
"the DPC and triggers DeviceAdminReceiver.onNetworkLogsAvailable() if needed.\n" +
"\n" +
"dpm " + COMMAND_FORCE_SECURITY_LOGS + ": makes all security logs available to " +
- "the DPC and triggers DeviceAdminReceiver.onSecurityLogsAvailable() if needed.");
+ "the DPC and triggers DeviceAdminReceiver.onSecurityLogsAvailable() if needed."
+ + "\n"
+ + "usage: dpm " + COMMAND_GRANT_PO_DEVICE_ID_ACCESS + ": "
+ + "[ --user <USER_ID> | current ] <COMPONENT>\n");
}
@Override
@@ -124,6 +129,9 @@ public final class Dpm extends BaseCommand {
case COMMAND_FORCE_SECURITY_LOGS:
runForceSecurityLogs();
break;
+ case COMMAND_GRANT_PO_DEVICE_ID_ACCESS:
+ runGrantProfileOwnerDeviceIdsAccess();
+ break;
default:
throw new IllegalArgumentException ("unknown command '" + command + "'");
}
@@ -242,6 +250,13 @@ public final class Dpm extends BaseCommand {
System.out.println("Success");
}
+
+ private void runGrantProfileOwnerDeviceIdsAccess() throws RemoteException {
+ parseArgs(/*canHaveName=*/ false);
+ mDevicePolicyManager.grantDeviceIdsAccessToProfileOwner(mComponent, mUserId);
+ System.out.println("Success");
+ }
+
private ComponentName parseComponentName(String component) {
ComponentName cn = ComponentName.unflattenFromString(component);
if (cn == null) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f129a717f2cd..24ee7f757f55 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9792,7 +9792,6 @@ public class DevicePolicyManager {
}
}
-
/**
* Sets the global Private DNS mode and host to be used.
* May only be called by the device owner.
@@ -9867,4 +9866,31 @@ public class DevicePolicyManager {
throw re.rethrowFromSystemServer();
}
}
+
+ /**
+ * Grants the profile owner of the given user access to device identifiers (such as
+ * serial number, IMEI and MEID).
+ *
+ * <p>This lets the profile owner request inclusion of device identifiers when calling
+ * {@link generateKeyPair}.
+ *
+ * <p>This grant is necessary to guarantee that profile owners can access device identifiers.
+ *
+ * <p>Privileged system API - meant to be called by the system, particularly the managed
+ * provisioning app, when a work profile is set up.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void setProfileOwnerCanAccessDeviceIdsForUser(
+ @NonNull ComponentName who, @NonNull UserHandle userHandle) {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.grantDeviceIdsAccessToProfileOwner(who, userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ce1f4ef9e2e4..918c1278a2fe 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -417,4 +417,6 @@ interface IDevicePolicyManager {
void setGlobalPrivateDns(in ComponentName admin, int mode, in String privateDnsHost);
int getGlobalPrivateDnsMode(in ComponentName admin);
String getGlobalPrivateDnsHost(in ComponentName admin);
+
+ void grantDeviceIdsAccessToProfileOwner(in ComponentName who, int userId);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 297687933018..5360e8a5dbc5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4224,7 +4224,13 @@
<!-- @SystemApi Allows modifying accessibility state.
@hide -->
<permission android:name="android.permission.MANAGE_ACCESSIBILITY"
- android:protectionLevel="signature|setup" />
+ android:protectionLevel="signature|setup" />
+
+ <!-- @SystemApi Allows an app to grant a profile owner access to device identifiers.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"
+ android:protectionLevel="signature" />
<application android:process="system"
android:persistent="true"
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 84cb5f80ece3..495e51ae573f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -91,6 +91,7 @@ applications that come with the platform
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.CRYPT_KEEPER"/>
<permission name="android.permission.DELETE_PACKAGES"/>
+ <permission name="android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"/>
<permission name="android.permission.INSTALL_PACKAGES"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 2dbbf55a9347..5926bddbf847 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -88,4 +88,7 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
public String getGlobalPrivateDnsHost(ComponentName who) {
return null;
}
+
+ @Override
+ public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a7542d70c0e0..bbbc40cedd3e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -55,16 +55,15 @@ import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATI
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN;
import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF;
import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN;
import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
-
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Telephony.Carriers.DPC_URI;
@@ -75,11 +74,8 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
.PROVISIONING_ENTRY_POINT_ADB;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
-
import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
-
-
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -228,19 +224,19 @@ import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.StatLogger;
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
-import com.android.internal.util.StatLogger;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.UserRestrictionsUtils;
import com.android.server.storage.DeviceStorageMonitorInternal;
-
import com.android.server.uri.UriGrantsManagerInternal;
+
import com.google.android.collect.Sets;
import org.xmlpull.v1.XmlPullParser;
@@ -267,7 +263,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -2701,7 +2696,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final DevicePolicyData policy = getUserData(userId);
ActiveAdmin admin = policy.mAdminMap.get(who);
if (admin == null) {
- throw new SecurityException("No active admin " + who);
+ throw new SecurityException("No active admin " + who + " for UID " + uid);
}
if (admin.getUid() != uid) {
throw new SecurityException("Admin " + who + " is not owned by uid " + uid);
@@ -2709,6 +2704,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return admin;
}
+ /**
+ * Returns the active admin for the user of the caller as denoted by uid, which implements
+ * the {@code reqPolicy}.
+ *
+ * The {@code who} parameter is used as a hint:
+ * If provided, it must be the component name of the active admin for that user and the caller
+ * uid must match the uid of the admin.
+ * If not provided, iterate over all of the active admins in the DevicePolicyData for that user
+ * and return the one with the uid specified as parameter, and has the policy specified.
+ */
private ActiveAdmin getActiveAdminWithPolicyForUidLocked(ComponentName who, int reqPolicy,
int uid) {
ensureLocked();
@@ -5435,23 +5440,54 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
- private void enforceIsDeviceOwnerOrCertInstallerOfDeviceOwner(
+ /**
+ * Enforce one the following conditions are met:
+ * (1) The device has a Device Owner, and one of the following holds:
+ * (1.1) The caller is the Device Owner
+ * (1.2) The caller is another app in the same user as the device owner, AND
+ * The caller is the delegated certificate installer.
+ * (2) The user has a profile owner, AND:
+ * (2.1) The profile owner has been granted access to Device IDs and one of the following
+ * holds:
+ * (2.1.1) The caller is the profile owner.
+ * (2.1.2) The caller is from another app in the same user as the profile owner, AND
+ * (2.1.2.1) The caller is the delegated cert installer.
+ *
+ * For the device owner case, simply check that the caller is the device owner or the
+ * delegated certificate installer.
+ *
+ * For the profile owner case, first check that the caller is the profile owner or can
+ * manage the DELEGATION_CERT_INSTALL scope.
+ * If that check succeeds, ensure the profile owner was granted access to device
+ * identifiers. The grant is transitive: The delegated cert installer is implicitly allowed
+ * access to device identifiers in this case as part of the delegation.
+ */
+ @VisibleForTesting
+ public void enforceCallerCanRequestDeviceIdAttestation(
ComponentName who, String callerPackage, int callerUid) throws SecurityException {
- if (who == null) {
- if (!mOwners.hasDeviceOwner()) {
- throw new SecurityException("Not in Device Owner mode.");
- }
- if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) {
- throw new SecurityException("Caller not from device owner user");
- }
- if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) {
- throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() +
- "has no permission to generate keys.");
+ final int userId = UserHandle.getUserId(callerUid);
+
+ /**
+ * First check if there's a profile owner because the device could be in COMP mode (where
+ * there's a device owner and profile owner on the same device).
+ * If the caller is from the work profile, then it must be the PO or the delegate, and
+ * it must have the right permission to access device identifiers.
+ */
+ if (hasProfileOwner(userId)) {
+ // Make sure that the caller is the profile owner or delegate.
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_CERT_INSTALL);
+ // Verify that the profile owner was granted access to Device IDs.
+ if (canProfileOwnerAccessDeviceIds(userId)) {
+ return;
}
- } else {
- // Caller provided - check it is the device owner.
- enforceDeviceOwner(who);
+ throw new SecurityException(
+ "Profile Owner is not allowed to access Device IDs.");
}
+
+ // If not, fall back to the device owner check.
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
+ DELEGATION_CERT_INSTALL);
}
@VisibleForTesting
@@ -5499,7 +5535,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final int callingUid = mInjector.binderGetCallingUid();
if (deviceIdAttestationRequired && attestationUtilsFlags.length > 0) {
- enforceIsDeviceOwnerOrCertInstallerOfDeviceOwner(who, callerPackage, callingUid);
+ enforceCallerCanRequestDeviceIdAttestation(who, callerPackage, callingUid);
} else {
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
DELEGATION_CERT_INSTALL);
@@ -7365,6 +7401,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return who != null && who.equals(profileOwner);
}
+ private boolean hasProfileOwner(int userId) {
+ synchronized (getLockObject()) {
+ return mOwners.hasProfileOwner(userId);
+ }
+ }
+
+ private boolean canProfileOwnerAccessDeviceIds(int userId) {
+ synchronized (getLockObject()) {
+ return mOwners.canProfileOwnerAccessDeviceIds(userId);
+ }
+ }
+
@Override
public ComponentName getDeviceOwnerComponent(boolean callingUserOnly) {
if (!mHasFeature) {
@@ -11583,6 +11631,53 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
+ private boolean hasGrantProfileOwnerDevcieIdAccessPermission() {
+ return mContext.checkCallingPermission(
+ android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ @Override
+ public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) {
+ // As the caller is the system, it must specify the component name of the profile owner
+ // as a sanity / safety check.
+ Preconditions.checkNotNull(who);
+
+ if (!mHasFeature) {
+ return;
+ }
+
+ // Only privileged system apps can grant the Profile Owner access to Device IDs.
+ if (!(isCallerWithSystemUid() || isAdb()
+ || hasGrantProfileOwnerDevcieIdAccessPermission())) {
+ throw new SecurityException(
+ "Only the system can grant Device IDs access for a profile owner.");
+ }
+
+ if (isAdb() && hasIncompatibleAccountsOrNonAdbNoLock(userId, who)) {
+ throw new SecurityException(
+ "Can only be called from ADB if the device has no accounts.");
+ }
+
+ // Grant access under lock.
+ synchronized (getLockObject()) {
+ // Sanity check: Make sure that the user has a profile owner and that the specified
+ // component is the profile owner of that user.
+ if (!isProfileOwner(who, userId)) {
+ throw new IllegalArgumentException(String.format(
+ "Component %s is not a Profile Owner of user %d",
+ who.flattenToString(), userId));
+ }
+
+ Slog.i(LOG_TAG, String.format("Granting Device ID access to %s, for user %d",
+ who.flattenToString(), userId));
+
+ // setProfileOwnerCanAccessDeviceIds will trigger writing of the profile owner
+ // data, no need to do it manually.
+ mOwners.setProfileOwnerCanAccessDeviceIds(userId);
+ }
+ }
+
private void pushMeteredDisabledPackagesLocked(int userId) {
mInjector.getNetworkPolicyManagerInternal().setMeteredRestrictedPackages(
getMeteredDisabledPackagesLocked(userId), userId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 632f0aad8880..a2bec7917e6f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -42,6 +42,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.LocalServices;
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -58,8 +60,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import libcore.io.IoUtils;
-
/**
* Stores and restores state for the Device and Profile owners and related device-wide information.
* By definition there can be only one device owner, but there may be a profile owner for each user.
@@ -99,6 +99,7 @@ class Owners {
private static final String ATTR_USER_RESTRICTIONS_MIGRATED = "userRestrictionsMigrated";
private static final String ATTR_FREEZE_RECORD_START = "start";
private static final String ATTR_FREEZE_RECORD_END = "end";
+ private static final String ATTR_CAN_ACCESS_DEVICE_IDS = "canAccessDeviceIds";
private final UserManager mUserManager;
private final UserManagerInternal mUserManagerInternal;
@@ -264,8 +265,12 @@ class Owners {
void setDeviceOwnerWithRestrictionsMigrated(ComponentName admin, String ownerName, int userId,
boolean userRestrictionsMigrated) {
synchronized (mLock) {
+ // A device owner is allowed to access device identifiers. Even though this flag
+ // is not currently checked for device owner, it is set to true here so that it is
+ // semantically compatible with the meaning of this flag.
mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated,
- /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null);
+ /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/
+ null, /* canAccessDeviceIds =*/true);
mDeviceOwnerUserId = userId;
mUserManagerInternal.setDeviceManaged(true);
@@ -290,7 +295,7 @@ class Owners {
// For a newly set PO, there's no need for migration.
mProfileOwners.put(userId, new OwnerInfo(ownerName, admin,
/* userRestrictionsMigrated =*/ true, /* remoteBugreportUri =*/ null,
- /* remoteBugreportHash =*/ null));
+ /* remoteBugreportHash =*/ null, /* canAccessDeviceIds =*/ false));
mUserManagerInternal.setUserManaged(userId, true);
pushToPackageManagerLocked();
pushToAppOpsLocked();
@@ -311,7 +316,8 @@ class Owners {
final OwnerInfo ownerInfo = mProfileOwners.get(userId);
final OwnerInfo newOwnerInfo = new OwnerInfo(target.getPackageName(), target,
ownerInfo.userRestrictionsMigrated, ownerInfo.remoteBugreportUri,
- ownerInfo.remoteBugreportHash);
+ ownerInfo.remoteBugreportHash, /* canAccessDeviceIds =*/
+ ownerInfo.canAccessDeviceIds);
mProfileOwners.put(userId, newOwnerInfo);
pushToPackageManagerLocked();
pushToAppOpsLocked();
@@ -324,7 +330,8 @@ class Owners {
// See DevicePolicyManagerService#getDeviceOwnerName
mDeviceOwner = new OwnerInfo(null, target,
mDeviceOwner.userRestrictionsMigrated, mDeviceOwner.remoteBugreportUri,
- mDeviceOwner.remoteBugreportHash);
+ mDeviceOwner.remoteBugreportHash, /* canAccessDeviceIds =*/
+ mDeviceOwner.canAccessDeviceIds);
pushToPackageManagerLocked();
pushToAppOpsLocked();
}
@@ -351,6 +358,17 @@ class Owners {
}
}
+ /**
+ * Returns true if {@code userId} has a profile owner and that profile owner was granted
+ * the ability to access device identifiers.
+ */
+ boolean canProfileOwnerAccessDeviceIds(int userId) {
+ synchronized (mLock) {
+ OwnerInfo profileOwner = mProfileOwners.get(userId);
+ return profileOwner != null ? profileOwner.canAccessDeviceIds : false;
+ }
+ }
+
Set<Integer> getProfileOwnerKeys() {
synchronized (mLock) {
return mProfileOwners.keySet();
@@ -486,6 +504,20 @@ class Owners {
}
}
+ /** Sets the grant to access device IDs, and also writes to file. */
+ void setProfileOwnerCanAccessDeviceIds(int userId) {
+ synchronized (mLock) {
+ OwnerInfo profileOwner = mProfileOwners.get(userId);
+ if (profileOwner != null) {
+ profileOwner.canAccessDeviceIds = true;
+ } else {
+ Slog.e(TAG, String.format(
+ "Cannot grant Device IDs access for user %d, no profile owner.", userId));
+ }
+ writeProfileOwner(userId);
+ }
+ }
+
private boolean readLegacyOwnerFileLocked(File file) {
if (!file.exists()) {
// Already migrated or the device has no owners.
@@ -507,7 +539,7 @@ class Owners {
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
mDeviceOwner = new OwnerInfo(name, packageName,
/* userRestrictionsMigrated =*/ false, /* remoteBugreportUri =*/ null,
- /* remoteBugreportHash =*/ null);
+ /* remoteBugreportHash =*/ null, /* canAccessDeviceIds =*/ true);
mDeviceOwnerUserId = UserHandle.USER_SYSTEM;
} else if (tag.equals(TAG_DEVICE_INITIALIZER)) {
// Deprecated tag
@@ -523,7 +555,8 @@ class Owners {
profileOwnerComponentStr);
if (admin != null) {
profileOwnerInfo = new OwnerInfo(profileOwnerName, admin,
- /* userRestrictionsMigrated =*/ false, null, null);
+ /* userRestrictionsMigrated =*/ false, null,
+ null, /* canAccessDeviceIds =*/ false);
} else {
// This shouldn't happen but switch from package name -> component name
// might have written bad device owner files. b/17652534
@@ -534,7 +567,8 @@ class Owners {
if (profileOwnerInfo == null) {
profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName,
/* userRestrictionsMigrated =*/ false,
- /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null);
+ /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/
+ null, /* canAccessDeviceIds =*/ false);
}
mProfileOwners.put(userId, profileOwnerInfo);
} else if (TAG_SYSTEM_UPDATE_POLICY.equals(tag)) {
@@ -894,25 +928,28 @@ class Owners {
public boolean userRestrictionsMigrated;
public String remoteBugreportUri;
public String remoteBugreportHash;
+ public boolean canAccessDeviceIds;
public OwnerInfo(String name, String packageName, boolean userRestrictionsMigrated,
- String remoteBugreportUri, String remoteBugreportHash) {
+ String remoteBugreportUri, String remoteBugreportHash, boolean canAccessDeviceIds) {
this.name = name;
this.packageName = packageName;
this.admin = new ComponentName(packageName, "");
this.userRestrictionsMigrated = userRestrictionsMigrated;
this.remoteBugreportUri = remoteBugreportUri;
this.remoteBugreportHash = remoteBugreportHash;
+ this.canAccessDeviceIds = canAccessDeviceIds;
}
public OwnerInfo(String name, ComponentName admin, boolean userRestrictionsMigrated,
- String remoteBugreportUri, String remoteBugreportHash) {
+ String remoteBugreportUri, String remoteBugreportHash, boolean canAccessDeviceIds) {
this.name = name;
this.admin = admin;
this.packageName = admin.getPackageName();
this.userRestrictionsMigrated = userRestrictionsMigrated;
this.remoteBugreportUri = remoteBugreportUri;
this.remoteBugreportHash = remoteBugreportHash;
+ this.canAccessDeviceIds = canAccessDeviceIds;
}
public void writeToXml(XmlSerializer out, String tag) throws IOException {
@@ -932,6 +969,8 @@ class Owners {
if (remoteBugreportHash != null) {
out.attribute(null, ATTR_REMOTE_BUGREPORT_HASH, remoteBugreportHash);
}
+ out.attribute(null, ATTR_CAN_ACCESS_DEVICE_IDS,
+ String.valueOf(canAccessDeviceIds));
out.endTag(null, tag);
}
@@ -948,13 +987,17 @@ class Owners {
ATTR_REMOTE_BUGREPORT_URI);
final String remoteBugreportHash = parser.getAttributeValue(null,
ATTR_REMOTE_BUGREPORT_HASH);
+ final String canAccessDeviceIdsStr =
+ parser.getAttributeValue(null, ATTR_CAN_ACCESS_DEVICE_IDS);
+ final boolean canAccessDeviceIds =
+ ("true".equals(canAccessDeviceIdsStr));
// Has component name? If so, return [name, component]
if (componentName != null) {
final ComponentName admin = ComponentName.unflattenFromString(componentName);
if (admin != null) {
return new OwnerInfo(name, admin, userRestrictionsMigrated,
- remoteBugreportUri, remoteBugreportHash);
+ remoteBugreportUri, remoteBugreportHash, canAccessDeviceIds);
} else {
// This shouldn't happen but switch from package name -> component name
// might have written bad device owner files. b/17652534
@@ -965,13 +1008,14 @@ class Owners {
// Else, build with [name, package]
return new OwnerInfo(name, packageName, userRestrictionsMigrated, remoteBugreportUri,
- remoteBugreportHash);
+ remoteBugreportHash, canAccessDeviceIds);
}
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "admin=" + admin);
pw.println(prefix + "name=" + name);
pw.println(prefix + "package=" + packageName);
+ pw.println(prefix + "canAccessDeviceIds=" + canAccessDeviceIds);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 16b127c714cc..5dc6d8373f27 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4973,6 +4973,176 @@ public class DevicePolicyManagerTest extends DpmTestBase {
assertProfileOwnershipRevertedWithFakeTransferMetadata();
}
+ public void testGrantDeviceIdsAccess_notToProfileOwner() throws Exception {
+ setupProfileOwner();
+ configureContextForAccess(mContext, false);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setProfileOwnerCanAccessDeviceIdsForUser(admin2,
+ UserHandle.of(DpmMockContext.CALLER_UID)));
+ }
+
+ public void testGrantDeviceIdsAccess_notByAuthorizedCaller() throws Exception {
+ setupProfileOwner();
+ configureContextForAccess(mContext, false);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setProfileOwnerCanAccessDeviceIdsForUser(admin1,
+ UserHandle.of(DpmMockContext.CALLER_UID)));
+ }
+
+ public void testGrantDeviceIdsAccess_byAuthorizedSystemCaller() throws Exception {
+ setupProfileOwner();
+
+ // This method will throw if the system context could not call
+ // setProfileOwnerCanAccessDeviceIds successfully.
+ configureProfileOwnerForDeviceIdAccess(admin1, DpmMockContext.CALLER_USER_HANDLE);
+ }
+
+ private static void configureContextForAccess(DpmMockContext context, boolean granted) {
+ when(context.spiedContext.checkCallingPermission(
+ android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS))
+ .thenReturn(granted ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED);
+ }
+
+ public void testGrantDeviceIdsAccess_byAuthorizedManagedProvisioning() throws Exception {
+ setupProfileOwner();
+
+ final long ident = mServiceContext.binder.clearCallingIdentity();
+ configureContextForAccess(mServiceContext, true);
+
+ mServiceContext.binder.callingUid =
+ UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE,
+ DpmMockContext.CALLER_MANAGED_PROVISIONING_UID);
+ try {
+ runAsCaller(mServiceContext, dpms, dpm -> {
+ dpm.setProfileOwnerCanAccessDeviceIdsForUser(admin1,
+ UserHandle.of(DpmMockContext.CALLER_USER_HANDLE));
+ });
+ } finally {
+ mServiceContext.binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ public void testEnforceCallerCanRequestDeviceIdAttestation_deviceOwnerCaller()
+ throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ configureContextForAccess(mContext, false);
+
+ // Device owner should be allowed to request Device ID attestation.
+ dpms.enforceCallerCanRequestDeviceIdAttestation(admin1, admin1.getPackageName(),
+ DpmMockContext.CALLER_SYSTEM_USER_UID);
+
+ // Another package must not be allowed to request Device ID attestation.
+ assertExpectException(SecurityException.class, null,
+ () -> dpms.enforceCallerCanRequestDeviceIdAttestation(null,
+ admin2.getPackageName(), DpmMockContext.CALLER_UID));
+ // Another component that is not the admin must not be allowed to request Device ID
+ // attestation.
+ assertExpectException(SecurityException.class, null,
+ () -> dpms.enforceCallerCanRequestDeviceIdAttestation(admin2,
+ admin1.getPackageName(), DpmMockContext.CALLER_UID));
+ }
+
+ public void testEnforceCallerCanRequestDeviceIdAttestation_profileOwnerCaller()
+ throws Exception {
+ configureContextForAccess(mContext, false);
+
+ // Make sure a security exception is thrown if the device has no profile owner.
+ assertExpectException(SecurityException.class, null,
+ () -> dpms.enforceCallerCanRequestDeviceIdAttestation(admin1,
+ admin1.getPackageName(), DpmMockContext.CALLER_SYSTEM_USER_UID));
+
+ setupProfileOwner();
+ configureProfileOwnerForDeviceIdAccess(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+ // The profile owner is allowed to request Device ID attestation.
+ mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpms.enforceCallerCanRequestDeviceIdAttestation(admin1, admin1.getPackageName(),
+ DpmMockContext.CALLER_UID);
+ // But not another package.
+ assertExpectException(SecurityException.class, null,
+ () -> dpms.enforceCallerCanRequestDeviceIdAttestation(null,
+ admin2.getPackageName(), DpmMockContext.CALLER_UID));
+ // Or another component which is not the admin.
+ assertExpectException(SecurityException.class, null,
+ () -> dpms.enforceCallerCanRequestDeviceIdAttestation(admin2,
+ admin2.getPackageName(), DpmMockContext.CALLER_UID));
+ }
+
+ public void runAsDelegatedCertInstaller(DpmRunnable action) throws Exception {
+ final long ident = mServiceContext.binder.clearCallingIdentity();
+
+ mServiceContext.binder.callingUid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE,
+ DpmMockContext.DELEGATE_CERT_INSTALLER_UID);
+ try {
+ runAsCaller(mServiceContext, dpms, action);
+ } finally {
+ mServiceContext.binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ public void testEnforceCallerCanRequestDeviceIdAttestation_delegateCaller() throws Exception {
+ setupProfileOwner();
+ markDelegatedCertInstallerAsInstalled();
+
+ // Configure a delegated cert installer.
+ runAsCaller(mServiceContext, dpms,
+ dpm -> dpm.setDelegatedScopes(admin1, DpmMockContext.DELEGATE_PACKAGE_NAME,
+ Arrays.asList(DELEGATION_CERT_INSTALL)));
+
+ configureProfileOwnerForDeviceIdAccess(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+ // Make sure that the profile owner can still request Device ID attestation.
+ mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpms.enforceCallerCanRequestDeviceIdAttestation(admin1, admin1.getPackageName(),
+ DpmMockContext.CALLER_UID);
+
+ runAsDelegatedCertInstaller(dpm -> {
+ dpms.enforceCallerCanRequestDeviceIdAttestation(null,
+ DpmMockContext.DELEGATE_PACKAGE_NAME,
+ UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE,
+ DpmMockContext.DELEGATE_CERT_INSTALLER_UID));
+ });
+ }
+
+ public void testEnforceCallerCanRequestDeviceIdAttestation_delegateCallerWithoutPermissions()
+ throws Exception {
+ setupProfileOwner();
+ markDelegatedCertInstallerAsInstalled();
+
+ // Configure a delegated cert installer.
+ runAsCaller(mServiceContext, dpms,
+ dpm -> dpm.setDelegatedScopes(admin1, DpmMockContext.DELEGATE_PACKAGE_NAME,
+ Arrays.asList(DELEGATION_CERT_INSTALL)));
+
+
+ assertExpectException(SecurityException.class, null,
+ () -> dpms.enforceCallerCanRequestDeviceIdAttestation(admin1,
+ admin1.getPackageName(),
+ DpmMockContext.CALLER_UID));
+
+ runAsDelegatedCertInstaller(dpm -> {
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpms.enforceCallerCanRequestDeviceIdAttestation(null,
+ DpmMockContext.DELEGATE_PACKAGE_NAME,
+ UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE,
+ DpmMockContext.DELEGATE_CERT_INSTALLER_UID)));
+ });
+ }
+
+ private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) {
+ final long ident = mServiceContext.binder.clearCallingIdentity();
+ mServiceContext.binder.callingUid =
+ UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
+ runAsCaller(mServiceContext, dpms, dpm -> {
+ dpm.setProfileOwnerCanAccessDeviceIdsForUser(who, UserHandle.of(userId));
+ });
+ mServiceContext.binder.restoreCallingIdentity(ident);
+ }
+
// admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index be00bb662d17..e411fb5d893f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -59,6 +59,12 @@ public class DpmMockContext extends MockContext {
public static final int CALLER_UID = UserHandle.getUid(CALLER_USER_HANDLE, 20123);
/**
+ * UID corresponding to {@link #CALLER_USER_HANDLE}.
+ */
+ public static final int CALLER_MANAGED_PROVISIONING_UID = UserHandle.getUid(CALLER_USER_HANDLE,
+ 20125);
+
+ /**
* UID used when a caller is on the system user.
*/
public static final int CALLER_SYSTEM_USER_UID = 20321;
@@ -81,6 +87,10 @@ public class DpmMockContext extends MockContext {
public static final String ANOTHER_PACKAGE_NAME = "com.another.package.name";
public static final int ANOTHER_UID = UserHandle.getUid(UserHandle.USER_SYSTEM, 18434);
+ public static final String DELEGATE_PACKAGE_NAME = "com.delegate.package.name";
+ public static final int DELEGATE_CERT_INSTALLER_UID = UserHandle.getUid(UserHandle.USER_SYSTEM,
+ 18437);
+
private final MockSystemServices mMockSystemServices;
public static class MockBinder {
@@ -427,4 +437,9 @@ public class DpmMockContext extends MockContext {
public int getUserId() {
return UserHandle.getUserId(binder.getCallingUid());
}
+
+ @Override
+ public int checkCallingPermission(String permission) {
+ return spiedContext.checkCallingPermission(permission);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 0c8a7879ef5e..a34c2ff8ce07 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -50,6 +50,7 @@ public abstract class DpmTestBase extends AndroidTestCase {
public ComponentName admin3;
public ComponentName adminAnotherPackage;
public ComponentName adminNoPerm;
+ public ComponentName delegateCertInstaller;
@Override
protected void setUp() throws Exception {
@@ -66,6 +67,8 @@ public abstract class DpmTestBase extends AndroidTestCase {
adminAnotherPackage = new ComponentName(DpmMockContext.ANOTHER_PACKAGE_NAME,
"whatever.random.class");
adminNoPerm = new ComponentName(mRealTestContext, DummyDeviceAdmins.AdminNoPerm.class);
+ delegateCertInstaller = new ComponentName(DpmMockContext.DELEGATE_PACKAGE_NAME,
+ "some.random.class");
mockSystemPropertiesToReturnDefault();
}
@@ -130,6 +133,20 @@ public abstract class DpmTestBase extends AndroidTestCase {
eq(userId));
}
+ protected void markDelegatedCertInstallerAsInstalled() throws Exception {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ ai.flags = ApplicationInfo.FLAG_HAS_CODE;
+ // Mark the package as installed on the work profile.
+ ai.uid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE,
+ DpmMockContext.DELEGATE_CERT_INSTALLER_UID);
+ ai.packageName = delegateCertInstaller.getPackageName();
+ ai.name = delegateCertInstaller.getClassName();
+
+ markPackageAsInstalled(delegateCertInstaller.getPackageName(), ai,
+ DpmMockContext.CALLER_USER_HANDLE);
+ }
+
protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid)
throws Exception {
setUpPackageManagerForAdmin(admin, packageUid,