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:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user