OSDN Git Service

Implement CACert queries in SecurityController
authorphweiss <phweiss@google.com>
Wed, 19 Apr 2017 18:15:06 +0000 (20:15 +0200)
committerphweiss <phweiss@google.com>
Fri, 5 May 2017 17:03:29 +0000 (19:03 +0200)
Queries are run (on a AsyncTask) when user is switched and when
ACTION_TRUST_STORE_CHANGED is broadcasted. Otherwise, the result is cached
in the SecurityController.

Bug: 37535489
Test: runtest --path frameworks/base/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java

Change-Id: I3b9cc3d85c9f49d0a892613b63d1fba184ab647e

keystore/java/android/security/KeyChain.java
packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java

index ea804d0..3fe730f 100644 (file)
@@ -599,9 +599,9 @@ public final class KeyChain {
         private final Context context;
         private final ServiceConnection serviceConnection;
         private final IKeyChainService service;
-        private KeyChainConnection(Context context,
-                                   ServiceConnection serviceConnection,
-                                   IKeyChainService service) {
+        protected KeyChainConnection(Context context,
+                                     ServiceConnection serviceConnection,
+                                     IKeyChainService service) {
             this.context = context;
             this.serviceConnection = serviceConnection;
             this.service = service;
index fcb7289..efbf76b 100644 (file)
@@ -17,7 +17,10 @@ package com.android.systemui.statusbar.policy;
 
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -28,16 +31,23 @@ import android.net.IConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.os.AsyncTask;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.security.KeyChain;
+import android.security.KeyChain.KeyChainConnection;
+import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.settings.CurrentUserTracker;
 
@@ -59,6 +69,8 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
 
     private static final String VPN_BRANDED_META_DATA = "com.android.systemui.IS_BRANDED";
 
+    private static final int CA_CERT_LOADING_RETRY_TIME_IN_MS = 30_000;
+
     private final Context mContext;
     private final ConnectivityManager mConnectivityManager;
     private final IConnectivityManager mConnectivityManagerService;
@@ -73,6 +85,10 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
     private int mCurrentUserId;
     private int mVpnUserId;
 
+    // Key: userId, Value: whether the user has CACerts installed
+    // Needs to be cached here since the query has to be asynchronous
+    private ArrayMap<Integer, Boolean> mHasCACerts = new ArrayMap<Integer, Boolean>();
+
     public SecurityControllerImpl(Context context) {
         super(context);
         mContext = context;
@@ -86,6 +102,11 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
         mUserManager = (UserManager)
                 context.getSystemService(Context.USER_SERVICE);
 
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
+        context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null,
+                new Handler(Dependency.get(Dependency.BG_LOOPER)));
+
         // TODO: re-register network callback on user change.
         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
         onUserSwitched(ActivityManager.getCurrentUser());
@@ -218,14 +239,16 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
 
     @Override
     public boolean hasCACertInCurrentUser() {
-        //TODO: implement
-        return false;
+        Boolean hasCACerts = mHasCACerts.get(mCurrentUserId);
+        return hasCACerts != null && hasCACerts.booleanValue();
     }
 
     @Override
     public boolean hasCACertInWorkProfile() {
-        //TODO: implement
-        return false;
+        int userId = getWorkProfileUserId(mCurrentUserId);
+        if (userId == UserHandle.USER_NULL) return false;
+        Boolean hasCACerts = mHasCACerts.get(userId);
+        return hasCACerts != null && hasCACerts.booleanValue();
     }
 
     @Override
@@ -256,9 +279,16 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
         } else {
             mVpnUserId = mCurrentUserId;
         }
+        refreshCACerts();
         fireCallbacks();
     }
 
+    private void refreshCACerts() {
+        new CACertLoader().execute(mCurrentUserId);
+        int workProfileId = getWorkProfileUserId(mCurrentUserId);
+        if (workProfileId != UserHandle.USER_NULL) new CACertLoader().execute(workProfileId);
+    }
+
     private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
         if (cfg.legacy) {
             return mContext.getString(R.string.legacy_vpn_name);
@@ -348,4 +378,39 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
             fireCallbacks();
         };
     };
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override public void onReceive(Context context, Intent intent) {
+            if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
+                refreshCACerts();
+            }
+        }
+    };
+
+    protected class CACertLoader extends AsyncTask<Integer, Void, Pair<Integer, Boolean> > {
+
+        @Override
+        protected Pair<Integer, Boolean> doInBackground(Integer... userId) {
+            try (KeyChainConnection conn = KeyChain.bindAsUser(mContext,
+                                                               UserHandle.of(userId[0]))) {
+                boolean hasCACerts = !(conn.getService().getUserCaAliases().getList().isEmpty());
+                return new Pair<Integer, Boolean>(userId[0], hasCACerts);
+            } catch (RemoteException | InterruptedException | AssertionError e) {
+                Log.i(TAG, e.getMessage());
+                new Handler(Dependency.get(Dependency.BG_LOOPER)).postDelayed(
+                        () -> new CACertLoader().execute(userId[0]),
+                        CA_CERT_LOADING_RETRY_TIME_IN_MS);
+                return new Pair<Integer, Boolean>(userId[0], null);
+            }
+        }
+
+        @Override
+        protected void onPostExecute(Pair<Integer, Boolean> result) {
+            if (DEBUG) Log.d(TAG, "onPostExecute " + result);
+            if (result.second != null) {
+                mHasCACerts.put(result.first, result.second);
+                fireCallbacks();
+            }
+        }
+    }
 }
