/* * 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.util.Base64; import com.android.keychain.internal.KeyInfoProvider; import com.google.common.collect.ImmutableList; 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; import java.io.ByteArrayInputStream; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.KeyStoreSpi; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.ArrayList; import java.util.Collections; 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; @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 mIssuers; private KeyStoreSpi mKeyStoreSpi; private KeyStore mKeyStore; private KeyInfoProvider mInfoProvider; private Certificate toCertificate(byte[] bytes) throws CertificateException { CertificateFactory cf = CertificateFactory.getInstance("X.509"); return cf.generateCertificate(new ByteArrayInputStream(bytes)); } private Certificate[] toCertificateChain(byte[] bytes) throws CertificateException { CertificateFactory cf = CertificateFactory.getInstance("X.509"); return new Certificate[]{cf.generateCertificate(new ByteArrayInputStream(bytes))}; } @Before public void setUp() throws Exception { 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(); 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); mKeyStoreSpi = mock(KeyStoreSpi.class); mKeyStore = new KeyStore(mKeyStoreSpi, null, "test") {}; mKeyStore.load(null); mInfoProvider = mock(KeyInfoProvider.class); } @Test public void testAliasLoader_loadsAllAliases() throws InterruptedException, ExecutionException, CancellationException, TimeoutException { prepareKeyStoreWithAliases(ImmutableList.of("b", "c", "a")); KeyChainActivity.AliasLoader loader = new KeyChainActivity.AliasLoader( mKeyStore, 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 { when(mKeyStoreSpi.engineAliases()).thenReturn(Collections.enumeration(ImmutableList.of())); KeyChainActivity.AliasLoader loader = new KeyChainActivity.AliasLoader( mKeyStore, 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 { prepareKeyStoreWithAliases(ImmutableList.of("b", "c", "a")); when(mInfoProvider.isUserSelectable("a")).thenReturn(false); when(mInfoProvider.isUserSelectable("b")).thenReturn(true); when(mInfoProvider.isUserSelectable("c")).thenReturn(false); KeyChainActivity.AliasLoader loader = new KeyChainActivity.AliasLoader( mKeyStore, RuntimeEnvironment.application, mInfoProvider, 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, KeyStoreException { prepareKeyStoreWithAliases(ImmutableList.of("a", "b", "c", "d")); when(mInfoProvider.isUserSelectable("a")).thenReturn(true); when(mInfoProvider.isUserSelectable("b")).thenReturn(true); when(mInfoProvider.isUserSelectable("c")).thenReturn(false); when(mInfoProvider.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( mKeyStore, RuntimeEnvironment.application, mInfoProvider, 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 void prepareKeyStoreWithAliases(ImmutableList aliases) { when(mKeyStoreSpi.engineAliases()).thenReturn(Collections.enumeration(aliases)); for (int i = 0; i < aliases.size(); i++) { when(mKeyStoreSpi.engineIsKeyEntry(aliases.get(i))).thenReturn(true); } } private void prepareKeyStoreWithCertificates() throws CertificateException { when(mKeyStoreSpi.engineGetCertificate("rsa1")).thenReturn(toCertificate(mRSACertOne)); when(mKeyStoreSpi.engineGetCertificate("ec1")).thenReturn(toCertificate(mECCertOne)); when(mKeyStoreSpi.engineGetCertificate("rsa2")).thenReturn(toCertificate(mRSACertTwo)); when(mKeyStoreSpi.engineGetCertificate("ec2")).thenReturn(toCertificate(mECCertTwo)); when(mKeyStoreSpi.engineGetCertificateChain("rsa1")) .thenReturn(toCertificateChain(mRSACertOne)); when(mKeyStoreSpi.engineGetCertificateChain("ec1")) .thenReturn(toCertificateChain(mECCertOne)); when(mKeyStoreSpi.engineGetCertificateChain("rsa2")) .thenReturn(toCertificateChain(mRSACertTwo)); when(mKeyStoreSpi.engineGetCertificateChain("ec2")) .thenReturn(toCertificateChain(mECCertTwo)); } @Test public void testCertificateParametersFilter_filtersByKey() throws CancellationException, CertificateException { prepareKeyStoreWithCertificates(); KeyChainActivity.CertificateParametersFilter ec_checker = new KeyChainActivity.CertificateParametersFilter( mKeyStore, new String[] {"EC"}, new ArrayList()); Assert.assertFalse(ec_checker.shouldPresentCertificate("rsa1")); Assert.assertTrue(ec_checker.shouldPresentCertificate("ec1")); KeyChainActivity.CertificateParametersFilter rsa_and_ec_checker = new KeyChainActivity.CertificateParametersFilter( mKeyStore, new String[] {"EC", "RSA"}, new ArrayList()); Assert.assertTrue(rsa_and_ec_checker.shouldPresentCertificate("rsa1")); Assert.assertTrue(rsa_and_ec_checker.shouldPresentCertificate("ec1")); } @Test public void testCertificateParametersFilter_filtersByIssuer() throws CancellationException, CertificateException { prepareKeyStoreWithCertificates(); KeyChainActivity.CertificateParametersFilter issuer_checker = new KeyChainActivity.CertificateParametersFilter( mKeyStore, 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 CancellationException, CertificateException { prepareKeyStoreWithCertificates(); KeyChainActivity.CertificateParametersFilter issuer_checker = new KeyChainActivity.CertificateParametersFilter( mKeyStore, 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")); } }