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