• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.keychain;
18 
19 import static org.mockito.Mockito.mock;
20 import static org.mockito.Mockito.when;
21 
22 import android.util.Base64;
23 
24 import com.android.keychain.internal.KeyInfoProvider;
25 
26 import com.google.common.collect.ImmutableList;
27 
28 import org.junit.Assert;
29 import org.junit.Before;
30 import org.junit.Test;
31 import org.junit.runner.RunWith;
32 import org.mockito.Mockito;
33 import org.robolectric.RobolectricTestRunner;
34 import org.robolectric.RuntimeEnvironment;
35 import org.robolectric.shadows.ShadowApplication;
36 
37 import java.io.ByteArrayInputStream;
38 import java.security.KeyStore;
39 import java.security.KeyStoreException;
40 import java.security.KeyStoreSpi;
41 import java.security.cert.Certificate;
42 import java.security.cert.CertificateException;
43 import java.security.cert.CertificateFactory;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.concurrent.CancellationException;
47 import java.util.concurrent.ExecutionException;
48 import java.util.concurrent.TimeUnit;
49 import java.util.concurrent.TimeoutException;
50 
51 import javax.security.auth.x500.X500Principal;
52 
53 @RunWith(RobolectricTestRunner.class)
54 public final class AliasLoaderTest {
55     // Generated using:
56     // openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 3650 -out certificate.pem
57     private static final String SELF_SIGNED_RSA_CERT_1_B64 =
58             "MIIDlDCCAnygAwIBAgIJAJsWcaXZlx7GMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV\n"
59                     + "BAYTAlVLMQ8wDQYDVQQIDAZMb25kb24xGzAZBgNVBAoMEkFPU1AgVGVzdCBkYXRh\n"
60                     + "IG9uZTEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDAeFw0xODA4\n"
61                     + "MjMxNjAwNTFaFw0yODA4MjAxNjAwNTFaMF8xCzAJBgNVBAYTAlVLMQ8wDQYDVQQI\n"
62                     + "DAZMb25kb24xGzAZBgNVBAoMEkFPU1AgVGVzdCBkYXRhIG9uZTEQMA4GA1UECwwH\n"
63                     + "QW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDCCASIwDQYJKoZIhvcNAQEBBQADggEP\n"
64                     + "ADCCAQoCggEBAMgyezTnRdmITmxXQNgG4UmCdvAaOQ7H+iB6wHfgT9iajoiGF9I9\n"
65                     + "Efdx6QnnM6S3N4BD5MGb9IPvF79aXJlWgd9Q+l1vOG0bcpB9KVDrui1IjNW/R+X3\n"
66                     + "0VKg2xa5+6kYTXnlI5GZF2pG8GCuoubsFkbfMTpmonAOdKDsfPLVSKbWoaNsFtli\n"
67                     + "zXIpJDpK+QHY9yMvJ7lBme3f8OVOBC2OzetCUScTWl1Q9JqFHNgluk2in8mfwban\n"
68                     + "DE7fdXGrnUNuJ31h5SBjLMAoaLTjxL9Vn0W3wiB/G8lVAgOJs+wJ5G7PVa/A7ZGB\n"
69                     + "RGNySfpWs1yrpSUIxx4p9Gh3SVJ+WAceaH0CAwEAAaNTMFEwHQYDVR0OBBYEFJdE\n"
70                     + "O9ssB/FgSEzvgrkLbthzJ4QpMB8GA1UdIwQYMBaAFJdEO9ssB/FgSEzvgrkLbthz\n"
71                     + "J4QpMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAB//kA875v/7\n"
72                     + "pOYFtacYYd6pU4JFhCnIpKTuc8ee3C1pJd9hScI1P5tiM8dnRscmTT5puE62OKE/\n"
73                     + "XdlnCkBm9oAD2xXwK+W2fTFVidLHrjSdihTmyUbjfNOm5SS3Z6S9OmPPb4Ei4WXh\n"
74                     + "qqCrk3OQ00A6agfTy0qzW1wQT9DE1uyLAZ1jdTMD2wNwzQP7IzoTx+ay985eRC1V\n"
75                     + "pquK9kOHhnhGn/kSrZgpQB1rUmpm+IrdpwkIUIdnMyiuIrQa40D+bKRmOWpNKUH9\n"
76                     + "4MCeitS4W9LfQyDj3hktD5hf4hxRIb185gN7v/Uf2Ft87rnFdtR1xum4JDagosOv\n"
77                     + "vvF/HE7ofuw=\n";
78 
79     private static final String SELF_SIGNED_RSA_CERT_2_B64 =
80             "MIIDlDCCAnygAwIBAgIJAL4ZhppdcG5IMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV\n"
81                     + "BAYTAlVLMQ8wDQYDVQQIDAZMb25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRh\n"
82                     + "IHR3bzEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDAeFw0xODA4\n"
83                     + "MjMxNjA2NDhaFw0yODA4MjAxNjA2NDhaMF8xCzAJBgNVBAYTAlVLMQ8wDQYDVQQI\n"
84                     + "DAZMb25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRhIHR3bzEQMA4GA1UECwwH\n"
85                     + "QW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDCCASIwDQYJKoZIhvcNAQEBBQADggEP\n"
86                     + "ADCCAQoCggEBAMn7UvVsQquyotNNt9B/JCa84jcPfIV3RBDSYvcTrr1KyVIIJSmo\n"
87                     + "JnUQYt6yRN9HjOOckuOSRAtzumqYuW1tpMDgDlORImwvX2pwQfJLT8dErgAaNGYu\n"
88                     + "xjIUJ1Dwusuw891F/nFvACHOPWfgpcz4WJo1SQCUIObeuENebUurDnyYCMOD0+t3\n"
89                     + "e4POaE6pF4VqjoDHX8slexonzkxZ3e7V2zrgpRBx+TUHq69GexpVj5frUSxjEllf\n"
90                     + "58hNwwbPArpW73102wkqb7bCqeJ9f7fSY01hmhgIRn1uqy6jPfq9HqaXF+QhGVkT\n"
91                     + "Oeoauu1o3/oTUmtbkifQvVGhsW/VX5v9hl0CAwEAAaNTMFEwHQYDVR0OBBYEFAO6\n"
92                     + "1Tw6JddbJZI+iV0hIyRLOCGBMB8GA1UdIwQYMBaAFAO61Tw6JddbJZI+iV0hIyRL\n"
93                     + "OCGBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAIQQEOvTqAn\n"
94                     + "EpdZmrwQIBRvWT8pYyf4168CXEFDTXj5ODQY3IZ2hCOpIhJHPGc8RTVPNL0/LwIH\n"
95                     + "fncgU+uLYL+CUH1DjqZK+99QvcxJkIi6+lQQynlCT4OXpo4AyLl4U4vpjKO9QUdi\n"
96                     + "TiFXDMgM0iYyZU88ABgGYDsNUZuL+zj3rABszzeoxyQDjVrfUZQ1SnPYnN9wLlPj\n"
97                     + "fFdyRBpJyZPOABXsJlB6AO3a5Erk6DBB3kj1EmN1b/oYqWgNBg2pBBW7fhxx8MVj\n"
98                     + "q1bnYwvwp2Iq/oHoGbr80ZNl97i8YQiXEHG0N9Y+PzaTJ12bREA+0Q6ZBfe9V9ya\n"
99                     + "IgPhDc8Rb0g=\n";
100 
101     // Generated with:
102     // openssl req -newkey ec:<(openssl ecparam -name secp256k1) -nodes -keyout key.pem -x509 \
103     //  -days 3650 -out certificate.pem
104     private static final String SELF_SIGNED_EC_CERT_1_B64 =
105             "MIICBDCCAaugAwIBAgIJAIOxj+lhuGDBMAoGCCqGSM49BAMCMF8xCzAJBgNVBAYT\n"
106                     + "AlVLMQ8wDQYDVQQIDAZMb25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRhIG9u\n"
107                     + "ZTEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDAeFw0xODA4MjMx\n"
108                     + "NjEzMzJaFw0yODA4MjAxNjEzMzJaMF8xCzAJBgNVBAYTAlVLMQ8wDQYDVQQIDAZM\n"
109                     + "b25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRhIG9uZTEQMA4GA1UECwwHQW5k\n"
110                     + "cm9pZDEQMA4GA1UEAwwHYW5kcm9pZDBWMBAGByqGSM49AgEGBSuBBAAKA0IABJ/K\n"
111                     + "6Z1d4T+LdDKdl+QkiLs/oJ0fBQmVezo4H0tY7EOugsydZGaem0CyEtZX/0Nki4To\n"
112                     + "XvgUB2jFGRERYPDkM/WjUzBRMB0GA1UdDgQWBBTsJksMH345+fGhJYmyiR9xJEXt\n"
113                     + "MDAfBgNVHSMEGDAWgBTsJksMH345+fGhJYmyiR9xJEXtMDAPBgNVHRMBAf8EBTAD\n"
114                     + "AQH/MAoGCCqGSM49BAMCA0cAMEQCIFDrt1eB11O/lD4CHAaaZQ82WvoXgJC89rol\n"
115                     + "EuDcG/j3AiBJ80KVSTmim6k6RWEMIHP78mCLnpwKnwVAk9On5xkJ0Q==";
116 
117     private static final String SELF_SIGNED_EC_CERT_2_B64 =
118             "MIICBDCCAaugAwIBAgIJAMDRz9Ey2tIPMAoGCCqGSM49BAMCMF8xCzAJBgNVBAYT\n"
119                     + "AlVLMQ8wDQYDVQQIDAZMb25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRhIHR3\n"
120                     + "bzEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHYW5kcm9pZDAeFw0xODA4MjMx\n"
121                     + "NjE0MDdaFw0yODA4MjAxNjE0MDdaMF8xCzAJBgNVBAYTAlVLMQ8wDQYDVQQIDAZM\n"
122                     + "b25kb24xGzAZBgNVBAoMEkFPU1AgdGVzdCBkYXRhIHR3bzEQMA4GA1UECwwHQW5k\n"
123                     + "cm9pZDEQMA4GA1UEAwwHYW5kcm9pZDBWMBAGByqGSM49AgEGBSuBBAAKA0IABA7p\n"
124                     + "osQCNiI+RBy29ydpFGBPypbIy8U3Ylgujo8k4B20evWj5CKYP9Cw0gCypwBB9uYM\n"
125                     + "706diiK6rFbKXFhhcUGjUzBRMB0GA1UdDgQWBBSYWWkKZqiiNXC75cZHoIpRwMMd\n"
126                     + "7zAfBgNVHSMEGDAWgBSYWWkKZqiiNXC75cZHoIpRwMMd7zAPBgNVHRMBAf8EBTAD\n"
127                     + "AQH/MAoGCCqGSM49BAMCA0cAMEQCIFh7LgrBMMMSqAF8PdWy+bV8jUuQqwOQ34Mo\n"
128                     + "MtghI6eYAiAOuAXmRZiwVjnB9rH3f2Vy3rbMgfD3/AYzREqVnuZD0Q==";
129 
130     private static final X500Principal ISSUER_ONE =
131             new X500Principal("CN=android, OU=Android, O=AOSP test data one, ST=London, C=UK");
132 
133     private KeyInfoProvider mDummyInfoProvider;
134     private KeyChainActivity.CertificateParametersFilter mDummyChecker;
135     private byte[] mRSACertOne;
136     private byte[] mRSACertTwo;
137     private byte[] mECCertOne;
138     private byte[] mECCertTwo;
139     private ArrayList<byte[]> mIssuers;
140     private KeyStoreSpi mKeyStoreSpi;
141     private KeyStore mKeyStore;
142     private KeyInfoProvider mInfoProvider;
143 
toCertificate(byte[] bytes)144     private Certificate toCertificate(byte[] bytes) throws CertificateException {
145         CertificateFactory cf = CertificateFactory.getInstance("X.509");
146         return cf.generateCertificate(new ByteArrayInputStream(bytes));
147     }
148 
toCertificateChain(byte[] bytes)149     private Certificate[] toCertificateChain(byte[] bytes) throws CertificateException {
150         CertificateFactory cf = CertificateFactory.getInstance("X.509");
151         return new Certificate[]{cf.generateCertificate(new ByteArrayInputStream(bytes))};
152     }
153 
154     @Before
setUp()155     public void setUp() throws Exception {
156         mRSACertOne = Base64.decode(SELF_SIGNED_RSA_CERT_1_B64, Base64.DEFAULT);
157         mRSACertTwo = Base64.decode(SELF_SIGNED_RSA_CERT_2_B64, Base64.DEFAULT);
158         mECCertOne = Base64.decode(SELF_SIGNED_EC_CERT_1_B64, Base64.DEFAULT);
159         mECCertTwo = Base64.decode(SELF_SIGNED_EC_CERT_2_B64, Base64.DEFAULT);
160         mIssuers = new ArrayList<byte[]>();
161         mIssuers.add(ISSUER_ONE.getEncoded());
162         mDummyInfoProvider =
163                 new KeyInfoProvider() {
164                     public boolean isUserSelectable(String alias) {
165                         return true;
166                     }
167                 };
168 
169         mDummyChecker = mock(KeyChainActivity.CertificateParametersFilter.class);
170         when(mDummyChecker.shouldPresentCertificate(Mockito.anyString())).thenReturn(true);
171 
172         mKeyStoreSpi = mock(KeyStoreSpi.class);
173         mKeyStore = new KeyStore(mKeyStoreSpi, null, "test") {};
174         mKeyStore.load(null);
175         mInfoProvider = mock(KeyInfoProvider.class);
176     }
177 
178     @Test
testAliasLoader_loadsAllAliases()179     public void testAliasLoader_loadsAllAliases()
180             throws InterruptedException, ExecutionException, CancellationException,
181             TimeoutException {
182         prepareKeyStoreWithAliases(ImmutableList.of("b", "c", "a"));
183 
184         KeyChainActivity.AliasLoader loader =
185                 new KeyChainActivity.AliasLoader(
186                         mKeyStore,
187                         RuntimeEnvironment.application,
188                         mDummyInfoProvider,
189                         mDummyChecker);
190         loader.execute();
191 
192         ShadowApplication.runBackgroundTasks();
193         KeyChainActivity.CertificateAdapter result = loader.get(5, TimeUnit.SECONDS);
194         Assert.assertNotNull(result);
195         Assert.assertEquals(3, result.getCount());
196         Assert.assertEquals("a", result.getItem(0));
197         Assert.assertEquals("b", result.getItem(1));
198         Assert.assertEquals("c", result.getItem(2));
199     }
200 
201     @Test
testAliasLoader_copesWithNoAliases()202     public void testAliasLoader_copesWithNoAliases()
203             throws InterruptedException, ExecutionException, CancellationException,
204                     TimeoutException {
205         when(mKeyStoreSpi.engineAliases()).thenReturn(Collections.enumeration(ImmutableList.of()));
206 
207         KeyChainActivity.AliasLoader loader =
208                 new KeyChainActivity.AliasLoader(
209                         mKeyStore,
210                         RuntimeEnvironment.application,
211                         mDummyInfoProvider,
212                         mDummyChecker);
213         loader.execute();
214 
215         ShadowApplication.runBackgroundTasks();
216         KeyChainActivity.CertificateAdapter result = loader.get(5, TimeUnit.SECONDS);
217         Assert.assertNotNull(result);
218         Assert.assertEquals(0, result.getCount());
219     }
220 
221     @Test
testAliasLoader_filtersNonUserSelectableAliases()222     public void testAliasLoader_filtersNonUserSelectableAliases()
223             throws InterruptedException, ExecutionException, CancellationException,
224                     TimeoutException {
225         prepareKeyStoreWithAliases(ImmutableList.of("b", "c", "a"));
226         when(mInfoProvider.isUserSelectable("a")).thenReturn(false);
227         when(mInfoProvider.isUserSelectable("b")).thenReturn(true);
228         when(mInfoProvider.isUserSelectable("c")).thenReturn(false);
229 
230         KeyChainActivity.AliasLoader loader =
231                 new KeyChainActivity.AliasLoader(
232                         mKeyStore, RuntimeEnvironment.application, mInfoProvider, mDummyChecker);
233         loader.execute();
234 
235         ShadowApplication.runBackgroundTasks();
236         KeyChainActivity.CertificateAdapter result = loader.get(5, TimeUnit.SECONDS);
237         Assert.assertNotNull(result);
238         Assert.assertEquals(1, result.getCount());
239         Assert.assertEquals("b", result.getItem(0));
240     }
241 
242     @Test
testAliasLoader_filtersAliasesWithNonConformingParameters()243     public void testAliasLoader_filtersAliasesWithNonConformingParameters()
244             throws InterruptedException, ExecutionException, CancellationException,
245                     TimeoutException, KeyStoreException {
246         prepareKeyStoreWithAliases(ImmutableList.of("a", "b", "c", "d"));
247         when(mInfoProvider.isUserSelectable("a")).thenReturn(true);
248         when(mInfoProvider.isUserSelectable("b")).thenReturn(true);
249         when(mInfoProvider.isUserSelectable("c")).thenReturn(false);
250         when(mInfoProvider.isUserSelectable("d")).thenReturn(false);
251 
252         KeyChainActivity.CertificateParametersFilter checker =
253                 mock(KeyChainActivity.CertificateParametersFilter.class);
254         // The first alias is user-selectable and should be presented.
255         when(checker.shouldPresentCertificate("a")).thenReturn(true);
256         // The second alias is user-selectable but should not be presented.
257         when(checker.shouldPresentCertificate("b")).thenReturn(false);
258         // The third alias is not user-selectable but should be presented.
259         when(checker.shouldPresentCertificate("c")).thenReturn(true);
260         // The fourth alias is not user-selectable and should not be presented.
261         when(checker.shouldPresentCertificate("d")).thenReturn(false);
262 
263         KeyChainActivity.AliasLoader loader =
264                 new KeyChainActivity.AliasLoader(
265                         mKeyStore, RuntimeEnvironment.application, mInfoProvider, checker);
266         loader.execute();
267 
268         ShadowApplication.runBackgroundTasks();
269         KeyChainActivity.CertificateAdapter result = loader.get(5, TimeUnit.SECONDS);
270         Assert.assertNotNull(result);
271         Assert.assertEquals(1, result.getCount());
272         Assert.assertEquals("a", result.getItem(0));
273     }
274 
prepareKeyStoreWithAliases(ImmutableList<String> aliases)275     private void prepareKeyStoreWithAliases(ImmutableList<String> aliases) {
276         when(mKeyStoreSpi.engineAliases()).thenReturn(Collections.enumeration(aliases));
277         for (int i = 0; i < aliases.size(); i++) {
278             when(mKeyStoreSpi.engineIsKeyEntry(aliases.get(i))).thenReturn(true);
279         }
280     }
281 
prepareKeyStoreWithCertificates()282     private void prepareKeyStoreWithCertificates() throws CertificateException {
283         when(mKeyStoreSpi.engineGetCertificate("rsa1")).thenReturn(toCertificate(mRSACertOne));
284         when(mKeyStoreSpi.engineGetCertificate("ec1")).thenReturn(toCertificate(mECCertOne));
285         when(mKeyStoreSpi.engineGetCertificate("rsa2")).thenReturn(toCertificate(mRSACertTwo));
286         when(mKeyStoreSpi.engineGetCertificate("ec2")).thenReturn(toCertificate(mECCertTwo));
287 
288         when(mKeyStoreSpi.engineGetCertificateChain("rsa1"))
289                 .thenReturn(toCertificateChain(mRSACertOne));
290         when(mKeyStoreSpi.engineGetCertificateChain("ec1"))
291                 .thenReturn(toCertificateChain(mECCertOne));
292         when(mKeyStoreSpi.engineGetCertificateChain("rsa2"))
293                 .thenReturn(toCertificateChain(mRSACertTwo));
294         when(mKeyStoreSpi.engineGetCertificateChain("ec2"))
295                 .thenReturn(toCertificateChain(mECCertTwo));
296     }
297 
298     @Test
testCertificateParametersFilter_filtersByKey()299     public void testCertificateParametersFilter_filtersByKey()
300             throws CancellationException, CertificateException {
301         prepareKeyStoreWithCertificates();
302 
303         KeyChainActivity.CertificateParametersFilter ec_checker =
304                 new KeyChainActivity.CertificateParametersFilter(
305                         mKeyStore, new String[] {"EC"}, new ArrayList<byte[]>());
306         Assert.assertFalse(ec_checker.shouldPresentCertificate("rsa1"));
307         Assert.assertTrue(ec_checker.shouldPresentCertificate("ec1"));
308 
309         KeyChainActivity.CertificateParametersFilter rsa_and_ec_checker =
310                 new KeyChainActivity.CertificateParametersFilter(
311                         mKeyStore, new String[] {"EC", "RSA"}, new ArrayList<byte[]>());
312         Assert.assertTrue(rsa_and_ec_checker.shouldPresentCertificate("rsa1"));
313         Assert.assertTrue(rsa_and_ec_checker.shouldPresentCertificate("ec1"));
314     }
315 
316     @Test
testCertificateParametersFilter_filtersByIssuer()317     public void testCertificateParametersFilter_filtersByIssuer()
318             throws CancellationException, CertificateException {
319         prepareKeyStoreWithCertificates();
320 
321         KeyChainActivity.CertificateParametersFilter issuer_checker =
322                 new KeyChainActivity.CertificateParametersFilter(
323                         mKeyStore, new String[] {}, mIssuers);
324         Assert.assertTrue(issuer_checker.shouldPresentCertificate("rsa1"));
325         Assert.assertTrue(issuer_checker.shouldPresentCertificate("ec1"));
326         Assert.assertFalse(issuer_checker.shouldPresentCertificate("rsa2"));
327         Assert.assertFalse(issuer_checker.shouldPresentCertificate("ec2"));
328     }
329 
330     @Test
testCertificateParametersFilter_filtersByIssuerAndKey()331     public void testCertificateParametersFilter_filtersByIssuerAndKey()
332             throws CancellationException, CertificateException {
333         prepareKeyStoreWithCertificates();
334 
335         KeyChainActivity.CertificateParametersFilter issuer_checker =
336                 new KeyChainActivity.CertificateParametersFilter(
337                         mKeyStore, new String[] {"EC"}, mIssuers);
338         Assert.assertFalse(issuer_checker.shouldPresentCertificate("rsa1"));
339         Assert.assertTrue(issuer_checker.shouldPresentCertificate("ec1"));
340         Assert.assertFalse(issuer_checker.shouldPresentCertificate("rsa2"));
341         Assert.assertFalse(issuer_checker.shouldPresentCertificate("ec2"));
342     }
343 }
344