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