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