summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Eran Messeri <eranm@google.com> 2018-10-17 18:27:50 +0100
committer Eran Messeri <eranm@google.com> 2018-11-15 10:34:20 +0000
commitbb27189f56d6db1cb66a9a89afc30cccbafea4e8 (patch)
tree9e504be0beaa314b3921afe2b7ceab251c9fbf90
parent10113a7c36838cb7da6b36e7f4800d6cfae6178d (diff)
Grant Device IDs access to Profile Owner
In order to allow inclusion of device identifiers in the key attestation record generated by the profile owner, the platform needs an explicit signal that it is OK for the profile owner to access those identifiers. Add a system-privileged method to the DevicePolicyManager that allows system applications, as well as Managed Provisioning to indicate that the profile owner may access those identifiers. In the DevicePolicyManagerService the following has changed: * The OwnerInfo now contains a flag indicating whether the profile owner was granted access to the device identifiers or not. * The permission check for use of the Device ID Attestation flags in generateKeyPair has been adjusted to allow profile owner (or its delegate) to use them, if device identifiers access has been granted. * A couple of utility methods have been added to ease checking of profile owner presence for a user and whether the profile owner can access device identifiers. Additionally, a new adb command has been added to give this grant to an existing profile owner for testing purposes. Bug: 111335970 Test: Manual, using TestDPC + ADB command. Test: atest FrameworksServicesTests:DevicePolicyManagerTest Test: Additional CTS tests, see cts change in the same topic. Change-Id: I05f2323d5edacd774cd3ce082ee9c551100f4afd
-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,