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.File;
20import java.io.FileOutputStream;
21import java.io.OutputStream;
22import java.security.KeyStore;
23import java.security.PrivateKey;
24import java.security.PublicKey;
25import java.security.cert.Certificate;
26import java.security.cert.X509Certificate;
27import java.util.Arrays;
28import java.util.Collections;
29import java.util.Enumeration;
30import java.util.HashSet;
31import java.util.List;
32import java.util.NoSuchElementException;
33import java.util.Set;
34import javax.security.auth.x500.X500Principal;
35import junit.framework.TestCase;
36import libcore.java.security.TestKeyStore;
37
38public class TrustedCertificateStoreTest extends TestCase {
39
40    private static final File DIR_TEMP = new File(System.getProperty("java.io.tmpdir"));
41    private static final File DIR_TEST = new File(DIR_TEMP, "test");
42    private static final File DIR_SYSTEM = new File(DIR_TEST, "system");
43    private static final File DIR_ADDED = new File(DIR_TEST, "added");
44    private static final File DIR_DELETED = new File(DIR_TEST, "removed");
45
46    private static X509Certificate CA1;
47    private static X509Certificate CA2;
48
49    private static KeyStore.PrivateKeyEntry PRIVATE;
50    private static X509Certificate[] CHAIN;
51
52    private static X509Certificate CA3_WITH_CA1_SUBJECT;
53    private static String ALIAS_SYSTEM_CA1;
54    private static String ALIAS_SYSTEM_CA2;
55    private static String ALIAS_USER_CA1;
56    private static String ALIAS_USER_CA2;
57
58    private static String ALIAS_SYSTEM_CHAIN0;
59    private static String ALIAS_SYSTEM_CHAIN1;
60    private static String ALIAS_SYSTEM_CHAIN2;
61    private static String ALIAS_USER_CHAIN0;
62    private static String ALIAS_USER_CHAIN1;
63    private static String ALIAS_USER_CHAIN2;
64
65    private static String ALIAS_SYSTEM_CA3;
66    private static String ALIAS_SYSTEM_CA3_COLLISION;
67    private static String ALIAS_USER_CA3;
68    private static String ALIAS_USER_CA3_COLLISION;
69
70    private static X509Certificate getCa1() {
71        initCerts();
72        return CA1;
73    }
74    private static X509Certificate getCa2() {
75        initCerts();
76        return CA2;
77    }
78
79    private static KeyStore.PrivateKeyEntry getPrivate() {
80        initCerts();
81        return PRIVATE;
82    }
83    private static X509Certificate[] getChain() {
84        initCerts();
85        return CHAIN;
86    }
87
88    private static X509Certificate getCa3WithCa1Subject() {
89        initCerts();
90        return CA3_WITH_CA1_SUBJECT;
91    }
92
93    private static String getAliasSystemCa1() {
94        initCerts();
95        return ALIAS_SYSTEM_CA1;
96    }
97    private static String getAliasSystemCa2() {
98        initCerts();
99        return ALIAS_SYSTEM_CA2;
100    }
101    private static String getAliasUserCa1() {
102        initCerts();
103        return ALIAS_USER_CA1;
104    }
105    private static String getAliasUserCa2() {
106        initCerts();
107        return ALIAS_USER_CA2;
108    }
109
110    private static String getAliasSystemChain0() {
111        initCerts();
112        return ALIAS_SYSTEM_CHAIN0;
113    }
114    private static String getAliasSystemChain1() {
115        initCerts();
116        return ALIAS_SYSTEM_CHAIN1;
117    }
118    private static String getAliasSystemChain2() {
119        initCerts();
120        return ALIAS_SYSTEM_CHAIN2;
121    }
122    private static String getAliasUserChain0() {
123        initCerts();
124        return ALIAS_USER_CHAIN0;
125    }
126    private static String getAliasUserChain1() {
127        initCerts();
128        return ALIAS_USER_CHAIN1;
129    }
130    private static String getAliasUserChain2() {
131        initCerts();
132        return ALIAS_USER_CHAIN2;
133    }
134
135    private static String getAliasSystemCa3() {
136        initCerts();
137        return ALIAS_SYSTEM_CA3;
138    }
139    private static String getAliasSystemCa3Collision() {
140        initCerts();
141        return ALIAS_SYSTEM_CA3_COLLISION;
142    }
143    private static String getAliasUserCa3() {
144        initCerts();
145        return ALIAS_USER_CA3;
146    }
147    private static String getAliasUserCa3Collision() {
148        initCerts();
149        return ALIAS_USER_CA3_COLLISION;
150    }
151
152    /**
153     * Lazily create shared test certificates.
154     */
155    private static synchronized void initCerts() {
156        if (CA1 != null) {
157            return;
158        }
159        try {
160            CA1 = TestKeyStore.getClient().getRootCertificate("RSA");
161            CA2 = TestKeyStore.getClientCA2().getRootCertificate("RSA");
162            PRIVATE = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
163            CHAIN = (X509Certificate[]) PRIVATE.getCertificateChain();
164            CA3_WITH_CA1_SUBJECT = new TestKeyStore.Builder()
165                    .aliasPrefix("unused")
166                    .subject(CA1.getSubjectX500Principal())
167                    .ca(true)
168                    .build().getRootCertificate("RSA");
169
170
171            ALIAS_SYSTEM_CA1 = alias(false, CA1, 0);
172            ALIAS_SYSTEM_CA2 = alias(false, CA2, 0);
173            ALIAS_USER_CA1 = alias(true, CA1, 0);
174            ALIAS_USER_CA2 = alias(true, CA2, 0);
175
176            ALIAS_SYSTEM_CHAIN0 = alias(false, getChain()[0], 0);
177            ALIAS_SYSTEM_CHAIN1 = alias(false, getChain()[1], 0);
178            ALIAS_SYSTEM_CHAIN2 = alias(false, getChain()[2], 0);
179            ALIAS_USER_CHAIN0 = alias(true, getChain()[0], 0);
180            ALIAS_USER_CHAIN1 = alias(true, getChain()[1], 0);
181            ALIAS_USER_CHAIN2 = alias(true, getChain()[2], 0);
182
183            ALIAS_SYSTEM_CA3 = alias(false, CA3_WITH_CA1_SUBJECT, 0);
184            ALIAS_SYSTEM_CA3_COLLISION = alias(false, CA3_WITH_CA1_SUBJECT, 1);
185            ALIAS_USER_CA3 = alias(true, CA3_WITH_CA1_SUBJECT, 0);
186            ALIAS_USER_CA3_COLLISION = alias(true, CA3_WITH_CA1_SUBJECT, 1);
187        } catch (Exception e) {
188            throw new RuntimeException(e);
189        }
190    }
191
192    private TrustedCertificateStore store;
193
194    @Override protected void setUp() {
195        setupStore();
196    }
197
198    private void setupStore() {
199        DIR_SYSTEM.mkdirs();
200        createStore();
201    }
202
203    private void createStore() {
204        store = new TrustedCertificateStore(DIR_SYSTEM, DIR_ADDED, DIR_DELETED);
205    }
206
207    @Override protected void tearDown() {
208        cleanStore();
209    }
210
211    private void cleanStore() {
212        for (File dir : new File[] { DIR_SYSTEM, DIR_ADDED, DIR_DELETED, DIR_TEST }) {
213            File[] files = dir.listFiles();
214            if (files == null) {
215                continue;
216            }
217            for (File file : files) {
218                assertTrue(file.delete());
219            }
220        }
221        store = null;
222    }
223
224    private void resetStore() {
225        cleanStore();
226        setupStore();
227    }
228
229    public void testEmptyDirectories() throws Exception {
230        assertEmpty();
231    }
232
233    public void testOneSystemOneDeleted() throws Exception {
234        install(getCa1(), getAliasSystemCa1());
235        store.deleteCertificateEntry(getAliasSystemCa1());
236        assertEmpty();
237        assertDeleted(getCa1(), getAliasSystemCa1());
238    }
239
240    public void testTwoSystemTwoDeleted() throws Exception {
241        install(getCa1(), getAliasSystemCa1());
242        store.deleteCertificateEntry(getAliasSystemCa1());
243        install(getCa2(), getAliasSystemCa2());
244        store.deleteCertificateEntry(getAliasSystemCa2());
245        assertEmpty();
246        assertDeleted(getCa1(), getAliasSystemCa1());
247        assertDeleted(getCa2(), getAliasSystemCa2());
248    }
249
250    public void testPartialFileIsIgnored() throws Exception {
251        File file = file(getAliasSystemCa1());
252        OutputStream os = new FileOutputStream(file);
253        os.write(0);
254        os.close();
255        assertTrue(file.exists());
256        assertEmpty();
257        assertTrue(file.exists());
258    }
259
260    private void assertEmpty() throws Exception {
261        try {
262            store.getCertificate(null);
263            fail();
264        } catch (NullPointerException expected) {
265        }
266        assertNull(store.getCertificate(""));
267
268        try {
269            store.getCreationDate(null);
270            fail();
271        } catch (NullPointerException expected) {
272        }
273        assertNull(store.getCreationDate(""));
274
275        Set<String> s = store.aliases();
276        assertNotNull(s);
277        assertTrue(s.isEmpty());
278        assertAliases();
279
280        Set<String> u = store.userAliases();
281        assertNotNull(u);
282        assertTrue(u.isEmpty());
283
284        try {
285            store.containsAlias(null);
286            fail();
287        } catch (NullPointerException expected) {
288        }
289        assertFalse(store.containsAlias(""));
290
291        assertNull(store.getCertificateAlias(null));
292        assertNull(store.getCertificateAlias(getCa1()));
293
294        try {
295            store.isTrustAnchor(null);
296            fail();
297        } catch (NullPointerException expected) {
298        }
299        assertFalse(store.isTrustAnchor(getCa1()));
300
301        try {
302            store.findIssuer(null);
303            fail();
304        } catch (NullPointerException expected) {
305        }
306        assertNull(store.findIssuer(getCa1()));
307
308        try {
309            store.installCertificate(null);
310            fail();
311        } catch (NullPointerException expected) {
312        }
313
314        store.deleteCertificateEntry(null);
315        store.deleteCertificateEntry("");
316
317        String[] userFiles = DIR_ADDED.list();
318        assertTrue(userFiles == null || userFiles.length == 0);
319    }
320
321    public void testTwoSystem() throws Exception {
322        testTwo(getCa1(), getAliasSystemCa1(),
323                getCa2(), getAliasSystemCa2());
324    }
325
326    public void testTwoUser() throws Exception {
327        testTwo(getCa1(), getAliasUserCa1(),
328                getCa2(), getAliasUserCa2());
329    }
330
331    public void testOneSystemOneUser() throws Exception {
332        testTwo(getCa1(), getAliasSystemCa1(),
333                getCa2(), getAliasUserCa2());
334    }
335
336    public void testTwoSystemSameSubject() throws Exception {
337        testTwo(getCa1(), getAliasSystemCa1(),
338                getCa3WithCa1Subject(), getAliasSystemCa3Collision());
339    }
340
341    public void testTwoUserSameSubject() throws Exception {
342        testTwo(getCa1(), getAliasUserCa1(),
343                getCa3WithCa1Subject(), getAliasUserCa3Collision());
344
345        store.deleteCertificateEntry(getAliasUserCa1());
346        assertDeleted(getCa1(), getAliasUserCa1());
347        assertTombstone(getAliasUserCa1());
348        assertRootCa(getCa3WithCa1Subject(), getAliasUserCa3Collision());
349        assertAliases(getAliasUserCa3Collision());
350
351        store.deleteCertificateEntry(getAliasUserCa3Collision());
352        assertDeleted(getCa3WithCa1Subject(), getAliasUserCa3Collision());
353        assertNoTombstone(getAliasUserCa3Collision());
354        assertNoTombstone(getAliasUserCa1());
355        assertEmpty();
356    }
357
358    public void testOneSystemOneUserSameSubject() throws Exception {
359        testTwo(getCa1(), getAliasSystemCa1(),
360                getCa3WithCa1Subject(), getAliasUserCa3());
361        testTwo(getCa1(), getAliasUserCa1(),
362                getCa3WithCa1Subject(), getAliasSystemCa3());
363    }
364
365    private void testTwo(X509Certificate x1, String alias1,
366                         X509Certificate x2, String alias2) {
367        install(x1, alias1);
368        install(x2, alias2);
369        assertRootCa(x1, alias1);
370        assertRootCa(x2, alias2);
371        assertAliases(alias1, alias2);
372    }
373
374
375    public void testOneSystemOneUserOneDeleted() throws Exception {
376        install(getCa1(), getAliasSystemCa1());
377        store.installCertificate(getCa2());
378        store.deleteCertificateEntry(getAliasSystemCa1());
379        assertDeleted(getCa1(), getAliasSystemCa1());
380        assertRootCa(getCa2(), getAliasUserCa2());
381        assertAliases(getAliasUserCa2());
382    }
383
384    public void testOneSystemOneUserOneDeletedSameSubject() throws Exception {
385        install(getCa1(), getAliasSystemCa1());
386        store.installCertificate(getCa3WithCa1Subject());
387        store.deleteCertificateEntry(getAliasSystemCa1());
388        assertDeleted(getCa1(), getAliasSystemCa1());
389        assertRootCa(getCa3WithCa1Subject(), getAliasUserCa3());
390        assertAliases(getAliasUserCa3());
391    }
392
393    public void testUserMaskingSystem() throws Exception {
394        install(getCa1(), getAliasSystemCa1());
395        install(getCa1(), getAliasUserCa1());
396        assertMasked(getCa1(), getAliasSystemCa1());
397        assertRootCa(getCa1(), getAliasUserCa1());
398        assertAliases(getAliasSystemCa1(), getAliasUserCa1());
399    }
400
401    public void testChain() throws Exception {
402        testChain(getAliasSystemChain1(), getAliasSystemChain2());
403        testChain(getAliasSystemChain1(), getAliasUserChain2());
404        testChain(getAliasUserChain1(), getAliasSystemCa1());
405        testChain(getAliasUserChain1(), getAliasUserChain2());
406    }
407
408    private void testChain(String alias1, String alias2) throws Exception {
409        install(getChain()[1], alias1);
410        install(getChain()[2], alias2);
411        assertIntermediateCa(getChain()[1], alias1);
412        assertRootCa(getChain()[2], alias2);
413        assertAliases(alias1, alias2);
414        assertEquals(getChain()[2], store.findIssuer(getChain()[1]));
415        assertEquals(getChain()[1], store.findIssuer(getChain()[0]));
416
417        X509Certificate[] expected = getChain();
418        List<X509Certificate> actualList = store.getCertificateChain(expected[0]);
419
420        assertEquals("Generated CA list should be same length", expected.length, actualList.size());
421        for (int i = 0; i < expected.length; i++) {
422            assertEquals("Chain value should be the same for position " + i, expected[i],
423                    actualList.get(i));
424        }
425        resetStore();
426    }
427
428    public void testMissingSystemDirectory() throws Exception {
429        cleanStore();
430        createStore();
431        assertEmpty();
432    }
433
434    public void testWithExistingUserDirectories() throws Exception {
435        DIR_ADDED.mkdirs();
436        DIR_DELETED.mkdirs();
437        install(getCa1(), getAliasSystemCa1());
438        assertRootCa(getCa1(), getAliasSystemCa1());
439        assertAliases(getAliasSystemCa1());
440    }
441
442    public void testIsTrustAnchorWithReissuedgetCa() throws Exception {
443        PublicKey publicKey = getPrivate().getCertificate().getPublicKey();
444        PrivateKey privateKey = getPrivate().getPrivateKey();
445        String name = "CN=CA4";
446        X509Certificate ca1 = TestKeyStore.createCa(publicKey, privateKey, name);
447        Thread.sleep(1 * 1000); // wait to ensure CAs vary by expiration
448        X509Certificate ca2 = TestKeyStore.createCa(publicKey, privateKey, name);
449        assertFalse(ca1.equals(ca2));
450
451        String systemAlias = alias(false, ca1, 0);
452        install(ca1, systemAlias);
453        assertRootCa(ca1, systemAlias);
454        assertTrue(store.isTrustAnchor(ca2));
455        assertEquals(ca1, store.findIssuer(ca2));
456        resetStore();
457
458        String userAlias = alias(true, ca1, 0);
459        store.installCertificate(ca1);
460        assertRootCa(ca1, userAlias);
461        assertTrue(store.isTrustAnchor(ca2));
462        assertEquals(ca1, store.findIssuer(ca2));
463        resetStore();
464    }
465
466    public void testInstallEmpty() throws Exception {
467        store.installCertificate(getCa1());
468        assertRootCa(getCa1(), getAliasUserCa1());
469        assertAliases(getAliasUserCa1());
470
471        // reinstalling should not change anything
472        store.installCertificate(getCa1());
473        assertRootCa(getCa1(), getAliasUserCa1());
474        assertAliases(getAliasUserCa1());
475    }
476
477    public void testInstallEmptySystemExists() throws Exception {
478        install(getCa1(), getAliasSystemCa1());
479        assertRootCa(getCa1(), getAliasSystemCa1());
480        assertAliases(getAliasSystemCa1());
481
482        // reinstalling should not affect system CA
483        store.installCertificate(getCa1());
484        assertRootCa(getCa1(), getAliasSystemCa1());
485        assertAliases(getAliasSystemCa1());
486
487    }
488
489    public void testInstallEmptyDeletedSystemExists() throws Exception {
490        install(getCa1(), getAliasSystemCa1());
491        store.deleteCertificateEntry(getAliasSystemCa1());
492        assertEmpty();
493        assertDeleted(getCa1(), getAliasSystemCa1());
494
495        // installing should restore deleted system CA
496        store.installCertificate(getCa1());
497        assertRootCa(getCa1(), getAliasSystemCa1());
498        assertAliases(getAliasSystemCa1());
499    }
500
501    public void testDeleteEmpty() throws Exception {
502        store.deleteCertificateEntry(getAliasSystemCa1());
503        assertEmpty();
504        assertDeleted(getCa1(), getAliasSystemCa1());
505    }
506
507    public void testDeleteUser() throws Exception {
508        store.installCertificate(getCa1());
509        assertRootCa(getCa1(), getAliasUserCa1());
510        assertAliases(getAliasUserCa1());
511
512        store.deleteCertificateEntry(getAliasUserCa1());
513        assertEmpty();
514        assertDeleted(getCa1(), getAliasUserCa1());
515        assertNoTombstone(getAliasUserCa1());
516    }
517
518    public void testDeleteSystem() throws Exception {
519        install(getCa1(), getAliasSystemCa1());
520        assertRootCa(getCa1(), getAliasSystemCa1());
521        assertAliases(getAliasSystemCa1());
522
523        store.deleteCertificateEntry(getAliasSystemCa1());
524        assertEmpty();
525        assertDeleted(getCa1(), getAliasSystemCa1());
526
527        // deleting again should not change anything
528        store.deleteCertificateEntry(getAliasSystemCa1());
529        assertEmpty();
530        assertDeleted(getCa1(), getAliasSystemCa1());
531    }
532
533    public void testIsUserAddedCertificate() throws Exception {
534        assertFalse(store.isUserAddedCertificate(getCa1()));
535        assertFalse(store.isUserAddedCertificate(getCa2()));
536        install(getCa1(), getAliasSystemCa1());
537        assertFalse(store.isUserAddedCertificate(getCa1()));
538        assertFalse(store.isUserAddedCertificate(getCa2()));
539        install(getCa1(), getAliasUserCa1());
540        assertTrue(store.isUserAddedCertificate(getCa1()));
541        assertFalse(store.isUserAddedCertificate(getCa2()));
542        install(getCa2(), getAliasUserCa2());
543        assertTrue(store.isUserAddedCertificate(getCa1()));
544        assertTrue(store.isUserAddedCertificate(getCa2()));
545        store.deleteCertificateEntry(getAliasUserCa1());
546        assertFalse(store.isUserAddedCertificate(getCa1()));
547        assertTrue(store.isUserAddedCertificate(getCa2()));
548        store.deleteCertificateEntry(getAliasUserCa2());
549        assertFalse(store.isUserAddedCertificate(getCa1()));
550        assertFalse(store.isUserAddedCertificate(getCa2()));
551    }
552
553    private void assertRootCa(X509Certificate x, String alias) {
554        assertIntermediateCa(x, alias);
555        assertEquals(x, store.findIssuer(x));
556    }
557
558    private void assertTrusted(X509Certificate x, String alias) {
559        assertEquals(x, store.getCertificate(alias));
560        assertEquals(file(alias).lastModified(), store.getCreationDate(alias).getTime());
561        assertTrue(store.containsAlias(alias));
562        assertTrue(store.isTrustAnchor(x));
563    }
564
565    private void assertIntermediateCa(X509Certificate x, String alias) {
566        assertTrusted(x, alias);
567        assertEquals(alias, store.getCertificateAlias(x));
568    }
569
570    private void assertMasked(X509Certificate x, String alias) {
571        assertTrusted(x, alias);
572        assertFalse(alias.equals(store.getCertificateAlias(x)));
573    }
574
575    private void assertDeleted(X509Certificate x, String alias) {
576        assertNull(store.getCertificate(alias));
577        assertFalse(store.containsAlias(alias));
578        assertNull(store.getCertificateAlias(x));
579        assertFalse(store.isTrustAnchor(x));
580        assertEquals(store.allSystemAliases().contains(alias),
581                     store.getCertificate(alias, true) != null);
582    }
583
584    private void assertTombstone(String alias) {
585        assertTrue(TrustedCertificateStore.isUser(alias));
586        File file = file(alias);
587        assertTrue(file.exists());
588        assertEquals(0, file.length());
589    }
590
591    private void assertNoTombstone(String alias) {
592        assertTrue(TrustedCertificateStore.isUser(alias));
593        assertFalse(file(alias).exists());
594    }
595
596    private void assertAliases(String... aliases) {
597        Set<String> expected = new HashSet<String>(Arrays.asList(aliases));
598        Set<String> actual = new HashSet<String>();
599        for (String alias : store.aliases()) {
600            boolean system = TrustedCertificateStore.isSystem(alias);
601            boolean user = TrustedCertificateStore.isUser(alias);
602            if (system || user) {
603                assertEquals(system, store.allSystemAliases().contains(alias));
604                assertEquals(user, store.userAliases().contains(alias));
605                actual.add(alias);
606            } else {
607                throw new AssertionError(alias);
608            }
609        }
610        assertEquals(expected, actual);
611    }
612
613    /**
614     * format a certificate alias
615     */
616    private static String alias(boolean user, X509Certificate x, int index) {
617        String prefix = user ? "user:" : "system:";
618
619        X500Principal subject = x.getSubjectX500Principal();
620        int intHash = NativeCrypto.X509_NAME_hash_old(subject);
621        String strHash = IntegralToString.intToHexString(intHash, false, 8);
622
623        return prefix + strHash + '.' + index;
624    }
625
626    /**
627     * Install certificate under specified alias
628     */
629    private static void install(X509Certificate x, String alias) {
630        try {
631            File file = file(alias);
632            file.getParentFile().mkdirs();
633            OutputStream out = new FileOutputStream(file);
634            out.write(x.getEncoded());
635            out.close();
636        } catch (Exception e) {
637            throw new RuntimeException(e);
638        }
639    }
640
641    /**
642     * Compute file for an alias
643     */
644    private static File file(String alias) {
645        File dir;
646        if (TrustedCertificateStore.isSystem(alias)) {
647            dir = DIR_SYSTEM;
648        } else if (TrustedCertificateStore.isUser(alias)) {
649            dir = DIR_ADDED;
650        } else {
651            throw new IllegalArgumentException(alias);
652        }
653
654        int index = alias.lastIndexOf(":");
655        if (index == -1) {
656            throw new IllegalArgumentException(alias);
657        }
658        String filename = alias.substring(index+1);
659
660        return new File(dir, filename);
661    }
662}
663