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