Merge "Add API for checking which CA certs were installed by the DO/PO"
diff --git a/api/test-current.txt b/api/test-current.txt
index 5e5c8b5..e496a96 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6266,6 +6266,7 @@
method public long getMaximumTimeToLock(android.content.ComponentName);
method public int getOrganizationColor(android.content.ComponentName);
method public java.lang.CharSequence getOrganizationName(android.content.ComponentName);
+ method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(android.os.UserHandle);
method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
method public long getPasswordExpiration(android.content.ComponentName);
method public long getPasswordExpirationTimeout(android.content.ComponentName);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6585793..449cca3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7958,4 +7958,28 @@
throw re.rethrowFromSystemServer();
}
}
+
+
+ /**
+ * Called by the system to get a list of CA certificates that were installed by the device or
+ * profile owner.
+ *
+ * <p> The caller must be the target user's Device Owner/Profile owner or hold the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+ *
+ * @param user The user for whom to retrieve information.
+ * @return list of aliases identifying CA certificates installed by the device or profile owner
+ * @throws SecurityException if the caller does not have permission to retrieve information
+ * about the given user's CA certificates.
+ *
+ * @hide
+ */
+ @TestApi
+ public List<String> getOwnerInstalledCaCerts(@NonNull UserHandle user) {
+ try {
+ return mService.getOwnerInstalledCaCerts(user).getList();
+ } 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 ec97c2c..97a4678 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
+import android.content.pm.StringParceledListSlice;
import android.graphics.Bitmap;
import android.net.ProxyInfo;
import android.net.Uri;
@@ -349,4 +350,5 @@
boolean resetPasswordWithToken(in ComponentName admin, String password, in byte[] token, int flags);
boolean isDefaultInputMethodSetByOwner(in UserHandle user);
+ StringParceledListSlice getOwnerInstalledCaCerts(in UserHandle user);
}
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index b685431..c4bb72c 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -29,8 +29,8 @@
byte[] getCertificate(String alias);
byte[] getCaCertificates(String alias);
- // APIs used by CertInstaller
- void installCaCertificate(in byte[] caCertificate);
+ // APIs used by CertInstaller and DevicePolicyManager
+ String installCaCertificate(in byte[] caCertificate);
// APIs used by DevicePolicyManager
boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8b94ca0..9c3ecd0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -99,6 +99,7 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.StringParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -166,6 +167,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
@@ -243,10 +245,14 @@
private static final String TAG_DEFAULT_INPUT_METHOD_SET = "default-ime-set";
+ private static final String TAG_OWNER_INSTALLED_CA_CERT = "owner-installed-ca-cert";
+
private static final String ATTR_ID = "id";
private static final String ATTR_VALUE = "value";
+ private static final String ATTR_ALIAS = "alias";
+
private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
@@ -480,6 +486,7 @@
final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
+ // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
// This is the list of component allowed to start lock task mode.
@@ -504,6 +511,9 @@
boolean mDefaultInputMethodSet = false;
+ // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
+ Set<String> mOwnerInstalledCaCerts = new ArraySet<>();
+
// Used for initialization of users created by createAndManageUsers.
boolean mAdminBroadcastPending = false;
PersistableBundle mInitBundle = null;
@@ -518,6 +528,7 @@
final SparseArray<DevicePolicyData> mUserData = new SparseArray<>();
final Handler mHandler;
+ final Handler mBackgroundHandler;
/** Listens on any device, even when mHasFeature == false. */
final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
@@ -619,6 +630,25 @@
handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle);
} else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)) {
clearWipeProfileNotification();
+ } else if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
+ mBackgroundHandler.post(() -> {
+ try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+ UserHandle.of(userHandle))) {
+ final List<String> caCerts =
+ keyChainConnection.getService().getUserCaAliases().getList();
+ synchronized (DevicePolicyManagerService.this) {
+ if (getUserData(userHandle).mOwnerInstalledCaCerts
+ .retainAll(caCerts)) {
+ saveSettingsLocked(userHandle);
+ }
+ }
+ } catch (InterruptedException e) {
+ Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
+ Thread.currentThread().interrupt();
+ } catch (RemoteException e) {
+ Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
+ }
+ });
}
}
@@ -1786,6 +1816,7 @@
.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
mIsWatch = mInjector.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_WATCH);
+ mBackgroundHandler = BackgroundThread.getHandler();
// Broadcast filter for changes to the trusted certificate store. These changes get a
// separate intent filter so we can listen to them even when device_admin is off.
@@ -1807,6 +1838,7 @@
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
+ filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
filter = new IntentFilter();
@@ -2580,6 +2612,12 @@
out.endTag(null, TAG_DEFAULT_INPUT_METHOD_SET);
}
+ for (final String cert : policy.mOwnerInstalledCaCerts) {
+ out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+ out.attribute(null, ATTR_ALIAS, cert);
+ out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+ }
+
out.endTag(null, "policies");
out.endDocument();
@@ -2696,6 +2734,7 @@
policy.mAdminList.clear();
policy.mAdminMap.clear();
policy.mAffiliationIds.clear();
+ policy.mOwnerInstalledCaCerts.clear();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -2791,6 +2830,8 @@
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_DEFAULT_INPUT_METHOD_SET.equals(tag)) {
policy.mDefaultInputMethodSet = true;
+ } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
+ policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -4691,17 +4732,15 @@
return false;
}
- final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+ final UserHandle userHandle = UserHandle.of(mInjector.userHandleGetCallingUserId());
final long id = mInjector.binderClearCallingIdentity();
+ String alias = null;
try {
- final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
- try {
- keyChainConnection.getService().installCaCertificate(pemCert);
- return true;
+ try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+ userHandle)) {
+ alias = keyChainConnection.getService().installCaCertificate(pemCert);
} catch (RemoteException e) {
Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
- } finally {
- keyChainConnection.close();
}
} catch (InterruptedException e1) {
Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
@@ -4709,6 +4748,16 @@
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
+ if (alias == null) {
+ Log.w(LOG_TAG, "Problem installing cert");
+ } else {
+ synchronized (this) {
+ final int userId = userHandle.getIdentifier();
+ getUserData(userId).mOwnerInstalledCaCerts.add(alias);
+ saveSettingsLocked(userId);
+ }
+ return true;
+ }
return false;
}
@@ -4722,25 +4771,31 @@
public void uninstallCaCerts(ComponentName admin, String callerPackage, String[] aliases) {
enforceCanManageCaCerts(admin, callerPackage);
- final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+ final int userId = mInjector.userHandleGetCallingUserId();
+ final UserHandle userHandle = UserHandle.of(userId);
final long id = mInjector.binderClearCallingIdentity();
try {
- final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
- try {
+ try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+ userHandle)) {
for (int i = 0 ; i < aliases.length; i++) {
keyChainConnection.getService().deleteCaCertificate(aliases[i]);
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
- } finally {
- keyChainConnection.close();
+ return;
}
} catch (InterruptedException ie) {
Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
Thread.currentThread().interrupt();
+ return;
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
+ synchronized (this) {
+ if (getUserData(userId).mOwnerInstalledCaCerts.removeAll(Arrays.asList(aliases))) {
+ saveSettingsLocked(userId);
+ }
+ }
}
@Override
@@ -6731,6 +6786,7 @@
}
final DevicePolicyData policyData = getUserData(userId);
policyData.mDefaultInputMethodSet = false;
+ policyData.mOwnerInstalledCaCerts.clear();
saveSettingsLocked(userId);
clearUserPoliciesLocked(userId);
mOwners.removeProfileOwner(userId);
@@ -7127,29 +7183,32 @@
}
private void enforceFullCrossUsersPermission(int userHandle) {
- enforceSystemUserOrPermission(userHandle,
+ enforceSystemUserOrPermissionIfCrossUser(userHandle,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
}
private void enforceCrossUsersPermission(int userHandle) {
- enforceSystemUserOrPermission(userHandle,
+ enforceSystemUserOrPermissionIfCrossUser(userHandle,
android.Manifest.permission.INTERACT_ACROSS_USERS);
}
- private void enforceSystemUserOrPermission(int userHandle, String permission) {
- if (userHandle < 0) {
- throw new IllegalArgumentException("Invalid userId " + userHandle);
- }
- final int callingUid = mInjector.binderGetCallingUid();
- if (userHandle == UserHandle.getUserId(callingUid)) {
- return;
- }
- if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
+ private void enforceSystemUserOrPermission(String permission) {
+ if (!(isCallerWithSystemUid() || mInjector.binderGetCallingUid() == Process.ROOT_UID)) {
mContext.enforceCallingOrSelfPermission(permission,
"Must be system or have " + permission + " permission");
}
}
+ private void enforceSystemUserOrPermissionIfCrossUser(int userHandle, String permission) {
+ if (userHandle < 0) {
+ throw new IllegalArgumentException("Invalid userId " + userHandle);
+ }
+ if (userHandle == mInjector.userHandleGetCallingUserId()) {
+ return;
+ }
+ enforceSystemUserOrPermission(permission);
+ }
+
private void enforceManagedProfile(int userHandle, String message) {
if(!isManagedProfile(userHandle)) {
throw new SecurityException("You can not " + message + " outside a managed profile.");
@@ -7184,6 +7243,21 @@
"Only profile owner, device owner and system may call this method.");
}
+ private void enforceProfileOwnerOrFullCrossUsersPermission(int userId) {
+ if (userId == mInjector.userHandleGetCallingUserId()) {
+ synchronized (this) {
+ if (getActiveAdminWithPolicyForUidLocked(null,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid())
+ != null) {
+ // Device Owner/Profile Owner may access the user it runs on.
+ return;
+ }
+ }
+ }
+ // Otherwise, INTERACT_ACROSS_USERS_FULL permission, system UID or root UID is required.
+ enforceSystemUserOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ }
+
private void ensureCallerPackage(@Nullable String packageName) {
if (packageName == null) {
Preconditions.checkState(isCallerWithSystemUid(),
@@ -10897,4 +10971,14 @@
}
return getUserData(userId).mDefaultInputMethodSet;
}
+
+ @Override
+ public StringParceledListSlice getOwnerInstalledCaCerts(@NonNull UserHandle user) {
+ final int userId = user.getIdentifier();
+ enforceProfileOwnerOrFullCrossUsersPermission(userId);
+ synchronized (this) {
+ return new StringParceledListSlice(
+ new ArrayList<>(getUserData(userId).mOwnerInstalledCaCerts));
+ }
+ }
}
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 756514b..f797f31 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -49,6 +49,8 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
import android.telephony.TelephonyManager;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;
@@ -122,6 +124,34 @@
public DevicePolicyManager dpm;
public DevicePolicyManagerServiceTestable dpms;
+ /*
+ * The CA cert below is the content of cacert.pem as generated by:
+ *
+ * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+ */
+ private static final String TEST_CA =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
+ "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
+ "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
+ "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
+ "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+ "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
+ "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
+ "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
+ "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
+ "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
+ "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
+ "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
+ "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
+ "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
+ "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
+ "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
+ "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
+ "wQ==\n" +
+ "-----END CERTIFICATE-----\n";
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -3916,6 +3946,124 @@
assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
}
+ public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
+ setDeviceOwner();
+
+ mContext.packageName = admin1.getPackageName();
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ verifyCanGetOwnerInstalledCaCerts(admin1);
+ }
+
+ public void testGetOwnerInstalledCaCertsForProfileOwner() throws Exception {
+ setAsProfileOwner(admin1);
+
+ mContext.packageName = admin1.getPackageName();
+ verifyCanGetOwnerInstalledCaCerts(admin1);
+ verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(admin1);
+ }
+
+ public void testGetOwnerInstalledCaCertsForDelegate() throws Exception {
+ setAsProfileOwner(admin1);
+
+ final String delegate = "com.example.delegate";
+ final int delegateUid = setupPackageInPackageManager(delegate, 20988);
+ dpm.setCertInstallerPackage(admin1, delegate);
+
+ mContext.packageName = delegate;
+ mContext.binder.callingUid = delegateUid;
+ verifyCanGetOwnerInstalledCaCerts(null);
+ verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(null);
+ }
+
+ private void verifyCanGetOwnerInstalledCaCerts(ComponentName caller) throws Exception {
+ final UserHandle user = UserHandle.getUserHandleForUid(mContext.binder.callingUid);
+ final int ownerUid = user.equals(UserHandle.SYSTEM) ?
+ DpmMockContext.CALLER_SYSTEM_USER_UID : DpmMockContext.CALLER_UID;
+
+ mContext.applicationInfo = new ApplicationInfo();
+ mContext.userContexts.put(user, mContext);
+ when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+ // Install a CA cert.
+ final String alias = "cert";
+ final byte[] caCert = TEST_CA.getBytes();
+ when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+ .thenReturn(alias);
+ assertTrue(dpm.installCaCert(caller, caCert));
+ when(mContext.keyChainConnection.getService().getUserCaAliases())
+ .thenReturn(asSlice(new String[] {alias}));
+ mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+ flushTasks();
+
+ // Device Owner / Profile Owner can find out which CA certs were installed by itself.
+ final String packageName = mContext.packageName;
+ mContext.packageName = admin1.getPackageName();
+ final long callerIdentity = mContext.binder.clearCallingIdentity();
+ mContext.binder.callingUid = ownerUid;
+ List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+ assertNotNull(ownerInstalledCaCerts);
+ assertEquals(1, ownerInstalledCaCerts.size());
+ assertTrue(ownerInstalledCaCerts.contains(alias));
+
+ // Restarting the DPMS should not lose information.
+ initializeDpms();
+ assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(user));
+
+ // System can find out which CA certs were installed by the Device Owner / Profile Owner.
+ mContext.packageName = "com.android.frameworks.servicestests";
+ mContext.binder.clearCallingIdentity();
+ assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(user));
+
+ // Remove the CA cert.
+ mContext.packageName = packageName;
+ mContext.binder.restoreCallingIdentity(callerIdentity);
+ reset(mContext.keyChainConnection.getService());
+ mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+ flushTasks();
+
+ // Verify that the CA cert is no longer reported as installed by the Device Owner / Profile
+ // Owner.
+ mContext.packageName = admin1.getPackageName();
+ mContext.binder.callingUid = ownerUid;
+ ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+ assertNotNull(ownerInstalledCaCerts);
+ assertTrue(ownerInstalledCaCerts.isEmpty());
+
+ mContext.packageName = packageName;
+ mContext.binder.restoreCallingIdentity(callerIdentity);
+ }
+
+ private void verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(ComponentName caller)
+ throws Exception {
+ final UserHandle user = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
+
+ mContext.applicationInfo = new ApplicationInfo();
+ mContext.userContexts.put(user, mContext);
+ when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+ // Install a CA cert.
+ final String alias = "cert";
+ final byte[] caCert = TEST_CA.getBytes();
+ when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+ .thenReturn(alias);
+ assertTrue(dpm.installCaCert(caller, caCert));
+ when(mContext.keyChainConnection.getService().getUserCaAliases())
+ .thenReturn(asSlice(new String[] {alias}));
+ mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+ flushTasks();
+
+ // Removing the Profile Owner should clear the information which CA certs were installed
+ // by it.
+ mContext.packageName = admin1.getPackageName();
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpm.clearProfileOwner(admin1);
+ mContext.packageName = "com.android.frameworks.servicestests";
+ mContext.binder.clearCallingIdentity();
+ final List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+ assertNotNull(ownerInstalledCaCerts);
+ assertTrue(ownerInstalledCaCerts.isEmpty());
+ }
+
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
@@ -3978,4 +4126,31 @@
private static StringParceledListSlice asSlice(String[] s) {
return new StringParceledListSlice(Arrays.asList(s));
}
+
+ private void flushTasks() throws Exception {
+ Boolean tasksFlushed[] = new Boolean[] {false};
+ final Runnable tasksFlushedNotifier = () -> {
+ synchronized (tasksFlushed) {
+ tasksFlushed[0] = true;
+ tasksFlushed.notify();
+ }
+ };
+
+ // Flush main thread handler.
+ dpms.mHandler.post(tasksFlushedNotifier);
+ synchronized (tasksFlushed) {
+ if (!tasksFlushed[0]) {
+ tasksFlushed.wait();
+ }
+ }
+
+ // Flush background thread handler.
+ tasksFlushed[0] = false;
+ dpms.mBackgroundHandler.post(tasksFlushedNotifier);
+ synchronized (tasksFlushed) {
+ if (!tasksFlushed[0]) {
+ tasksFlushed.wait();
+ }
+ }
+ }
}
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 258b393..7d017c5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -316,6 +316,41 @@
public ApplicationInfo applicationInfo = null;
+ // We have to keep track of broadcast receivers registered for a given intent ourselves as the
+ // DPM unit tests mock out the package manager and PackageManager.queryBroadcastReceivers() does
+ // not work.
+ private class BroadcastReceiverRegistration {
+ public final BroadcastReceiver receiver;
+ public final IntentFilter filter;
+ public final Handler scheduler;
+
+ public BroadcastReceiverRegistration(BroadcastReceiver receiver, IntentFilter filter,
+ Handler scheduler) {
+ this.receiver = receiver;
+ this.filter = filter;
+ this.scheduler = scheduler;
+ }
+
+ public void sendBroadcastIfApplicable(int userId, Intent intent) {
+ final BroadcastReceiver.PendingResult result = new BroadcastReceiver.PendingResult(
+ 0 /* resultCode */, null /* resultData */, null /* resultExtras */,
+ 0 /* type */, false /* ordered */, false /* sticky */, null /* token */, userId,
+ 0 /* flags */);
+ if (filter.match(null, intent, false, "DpmMockContext") > 0) {
+ if (scheduler != null) {
+ scheduler.post(() -> {
+ receiver.setPendingResult(result);
+ receiver.onReceive(DpmMockContext.this, intent);
+ });
+ } else {
+ receiver.setPendingResult(result);
+ receiver.onReceive(DpmMockContext.this, intent);
+ }
+ }
+ }
+ }
+ private List<BroadcastReceiverRegistration> mBroadcastReceivers = new ArrayList<>();
+
public DpmMockContext(Context context, File dataDir) {
realTestContext = context;
@@ -476,6 +511,13 @@
.thenReturn(isRunning);
}
+ public void injectBroadcast(Intent intent) {
+ final int userId = UserHandle.getUserId(binder.getCallingUid());
+ for (final BroadcastReceiverRegistration receiver : mBroadcastReceivers) {
+ receiver.sendBroadcastIfApplicable(userId, intent);
+ }
+ }
+
@Override
public Resources getResources() {
return resources;
@@ -681,24 +723,28 @@
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, null));
return spiedContext.registerReceiver(receiver, filter);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
+ mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
return spiedContext.registerReceiver(receiver, filter, broadcastPermission, scheduler);
}
@Override
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
return spiedContext.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
scheduler);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
+ mBroadcastReceivers.removeIf(r -> r.receiver == receiver);
spiedContext.unregisterReceiver(receiver);
}