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