index 87c4c66..0ecee98 100644 (file)
@@ -21,15 +21,28 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.doNothing;
 
 import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.StringParceledListSlice;
 import android.net.ConnectivityManager;
+import android.security.IKeyChainService;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
 import com.android.systemui.SysuiTestCase;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.List;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,14 +50,34 @@ import org.junit.runner.RunWith;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SecurityControllerTest extends SysuiTestCase {
+public class SecurityControllerTest extends SysuiTestCase implements SecurityControllerCallback {
     private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
+    private final IKeyChainService.Stub mKeyChainService = mock(IKeyChainService.Stub.class);
     private SecurityControllerImpl mSecurityController;
+    private CountDownLatch stateChangedLatch;
+
+    // implementing SecurityControllerCallback
+    @Override
+    public void onStateChanged() {
+        stateChangedLatch.countDown();
+    }
 
     @Before
     public void setUp() throws Exception {
         mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
         mContext.addMockSystemService(Context.CONNECTIVITY_SERVICE, mock(ConnectivityManager.class));
+
+        Intent intent = new Intent(IKeyChainService.class.getName());
+        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        mContext.addMockService(comp, mKeyChainService);
+
+        when(mKeyChainService.getUserCaAliases())
+                .thenReturn(new StringParceledListSlice(new ArrayList<String>()));
+        // Without this line, mKeyChainService gets wrapped in a proxy when Stub.asInterface() is
+        // used on it, and the mocking above does not work.
+        when(mKeyChainService.queryLocalInterface("android.security.IKeyChainService"))
+                .thenReturn(mKeyChainService);
+
         mSecurityController = new SecurityControllerImpl(mContext);
     }
 
@@ -62,4 +95,47 @@ public class SecurityControllerTest extends SysuiTestCase {
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn("organization");
         assertEquals("organization", mSecurityController.getDeviceOwnerOrganizationName());
     }
+
+    @Test
+    public void testCaCertLoader() throws Exception {
+        // Wait for one or two state changes from the CACertLoader(s) in the constructor of
+        // mSecurityController
+        stateChangedLatch = new CountDownLatch(mSecurityController.hasWorkProfile() ? 2 : 1);
+        mSecurityController.addCallback(this);
+
+        assertTrue(stateChangedLatch.await(1, TimeUnit.SECONDS));
+        assertFalse(mSecurityController.hasCACertInCurrentUser());
+
+        // With a CA cert
+
+        stateChangedLatch = new CountDownLatch(1);
+
+        when(mKeyChainService.getUserCaAliases())
+                .thenReturn(new StringParceledListSlice(Arrays.asList("One CA Alias")));
+
+        mSecurityController.new CACertLoader()
+                           .execute(0);
+
+        assertTrue(stateChangedLatch.await(1, TimeUnit.SECONDS));
+        assertTrue(mSecurityController.hasCACertInCurrentUser());
+
+        // Exception
+
+        stateChangedLatch = new CountDownLatch(1);
+
+        when(mKeyChainService.getUserCaAliases())
+                .thenThrow(new AssertionError("Test AssertionError"))
+                .thenReturn(new StringParceledListSlice(new ArrayList<String>()));
+
+        mSecurityController.new CACertLoader()
+                           .execute(0);
+
+        assertFalse(stateChangedLatch.await(1, TimeUnit.SECONDS));
+        assertTrue(mSecurityController.hasCACertInCurrentUser());
+        // The retry takes 30s
+        //assertTrue(stateChangedLatch.await(31, TimeUnit.SECONDS));
+        //assertFalse(mSecurityController.hasCACertInCurrentUser());
+
+        mSecurityController.removeCallback(this);
+    }
 }