Merge "API to approve CA certificates" into nyc-dev
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c9b1d0c..656c7ff 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2655,6 +2655,43 @@
     }
 
     /**
+     * Mark a CA certificate as approved by the device user. This means that they have been notified
+     * of the installation, were made aware of the risks, viewed the certificate and still wanted to
+     * keep the certificate on the device.
+     *
+     * Calling with {@param approval} as {@code true} will cancel any ongoing warnings related to
+     * this certificate.
+     *
+     * @hide
+     */
+    public boolean approveCaCert(String alias, int userHandle, boolean approval) {
+        if (mService != null) {
+            try {
+                return mService.approveCaCert(alias, userHandle, approval);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check whether a CA certificate has been approved by the device user.
+     *
+     * @hide
+     */
+    public boolean isCaCertApproved(String alias, int userHandle) {
+        if (mService != null) {
+            try {
+                return mService.isCaCertApproved(alias, userHandle);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * Installs the given certificate as a user CA.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8fa4c3a..6ee56aa 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -144,6 +144,8 @@
     boolean installCaCert(in ComponentName admin, in byte[] certBuffer);
     void uninstallCaCerts(in ComponentName admin, in String[] aliases);
     void enforceCanManageCaCerts(in ComponentName admin);
+    boolean approveCaCert(in String alias, int userHandle, boolean approval);
+    boolean isCaCertApproved(in String alias, int userHandle);
 
     boolean installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer,
             in byte[] certChainBuffer, String alias, boolean requestAccess);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ac6fc58..6fe5c16 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -136,6 +136,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
+import com.android.internal.util.ParcelableString;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
@@ -184,12 +185,16 @@
 
     private static final String DEVICE_POLICIES_XML = "device_policies.xml";
 
+    private static final String TAG_ACCEPTED_CA_CERTIFICATES = "accepted-ca-certificate";
+
     private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
 
     private static final String TAG_STATUS_BAR = "statusbar";
 
     private static final String ATTR_DISABLED = "disabled";
 
+    private static final String ATTR_NAME = "name";
+
     private static final String DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML =
             "do-not-ask-credentials-on-boot";
 
@@ -420,6 +425,8 @@
         final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
         final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
 
+        final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
+
         // This is the list of component allowed to start lock task mode.
         List<String> mLockTaskPackages = new ArrayList<>();
 
@@ -483,7 +490,8 @@
             }
             if (Intent.ACTION_BOOT_COMPLETED.equals(action)
                     || KeyChain.ACTION_STORAGE_CHANGED.equals(action)) {
-                new MonitoringCertNotificationTask().execute(intent);
+                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL);
+                new MonitoringCertNotificationTask().execute(userId);
             }
             if (Intent.ACTION_USER_ADDED.equals(action)) {
                 disableSecurityLoggingIfNotCompliant();
@@ -2221,6 +2229,12 @@
                 out.endTag(null, "active-password");
             }
 
+            for (int i = 0; i < policy.mAcceptedCaCertificates.size(); i++) {
+                out.startTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
+                out.attribute(null, ATTR_NAME, policy.mAcceptedCaCertificates.valueAt(i));
+                out.endTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
+            }
+
             for (int i=0; i<policy.mLockTaskPackages.size(); i++) {
                 String component = policy.mLockTaskPackages.get(i);
                 out.startTag(null, TAG_LOCK_TASK_COMPONENTS);
@@ -2387,6 +2401,8 @@
                             parser.getAttributeValue(null, "symbols"));
                     policy.mActivePasswordNonLetter = Integer.parseInt(
                             parser.getAttributeValue(null, "nonletter"));
+                } else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) {
+                    policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
                 } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
                     policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name"));
                 } else if (TAG_STATUS_BAR.equals(tag)) {
@@ -2638,17 +2654,17 @@
         }
     }
 
