From 05dc9f764c9d399add8b7495e680f66d098c55eb Mon Sep 17 00:00:00 2001 From: Bartosz Fabianowski Date: Wed, 22 Feb 2017 23:41:14 +0100 Subject: [PATCH] Add API for checking which CA certs were installed by the DO/PO With this API, the system can determine whether a CA cert was installed by the user or the user's DO/PO. Bug: 32692748 Test: unit tests (see DevicePolicyManagerTest.java for invocation) Test: cts-tradefed run cts-dev --module CtsDevicePolicyManagerTestCases Change-Id: I3bcae5ac18ec2b110154184fc515df804fd73da6 --- api/test-current.txt | 1 + .../app/admin/DevicePolicyManager.java | 24 +++ .../app/admin/IDevicePolicyManager.aidl | 2 + .../android/security/IKeyChainService.aidl | 4 +- .../DevicePolicyManagerService.java | 130 ++++++++++--- .../devicepolicy/DevicePolicyManagerTest.java | 175 ++++++++++++++++++ .../server/devicepolicy/DpmMockContext.java | 46 +++++ 7 files changed, 357 insertions(+), 25 deletions(-) diff --git a/api/test-current.txt b/api/test-current.txt index 478a3ed42444e..5721b3d0fca00 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6264,6 +6264,7 @@ package android.app.admin { 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 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 65857935b07fe..449cca365678f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -7958,4 +7958,28 @@ public class DevicePolicyManager { throw re.rethrowFromSystemServer(); } } + + + /** + * Called by the system to get a list of CA certificates that were installed by the device or + * profile owner. + * + *

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 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 ec97c2c2084cb..97a4678070c47 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -27,6 +27,7 @@ import android.content.ComponentName; 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 @@ interface IDevicePolicyManager { 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 b68543146a511..c4bb72c71d176 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -29,8 +29,8 @@ interface IKeyChainService { 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 8b94ca0676215..9c3ecd094b8ac 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.PackageManagerInternal; 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.GuardedBy; 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final ArrayList mAdminList = new ArrayList<>(); final ArrayList mRemovingAdmins = new ArrayList<>(); + // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead. final ArraySet mAcceptedCaCertificates = new ArraySet<>(); // This is the list of component allowed to start lock task mode. @@ -504,6 +511,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean mDefaultInputMethodSet = false; + // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead. + Set mOwnerInstalledCaCerts = new ArraySet<>(); + // Used for initialization of users created by createAndManageUsers. boolean mAdminBroadcastPending = false; PersistableBundle mInitBundle = null; @@ -518,6 +528,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final SparseArray 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { .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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } 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 class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final DevicePolicyData policyData = getUserData(userId); policyData.mDefaultInputMethodSet = false; + policyData.mOwnerInstalledCaCerts.clear(); saveSettingsLocked(userId); clearUserPoliciesLocked(userId); mOwners.removeProfileOwner(userId); @@ -7127,29 +7183,32 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } 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 756514bbecc40..f797f3114565b 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.UserHandle; 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 class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 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 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 258b3933a2943..7d017c54002c3 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 class DpmMockContext extends MockContext { 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 mBroadcastReceivers = new ArrayList<>(); + public DpmMockContext(Context context, File dataDir) { realTestContext = context; @@ -476,6 +511,13 @@ public class DpmMockContext extends MockContext { .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 @@ public class DpmMockContext extends MockContext { @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); }