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;
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;
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;
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;
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());
@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
} 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);
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();
+ }
+ }
+ }
}
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.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
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 mStateChangedLatch;
+
+ // implementing SecurityControllerCallback
+ @Override
+ public void onStateChanged() {
+ mStateChangedLatch.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);
+
+ // Wait for one or two state changes from the CACertLoader(s) in the constructor of
+ // mSecurityController
+ mStateChangedLatch = new CountDownLatch(mSecurityController.hasWorkProfile() ? 2 : 1);
+ mSecurityController.addCallback(this);
+ }
+
+ @After
+ public void tearDown() {
+ mSecurityController.removeCallback(this);
}
@Test
when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn("organization");
assertEquals("organization", mSecurityController.getDeviceOwnerOrganizationName());
}
+
+ @Test
+ @Ignore("Flaky")
+ public void testCaCertLoader() throws Exception {
+ assertTrue(mStateChangedLatch.await(3, TimeUnit.SECONDS));
+ assertFalse(mSecurityController.hasCACertInCurrentUser());
+
+ // With a CA cert
+
+ mStateChangedLatch = new CountDownLatch(1);
+
+ when(mKeyChainService.getUserCaAliases())
+ .thenReturn(new StringParceledListSlice(Arrays.asList("One CA Alias")));
+
+ mSecurityController.new CACertLoader()
+ .execute(0);
+
+ assertTrue(mStateChangedLatch.await(3, TimeUnit.SECONDS));
+ assertTrue(mSecurityController.hasCACertInCurrentUser());
+
+ // Exception
+
+ mStateChangedLatch = new CountDownLatch(1);
+
+ when(mKeyChainService.getUserCaAliases())
+ .thenThrow(new AssertionError("Test AssertionError"))
+ .thenReturn(new StringParceledListSlice(new ArrayList<String>()));
+
+ mSecurityController.new CACertLoader()
+ .execute(0);
+
+ assertFalse(mStateChangedLatch.await(3, TimeUnit.SECONDS));
+ assertTrue(mSecurityController.hasCACertInCurrentUser());
+ // The retry takes 30s
+ //assertTrue(mStateChangedLatch.await(31, TimeUnit.SECONDS));
+ //assertFalse(mSecurityController.hasCACertInCurrentUser());
+ }
}