Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2313a0d3 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement CACert queries in SecurityController"

parents 0a143858 e375fc44
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -599,7 +599,7 @@ public final class KeyChain {
        private final Context context;
        private final ServiceConnection serviceConnection;
        private final IKeyChainService service;
        private KeyChainConnection(Context context,
        protected KeyChainConnection(Context context,
                                     ServiceConnection serviceConnection,
                                     IKeyChainService service) {
            this.context = context;
+69 −4
Original line number Diff line number Diff line
@@ -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();
            }
        }
    }
}
+77 −1
Original line number Diff line number Diff line
@@ -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);
    }
}