blob: 78c6c856dd36cabc4c0b322eb3e1edf7d907ae22 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.keychain;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.security.Credentials;
import android.security.KeyStore;
import android.util.Base64;
import com.android.keychain.internal.KeyInfoProvider;
import java.util.ArrayList;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.security.auth.x500.X500Principal;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowApplication;
@RunWith(RobolectricTestRunner.class)
public final class AliasLoaderTest {
// Generated using:
// openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 3650 -out certificate.pem
private static final String SELF_SIGNED_RSA_CERT_1_B64 =
"MIIDlDCCAnygAwIBAgIJAJsWcaXZlx7GMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV\n"
+ "BAYTAlVLMQ8wDQYDVQQIDAZMb25kb24xGzAZBgNVBAoMEkFPU1AgVGVzdCBkYXRh\n"
+ "IG9uZTEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDAeFw0xODA4\n"
+ "MjMxNjAwNTFaFw0yODA4MjAxNjAwNTFaMF8xCzAJBgNVBAYTAlVLMQ8wDQYDVQQI\n"
+ "DAZMb25kb24xGzAZBgNVBAoMEkFPU1AgVGVzdCBkYXRhIG9uZTEQMA4GA1UECwwH\n"
+ "QW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDCCASIwDQYJKoZIhvcNAQEBBQADggEP\n"
+ "ADCCAQoCggEBAMgyezTnRdmITmxXQNgG4UmCdvAaOQ7H+iB6wHfgT9iajoiGF9I9\n"
+ "Efdx6QnnM6S3N4BD5MGb9IPvF79aXJlWgd9Q+l1vOG0bcpB9KVDrui1IjNW/R+X3\n"
+ "0VKg2xa5+6kYTXnlI5GZF2pG8GCuoubsFkbfMTpmonAOdKDsfPLVSKbWoaNsFtli\n"
+ "zXIpJDpK+QHY9yMvJ7lBme3f8OVOBC2OzetCUScTWl1Q9JqFHNgluk2in8mfwban\n"
+ "DE7fdXGrnUNuJ31h5SBjLMAoaLTjxL9Vn0W3wiB/G8lVAgOJs+wJ5G7PVa/A7ZGB\n"
+ "RGNySfpWs1yrpSUIxx4p9Gh3SVJ+WAceaH0CAwEAAaNTMFEwHQYDVR0OBBYEFJdE\n"
+ "O9ssB/FgSEzvgrkLbthzJ4QpMB8GA1UdIwQYMBaAFJdEO9ssB/FgSEzvgrkLbthz\n"
+ "J4QpMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAB//kA875v/7\n"
+ "pOYFtacYYd6pU4JFhCnIpKTuc8ee3C1pJd9hScI1P5tiM8dnRscmTT5puE62OKE/\n"
+ "XdlnCkBm9oAD2xXwK+W2fTFVidLHrjSdihTmyUbjfNOm5SS3Z6S9OmPPb4Ei4WXh\n"
+ "qqCrk3OQ00A6agfTy0qzW1wQT9DE1uyLAZ1jdTMD2wNwzQP7IzoTx+ay985eRC1V\n"
+ "pquK9kOHhnhGn/kSrZgpQB1rUmpm+IrdpwkIUIdnMyiuIrQa40D+bKRmOWpNKUH9\n"
+ "4MCeitS4W9LfQyDj3hktD5hf4hxRIb185gN7v/Uf2Ft87rnFdtR1xum4JDagosOv\n"
+ "vvF/HE7ofuw=\n";
private static final String SELF_SIGNED_RSA_CERT_2_B64 =
"MIIDlDCCAnygAwIBAgIJAL4ZhppdcG5IMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV\n"
+ "BAYTAlVLMQ8wDQYDVQQIDAZMb25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRh\n"
+ "IHR3bzEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDAeFw0xODA4\n"
+ "MjMxNjA2NDhaFw0yODA4MjAxNjA2NDhaMF8xCzAJBgNVBAYTAlVLMQ8wDQYDVQQI\n"
+ "DAZMb25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRhIHR3bzEQMA4GA1UECwwH\n"
+ "QW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDCCASIwDQYJKoZIhvcNAQEBBQADggEP\n"
+ "ADCCAQoCggEBAMn7UvVsQquyotNNt9B/JCa84jcPfIV3RBDSYvcTrr1KyVIIJSmo\n"
+ "JnUQYt6yRN9HjOOckuOSRAtzumqYuW1tpMDgDlORImwvX2pwQfJLT8dErgAaNGYu\n"
+ "xjIUJ1Dwusuw891F/nFvACHOPWfgpcz4WJo1SQCUIObeuENebUurDnyYCMOD0+t3\n"
+ "e4POaE6pF4VqjoDHX8slexonzkxZ3e7V2zrgpRBx+TUHq69GexpVj5frUSxjEllf\n"
+ "58hNwwbPArpW73102wkqb7bCqeJ9f7fSY01hmhgIRn1uqy6jPfq9HqaXF+QhGVkT\n"
+ "Oeoauu1o3/oTUmtbkifQvVGhsW/VX5v9hl0CAwEAAaNTMFEwHQYDVR0OBBYEFAO6\n"
+ "1Tw6JddbJZI+iV0hIyRLOCGBMB8GA1UdIwQYMBaAFAO61Tw6JddbJZI+iV0hIyRL\n"
+ "OCGBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAIQQEOvTqAn\n"
+ "EpdZmrwQIBRvWT8pYyf4168CXEFDTXj5ODQY3IZ2hCOpIhJHPGc8RTVPNL0/LwIH\n"
+ "fncgU+uLYL+CUH1DjqZK+99QvcxJkIi6+lQQynlCT4OXpo4AyLl4U4vpjKO9QUdi\n"
+ "TiFXDMgM0iYyZU88ABgGYDsNUZuL+zj3rABszzeoxyQDjVrfUZQ1SnPYnN9wLlPj\n"
+ "fFdyRBpJyZPOABXsJlB6AO3a5Erk6DBB3kj1EmN1b/oYqWgNBg2pBBW7fhxx8MVj\n"
+ "q1bnYwvwp2Iq/oHoGbr80ZNl97i8YQiXEHG0N9Y+PzaTJ12bREA+0Q6ZBfe9V9ya\n"
+ "IgPhDc8Rb0g=\n";
// Generated with:
// openssl req -newkey ec:<(openssl ecparam -name secp256k1) -nodes -keyout key.pem -x509 \
// -days 3650 -out certificate.pem
private static final String SELF_SIGNED_EC_CERT_1_B64 =
"MIICBDCCAaugAwIBAgIJAIOxj+lhuGDBMAoGCCqGSM49BAMCMF8xCzAJBgNVBAYT\n"
+ "AlVLMQ8wDQYDVQQIDAZMb25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRhIG9u\n"
+ "ZTEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDAeFw0xODA4MjMx\n"
+ "NjEzMzJaFw0yODA4MjAxNjEzMzJaMF8xCzAJBgNVBAYTAlVLMQ8wDQYDVQQIDAZM\n"
+ "b25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRhIG9uZTEQMA4GA1UECwwHQW5k\n"
+ "cm9pZDEQMA4GA1UEAwwHYW5kcm9pZDBWMBAGByqGSM49AgEGBSuBBAAKA0IABJ/K\n"
+ "6Z1d4T+LdDKdl+QkiLs/oJ0fBQmVezo4H0tY7EOugsydZGaem0CyEtZX/0Nki4To\n"
+ "XvgUB2jFGRERYPDkM/WjUzBRMB0GA1UdDgQWBBTsJksMH345+fGhJYmyiR9xJEXt\n"
+ "MDAfBgNVHSMEGDAWgBTsJksMH345+fGhJYmyiR9xJEXtMDAPBgNVHRMBAf8EBTAD\n"
+ "AQH/MAoGCCqGSM49BAMCA0cAMEQCIFDrt1eB11O/lD4CHAaaZQ82WvoXgJC89rol\n"
+ "EuDcG/j3AiBJ80KVSTmim6k6RWEMIHP78mCLnpwKnwVAk9On5xkJ0Q==";
private static final String SELF_SIGNED_EC_CERT_2_B64 =
"MIICBDCCAaugAwIBAgIJAMDRz9Ey2tIPMAoGCCqGSM49BAMCMF8xCzAJBgNVBAYT\n"
+ "AlVLMQ8wDQYDVQQIDAZMb25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRhIHR3\n"
+ "bzEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDAeFw0xODA4MjMx\n"
+ "NjE0MDdaFw0yODA4MjAxNjE0MDdaMF8xCzAJBgNVBAYTAlVLMQ8wDQYDVQQIDAZM\n"
+ "b25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRhIHR3bzEQMA4GA1UECwwHQW5k\n"
+ "cm9pZDEQMA4GA1UEAwwHYW5kcm9pZDBWMBAGByqGSM49AgEGBSuBBAAKA0IABA7p\n"
+ "osQCNiI+RBy29ydpFGBPypbIy8U3Ylgujo8k4B20evWj5CKYP9Cw0gCypwBB9uYM\n"
+ "706diiK6rFbKXFhhcUGjUzBRMB0GA1UdDgQWBBSYWWkKZqiiNXC75cZHoIpRwMMd\n"
+ "7zAfBgNVHSMEGDAWgBSYWWkKZqiiNXC75cZHoIpRwMMd7zAPBgNVHRMBAf8EBTAD\n"
+ "AQH/MAoGCCqGSM49BAMCA0cAMEQCIFh7LgrBMMMSqAF8PdWy+bV8jUuQqwOQ34Mo\n"
+ "MtghI6eYAiAOuAXmRZiwVjnB9rH3f2Vy3rbMgfD3/AYzREqVnuZD0Q==";
private static final X500Principal ISSUER_ONE =
new X500Principal("CN=android, OU=Android, O=AOSP test data one, ST=London, C=UK");
private KeyInfoProvider mDummyInfoProvider;
private KeyChainActivity.CertificateParametersFilter mDummyChecker;
private byte[] mRSACertOne;
private byte[] mRSACertTwo;
private byte[] mECCertOne;
private byte[] mECCertTwo;
private ArrayList<byte[]> mIssuers;
@Before
public void setUp() {
mRSACertOne = Base64.decode(SELF_SIGNED_RSA_CERT_1_B64, Base64.DEFAULT);
mRSACertTwo = Base64.decode(SELF_SIGNED_RSA_CERT_2_B64, Base64.DEFAULT);
mECCertOne = Base64.decode(SELF_SIGNED_EC_CERT_1_B64, Base64.DEFAULT);
mECCertTwo = Base64.decode(SELF_SIGNED_EC_CERT_2_B64, Base64.DEFAULT);
mIssuers = new ArrayList<byte[]>();
mIssuers.add(ISSUER_ONE.getEncoded());
mDummyInfoProvider =
new KeyInfoProvider() {
public boolean isUserSelectable(String alias) {
return true;
}
};
mDummyChecker = mock(KeyChainActivity.CertificateParametersFilter.class);
when(mDummyChecker.shouldPresentCertificate(Mockito.anyString())).thenReturn(true);
}
@Test
public void testAliasLoader_loadsAllAliases()
throws InterruptedException, ExecutionException, CancellationException,
TimeoutException {
KeyStore keyStore = mock(KeyStore.class);
when(keyStore.list(Credentials.USER_PRIVATE_KEY)).thenReturn(new String[] {"b", "c", "a"});
KeyChainActivity.AliasLoader loader =
new KeyChainActivity.AliasLoader(
keyStore,
RuntimeEnvironment.application,
mDummyInfoProvider,
mDummyChecker);
loader.execute();
ShadowApplication.runBackgroundTasks();
KeyChainActivity.CertificateAdapter result = loader.get(5, TimeUnit.SECONDS);
Assert.assertNotNull(result);
Assert.assertEquals(3, result.getCount());
Assert.assertEquals("a", result.getItem(0));
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,
mDummyChecker);
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, mDummyChecker);
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));
}
@Test
public void testAliasLoader_filtersAliasesWithNonConformingParameters()
throws InterruptedException, ExecutionException, CancellationException,
TimeoutException {
KeyStore keyStore = mock(KeyStore.class);
when(keyStore.list(Credentials.USER_PRIVATE_KEY))
.thenReturn(new String[] {"a", "b", "c", "d"});
KeyInfoProvider infoProvider = mock(KeyInfoProvider.class);
when(infoProvider.isUserSelectable("a")).thenReturn(true);
when(infoProvider.isUserSelectable("b")).thenReturn(true);
when(infoProvider.isUserSelectable("c")).thenReturn(false);
when(infoProvider.isUserSelectable("d")).thenReturn(false);
KeyChainActivity.CertificateParametersFilter checker =
mock(KeyChainActivity.CertificateParametersFilter.class);
// The first alias is user-selectable and should be presented.
when(checker.shouldPresentCertificate("a")).thenReturn(true);
// The second alias is user-selectable but should not be presented.
when(checker.shouldPresentCertificate("b")).thenReturn(false);
// The third alias is not user-selectable but should be presented.
when(checker.shouldPresentCertificate("c")).thenReturn(true);
// The fourth alias is not user-selectable and should not be presented.
when(checker.shouldPresentCertificate("d")).thenReturn(false);
KeyChainActivity.AliasLoader loader =
new KeyChainActivity.AliasLoader(
keyStore, RuntimeEnvironment.application, infoProvider, checker);
loader.execute();
ShadowApplication.runBackgroundTasks();
KeyChainActivity.CertificateAdapter result = loader.get(5, TimeUnit.SECONDS);
Assert.assertNotNull(result);
Assert.assertEquals(1, result.getCount());
Assert.assertEquals("a", result.getItem(0));
}
private KeyStore prepareKeyStoreWithCertificates() {
KeyStore keyStore = mock(KeyStore.class);
when(keyStore.get(Credentials.USER_CERTIFICATE + "rsa1")).thenReturn(mRSACertOne);
when(keyStore.get(Credentials.USER_CERTIFICATE + "ec1")).thenReturn(mECCertOne);
when(keyStore.get(Credentials.USER_CERTIFICATE + "rsa2")).thenReturn(mRSACertTwo);
when(keyStore.get(Credentials.USER_CERTIFICATE + "ec2")).thenReturn(mECCertTwo);
return keyStore;
}
@Test
public void testCertificateParametersFilter_filtersByKey() throws CancellationException {
KeyStore keyStore = prepareKeyStoreWithCertificates();
KeyChainActivity.CertificateParametersFilter ec_checker =
new KeyChainActivity.CertificateParametersFilter(
keyStore, new String[] {"EC"}, new ArrayList<byte[]>());
Assert.assertFalse(ec_checker.shouldPresentCertificate("rsa1"));
Assert.assertTrue(ec_checker.shouldPresentCertificate("ec1"));
KeyChainActivity.CertificateParametersFilter rsa_and_ec_checker =
new KeyChainActivity.CertificateParametersFilter(
keyStore, new String[] {"EC", "RSA"}, new ArrayList<byte[]>());
Assert.assertTrue(rsa_and_ec_checker.shouldPresentCertificate("rsa1"));
Assert.assertTrue(rsa_and_ec_checker.shouldPresentCertificate("ec1"));
}
@Test
public void testCertificateParametersFilter_filtersByIssuer() throws CancellationException {
KeyStore keyStore = prepareKeyStoreWithCertificates();
KeyChainActivity.CertificateParametersFilter issuer_checker =
new KeyChainActivity.CertificateParametersFilter(
keyStore, new String[] {}, mIssuers);
Assert.assertTrue(issuer_checker.shouldPresentCertificate("rsa1"));
Assert.assertTrue(issuer_checker.shouldPresentCertificate("ec1"));
Assert.assertFalse(issuer_checker.shouldPresentCertificate("rsa2"));
Assert.assertFalse(issuer_checker.shouldPresentCertificate("ec2"));
}
@Test
public void testCertificateParametersFilter_filtersByIssuerAndKey()
throws InterruptedException, ExecutionException, CancellationException,
TimeoutException {
KeyStore keyStore = prepareKeyStoreWithCertificates();
KeyChainActivity.CertificateParametersFilter issuer_checker =
new KeyChainActivity.CertificateParametersFilter(
keyStore, new String[] {"EC"}, mIssuers);
Assert.assertFalse(issuer_checker.shouldPresentCertificate("rsa1"));
Assert.assertTrue(issuer_checker.shouldPresentCertificate("ec1"));
Assert.assertFalse(issuer_checker.shouldPresentCertificate("rsa2"));
Assert.assertFalse(issuer_checker.shouldPresentCertificate("ec2"));
}
}