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