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