1/*
2 * Copyright (C) 2011 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
17package org.conscrypt;
18
19import java.io.ByteArrayInputStream;
20import java.io.ByteArrayOutputStream;
21import java.io.File;
22import java.io.FileInputStream;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.InputStream;
26import java.io.OutputStream;
27import java.security.KeyStore;
28import java.security.KeyStore.PrivateKeyEntry;
29import java.security.KeyStore.TrustedCertificateEntry;
30import java.security.PrivateKey;
31import java.security.PublicKey;
32import java.security.cert.Certificate;
33import java.security.cert.CertificateFactory;
34import java.security.cert.X509Certificate;
35import java.util.Arrays;
36import java.util.HashSet;
37import java.util.List;
38import java.util.Random;
39import java.util.Set;
40import java.util.concurrent.Callable;
41import java.util.concurrent.ExecutorService;
42import java.util.concurrent.Executors;
43import java.util.concurrent.Future;
44import java.util.concurrent.TimeUnit;
45import java.util.concurrent.TimeoutException;
46import javax.security.auth.x500.X500Principal;
47import junit.framework.TestCase;
48import libcore.java.security.TestKeyStore;
49
50public class TrustedCertificateStoreTest extends TestCase {
51    private static final Random tempFileRandom = new Random();
52
53    private final File dirTest = new File(System.getProperty("java.io.tmpdir", "."),
54            "cert-store-test" + tempFileRandom.nextInt());
55    private final File dirSystem = new File(dirTest, "system");
56    private final File dirAdded = new File(dirTest, "added");
57    private final File dirDeleted = new File(dirTest, "removed");
58
59    private static X509Certificate CA1;
60    private static X509Certificate CA2;
61
62    private static KeyStore.PrivateKeyEntry PRIVATE;
63    private static X509Certificate[] CHAIN;
64
65    private static X509Certificate CA3_WITH_CA1_SUBJECT;
66    private static String ALIAS_SYSTEM_CA1;
67    private static String ALIAS_SYSTEM_CA2;
68    private static String ALIAS_USER_CA1;
69    private static String ALIAS_USER_CA2;
70
71    private static String ALIAS_SYSTEM_CHAIN0;
72    private static String ALIAS_SYSTEM_CHAIN1;
73    private static String ALIAS_SYSTEM_CHAIN2;
74    private static String ALIAS_USER_CHAIN0;
75    private static String ALIAS_USER_CHAIN1;
76    private static String ALIAS_USER_CHAIN2;
77
78    private static String ALIAS_SYSTEM_CA3;
79    private static String ALIAS_SYSTEM_CA3_COLLISION;
80    private static String ALIAS_USER_CA3;
81    private static String ALIAS_USER_CA3_COLLISION;
82
83    private static X509Certificate CERTLOOP_EE;
84    private static X509Certificate CERTLOOP_CA1;
85    private static X509Certificate CERTLOOP_CA2;
86    private static String ALIAS_USER_CERTLOOP_EE;
87    private static String ALIAS_USER_CERTLOOP_CA1;
88    private static String ALIAS_USER_CERTLOOP_CA2;
89
90    private static X509Certificate MULTIPLE_ISSUERS_CA1;
91    private static X509Certificate MULTIPLE_ISSUERS_CA1_CROSS;
92    private static X509Certificate MULTIPLE_ISSUERS_CA2;
93    private static X509Certificate MULTIPLE_ISSUERS_EE;
94    private static String ALIAS_MULTIPLE_ISSUERS_CA1;
95    private static String ALIAS_MULTIPLE_ISSUERS_CA1_CROSS;
96    private static String ALIAS_MULTIPLE_ISSUERS_CA2;
97    private static String ALIAS_MULTIPLE_ISSUERS_EE;
98
99    private static X509Certificate getCa1() {
100        initCerts();
101        return CA1;
102    }
103    private static X509Certificate getCa2() {
104        initCerts();
105        return CA2;
106    }
107
108    private static KeyStore.PrivateKeyEntry getPrivate() {
109        initCerts();
110        return PRIVATE;
111    }
112    private static X509Certificate[] getChain() {
113        initCerts();
114        return CHAIN;
115    }
116
117    private static X509Certificate getCa3WithCa1Subject() {
118        initCerts();
119        return CA3_WITH_CA1_SUBJECT;
120    }
121
122    private static String getAliasSystemCa1() {
123        initCerts();
124        return ALIAS_SYSTEM_CA1;
125    }
126    private static String getAliasSystemCa2() {
127        initCerts();
128        return ALIAS_SYSTEM_CA2;
129    }
130    private static String getAliasUserCa1() {
131        initCerts();
132        return ALIAS_USER_CA1;
133    }
134    private static String getAliasUserCa2() {
135        initCerts();
136        return ALIAS_USER_CA2;
137    }
138
139    private static String getAliasSystemChain0() {
140        initCerts();
141        return ALIAS_SYSTEM_CHAIN0;
142    }
143    private static String getAliasSystemChain1() {
144        initCerts();
145        return ALIAS_SYSTEM_CHAIN1;
146    }
147    private static String getAliasSystemChain2() {
148        initCerts();
149        return ALIAS_SYSTEM_CHAIN2;
150    }
151    private static String getAliasUserChain0() {
152        initCerts();
153        return ALIAS_USER_CHAIN0;
154    }
155    private static String getAliasUserChain1() {
156        initCerts();
157        return ALIAS_USER_CHAIN1;
158    }
159    private static String getAliasUserChain2() {
160        initCerts();
161        return ALIAS_USER_CHAIN2;
162    }
163
164    private static String getAliasSystemCa3() {
165        initCerts();
166        return ALIAS_SYSTEM_CA3;
167    }
168    private static String getAliasSystemCa3Collision() {
169        initCerts();
170        return ALIAS_SYSTEM_CA3_COLLISION;
171    }
172    private static String getAliasUserCa3() {
173        initCerts();
174        return ALIAS_USER_CA3;
175    }
176    private static String getAliasUserCa3Collision() {
177        initCerts();
178        return ALIAS_USER_CA3_COLLISION;
179    }
180    private static X509Certificate getCertLoopEe() {
181        initCerts();
182        return CERTLOOP_EE;
183    }
184    private static X509Certificate getCertLoopCa1() {
185        initCerts();
186        return CERTLOOP_CA1;
187    }
188    private static X509Certificate getCertLoopCa2() {
189        initCerts();
190        return CERTLOOP_CA2;
191    }
192    private static String getAliasCertLoopEe() {
193        initCerts();
194        return ALIAS_USER_CERTLOOP_EE;
195    }
196    private static String getAliasCertLoopCa1() {
197        initCerts();
198        return ALIAS_USER_CERTLOOP_CA1;
199    }
200    private static String getAliasCertLoopCa2() {
201        initCerts();
202        return ALIAS_USER_CERTLOOP_CA2;
203    }
204    private static String getAliasMultipleIssuersCa1() {
205        initCerts();
206        return ALIAS_MULTIPLE_ISSUERS_CA1;
207    }
208    private static String getAliasMultipleIssuersCa2() {
209        initCerts();
210        return ALIAS_MULTIPLE_ISSUERS_CA2;
211    }
212    private static String getAliasMultipleIssuersCa1Cross() {
213        initCerts();
214        return ALIAS_MULTIPLE_ISSUERS_CA1_CROSS;
215    }
216    private static String getAliasMultipleIssuersEe() {
217        initCerts();
218        return ALIAS_MULTIPLE_ISSUERS_EE;
219    }
220    private static X509Certificate getMultipleIssuersCa1() {
221        initCerts();
222        return MULTIPLE_ISSUERS_CA1;
223    }
224    private static X509Certificate getMultipleIssuersCa2() {
225        initCerts();
226        return MULTIPLE_ISSUERS_CA2;
227    }
228    private static X509Certificate getMultipleIssuersCa1Cross() {
229        initCerts();
230        return MULTIPLE_ISSUERS_CA1_CROSS;
231    }
232    private static X509Certificate getMultipleIssuersEe() {
233        initCerts();
234        return MULTIPLE_ISSUERS_EE;
235    }
236
237    /**
238     * Lazily create shared test certificates.
239     */
240    private static synchronized void initCerts() {
241        if (CA1 != null) {
242            return;
243        }
244        try {
245            CA1 = TestKeyStore.getClient().getRootCertificate("RSA");
246            CA2 = TestKeyStore.getClientCA2().getRootCertificate("RSA");
247            PRIVATE = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
248            CHAIN = (X509Certificate[]) PRIVATE.getCertificateChain();
249            CA3_WITH_CA1_SUBJECT = new TestKeyStore.Builder()
250                    .aliasPrefix("unused")
251                    .subject(CA1.getSubjectX500Principal())
252                    .ca(true)
253                    .build().getRootCertificate("RSA");
254
255
256            ALIAS_SYSTEM_CA1 = alias(false, CA1, 0);
257            ALIAS_SYSTEM_CA2 = alias(false, CA2, 0);
258            ALIAS_USER_CA1 = alias(true, CA1, 0);
259            ALIAS_USER_CA2 = alias(true, CA2, 0);
260
261            ALIAS_SYSTEM_CHAIN0 = alias(false, getChain()[0], 0);
262            ALIAS_SYSTEM_CHAIN1 = alias(false, getChain()[1], 0);
263            ALIAS_SYSTEM_CHAIN2 = alias(false, getChain()[2], 0);
264            ALIAS_USER_CHAIN0 = alias(true, getChain()[0], 0);
265            ALIAS_USER_CHAIN1 = alias(true, getChain()[1], 0);
266            ALIAS_USER_CHAIN2 = alias(true, getChain()[2], 0);
267
268            ALIAS_SYSTEM_CA3 = alias(false, CA3_WITH_CA1_SUBJECT, 0);
269            ALIAS_SYSTEM_CA3_COLLISION = alias(false, CA3_WITH_CA1_SUBJECT, 1);
270            ALIAS_USER_CA3 = alias(true, CA3_WITH_CA1_SUBJECT, 0);
271            ALIAS_USER_CA3_COLLISION = alias(true, CA3_WITH_CA1_SUBJECT, 1);
272
273            /*
274             * The construction below is to build a certificate chain that has a loop
275             * in it:
276             *
277             *   EE ---> CA1 ---> CA2 ---+
278             *            ^              |
279             *            |              |
280             *            +--------------+
281             */
282            TestKeyStore certLoopTempCa1 = new TestKeyStore.Builder()
283                    .keyAlgorithms("RSA")
284                    .aliasPrefix("certloop-ca1")
285                    .subject("CN=certloop-ca1")
286                    .ca(true)
287                    .build();
288            Certificate certLoopTempCaCert1 = ((TrustedCertificateEntry) certLoopTempCa1
289                    .getEntryByAlias("certloop-ca1-public-RSA")).getTrustedCertificate();
290            PrivateKeyEntry certLoopCaKey1 = (PrivateKeyEntry) certLoopTempCa1
291                    .getEntryByAlias("certloop-ca1-private-RSA");
292
293            TestKeyStore certLoopCa2 = new TestKeyStore.Builder()
294                    .keyAlgorithms("RSA")
295                    .aliasPrefix("certloop-ca2")
296                    .subject("CN=certloop-ca2")
297                    .rootCa(certLoopTempCaCert1)
298                    .signer(certLoopCaKey1)
299                    .ca(true)
300                    .build();
301            CERTLOOP_CA2 = (X509Certificate) ((TrustedCertificateEntry) certLoopCa2
302                    .getEntryByAlias("certloop-ca2-public-RSA")).getTrustedCertificate();
303            ALIAS_USER_CERTLOOP_CA2 = alias(true, CERTLOOP_CA2, 0);
304            PrivateKeyEntry certLoopCaKey2 = (PrivateKeyEntry) certLoopCa2
305                    .getEntryByAlias("certloop-ca2-private-RSA");
306
307            TestKeyStore certLoopCa1 = new TestKeyStore.Builder()
308                    .keyAlgorithms("RSA")
309                    .aliasPrefix("certloop-ca1")
310                    .subject("CN=certloop-ca1")
311                    .privateEntry(certLoopCaKey1)
312                    .rootCa(CERTLOOP_CA2)
313                    .signer(certLoopCaKey2)
314                    .ca(true)
315                    .build();
316            CERTLOOP_CA1 = (X509Certificate) ((TrustedCertificateEntry) certLoopCa1
317                    .getEntryByAlias("certloop-ca1-public-RSA")).getTrustedCertificate();
318            ALIAS_USER_CERTLOOP_CA1 = alias(true, CERTLOOP_CA1, 0);
319
320            TestKeyStore certLoopEe = new TestKeyStore.Builder()
321                    .keyAlgorithms("RSA")
322                    .aliasPrefix("certloop-ee")
323                    .subject("CN=certloop-ee")
324                    .rootCa(CERTLOOP_CA1)
325                    .signer(certLoopCaKey1)
326                    .build();
327            CERTLOOP_EE = (X509Certificate) ((TrustedCertificateEntry) certLoopEe
328                    .getEntryByAlias("certloop-ee-public-RSA")).getTrustedCertificate();
329            ALIAS_USER_CERTLOOP_EE = alias(true, CERTLOOP_EE, 0);
330
331            /*
332             * The construction below creates a certificate with multiple possible issuer certs.
333             *
334             *    EE ----> CA1 ---> CA2
335             *
336             *    Where CA1 also exists in a self-issued form.
337             */
338            TestKeyStore multipleIssuersCa1 = new TestKeyStore.Builder()
339                    .keyAlgorithms("RSA")
340                    .aliasPrefix("multiple-issuers-ca1")
341                    .subject("CN=multiple-issuers-ca1")
342                    .ca(true)
343                    .build();
344            MULTIPLE_ISSUERS_CA1 = (X509Certificate) ((TrustedCertificateEntry) multipleIssuersCa1
345                    .getEntryByAlias("multiple-issuers-ca1-public-RSA")).getTrustedCertificate();
346            ALIAS_MULTIPLE_ISSUERS_CA1 = alias(false, MULTIPLE_ISSUERS_CA1, 0);
347            PrivateKeyEntry multipleIssuersCa1Key = (PrivateKeyEntry) multipleIssuersCa1
348                    .getEntryByAlias("multiple-issuers-ca1-private-RSA");
349
350            TestKeyStore multipleIssuersCa2 = new TestKeyStore.Builder()
351                    .keyAlgorithms("RSA")
352                    .aliasPrefix("multiple-issuers-ca2")
353                    .subject("CN=multiple-issuers-ca2")
354                    .ca(true)
355                    .build();
356            MULTIPLE_ISSUERS_CA2 = (X509Certificate) ((TrustedCertificateEntry) multipleIssuersCa2
357                    .getEntryByAlias("multiple-issuers-ca2-public-RSA")).getTrustedCertificate();
358            ALIAS_MULTIPLE_ISSUERS_CA2 = alias(false, MULTIPLE_ISSUERS_CA2, 0);
359            PrivateKeyEntry multipleIssuersCa2Key = (PrivateKeyEntry) multipleIssuersCa2
360                    .getEntryByAlias("multiple-issuers-ca2-private-RSA");
361
362            TestKeyStore multipleIssuersCa1SignedByCa2 = new TestKeyStore.Builder()
363                    .keyAlgorithms("RSA")
364                    .aliasPrefix("multiple-issuers-ca1")
365                    .subject("CN=multiple-issuers-ca1")
366                    .privateEntry(multipleIssuersCa1Key)
367                    .rootCa(MULTIPLE_ISSUERS_CA2)
368                    .signer(multipleIssuersCa2Key)
369                    .ca(true)
370                    .build();
371            MULTIPLE_ISSUERS_CA1_CROSS =
372                    (X509Certificate) ((TrustedCertificateEntry) multipleIssuersCa1SignedByCa2
373                            .getEntryByAlias("multiple-issuers-ca1-public-RSA"))
374                    .getTrustedCertificate();
375            ALIAS_MULTIPLE_ISSUERS_CA1_CROSS = alias(false, MULTIPLE_ISSUERS_CA1_CROSS, 1);
376
377            TestKeyStore multipleIssuersEe = new TestKeyStore.Builder()
378                    .keyAlgorithms("RSA")
379                    .aliasPrefix("multiple-issuers-ee")
380                    .subject("CN=multiple-issuers-ee")
381                    .rootCa(MULTIPLE_ISSUERS_CA1)
382                    .signer(multipleIssuersCa1Key)
383                    .build();
384            MULTIPLE_ISSUERS_EE = (X509Certificate) ((TrustedCertificateEntry) multipleIssuersEe
385                    .getEntryByAlias("multiple-issuers-ee-public-RSA")).getTrustedCertificate();
386            ALIAS_MULTIPLE_ISSUERS_EE = alias(false, MULTIPLE_ISSUERS_EE, 0);
387        } catch (Exception e) {
388            throw new RuntimeException(e);
389        }
390    }
391
392    private TrustedCertificateStore store;
393
394    @Override protected void setUp() {
395        setupStore();
396    }
397
398    private void setupStore() {
399        dirSystem.mkdirs();
400        cleanStore();
401        createStore();
402    }
403
404    private void createStore() {
405        store = new TrustedCertificateStore(dirSystem, dirAdded, dirDeleted);
406    }
407
408    @Override protected void tearDown() {
409        cleanStore();
410    }
411
412    private void cleanStore() {
413        for (File dir : new File[] { dirSystem, dirAdded, dirDeleted, dirTest }) {
414            File[] files = dir.listFiles();
415            if (files == null) {
416                continue;
417            }
418            for (File file : files) {
419                assertTrue("Should delete " + file.getPath(), file.delete());
420            }
421        }
422        store = null;
423    }
424
425    private void resetStore() {
426        cleanStore();
427        setupStore();
428    }
429
430    public void testEmptyDirectories() throws Exception {
431        assertEmpty();
432    }
433
434    public void testOneSystemOneDeleted() throws Exception {
435        install(getCa1(), getAliasSystemCa1());
436        store.deleteCertificateEntry(getAliasSystemCa1());
437        assertEmpty();
438        assertDeleted(getCa1(), getAliasSystemCa1());
439    }
440
441    public void testTwoSystemTwoDeleted() throws Exception {
442        install(getCa1(), getAliasSystemCa1());
443        store.deleteCertificateEntry(getAliasSystemCa1());
444        install(getCa2(), getAliasSystemCa2());
445        store.deleteCertificateEntry(getAliasSystemCa2());
446        assertEmpty();
447        assertDeleted(getCa1(), getAliasSystemCa1());
448        assertDeleted(getCa2(), getAliasSystemCa2());
449    }
450
451    public void testPartialFileIsIgnored() throws Exception {
452        File file = file(getAliasSystemCa1());
453        file.getParentFile().mkdirs();
454        OutputStream os = new FileOutputStream(file);
455        os.write(0);
456        os.close();
457        assertTrue(file.exists());
458        assertEmpty();
459        assertTrue(file.exists());
460    }
461
462    private void assertEmpty() throws Exception {
463        try {
464            store.getCertificate(null);
465            fail();
466        } catch (NullPointerException expected) {
467        }
468        assertNull(store.getCertificate(""));
469
470        try {
471            store.getCreationDate(null);
472            fail();
473        } catch (NullPointerException expected) {
474        }
475        assertNull(store.getCreationDate(""));
476
477        Set<String> s = store.aliases();
478        assertNotNull(s);
479        assertTrue(s.isEmpty());
480        assertAliases();
481
482        Set<String> u = store.userAliases();
483        assertNotNull(u);
484        assertTrue(u.isEmpty());
485
486        try {
487            store.containsAlias(null);
488            fail();
489        } catch (NullPointerException expected) {
490        }
491        assertFalse(store.containsAlias(""));
492
493        assertNull(store.getCertificateAlias(null));
494        assertNull(store.getCertificateAlias(getCa1()));
495
496        try {
497            store.getTrustAnchor(null);
498            fail();
499        } catch (NullPointerException expected) {
500        }
501        assertNull(store.getTrustAnchor(getCa1()));
502
503        try {
504            store.findIssuer(null);
505            fail();
506        } catch (NullPointerException expected) {
507        }
508        assertNull(store.findIssuer(getCa1()));
509
510        try {
511            store.installCertificate(null);
512            fail();
513        } catch (NullPointerException expected) {
514        }
515
516        store.deleteCertificateEntry(null);
517        store.deleteCertificateEntry("");
518
519        String[] userFiles = dirAdded.list();
520        assertTrue(userFiles == null || userFiles.length == 0);
521    }
522
523    public void testTwoSystem() throws Exception {
524        testTwo(getCa1(), getAliasSystemCa1(),
525                getCa2(), getAliasSystemCa2());
526    }
527
528    public void testTwoUser() throws Exception {
529        testTwo(getCa1(), getAliasUserCa1(),
530                getCa2(), getAliasUserCa2());
531    }
532
533    public void testOneSystemOneUser() throws Exception {
534        testTwo(getCa1(), getAliasSystemCa1(),
535                getCa2(), getAliasUserCa2());
536    }
537
538    public void testTwoSystemSameSubject() throws Exception {
539        testTwo(getCa1(), getAliasSystemCa1(),
540                getCa3WithCa1Subject(), getAliasSystemCa3Collision());
541    }
542
543    public void testTwoUserSameSubject() throws Exception {
544        testTwo(getCa1(), getAliasUserCa1(),
545                getCa3WithCa1Subject(), getAliasUserCa3Collision());
546
547        store.deleteCertificateEntry(getAliasUserCa1());
548        assertDeleted(getCa1(), getAliasUserCa1());
549        assertTombstone(getAliasUserCa1());
550        assertRootCa(getCa3WithCa1Subject(), getAliasUserCa3Collision());
551        assertAliases(getAliasUserCa3Collision());
552
553        store.deleteCertificateEntry(getAliasUserCa3Collision());
554        assertDeleted(getCa3WithCa1Subject(), getAliasUserCa3Collision());
555        assertNoTombstone(getAliasUserCa3Collision());
556        assertNoTombstone(getAliasUserCa1());
557        assertEmpty();
558    }
559
560    public void testOneSystemOneUserSameSubject() throws Exception {
561        testTwo(getCa1(), getAliasSystemCa1(),
562                getCa3WithCa1Subject(), getAliasUserCa3());
563        testTwo(getCa1(), getAliasUserCa1(),
564                getCa3WithCa1Subject(), getAliasSystemCa3());
565    }
566
567    private void testTwo(X509Certificate x1, String alias1,
568                         X509Certificate x2, String alias2) {
569        install(x1, alias1);
570        install(x2, alias2);
571        assertRootCa(x1, alias1);
572        assertRootCa(x2, alias2);
573        assertAliases(alias1, alias2);
574    }
575
576
577    public void testOneSystemOneUserOneDeleted() throws Exception {
578        install(getCa1(), getAliasSystemCa1());
579        store.installCertificate(getCa2());
580        store.deleteCertificateEntry(getAliasSystemCa1());
581        assertDeleted(getCa1(), getAliasSystemCa1());
582        assertRootCa(getCa2(), getAliasUserCa2());
583        assertAliases(getAliasUserCa2());
584    }
585
586    public void testOneSystemOneUserOneDeletedSameSubject() throws Exception {
587        install(getCa1(), getAliasSystemCa1());
588        store.installCertificate(getCa3WithCa1Subject());
589        store.deleteCertificateEntry(getAliasSystemCa1());
590        assertDeleted(getCa1(), getAliasSystemCa1());
591        assertRootCa(getCa3WithCa1Subject(), getAliasUserCa3());
592        assertAliases(getAliasUserCa3());
593    }
594
595    public void testUserMaskingSystem() throws Exception {
596        install(getCa1(), getAliasSystemCa1());
597        install(getCa1(), getAliasUserCa1());
598        assertMasked(getCa1(), getAliasSystemCa1());
599        assertRootCa(getCa1(), getAliasUserCa1());
600        assertAliases(getAliasSystemCa1(), getAliasUserCa1());
601    }
602
603    public void testChain() throws Exception {
604        testChain(getAliasSystemChain1(), getAliasSystemChain2());
605        testChain(getAliasSystemChain1(), getAliasUserChain2());
606        testChain(getAliasUserChain1(), getAliasSystemCa1());
607        testChain(getAliasUserChain1(), getAliasUserChain2());
608    }
609
610    private void testChain(String alias1, String alias2) throws Exception {
611        install(getChain()[1], alias1);
612        install(getChain()[2], alias2);
613        assertIntermediateCa(getChain()[1], alias1);
614        assertRootCa(getChain()[2], alias2);
615        assertAliases(alias1, alias2);
616        assertEquals(getChain()[2], store.findIssuer(getChain()[1]));
617        assertEquals(getChain()[1], store.findIssuer(getChain()[0]));
618
619        X509Certificate[] expected = getChain();
620        List<X509Certificate> actualList = store.getCertificateChain(expected[0]);
621
622        assertEquals("Generated CA list should be same length", expected.length, actualList.size());
623        for (int i = 0; i < expected.length; i++) {
624            assertEquals("Chain value should be the same for position " + i, expected[i],
625                    actualList.get(i));
626        }
627        resetStore();
628    }
629
630    public void testMissingSystemDirectory() throws Exception {
631        cleanStore();
632        createStore();
633        assertEmpty();
634    }
635
636    public void testWithExistingUserDirectories() throws Exception {
637        dirAdded.mkdirs();
638        dirDeleted.mkdirs();
639        install(getCa1(), getAliasSystemCa1());
640        assertRootCa(getCa1(), getAliasSystemCa1());
641        assertAliases(getAliasSystemCa1());
642    }
643
644    public void testIsTrustAnchorWithReissuedgetCa() throws Exception {
645        PublicKey publicKey = getPrivate().getCertificate().getPublicKey();
646        PrivateKey privateKey = getPrivate().getPrivateKey();
647        String name = "CN=CA4";
648        X509Certificate ca1 = TestKeyStore.createCa(publicKey, privateKey, name);
649        Thread.sleep(1 * 1000); // wait to ensure CAs vary by expiration
650        X509Certificate ca2 = TestKeyStore.createCa(publicKey, privateKey, name);
651        assertFalse(ca1.equals(ca2));
652
653        String systemAlias = alias(false, ca1, 0);
654        install(ca1, systemAlias);
655        assertRootCa(ca1, systemAlias);
656        assertEquals(ca1, store.getTrustAnchor(ca2));
657        assertEquals(ca1, store.findIssuer(ca2));
658        resetStore();
659
660        String userAlias = alias(true, ca1, 0);
661        store.installCertificate(ca1);
662        assertRootCa(ca1, userAlias);
663        assertNotNull(store.getTrustAnchor(ca2));
664        assertEquals(ca1, store.findIssuer(ca2));
665        resetStore();
666    }
667
668    public void testInstallEmpty() throws Exception {
669        store.installCertificate(getCa1());
670        assertRootCa(getCa1(), getAliasUserCa1());
671        assertAliases(getAliasUserCa1());
672
673        // reinstalling should not change anything
674        store.installCertificate(getCa1());
675        assertRootCa(getCa1(), getAliasUserCa1());
676        assertAliases(getAliasUserCa1());
677    }
678
679    public void testInstallEmptySystemExists() throws Exception {
680        install(getCa1(), getAliasSystemCa1());
681        assertRootCa(getCa1(), getAliasSystemCa1());
682        assertAliases(getAliasSystemCa1());
683
684        // reinstalling should not affect system CA
685        store.installCertificate(getCa1());
686        assertRootCa(getCa1(), getAliasSystemCa1());
687        assertAliases(getAliasSystemCa1());
688
689    }
690
691    public void testInstallEmptyDeletedSystemExists() throws Exception {
692        install(getCa1(), getAliasSystemCa1());
693        store.deleteCertificateEntry(getAliasSystemCa1());
694        assertEmpty();
695        assertDeleted(getCa1(), getAliasSystemCa1());
696
697        // installing should restore deleted system CA
698        store.installCertificate(getCa1());
699        assertRootCa(getCa1(), getAliasSystemCa1());
700        assertAliases(getAliasSystemCa1());
701    }
702
703    public void testDeleteEmpty() throws Exception {
704        store.deleteCertificateEntry(getAliasSystemCa1());
705        assertEmpty();
706        assertDeleted(getCa1(), getAliasSystemCa1());
707    }
708
709    public void testDeleteUser() throws Exception {
710        store.installCertificate(getCa1());
711        assertRootCa(getCa1(), getAliasUserCa1());
712        assertAliases(getAliasUserCa1());
713
714        store.deleteCertificateEntry(getAliasUserCa1());
715        assertEmpty();
716        assertDeleted(getCa1(), getAliasUserCa1());
717        assertNoTombstone(getAliasUserCa1());
718    }
719
720    public void testDeleteSystem() throws Exception {
721        install(getCa1(), getAliasSystemCa1());
722        assertRootCa(getCa1(), getAliasSystemCa1());
723        assertAliases(getAliasSystemCa1());
724
725        store.deleteCertificateEntry(getAliasSystemCa1());
726        assertEmpty();
727        assertDeleted(getCa1(), getAliasSystemCa1());
728
729        // deleting again should not change anything
730        store.deleteCertificateEntry(getAliasSystemCa1());
731        assertEmpty();
732        assertDeleted(getCa1(), getAliasSystemCa1());
733    }
734
735    public void testGetLoopedCert() throws Exception {
736        install(getCertLoopEe(), getAliasCertLoopEe());
737        install(getCertLoopCa1(), getAliasCertLoopCa1());
738        install(getCertLoopCa2(), getAliasCertLoopCa2());
739
740        ExecutorService executor = Executors.newSingleThreadExecutor();
741        Future<List<X509Certificate>> future = executor
742                .submit(new Callable<List<X509Certificate>>() {
743                    @Override
744                    public List<X509Certificate> call() throws Exception {
745                        return store.getCertificateChain(getCertLoopEe());
746                    }
747                });
748        executor.shutdown();
749        final List<X509Certificate> certs;
750        try {
751            certs = future.get(10, TimeUnit.SECONDS);
752        } catch (TimeoutException e) {
753            fail("Could not finish building chain; possibly confused by loops");
754            return; // Not actually reached.
755        }
756        assertEquals(3, certs.size());
757        assertEquals(getCertLoopEe(), certs.get(0));
758        assertEquals(getCertLoopCa1(), certs.get(1));
759        assertEquals(getCertLoopCa2(), certs.get(2));
760    }
761
762    public void testIsUserAddedCertificate() throws Exception {
763        assertFalse(store.isUserAddedCertificate(getCa1()));
764        assertFalse(store.isUserAddedCertificate(getCa2()));
765        install(getCa1(), getAliasSystemCa1());
766        assertFalse(store.isUserAddedCertificate(getCa1()));
767        assertFalse(store.isUserAddedCertificate(getCa2()));
768        install(getCa1(), getAliasUserCa1());
769        assertTrue(store.isUserAddedCertificate(getCa1()));
770        assertFalse(store.isUserAddedCertificate(getCa2()));
771        install(getCa2(), getAliasUserCa2());
772        assertTrue(store.isUserAddedCertificate(getCa1()));
773        assertTrue(store.isUserAddedCertificate(getCa2()));
774        store.deleteCertificateEntry(getAliasUserCa1());
775        assertFalse(store.isUserAddedCertificate(getCa1()));
776        assertTrue(store.isUserAddedCertificate(getCa2()));
777        store.deleteCertificateEntry(getAliasUserCa2());
778        assertFalse(store.isUserAddedCertificate(getCa1()));
779        assertFalse(store.isUserAddedCertificate(getCa2()));
780    }
781
782    public void testSystemCaCertsUseCorrectFileNames() throws Exception {
783        TrustedCertificateStore store = new TrustedCertificateStore();
784
785        // Assert that all the certificates in the system cacerts directory are stored in files with
786        // expected names.
787        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
788        File dir = new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
789        int systemCertFileCount = 0;
790        for (File actualFile : listFilesNoNull(dir)) {
791            if (!actualFile.isFile()) {
792                continue;
793            }
794            systemCertFileCount++;
795            X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(
796                    new ByteArrayInputStream(readFully(actualFile)));
797
798            File expectedFile = store.getCertificateFile(dir, cert);
799            assertEquals("System certificate stored in the wrong file",
800                    expectedFile.getAbsolutePath(), actualFile.getAbsolutePath());
801
802            // The two statements below indirectly assert that the certificate can be looked up
803            // from a file (hopefully the same one as the expectedFile above). As opposed to
804            // getCertifiacteFile above, these are the actual methods used when verifying chain of
805            // trust. Thus, we assert that they work as expected for all system certificates.
806            assertNotNull("Issuer certificate not found for system certificate " + actualFile,
807                    store.findIssuer(cert));
808            assertNotNull("Trust anchor not found for system certificate " + actualFile,
809                    store.getTrustAnchor(cert));
810        }
811
812        // Assert that all files corresponding to all system certs/aliases known to the store are
813        // present.
814        int systemCertAliasCount = 0;
815        for (String alias : store.aliases()) {
816            if (!TrustedCertificateStore.isSystem(alias)) {
817                continue;
818            }
819            systemCertAliasCount++;
820            // Checking that the certificate is stored in a file is extraneous given the current
821            // implementation of the class under test. We do it just in case the implementation
822            // changes.
823            X509Certificate cert = (X509Certificate) store.getCertificate(alias);
824            File expectedFile = store.getCertificateFile(dir, cert);
825            if (!expectedFile.isFile()) {
826                fail("Missing certificate file for alias " + alias
827                        + ": " + expectedFile.getAbsolutePath());
828            }
829        }
830
831        assertEquals("Number of system cert files and aliases doesn't match",
832                systemCertFileCount, systemCertAliasCount);
833    }
834
835    public void testMultipleIssuers() throws Exception {
836        Set<X509Certificate> result;
837        install(getMultipleIssuersCa1(), getAliasMultipleIssuersCa1());
838        result = store.findAllIssuers(getMultipleIssuersEe());
839        assertEquals("Unexpected number of issuers found", 1, result.size());
840        assertTrue("findAllIssuers does not contain expected issuer",
841                result.contains(getMultipleIssuersCa1()));
842        install(getMultipleIssuersCa1Cross(), getAliasMultipleIssuersCa1Cross());
843        result = store.findAllIssuers(getMultipleIssuersEe());
844        assertEquals("findAllIssuers did not return all issuers", 2, result.size());
845        assertTrue("findAllIssuers does not contain CA1",
846                result.contains(getMultipleIssuersCa1()));
847        assertTrue("findAllIssuers does not contain CA1 signed by CA2",
848                result.contains(getMultipleIssuersCa1Cross()));
849    }
850
851    private static File[] listFilesNoNull(File dir) {
852        File[] files = dir.listFiles();
853        return (files != null) ? files : new File[0];
854    }
855
856    private static byte[] readFully(File file) throws IOException {
857        InputStream in = null;
858        try {
859            in = new FileInputStream(file);
860            ByteArrayOutputStream out = new ByteArrayOutputStream();
861            byte[] buf = new byte[16384];
862            int chunkSize;
863            while ((chunkSize = in.read(buf)) != -1) {
864                out.write(buf, 0, chunkSize);
865            }
866            return out.toByteArray();
867        } finally {
868            if (in != null) {
869                in.close();
870            }
871        }
872    }
873
874    private void assertRootCa(X509Certificate x, String alias) {
875        assertIntermediateCa(x, alias);
876        assertEquals(x, store.findIssuer(x));
877    }
878
879    private void assertTrusted(X509Certificate x, String alias) {
880        assertEquals(x, store.getCertificate(alias));
881        assertEquals(file(alias).lastModified(), store.getCreationDate(alias).getTime());
882        assertTrue(store.containsAlias(alias));
883        assertEquals(x, store.getTrustAnchor(x));
884    }
885
886    private void assertIntermediateCa(X509Certificate x, String alias) {
887        assertTrusted(x, alias);
888        assertEquals(alias, store.getCertificateAlias(x));
889    }
890
891    private void assertMasked(X509Certificate x, String alias) {
892        assertTrusted(x, alias);
893        assertFalse(alias.equals(store.getCertificateAlias(x)));
894    }
895
896    private void assertDeleted(X509Certificate x, String alias) {
897        assertNull(store.getCertificate(alias));
898        assertFalse(store.containsAlias(alias));
899        assertNull(store.getCertificateAlias(x));
900        assertNull(store.getTrustAnchor(x));
901        assertEquals(store.allSystemAliases().contains(alias),
902                     store.getCertificate(alias, true) != null);
903    }
904
905    private void assertTombstone(String alias) {
906        assertTrue(TrustedCertificateStore.isUser(alias));
907        File file = file(alias);
908        assertTrue(file.exists());
909        assertEquals(0, file.length());
910    }
911
912    private void assertNoTombstone(String alias) {
913        assertTrue(TrustedCertificateStore.isUser(alias));
914        assertFalse(file(alias).exists());
915    }
916
917    private void assertAliases(String... aliases) {
918        Set<String> expected = new HashSet<String>(Arrays.asList(aliases));
919        Set<String> actual = new HashSet<String>();
920        for (String alias : store.aliases()) {
921            boolean system = TrustedCertificateStore.isSystem(alias);
922            boolean user = TrustedCertificateStore.isUser(alias);
923            if (system || user) {
924                assertEquals(system, store.allSystemAliases().contains(alias));
925                assertEquals(user, store.userAliases().contains(alias));
926                actual.add(alias);
927            } else {
928                throw new AssertionError(alias);
929            }
930        }
931        assertEquals(expected, actual);
932    }
933
934    /**
935     * format a certificate alias
936     */
937    private static String alias(boolean user, X509Certificate x, int index) {
938        String prefix = user ? "user:" : "system:";
939
940        X500Principal subject = x.getSubjectX500Principal();
941        int intHash = NativeCrypto.X509_NAME_hash_old(subject);
942        String strHash = Hex.intToHexString(intHash, 8);
943
944        return prefix + strHash + '.' + index;
945    }
946
947    /**
948     * Install certificate under specified alias
949     */
950    private void install(X509Certificate x, String alias) {
951        try {
952            File file = file(alias);
953            file.getParentFile().mkdirs();
954            OutputStream out = new FileOutputStream(file);
955            out.write(x.getEncoded());
956            out.close();
957        } catch (Exception e) {
958            throw new RuntimeException(e);
959        }
960    }
961
962    /**
963     * Compute file for an alias
964     */
965    private File file(String alias) {
966        File dir;
967        if (TrustedCertificateStore.isSystem(alias)) {
968            dir = dirSystem;
969        } else if (TrustedCertificateStore.isUser(alias)) {
970            dir = dirAdded;
971        } else {
972            throw new IllegalArgumentException(alias);
973        }
974
975        int index = alias.lastIndexOf(":");
976        if (index == -1) {
977            throw new IllegalArgumentException(alias);
978        }
979        String filename = alias.substring(index+1);
980
981        return new File(dir, filename);
982    }
983}
984