-    private class MonitoringCertNotificationTask extends AsyncTask<Intent, Void, Void> {
+    private class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
         @Override
-        protected Void doInBackground(Intent... params) {
-            int userHandle = params[0].getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL);
+        protected Void doInBackground(Integer... params) {
+            int userHandle = params[0];
 
             if (userHandle == UserHandle.USER_ALL) {
                 for (UserInfo userInfo : mUserManager.getUsers()) {
                     manageNotification(userInfo.getUserHandle());
                 }
             } else {
-                manageNotification(new UserHandle(userHandle));
+                manageNotification(UserHandle.of(userHandle));
             }
             return null;
         }
@@ -2658,25 +2674,27 @@
                 return;
             }
 
-            // Call out to KeyChain to check for user-added CAs
-            boolean hasCert = false;
+            // Call out to KeyChain to check for CAs which are waiting for approval.
+            final List<String> pendingCertificates;
             try {
-                KeyChainConnection kcs = KeyChain.bindAsUser(mContext, userHandle);
-                try {
-                    if (!kcs.getService().getUserCaAliases().getList().isEmpty()) {
-                        hasCert = true;
-                    }
-                } catch (RemoteException e) {
-                    Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
-                } finally {
-                    kcs.close();
-                }
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-            } catch (RuntimeException | AssertionError e) {
-                Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
+                pendingCertificates = getInstalledCaCertificates(userHandle);
+            } catch (RemoteException | RuntimeException e) {
+                Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
+                return;
             }
-            if (!hasCert) {
+
+            synchronized (DevicePolicyManagerService.this) {
+                final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
+
+                // Remove deleted certificates. Flush xml if necessary.
+                if (policy.mAcceptedCaCertificates.retainAll(pendingCertificates)) {
+                    saveSettingsLocked(userHandle.getIdentifier());
+                }
+                // Trim to approved certificates.
+                pendingCertificates.removeAll(policy.mAcceptedCaCertificates);
+            }
+
+            if (pendingCertificates.isEmpty()) {
                 mInjector.getNotificationManager().cancelAsUser(
                         null, MONITORING_CERT_NOTIFICATION_ID, userHandle);
                 return;
@@ -2707,7 +2725,8 @@
 
             final Context userContext;
             try {
-                userContext = mContext.createPackageContextAsUser("android", 0, userHandle);
+                final String packageName = mContext.getPackageName();
+                userContext = mContext.createPackageContextAsUser(packageName, 0, userHandle);
             } catch (PackageManager.NameNotFoundException e) {
                 Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
                 return;
@@ -2726,6 +2745,29 @@
             mInjector.getNotificationManager().notifyAsUser(
                     null, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
         }
+
+        private List<String> getInstalledCaCertificates(UserHandle userHandle)
+                throws RemoteException, RuntimeException {
+            KeyChainConnection conn = null;
+            try {
+                conn = KeyChain.bindAsUser(mContext, userHandle);
+                List<ParcelableString> aliases = conn.getService().getUserCaAliases().getList();
+                List<String> result = new ArrayList<>(aliases.size());
+                for (int i = 0; i < aliases.size(); i++) {
+                    result.add(aliases.get(i).string);
+                }
+                return result;
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                return null;
+            } catch (AssertionError e) {
+                throw new RuntimeException(e);
+            } finally {
+                if (conn != null) {
+                    conn.close();
+                }
+            }
+        }
     }
 
     /**
@@ -4076,6 +4118,29 @@
     }
 
     @Override
+    public boolean approveCaCert(String alias, int userId, boolean approval) {
+        enforceManageUsers();
+        synchronized (this) {
+            Set<String> certs = getUserData(userId).mAcceptedCaCertificates;
+            boolean changed = (approval ? certs.add(alias) : certs.remove(alias));
+            if (!changed) {
+                return false;
+            }
+            saveSettingsLocked(userId);
+        }
+        new MonitoringCertNotificationTask().execute(userId);
+        return true;
+    }
+
+    @Override
+    public boolean isCaCertApproved(String alias, int userId) {
+        enforceManageUsers();
+        synchronized (this) {
+            return getUserData(userId).mAcceptedCaCertificates.contains(alias);
+        }
+    }
+
+    @Override
     public boolean installCaCert(ComponentName admin, byte[] certBuffer) throws RemoteException {
         enforceCanManageCaCerts(admin);