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