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