Implement CACert queries in SecurityController

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
This commit is contained in:
phweiss
2017-04-19 20:15:06 +02:00
parent 83abc4f26d
commit e375fc441c
3 changed files with 149 additions and 8 deletions

View 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;

View 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();
}
}
}
}

View 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);
}
}