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