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