1 /* 2 * Copyright (c) 1997, 2018, 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 sun.security.provider; 27 28 import java.io.*; 29 import java.security.*; 30 import java.security.cert.Certificate; 31 import java.security.cert.CertificateFactory; 32 import java.security.cert.CertificateException; 33 import java.util.*; 34 35 import sun.misc.IOUtils; 36 import sun.security.pkcs.EncryptedPrivateKeyInfo; 37 import sun.security.pkcs12.PKCS12KeyStore; 38 39 /** 40 * This class provides the keystore implementation referred to as "JKS". 41 * 42 * @author Jan Luehe 43 * @author David Brownell 44 * 45 * 46 * @see KeyProtector 47 * @see java.security.KeyStoreSpi 48 * @see KeyTool 49 * 50 * @since 1.2 51 */ 52 53 abstract class JavaKeyStore extends KeyStoreSpi { 54 55 // regular JKS 56 public static final class JKS extends JavaKeyStore { 57 String convertAlias(String alias) { 58 return alias.toLowerCase(Locale.ENGLISH); 59 } 60 } 61 62 // special JKS that uses case sensitive aliases 63 public static final class CaseExactJKS extends JavaKeyStore { 64 String convertAlias(String alias) { 65 return alias; 66 } 67 } 68 69 // special JKS that supports JKS and PKCS12 file formats 70 public static final class DualFormatJKS extends KeyStoreDelegator { 71 public DualFormatJKS() { 72 super("JKS", JKS.class, "PKCS12", PKCS12KeyStore.class); 73 } 74 } 75 76 private static final int MAGIC = 0xfeedfeed; 77 private static final int VERSION_1 = 0x01; 78 private static final int VERSION_2 = 0x02; 79 80 // Private keys and their supporting certificate chains 81 private static class KeyEntry { 82 Date date; // the creation date of this entry 83 byte[] protectedPrivKey; 84 Certificate chain[]; 85 }; 86 87 // Trusted certificates 88 private static class TrustedCertEntry { 89 Date date; // the creation date of this entry 90 Certificate cert; 91 }; 92 93 /** 94 * Private keys and certificates are stored in a hashtable. 95 * Hash entries are keyed by alias names. 96 */ 97 private final Hashtable<String, Object> entries; 98 99 JavaKeyStore() { 100 entries = new Hashtable<String, Object>(); 101 } 102 103 // convert an alias to internal form, overridden in subclasses: 104 // lower case for regular JKS 105 // original string for CaseExactJKS 106 abstract String convertAlias(String alias); 107 108 /** 109 * Returns the key associated with the given alias, using the given 110 * password to recover it. 111 * 112 * @param alias the alias name 113 * @param password the password for recovering the key 114 * 115 * @return the requested key, or null if the given alias does not exist 116 * or does not identify a <i>key entry</i>. 117 * 118 * @exception NoSuchAlgorithmException if the algorithm for recovering the 119 * key cannot be found 120 * @exception UnrecoverableKeyException if the key cannot be recovered 121 * (e.g., the given password is wrong). 122 */ 123 public Key engineGetKey(String alias, char[] password) 124 throws NoSuchAlgorithmException, UnrecoverableKeyException 125 { 126 Object entry = entries.get(convertAlias(alias)); 127 128 if (entry == null || !(entry instanceof KeyEntry)) { 129 return null; 130 } 131 if (password == null) { 132 throw new UnrecoverableKeyException("Password must not be null"); 133 } 134 135 byte[] passwordBytes = convertToBytes(password); 136 KeyProtector keyProtector = new KeyProtector(passwordBytes); 137 byte[] encrBytes = ((KeyEntry)entry).protectedPrivKey; 138 EncryptedPrivateKeyInfo encrInfo; 139 try { 140 encrInfo = new EncryptedPrivateKeyInfo(encrBytes); 141 return keyProtector.recover(encrInfo); 142 } catch (IOException ioe) { 143 throw new UnrecoverableKeyException("Private key not stored as " 144 + "PKCS #8 " 145 + "EncryptedPrivateKeyInfo"); 146 } finally { 147 Arrays.fill(passwordBytes, (byte) 0x00); 148 } 149 } 150 151 /** 152 * Returns the certificate chain associated with the given alias. 153 * 154 * @param alias the alias name 155 * 156 * @return the certificate chain (ordered with the user's certificate first 157 * and the root certificate authority last), or null if the given alias 158 * does not exist or does not contain a certificate chain (i.e., the given 159 * alias identifies either a <i>trusted certificate entry</i> or a 160 * <i>key entry</i> without a certificate chain). 161 */ 162 public Certificate[] engineGetCertificateChain(String alias) { 163 Object entry = entries.get(convertAlias(alias)); 164 165 if (entry != null && entry instanceof KeyEntry) { 166 if (((KeyEntry)entry).chain == null) { 167 return null; 168 } else { 169 return ((KeyEntry)entry).chain.clone(); 170 } 171 } else { 172 return null; 173 } 174 } 175 176 /** 177 * Returns the certificate associated with the given alias. 178 * 179 * <p>If the given alias name identifies a 180 * <i>trusted certificate entry</i>, the certificate associated with that 181 * entry is returned. If the given alias name identifies a 182 * <i>key entry</i>, the first element of the certificate chain of that 183 * entry is returned, or null if that entry does not have a certificate 184 * chain. 185 * 186 * @param alias the alias name 187 * 188 * @return the certificate, or null if the given alias does not exist or 189 * does not contain a certificate. 190 */ 191 public Certificate engineGetCertificate(String alias) { 192 Object entry = entries.get(convertAlias(alias)); 193 194 if (entry != null) { 195 if (entry instanceof TrustedCertEntry) { 196 return ((TrustedCertEntry)entry).cert; 197 } else { 198 if (((KeyEntry)entry).chain == null) { 199 return null; 200 } else { 201 return ((KeyEntry)entry).chain[0]; 202 } 203 } 204 } else { 205 return null; 206 } 207 } 208 209 /** 210 * Returns the creation date of the entry identified by the given alias. 211 * 212 * @param alias the alias name 213 * 214 * @return the creation date of this entry, or null if the given alias does 215 * not exist 216 */ 217 public Date engineGetCreationDate(String alias) { 218 Object entry = entries.get(convertAlias(alias)); 219 220 if (entry != null) { 221 if (entry instanceof TrustedCertEntry) { 222 return new Date(((TrustedCertEntry)entry).date.getTime()); 223 } else { 224 return new Date(((KeyEntry)entry).date.getTime()); 225 } 226 } else { 227 return null; 228 } 229 } 230 231 /** 232 * Assigns the given private key to the given alias, protecting 233 * it with the given password as defined in PKCS8. 234 * 235 * <p>The given java.security.PrivateKey <code>key</code> must 236 * be accompanied by a certificate chain certifying the 237 * corresponding public key. 238 * 239 * <p>If the given alias already exists, the keystore information 240 * associated with it is overridden by the given key and certificate 241 * chain. 242 * 243 * @param alias the alias name 244 * @param key the private key to be associated with the alias 245 * @param password the password to protect the key 246 * @param chain the certificate chain for the corresponding public 247 * key (only required if the given key is of type 248 * <code>java.security.PrivateKey</code>). 249 * 250 * @exception KeyStoreException if the given key is not a private key, 251 * cannot be protected, or this operation fails for some other reason 252 */ 253 public void engineSetKeyEntry(String alias, Key key, char[] password, 254 Certificate[] chain) 255 throws KeyStoreException 256 { 257 KeyProtector keyProtector; 258 byte[] passwordBytes = null; 259 260 if (!(key instanceof java.security.PrivateKey)) { 261 throw new KeyStoreException("Cannot store non-PrivateKeys"); 262 } 263 try { 264 synchronized(entries) { 265 KeyEntry entry = new KeyEntry(); 266 entry.date = new Date(); 267 268 // Protect the encoding of the key 269 passwordBytes = convertToBytes(password); 270 keyProtector = new KeyProtector(passwordBytes); 271 entry.protectedPrivKey = keyProtector.protect(key); 272 273 // clone the chain 274 if ((chain != null) && 275 (chain.length != 0)) { 276 entry.chain = chain.clone(); 277 } else { 278 entry.chain = null; 279 } 280 281 entries.put(convertAlias(alias), entry); 282 } 283 } catch (NoSuchAlgorithmException nsae) { 284 throw new KeyStoreException("Key protection algorithm not found"); 285 } finally { 286 if (passwordBytes != null) 287 Arrays.fill(passwordBytes, (byte) 0x00); 288 } 289 } 290 291 /** 292 * Assigns the given key (that has already been protected) to the given 293 * alias. 294 * 295 * <p>If the protected key is of type 296 * <code>java.security.PrivateKey</code>, it must be accompanied by a 297 * certificate chain certifying the corresponding public key. If the 298 * underlying keystore implementation is of type <code>jks</code>, 299 * <code>key</code> must be encoded as an 300 * <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard. 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 // key must be encoded as EncryptedPrivateKeyInfo as defined in 320 // PKCS#8 321 try { 322 new EncryptedPrivateKeyInfo(key); 323 } catch (IOException ioe) { 324 throw new KeyStoreException("key is not encoded as " 325 + "EncryptedPrivateKeyInfo"); 326 } 327 328 KeyEntry entry = new KeyEntry(); 329 entry.date = new Date(); 330 331 entry.protectedPrivKey = key.clone(); 332 if ((chain != null) && 333 (chain.length != 0)) { 334 entry.chain = chain.clone(); 335 } else { 336 entry.chain = null; 337 } 338 339 entries.put(convertAlias(alias), entry); 340 } 341 } 342 343 /** 344 * Assigns the given certificate to the given alias. 345 * 346 * <p>If the given alias already exists in this keystore and identifies a 347 * <i>trusted certificate entry</i>, the certificate associated with it is 348 * overridden by the given certificate. 349 * 350 * @param alias the alias name 351 * @param cert the certificate 352 * 353 * @exception KeyStoreException if the given alias already exists and does 354 * not identify a <i>trusted certificate entry</i>, or this operation 355 * fails for some other reason. 356 */ 357 public void engineSetCertificateEntry(String alias, Certificate cert) 358 throws KeyStoreException 359 { 360 synchronized(entries) { 361 362 Object entry = entries.get(convertAlias(alias)); 363 if ((entry != null) && (entry instanceof KeyEntry)) { 364 throw new KeyStoreException 365 ("Cannot overwrite own certificate"); 366 } 367 368 TrustedCertEntry trustedCertEntry = new TrustedCertEntry(); 369 trustedCertEntry.cert = cert; 370 trustedCertEntry.date = new Date(); 371 entries.put(convertAlias(alias), trustedCertEntry); 372 } 373 } 374 375 /** 376 * Deletes the entry identified by the given alias from this keystore. 377 * 378 * @param alias the alias name 379 * 380 * @exception KeyStoreException if the entry cannot be removed. 381 */ 382 public void engineDeleteEntry(String alias) 383 throws KeyStoreException 384 { 385 synchronized(entries) { 386 entries.remove(convertAlias(alias)); 387 } 388 } 389 390 /** 391 * Lists all the alias names of this keystore. 392 * 393 * @return enumeration of the alias names 394 */ 395 public Enumeration<String> engineAliases() { 396 return entries.keys(); 397 } 398 399 /** 400 * Checks if the given alias exists in this keystore. 401 * 402 * @param alias the alias name 403 * 404 * @return true if the alias exists, false otherwise 405 */ 406 public boolean engineContainsAlias(String alias) { 407 return entries.containsKey(convertAlias(alias)); 408 } 409 410 /** 411 * Retrieves the number of entries in this keystore. 412 * 413 * @return the number of entries in this keystore 414 */ 415 public int engineSize() { 416 return entries.size(); 417 } 418 419 /** 420 * Returns true if the entry identified by the given alias is a 421 * <i>key entry</i>, and false otherwise. 422 * 423 * @return true if the entry identified by the given alias is a 424 * <i>key entry</i>, false otherwise. 425 */ 426 public boolean engineIsKeyEntry(String alias) { 427 Object entry = entries.get(convertAlias(alias)); 428 if ((entry != null) && (entry instanceof KeyEntry)) { 429 return true; 430 } else { 431 return false; 432 } 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 Object entry = entries.get(convertAlias(alias)); 444 if ((entry != null) && (entry instanceof TrustedCertEntry)) { 445 return true; 446 } else { 447 return false; 448 } 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 for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) { 471 String alias = e.nextElement(); 472 Object entry = entries.get(alias); 473 if (entry instanceof TrustedCertEntry) { 474 certElem = ((TrustedCertEntry)entry).cert; 475 } else if (((KeyEntry)entry).chain != null) { 476 certElem = ((KeyEntry)entry).chain[0]; 477 } else { 478 continue; 479 } 480 if (certElem.equals(cert)) { 481 return alias; 482 } 483 } 484 return null; 485 } 486 487 /** 488 * Stores this keystore to the given output stream, and protects its 489 * integrity with the given password. 490 * 491 * @param stream the output stream to which this keystore is written. 492 * @param password the password to generate the keystore integrity check 493 * 494 * @exception IOException if there was an I/O problem with data 495 * @exception NoSuchAlgorithmException if the appropriate data integrity 496 * algorithm could not be found 497 * @exception CertificateException if any of the certificates included in 498 * the keystore data could not be stored 499 */ 500 public void engineStore(OutputStream stream, char[] password) 501 throws IOException, NoSuchAlgorithmException, CertificateException 502 { 503 synchronized(entries) { 504 /* 505 * KEYSTORE FORMAT: 506 * 507 * Magic number (big-endian integer), 508 * Version of this file format (big-endian integer), 509 * 510 * Count (big-endian integer), 511 * followed by "count" instances of either: 512 * 513 * { 514 * tag=1 (big-endian integer), 515 * alias (UTF string) 516 * timestamp 517 * encrypted private-key info according to PKCS #8 518 * (integer length followed by encoding) 519 * cert chain (integer count, then certs; for each cert, 520 * integer length followed by encoding) 521 * } 522 * 523 * or: 524 * 525 * { 526 * tag=2 (big-endian integer) 527 * alias (UTF string) 528 * timestamp 529 * cert (integer length followed by encoding) 530 * } 531 * 532 * ended by a keyed SHA1 hash (bytes only) of 533 * { password + whitener + preceding body } 534 */ 535 536 // password is mandatory when storing 537 if (password == null) { 538 throw new IllegalArgumentException("password can't be null"); 539 } 540 541 byte[] encoded; // the certificate encoding 542 543 MessageDigest md = getPreKeyedHash(password); 544 DataOutputStream dos 545 = new DataOutputStream(new DigestOutputStream(stream, md)); 546 547 dos.writeInt(MAGIC); 548 // always write the latest version 549 dos.writeInt(VERSION_2); 550 551 dos.writeInt(entries.size()); 552 553 for (Enumeration<String> e = entries.keys(); e.hasMoreElements();) { 554 555 String alias = e.nextElement(); 556 Object entry = entries.get(alias); 557 558 if (entry instanceof KeyEntry) { 559 560 // Store this entry as a KeyEntry 561 dos.writeInt(1); 562 563 // Write the alias 564 dos.writeUTF(alias); 565 566 // Write the (entry creation) date 567 dos.writeLong(((KeyEntry)entry).date.getTime()); 568 569 // Write the protected private key 570 dos.writeInt(((KeyEntry)entry).protectedPrivKey.length); 571 dos.write(((KeyEntry)entry).protectedPrivKey); 572 573 // Write the certificate chain 574 int chainLen; 575 if (((KeyEntry)entry).chain == null) { 576 chainLen = 0; 577 } else { 578 chainLen = ((KeyEntry)entry).chain.length; 579 } 580 dos.writeInt(chainLen); 581 for (int i = 0; i < chainLen; i++) { 582 encoded = ((KeyEntry)entry).chain[i].getEncoded(); 583 dos.writeUTF(((KeyEntry)entry).chain[i].getType()); 584 dos.writeInt(encoded.length); 585 dos.write(encoded); 586 } 587 } else { 588 589 // Store this entry as a certificate 590 dos.writeInt(2); 591 592 // Write the alias 593 dos.writeUTF(alias); 594 595 // Write the (entry creation) date 596 dos.writeLong(((TrustedCertEntry)entry).date.getTime()); 597 598 // Write the trusted certificate 599 encoded = ((TrustedCertEntry)entry).cert.getEncoded(); 600 dos.writeUTF(((TrustedCertEntry)entry).cert.getType()); 601 dos.writeInt(encoded.length); 602 dos.write(encoded); 603 } 604 } 605 606 /* 607 * Write the keyed hash which is used to detect tampering with 608 * the keystore (such as deleting or modifying key or 609 * certificate entries). 610 */ 611 byte digest[] = md.digest(); 612 613 dos.write(digest); 614 dos.flush(); 615 } 616 } 617 618 /** 619 * Loads the keystore from the given input stream. 620 * 621 * <p>If a password is given, it is used to check the integrity of the 622 * keystore data. Otherwise, the integrity of the keystore is not checked. 623 * 624 * @param stream the input stream from which the keystore is loaded 625 * @param password the (optional) password used to check the integrity of 626 * the keystore. 627 * 628 * @exception IOException if there is an I/O or format problem with the 629 * keystore data 630 * @exception NoSuchAlgorithmException if the algorithm used to check 631 * the integrity of the keystore cannot be found 632 * @exception CertificateException if any of the certificates in the 633 * keystore could not be loaded 634 */ 635 public void engineLoad(InputStream stream, char[] password) 636 throws IOException, NoSuchAlgorithmException, CertificateException 637 { 638 synchronized(entries) { 639 DataInputStream dis; 640 MessageDigest md = null; 641 CertificateFactory cf = null; 642 Hashtable<String, CertificateFactory> cfs = null; 643 ByteArrayInputStream bais = null; 644 byte[] encoded = null; 645 646 if (stream == null) 647 return; 648 649 if (password != null) { 650 md = getPreKeyedHash(password); 651 dis = new DataInputStream(new DigestInputStream(stream, md)); 652 } else { 653 dis = new DataInputStream(stream); 654 } 655 656 // Body format: see store method 657 658 int xMagic = dis.readInt(); 659 int xVersion = dis.readInt(); 660 661 if (xMagic!=MAGIC || 662 (xVersion!=VERSION_1 && xVersion!=VERSION_2)) { 663 throw new IOException("Invalid keystore format"); 664 } 665 666 if (xVersion == VERSION_1) { 667 cf = CertificateFactory.getInstance("X509"); 668 } else { 669 // version 2 670 cfs = new Hashtable<String, CertificateFactory>(3); 671 } 672 673 entries.clear(); 674 int count = dis.readInt(); 675 676 for (int i = 0; i < count; i++) { 677 int tag; 678 String alias; 679 680 tag = dis.readInt(); 681 682 if (tag == 1) { // private key entry 683 684 KeyEntry entry = new KeyEntry(); 685 686 // Read the alias 687 alias = dis.readUTF(); 688 689 // Read the (entry creation) date 690 entry.date = new Date(dis.readLong()); 691 692 // Read the private key 693 entry.protectedPrivKey = 694 IOUtils.readFully(dis, dis.readInt(), true); 695 696 // Read the certificate chain 697 int numOfCerts = dis.readInt(); 698 if (numOfCerts > 0) { 699 List<Certificate> certs = new ArrayList<>( 700 numOfCerts > 10 ? 10 : numOfCerts); 701 for (int j = 0; j < numOfCerts; j++) { 702 if (xVersion == 2) { 703 // read the certificate type, and instantiate a 704 // certificate factory of that type (reuse 705 // existing factory if possible) 706 String certType = dis.readUTF(); 707 if (cfs.containsKey(certType)) { 708 // reuse certificate factory 709 cf = cfs.get(certType); 710 } else { 711 // create new certificate factory 712 cf = CertificateFactory.getInstance(certType); 713 // store the certificate factory so we can 714 // reuse it later 715 cfs.put(certType, cf); 716 } 717 } 718 // instantiate the certificate 719 encoded = IOUtils.readFully(dis, dis.readInt(), true); 720 bais = new ByteArrayInputStream(encoded); 721 certs.add(cf.generateCertificate(bais)); 722 bais.close(); 723 } 724 // We can be sure now that numOfCerts of certs are read 725 entry.chain = certs.toArray(new Certificate[numOfCerts]); 726 } 727 728 // Add the entry to the list 729 entries.put(alias, entry); 730 731 } else if (tag == 2) { // trusted certificate entry 732 733 TrustedCertEntry entry = new TrustedCertEntry(); 734 735 // Read the alias 736 alias = dis.readUTF(); 737 738 // Read the (entry creation) date 739 entry.date = new Date(dis.readLong()); 740 741 // Read the trusted certificate 742 if (xVersion == 2) { 743 // read the certificate type, and instantiate a 744 // certificate factory of that type (reuse 745 // existing factory if possible) 746 String certType = dis.readUTF(); 747 if (cfs.containsKey(certType)) { 748 // reuse certificate factory 749 cf = cfs.get(certType); 750 } else { 751 // create new certificate factory 752 cf = CertificateFactory.getInstance(certType); 753 // store the certificate factory so we can 754 // reuse it later 755 cfs.put(certType, cf); 756 } 757 } 758 encoded = IOUtils.readFully(dis, dis.readInt(), true); 759 bais = new ByteArrayInputStream(encoded); 760 entry.cert = cf.generateCertificate(bais); 761 bais.close(); 762 763 // Add the entry to the list 764 entries.put(alias, entry); 765 766 } else { 767 throw new IOException("Unrecognized keystore entry"); 768 } 769 } 770 771 /* 772 * If a password has been provided, we check the keyed digest 773 * at the end. If this check fails, the store has been tampered 774 * with 775 */ 776 if (password != null) { 777 byte computed[], actual[]; 778 computed = md.digest(); 779 actual = new byte[computed.length]; 780 dis.readFully(actual); 781 for (int i = 0; i < computed.length; i++) { 782 if (computed[i] != actual[i]) { 783 Throwable t = new UnrecoverableKeyException 784 ("Password verification failed"); 785 throw (IOException)new IOException 786 ("Keystore was tampered with, or " 787 + "password was incorrect").initCause(t); 788 } 789 } 790 } 791 } 792 } 793 794 /** 795 * To guard against tampering with the keystore, we append a keyed 796 * hash with a bit of whitener. 797 */ 798 private MessageDigest getPreKeyedHash(char[] password) 799 throws NoSuchAlgorithmException, UnsupportedEncodingException 800 { 801 802 MessageDigest md = MessageDigest.getInstance("SHA"); 803 byte[] passwdBytes = convertToBytes(password); 804 md.update(passwdBytes); 805 Arrays.fill(passwdBytes, (byte) 0x00); 806 md.update("Mighty Aphrodite".getBytes("UTF8")); 807 return md; 808 } 809 810 /** 811 * Helper method to convert char[] to byte[] 812 */ 813 814 private byte[] convertToBytes(char[] password) { 815 int i, j; 816 byte[] passwdBytes = new byte[password.length * 2]; 817 for (i=0, j=0; i<password.length; i++) { 818 passwdBytes[j++] = (byte)(password[i] >> 8); 819 passwdBytes[j++] = (byte)password[i]; 820 } 821 return passwdBytes; 822 } 823 }