1 /* 2 * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.crypto.provider; 27 28 import java.io.*; 29 import java.util.*; 30 import java.security.DigestInputStream; 31 import java.security.DigestOutputStream; 32 import java.security.MessageDigest; 33 import java.security.NoSuchAlgorithmException; 34 import java.security.Key; 35 import java.security.PrivateKey; 36 import java.security.KeyStoreSpi; 37 import java.security.KeyStoreException; 38 import java.security.UnrecoverableKeyException; 39 import java.security.cert.Certificate; 40 import java.security.cert.CertificateFactory; 41 import java.security.cert.CertificateException; 42 import javax.crypto.SealedObject; 43 44 /** 45 * This class provides the keystore implementation referred to as "jceks". 46 * This implementation strongly protects the keystore private keys using 47 * triple-DES, where the triple-DES encryption/decryption key is derived from 48 * the user's password. 49 * The encrypted private keys are stored in the keystore in a standard format, 50 * namely the <code>EncryptedPrivateKeyInfo</code> format defined in PKCS #8. 51 * 52 * @author Jan Luehe 53 * 54 * 55 * @see java.security.KeyStoreSpi 56 */ 57 58 public final class JceKeyStore extends KeyStoreSpi { 59 60 private static final int JCEKS_MAGIC = 0xcececece; 61 private static final int JKS_MAGIC = 0xfeedfeed; 62 private static final int VERSION_1 = 0x01; 63 private static final int VERSION_2 = 0x02; 64 65 // Private key and supporting certificate chain 66 private static final class PrivateKeyEntry { 67 Date date; // the creation date of this entry 68 byte[] protectedKey; 69 Certificate chain[]; 70 }; 71 72 // Secret key 73 private static final class SecretKeyEntry { 74 Date date; // the creation date of this entry 75 SealedObject sealedKey; 76 } 77 78 // Trusted certificate 79 private static final class TrustedCertEntry { 80 Date date; // the creation date of this entry 81 Certificate cert; 82 }; 83 84 /** 85 * Private keys and certificates are stored in a hashtable. 86 * Hash entries are keyed by alias names. 87 */ 88 private Hashtable<String, Object> entries = new Hashtable<String, Object>(); 89 90 /** 91 * Returns the key associated with the given alias, using the given 92 * password to recover it. 93 * 94 * @param alias the alias name 95 * @param password the password for recovering the key 96 * 97 * @return the requested key, or null if the given alias does not exist 98 * or does not identify a <i>key entry</i>. 99 * 100 * @exception NoSuchAlgorithmException if the algorithm for recovering the 101 * key cannot be found 102 * @exception UnrecoverableKeyException if the key cannot be recovered 103 * (e.g., the given password is wrong). 104 */ 105 public Key engineGetKey(String alias, char[] password) 106 throws NoSuchAlgorithmException, UnrecoverableKeyException 107 { 108 Key key = null; 109 110 Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); 111 112 if (!((entry instanceof PrivateKeyEntry) || 113 (entry instanceof SecretKeyEntry))) { 114 return null; 115 } 116 117 KeyProtector keyProtector = new KeyProtector(password); 118 119 if (entry instanceof PrivateKeyEntry) { 120 byte[] encrBytes = ((PrivateKeyEntry)entry).protectedKey; 121 EncryptedPrivateKeyInfo encrInfo; 122 try { 123 encrInfo = new EncryptedPrivateKeyInfo(encrBytes); 124 } catch (IOException ioe) { 125 throw new UnrecoverableKeyException("Private key not stored " 126 + "as PKCS #8 " + 127 "EncryptedPrivateKeyInfo"); 128 } 129 key = keyProtector.recover(encrInfo); 130 } else { 131 key = 132 keyProtector.unseal(((SecretKeyEntry)entry).sealedKey); 133 } 134 135 return key; 136 } 137 138 /** 139 * Returns the certificate chain associated with the given alias. 140 * 141 * @param alias the alias name 142 * 143 * @return the certificate chain (ordered with the user's certificate first 144 * and the root certificate authority last), or null if the given alias 145 * does not exist or does not contain a certificate chain (i.e., the given 146 * alias identifies either a <i>trusted certificate entry</i> or a 147 * <i>key entry</i> without a certificate chain). 148 */ 149 public Certificate[] engineGetCertificateChain(String alias) 150 { 151 Certificate[] chain = null; 152 153 Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); 154 155 if ((entry instanceof PrivateKeyEntry) 156 && (((PrivateKeyEntry)entry).chain != null)) { 157 chain = ((PrivateKeyEntry)entry).chain.clone(); 158 } 159 160 return chain; 161 } 162 163 /** 164 * Returns the certificate associated with the given alias. 165 * 166 * <p>If the given alias name identifies a 167 * <i>trusted certificate entry</i>, the certificate associated with that 168 * entry is returned. If the given alias name identifies a 169 * <i>key entry</i>, the first element of the certificate chain of that 170 * entry is returned, or null if that entry does not have a certificate 171 * chain. 172 * 173 * @param alias the alias name 174 * 175 * @return the certificate, or null if the given alias does not exist or 176 * does not contain a certificate. 177 */ 178 public Certificate engineGetCertificate(String alias) { 179 Certificate cert = null; 180 181 Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); 182 183 if (entry != null) { 184 if (entry instanceof TrustedCertEntry) { 185 cert = ((TrustedCertEntry)entry).cert; 186 } else if ((entry instanceof PrivateKeyEntry) && 187 (((PrivateKeyEntry)entry).chain != null)) { 188 cert = ((PrivateKeyEntry)entry).chain[0]; 189 } 190 } 191 192 return cert; 193 } 194 195 /** 196 * Returns the creation date of the entry identified by the given alias. 197 * 198 * @param alias the alias name 199 * 200 * @return the creation date of this entry, or null if the given alias does 201 * not exist 202 */ 203 public Date engineGetCreationDate(String alias) { 204 Date date = null; 205 206 Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); 207 208 if (entry != null) { 209 // We have to create a new instance of java.util.Date because 210 // dates are not immutable 211 if (entry instanceof TrustedCertEntry) { 212 date = new Date(((TrustedCertEntry)entry).date.getTime()); 213 } else if (entry instanceof PrivateKeyEntry) { 214 date = new Date(((PrivateKeyEntry)entry).date.getTime()); 215 } else { 216 date = new Date(((SecretKeyEntry)entry).date.getTime()); 217 } 218 } 219 220 return date; 221 } 222 223 /** 224 * Assigns the given key to the given alias, protecting it with the given 225 * password. 226 * 227 * <p>If the given key is of type <code>java.security.PrivateKey</code>, 228 * it must be accompanied by a certificate chain certifying the 229 * corresponding public key. 230 * 231 * <p>If the given alias already exists, the keystore information 232 * associated with it is overridden by the given key (and possibly 233 * certificate chain). 234 * 235 * @param alias the alias name 236 * @param key the key to be associated with the alias 237 * @param password the password to protect the key 238 * @param chain the certificate chain for the corresponding public 239 * key (only required if the given key is of type 240 * <code>java.security.PrivateKey</code>). 241 * 242 * @exception KeyStoreException if the given key cannot be protected, or 243 * this operation fails for some other reason 244 */ 245 public void engineSetKeyEntry(String alias, Key key, char[] password, 246 Certificate[] chain) 247 throws KeyStoreException 248 { 249 synchronized(entries) { 250 try { 251 KeyProtector keyProtector = new KeyProtector(password); 252 253 if (key instanceof PrivateKey) { 254 PrivateKeyEntry entry = new PrivateKeyEntry(); 255 entry.date = new Date(); 256 257 // protect the private key 258 entry.protectedKey = keyProtector.protect((PrivateKey)key); 259 260 // clone the chain 261 if ((chain != null) && 262 (chain.length !=0)) { 263 entry.chain = chain.clone(); 264 } else { 265 entry.chain = null; 266 } 267 268 // store the entry 269 entries.put(alias.toLowerCase(Locale.ENGLISH), entry); 270 271 } else { 272 SecretKeyEntry entry = new SecretKeyEntry(); 273 entry.date = new Date(); 274 275 // seal and store the key 276 entry.sealedKey = keyProtector.seal(key); 277 entries.put(alias.toLowerCase(Locale.ENGLISH), entry); 278 } 279 280 } catch (Exception e) { 281 throw new KeyStoreException(e.getMessage()); 282 } 283 } 284 } 285 286 /** 287 * Assigns the given key (that has already been protected) to the given 288 * alias. 289 * 290 * <p>If the protected key is of type 291 * <code>java.security.PrivateKey</code>, 292 * it must be accompanied by a certificate chain certifying the 293 * corresponding public key. 294 * 295 * <p>If the given alias already exists, the keystore information 296 * associated with it is overridden by the given key (and possibly 297 * certificate chain). 298 * 299 * @param alias the alias name 300 * @param key the key (in protected format) to be associated with the alias 301 * @param chain the certificate chain for the corresponding public 302 * key (only useful if the protected key is of type 303 * <code>java.security.PrivateKey</code>). 304 * 305 * @exception KeyStoreException if this operation fails. 306 */ 307 public void engineSetKeyEntry(String alias, byte[] key, 308 Certificate[] chain) 309 throws KeyStoreException 310 { 311 synchronized(entries) { 312 // We assume it's a private key, because there is no standard 313 // (ASN.1) encoding format for wrapped secret keys 314 PrivateKeyEntry entry = new PrivateKeyEntry(); 315 entry.date = new Date(); 316 317 entry.protectedKey = key.clone(); 318 if ((chain != null) && 319 (chain.length != 0)) { 320 entry.chain = chain.clone(); 321 } else { 322 entry.chain = null; 323 } 324 325 entries.put(alias.toLowerCase(Locale.ENGLISH), entry); 326 } 327 } 328 329 /** 330 * Assigns the given certificate to the given alias. 331 * 332 * <p>If the given alias already exists in this keystore and identifies a 333 * <i>trusted certificate entry</i>, the certificate associated with it is 334 * overridden by the given certificate. 335 * 336 * @param alias the alias name 337 * @param cert the certificate 338 * 339 * @exception KeyStoreException if the given alias already exists and does 340 * not identify a <i>trusted certificate entry</i>, or this operation 341 * fails for some other reason. 342 */ 343 public void engineSetCertificateEntry(String alias, Certificate cert) 344 throws KeyStoreException 345 { 346 synchronized(entries) { 347 348 Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); 349 if (entry != null) { 350 if (entry instanceof PrivateKeyEntry) { 351 throw new KeyStoreException("Cannot overwrite own " 352 + "certificate"); 353 } else if (entry instanceof SecretKeyEntry) { 354 throw new KeyStoreException("Cannot overwrite secret key"); 355 } 356 } 357 358 TrustedCertEntry trustedCertEntry = new TrustedCertEntry(); 359 trustedCertEntry.cert = cert; 360 trustedCertEntry.date = new Date(); 361 entries.put(alias.toLowerCase(Locale.ENGLISH), trustedCertEntry); 362 } 363 } 364 365 /** 366 * Deletes the entry identified by the given alias from this keystore. 367 * 368 * @param alias the alias name 369 * 370 * @exception KeyStoreException if the entry cannot be removed. 371 */ 372 public void engineDeleteEntry(String alias) 373 throws KeyStoreException 374 { 375 synchronized(entries) { 376 entries.remove(alias.toLowerCase(Locale.ENGLISH)); 377 } 378 } 379 380 /** 381 * Lists all the alias names of this keystore. 382 * 383 * @return enumeration of the alias names 384 */ 385 public Enumeration<String> engineAliases() { 386 return entries.keys(); 387 } 388 389 /** 390 * Checks if the given alias exists in this keystore. 391 * 392 * @param alias the alias name 393 * 394 * @return true if the alias exists, false otherwise 395 */ 396 public boolean engineContainsAlias(String alias) { 397 return entries.containsKey(alias.toLowerCase(Locale.ENGLISH)); 398 } 399 400 /** 401 * Retrieves the number of entries in this keystore. 402 * 403 * @return the number of entries in this keystore 404 */ 405 public int engineSize() { 406 return entries.size(); 407 } 408 409 /** 410 * Returns true if the entry identified by the given alias is a 411 * <i>key entry</i>, and false otherwise. 412 * 413 * @return true if the entry identified by the given alias is a 414 * <i>key entry</i>, false otherwise. 415 */ 416 public boolean engineIsKeyEntry(String alias) { 417 boolean isKey = false; 418 419 Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); 420 if ((entry instanceof PrivateKeyEntry) 421 || (entry instanceof SecretKeyEntry)) { 422 isKey = true; 423 } 424 425 return isKey; 426 } 427 428 /** 429 * Returns true if the entry identified by the given alias is a 430 * <i>trusted certificate entry</i>, and false otherwise. 431 * 432 * @return true if the entry identified by the given alias is a 433 * <i>trusted certificate entry</i>, false otherwise. 434 */ 435 public boolean engineIsCertificateEntry(String alias) { 436 boolean isCert = false; 437 Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); 438 if (entry instanceof TrustedCertEntry) { 439 isCert = true; 440 } 441 return isCert; 442 } 443 444 /** 445 * Returns the (alias) name of the first keystore entry whose certificate 446 * matches the given certificate. 447 * 448 * <p>This method attempts to match the given certificate with each 449 * keystore entry. If the entry being considered 450 * is a <i>trusted certificate entry</i>, the given certificate is 451 * compared to that entry's certificate. If the entry being considered is 452 * a <i>key entry</i>, the given certificate is compared to the first 453 * element of that entry's certificate chain (if a chain exists). 454 * 455 * @param cert the certificate to match with. 456 * 457 * @return the (alias) name of the first entry with matching certificate, 458 * or null if no such entry exists in this keystore. 459 */ 460 public String engineGetCertificateAlias(Certificate cert) { 461 Certificate certElem; 462 463 Enumeration<String> e = entries.keys(); 464 while (e.hasMoreElements()) { 465 String alias = e.nextElement(); 466 Object entry = entries.get(alias); 467 if (entry instanceof TrustedCertEntry) { 468 certElem = ((TrustedCertEntry)entry).cert; 469 } else if ((entry instanceof PrivateKeyEntry) && 470 (((PrivateKeyEntry)entry).chain != null)) { 471 certElem = ((PrivateKeyEntry)entry).chain[0]; 472 } else { 473 continue; 474 } 475 if (certElem.equals(cert)) { 476 return alias; 477 } 478 } 479 return null; 480 } 481 482 /** 483 * Stores this keystore to the given output stream, and protects its 484 * integrity with the given password. 485 * 486 * @param stream the output stream to which this keystore is written. 487 * @param password the password to generate the keystore integrity check 488 * 489 * @exception IOException if there was an I/O problem with data 490 * @exception NoSuchAlgorithmException if the appropriate data integrity 491 * algorithm could not be found 492 * @exception CertificateException if any of the certificates included in 493 * the keystore data could not be stored 494 */ 495 public void engineStore(OutputStream stream, char[] password) 496 throws IOException, NoSuchAlgorithmException, CertificateException 497 { 498 synchronized(entries) { 499 /* 500 * KEYSTORE FORMAT: 501 * 502 * Magic number (big-endian integer), 503 * Version of this file format (big-endian integer), 504 * 505 * Count (big-endian integer), 506 * followed by "count" instances of either: 507 * 508 * { 509 * tag=1 (big-endian integer) 510 * alias (UTF string) 511 * timestamp 512 * encrypted private-key info according to PKCS #8 513 * (integer length followed by encoding) 514 * cert chain (integer count followed by certs; 515 * for each cert: type UTF string, followed by integer 516 * length, followed by encoding) 517 * } 518 * 519 * or: 520 * 521 * { 522 * tag=2 (big-endian integer) 523 * alias (UTF string) 524 * timestamp 525 * cert (type UTF string, followed by integer length, 526 * followed by encoding) 527 * } 528 * 529 * or: 530 * 531 * { 532 * tag=3 (big-endian integer) 533 * alias (UTF string) 534 * timestamp 535 * sealed secret key (in serialized form) 536 * } 537 * 538 * ended by a keyed SHA1 hash (bytes only) of 539 * { password + whitener + preceding body } 540 */ 541 542 // password is mandatory when storing 543 if (password == null) { 544 throw new IllegalArgumentException("password can't be null"); 545 } 546 547 byte[] encoded; // the certificate encoding 548 549 MessageDigest md = getPreKeyedHash(password); 550 DataOutputStream dos 551 = new DataOutputStream(new DigestOutputStream(stream, md)); 552 // NOTE: don't pass dos to oos at this point or it'll corrupt 553 // the keystore!!! 554 ObjectOutputStream oos = null; 555 try { 556 dos.writeInt(JCEKS_MAGIC); 557 dos.writeInt(VERSION_2); // always write the latest version 558 559 dos.writeInt(entries.size()); 560 561 Enumeration<String> e = entries.keys(); 562 while (e.hasMoreElements()) { 563 564 String alias = e.nextElement(); 565 Object entry = entries.get(alias); 566 567 if (entry instanceof PrivateKeyEntry) { 568 569 PrivateKeyEntry pentry = (PrivateKeyEntry)entry; 570 571 // write PrivateKeyEntry tag 572 dos.writeInt(1); 573 574 // write the alias 575 dos.writeUTF(alias); 576 577 // write the (entry creation) date 578 dos.writeLong(pentry.date.getTime()); 579 580 // write the protected private key 581 dos.writeInt(pentry.protectedKey.length); 582 dos.write(pentry.protectedKey); 583 584 // write the certificate chain 585 int chainLen; 586 if (pentry.chain == null) { 587 chainLen = 0; 588 } else { 589 chainLen = pentry.chain.length; 590 } 591 dos.writeInt(chainLen); 592 for (int i = 0; i < chainLen; i++) { 593 encoded = pentry.chain[i].getEncoded(); 594 dos.writeUTF(pentry.chain[i].getType()); 595 dos.writeInt(encoded.length); 596 dos.write(encoded); 597 } 598 599 } else if (entry instanceof TrustedCertEntry) { 600 601 // write TrustedCertEntry tag 602 dos.writeInt(2); 603 604 // write the alias 605 dos.writeUTF(alias); 606 607 // write the (entry creation) date 608 dos.writeLong(((TrustedCertEntry)entry).date.getTime()); 609 610 // write the trusted certificate 611 encoded = ((TrustedCertEntry)entry).cert.getEncoded(); 612 dos.writeUTF(((TrustedCertEntry)entry).cert.getType()); 613 dos.writeInt(encoded.length); 614 dos.write(encoded); 615 616 } else { 617 618 // write SecretKeyEntry tag 619 dos.writeInt(3); 620 621 // write the alias 622 dos.writeUTF(alias); 623 624 // write the (entry creation) date 625 dos.writeLong(((SecretKeyEntry)entry).date.getTime()); 626 627 // write the sealed key 628 oos = new ObjectOutputStream(dos); 629 oos.writeObject(((SecretKeyEntry)entry).sealedKey); 630 // NOTE: don't close oos here since we are still 631 // using dos!!! 632 } 633 } 634 635 /* 636 * Write the keyed hash which is used to detect tampering with 637 * the keystore (such as deleting or modifying key or 638 * certificate entries). 639 */ 640 byte digest[] = md.digest(); 641 642 dos.write(digest); 643 dos.flush(); 644 } finally { 645 if (oos != null) { 646 oos.close(); 647 } else { 648 dos.close(); 649 } 650 } 651 } 652 } 653 654 /** 655 * Loads the keystore from the given input stream. 656 * 657 * <p>If a password is given, it is used to check the integrity of the 658 * keystore data. Otherwise, the integrity of the keystore is not checked. 659 * 660 * @param stream the input stream from which the keystore is loaded 661 * @param password the (optional) password used to check the integrity of 662 * the keystore. 663 * 664 * @exception IOException if there is an I/O or format problem with the 665 * keystore data 666 * @exception NoSuchAlgorithmException if the algorithm used to check 667 * the integrity of the keystore cannot be found 668 * @exception CertificateException if any of the certificates in the 669 * keystore could not be loaded 670 */ 671 public void engineLoad(InputStream stream, char[] password) 672 throws IOException, NoSuchAlgorithmException, CertificateException 673 { 674 synchronized(entries) { 675 DataInputStream dis; 676 MessageDigest md = null; 677 CertificateFactory cf = null; 678 Hashtable<String, CertificateFactory> cfs = null; 679 ByteArrayInputStream bais = null; 680 byte[] encoded = null; 681 682 if (stream == null) 683 return; 684 685 if (password != null) { 686 md = getPreKeyedHash(password); 687 dis = new DataInputStream(new DigestInputStream(stream, md)); 688 } else { 689 dis = new DataInputStream(stream); 690 } 691 // NOTE: don't pass dis to ois at this point or it'll fail to load 692 // the keystore!!! 693 ObjectInputStream ois = null; 694 695 try { 696 // Body format: see store method 697 698 int xMagic = dis.readInt(); 699 int xVersion = dis.readInt(); 700 701 // Accept the following keystore implementations: 702 // - JCEKS (this implementation), versions 1 and 2 703 // - JKS (Sun's keystore implementation in JDK 1.2), 704 // versions 1 and 2 705 if (((xMagic != JCEKS_MAGIC) && (xMagic != JKS_MAGIC)) || 706 ((xVersion != VERSION_1) && (xVersion != VERSION_2))) { 707 throw new IOException("Invalid keystore format"); 708 } 709 710 if (xVersion == VERSION_1) { 711 cf = CertificateFactory.getInstance("X509"); 712 } else { 713 // version 2 714 cfs = new Hashtable<>(3); 715 } 716 717 entries.clear(); 718 int count = dis.readInt(); 719 720 for (int i = 0; i < count; i++) { 721 int tag; 722 String alias; 723 724 tag = dis.readInt(); 725 726 if (tag == 1) { // private-key entry 727 728 PrivateKeyEntry entry = new PrivateKeyEntry(); 729 730 // read the alias 731 alias = dis.readUTF(); 732 733 // read the (entry creation) date 734 entry.date = new Date(dis.readLong()); 735 736 // read the private key 737 try { 738 entry.protectedKey = new byte[dis.readInt()]; 739 } catch (OutOfMemoryError e) { 740 throw new IOException("Keysize too big"); 741 } 742 dis.readFully(entry.protectedKey); 743 744 // read the certificate chain 745 int numOfCerts = dis.readInt(); 746 try { 747 if (numOfCerts > 0) { 748 entry.chain = new Certificate[numOfCerts]; 749 } 750 } catch (OutOfMemoryError e) { 751 throw new IOException("Too many certificates in " 752 + "chain"); 753 } 754 for (int j = 0; j < numOfCerts; j++) { 755 if (xVersion == 2) { 756 // read the certificate type, and instantiate a 757 // certificate factory of that type (reuse 758 // existing factory if possible) 759 String certType = dis.readUTF(); 760 if (cfs.containsKey(certType)) { 761 // reuse certificate factory 762 cf = cfs.get(certType); 763 } else { 764 // create new certificate factory 765 cf = CertificateFactory.getInstance( 766 certType); 767 // store the certificate factory so we can 768 // reuse it later 769 cfs.put(certType, cf); 770 } 771 } 772 // instantiate the certificate 773 try { 774 encoded = new byte[dis.readInt()]; 775 } catch (OutOfMemoryError e) { 776 throw new IOException("Certificate too big"); 777 } 778 dis.readFully(encoded); 779 bais = new ByteArrayInputStream(encoded); 780 entry.chain[j] = cf.generateCertificate(bais); 781 } 782 783 // Add the entry to the list 784 entries.put(alias, entry); 785 786 } else if (tag == 2) { // trusted certificate entry 787 788 TrustedCertEntry entry = new TrustedCertEntry(); 789 790 // read the alias 791 alias = dis.readUTF(); 792 793 // read the (entry creation) date 794 entry.date = new Date(dis.readLong()); 795 796 // read the trusted certificate 797 if (xVersion == 2) { 798 // read the certificate type, and instantiate a 799 // certificate factory of that type (reuse 800 // existing factory if possible) 801 String certType = dis.readUTF(); 802 if (cfs.containsKey(certType)) { 803 // reuse certificate factory 804 cf = cfs.get(certType); 805 } else { 806 // create new certificate factory 807 cf = CertificateFactory.getInstance(certType); 808 // store the certificate factory so we can 809 // reuse it later 810 cfs.put(certType, cf); 811 } 812 } 813 try { 814 encoded = new byte[dis.readInt()]; 815 } catch (OutOfMemoryError e) { 816 throw new IOException("Certificate too big"); 817 } 818 dis.readFully(encoded); 819 bais = new ByteArrayInputStream(encoded); 820 entry.cert = cf.generateCertificate(bais); 821 822 // Add the entry to the list 823 entries.put(alias, entry); 824 825 } else if (tag == 3) { // secret-key entry 826 827 SecretKeyEntry entry = new SecretKeyEntry(); 828 829 // read the alias 830 alias = dis.readUTF(); 831 832 // read the (entry creation) date 833 entry.date = new Date(dis.readLong()); 834 835 // read the sealed key 836 try { 837 ois = new ObjectInputStream(dis); 838 entry.sealedKey = (SealedObject)ois.readObject(); 839 // NOTE: don't close ois here since we are still 840 // using dis!!! 841 } catch (ClassNotFoundException cnfe) { 842 throw new IOException(cnfe.getMessage()); 843 } 844 845 // Add the entry to the list 846 entries.put(alias, entry); 847 848 } else { 849 throw new IOException("Unrecognized keystore entry"); 850 } 851 } 852 853 /* 854 * If a password has been provided, we check the keyed digest 855 * at the end. If this check fails, the store has been tampered 856 * with 857 */ 858 if (password != null) { 859 byte computed[], actual[]; 860 computed = md.digest(); 861 actual = new byte[computed.length]; 862 dis.readFully(actual); 863 for (int i = 0; i < computed.length; i++) { 864 if (computed[i] != actual[i]) { 865 throw new IOException( 866 "Keystore was tampered with, or " 867 + "password was incorrect"); 868 } 869 } 870 } 871 } finally { 872 if (ois != null) { 873 ois.close(); 874 } else { 875 dis.close(); 876 } 877 } 878 } 879 } 880 881 /** 882 * To guard against tampering with the keystore, we append a keyed 883 * hash with a bit of whitener. 884 */ 885 private MessageDigest getPreKeyedHash(char[] password) 886 throws NoSuchAlgorithmException, UnsupportedEncodingException { 887 int i, j; 888 889 MessageDigest md = MessageDigest.getInstance("SHA"); 890 byte[] passwdBytes = new byte[password.length * 2]; 891 for (i=0, j=0; i<password.length; i++) { 892 passwdBytes[j++] = (byte)(password[i] >> 8); 893 passwdBytes[j++] = (byte)password[i]; 894 } 895 md.update(passwdBytes); 896 for (i=0; i<passwdBytes.length; i++) 897 passwdBytes[i] = 0; 898 md.update("Mighty Aphrodite".getBytes("UTF8")); 899 return md; 900 } 901 902 /** 903 * Probe the first few bytes of the keystore data stream for a valid 904 * JCEKS keystore encoding. 905 */ 906 @Override 907 public boolean engineProbe(InputStream stream) throws IOException { 908 DataInputStream dataStream; 909 if (stream instanceof DataInputStream) { 910 dataStream = (DataInputStream)stream; 911 } else { 912 dataStream = new DataInputStream(stream); 913 } 914 915 return JCEKS_MAGIC == dataStream.readInt(); 916 } 917 }