KeyChain: Start supporting user-selectability of keys.
This change makes the AliasLoader filter out aliases for keys that are
not user-selectable.
This is the beginning of the work to enable controlling access to
DPC-installed keys in KeyChain.
Bug: 65624467
Test: New unit tests.
Change-Id: Ie829bfdbd31c4738702bef661d00064c691143c7
diff --git a/robotests/src/com/android/keychain/AliasLoaderTest.java b/robotests/src/com/android/keychain/AliasLoaderTest.java
index 1c4c50c..f2a05c8 100644
--- a/robotests/src/com/android/keychain/AliasLoaderTest.java
+++ b/robotests/src/com/android/keychain/AliasLoaderTest.java
@@ -21,6 +21,7 @@
import android.security.Credentials;
import android.security.KeyStore;
+import com.android.keychain.internal.KeyInfoProvider;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -37,9 +38,17 @@
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class AliasLoaderTest {
+ private KeyInfoProvider mDummyInfoProvider;
@Before
- public void setUp() {}
+ public void setUp() {
+ mDummyInfoProvider =
+ new KeyInfoProvider() {
+ public boolean isUserSelectable(String alias) {
+ return true;
+ }
+ };
+ }
@Test
public void testAliasLoader_loadsAllAliases()
@@ -49,7 +58,8 @@
when(keyStore.list(Credentials.USER_PRIVATE_KEY)).thenReturn(new String[] {"b", "c", "a"});
KeyChainActivity.AliasLoader loader =
- new KeyChainActivity.AliasLoader(keyStore, RuntimeEnvironment.application);
+ new KeyChainActivity.AliasLoader(
+ keyStore, RuntimeEnvironment.application, mDummyInfoProvider);
loader.execute();
ShadowApplication.runBackgroundTasks();
@@ -60,4 +70,45 @@
Assert.assertEquals("b", result.getItem(1));
Assert.assertEquals("c", result.getItem(2));
}
+
+ @Test
+ public void testAliasLoader_copesWithNoAliases()
+ throws InterruptedException, ExecutionException, CancellationException,
+ TimeoutException {
+ KeyStore keyStore = mock(KeyStore.class);
+ when(keyStore.list(Credentials.USER_PRIVATE_KEY)).thenReturn(null);
+
+ KeyChainActivity.AliasLoader loader =
+ new KeyChainActivity.AliasLoader(
+ keyStore, RuntimeEnvironment.application, mDummyInfoProvider);
+ loader.execute();
+
+ ShadowApplication.runBackgroundTasks();
+ KeyChainActivity.CertificateAdapter result = loader.get(5, TimeUnit.SECONDS);
+ Assert.assertNotNull(result);
+ Assert.assertEquals(0, result.getCount());
+ }
+
+ @Test
+ public void testAliasLoader_filtersNonUserSelectableAliases()
+ throws InterruptedException, ExecutionException, CancellationException,
+ TimeoutException {
+ KeyStore keyStore = mock(KeyStore.class);
+ when(keyStore.list(Credentials.USER_PRIVATE_KEY)).thenReturn(new String[] {"a", "b", "c"});
+ KeyInfoProvider infoProvider = mock(KeyInfoProvider.class);
+ when(infoProvider.isUserSelectable("a")).thenReturn(false);
+ when(infoProvider.isUserSelectable("b")).thenReturn(true);
+ when(infoProvider.isUserSelectable("c")).thenReturn(false);
+
+ KeyChainActivity.AliasLoader loader =
+ new KeyChainActivity.AliasLoader(
+ keyStore, RuntimeEnvironment.application, infoProvider);
+ loader.execute();
+
+ ShadowApplication.runBackgroundTasks();
+ KeyChainActivity.CertificateAdapter result = loader.get(5, TimeUnit.SECONDS);
+ Assert.assertNotNull(result);
+ Assert.assertEquals(1, result.getCount());
+ Assert.assertEquals("b", result.getItem(0));
+ }
}
diff --git a/src/com/android/keychain/KeyChainActivity.java b/src/com/android/keychain/KeyChainActivity.java
index ed8cd8c..79ecc79 100644
--- a/src/com/android/keychain/KeyChainActivity.java
+++ b/src/com/android/keychain/KeyChainActivity.java
@@ -47,6 +47,7 @@
import android.widget.RadioButton;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.keychain.internal.KeyInfoProvider;
import com.android.org.bouncycastle.asn1.x509.X509Name;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@@ -58,6 +59,7 @@
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.List;
+import java.util.stream.Collectors;
import javax.security.auth.x500.X500Principal;
@@ -145,7 +147,8 @@
private void chooseCertificate() {
// Start loading the set of certs to choose from now- if device policy doesn't return an
// alias, having aliases loading already will save some time waiting for UI to start.
- final AliasLoader loader = new AliasLoader(mKeyStore, this);
+ // TODO: Once KeyChainService supports this interface, replace with that instance.
+ final AliasLoader loader = new AliasLoader(mKeyStore, this, (alias) -> { return true; });
loader.execute();
final IKeyChainAliasCallback.Stub callback = new IKeyChainAliasCallback.Stub() {
@@ -197,18 +200,22 @@
static class AliasLoader extends AsyncTask<Void, Void, CertificateAdapter> {
private final KeyStore mKeyStore;
private final Context mContext;
- public AliasLoader(KeyStore keyStore, Context context) {
+ private final KeyInfoProvider mInfoProvider;
+
+ public AliasLoader(KeyStore keyStore, Context context, KeyInfoProvider infoProvider) {
mKeyStore = keyStore;
mContext = context;
+ mInfoProvider = infoProvider;
}
@Override protected CertificateAdapter doInBackground(Void... params) {
String[] aliasArray = mKeyStore.list(Credentials.USER_PRIVATE_KEY);
- List<String> aliasList = ((aliasArray == null)
+ List<String> rawAliasList = ((aliasArray == null)
? Collections.<String>emptyList()
: Arrays.asList(aliasArray));
- Collections.sort(aliasList);
- return new CertificateAdapter(mKeyStore, mContext, aliasList);
+ return new CertificateAdapter(mKeyStore, mContext,
+ rawAliasList.stream().filter(mInfoProvider::isUserSelectable).sorted()
+ .collect(Collectors.toList()));
}
}
diff --git a/src/com/android/keychain/internal/KeyInfoProvider.java b/src/com/android/keychain/internal/KeyInfoProvider.java
new file mode 100644
index 0000000..c5d1e30
--- /dev/null
+++ b/src/com/android/keychain/internal/KeyInfoProvider.java
@@ -0,0 +1,14 @@
+package com.android.keychain.internal;
+
+/** Interface for classes that provide information about keys in KeyChain. */
+
+public interface KeyInfoProvider {
+ /**
+ * Indicates whether a key associated with the given alias is allowed
+ * to be selected by users.
+ * @param alias Alias of the key to check.
+ *
+ * @return true if the provided alias is selectable by users.
+ */
+ public boolean isUserSelectable(String alias);
+}