1 /*
   2  * Copyright (c) 1997, 2015, 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         KeyProtector keyProtector = new KeyProtector(password);
 136         byte[] encrBytes = ((KeyEntry)entry).protectedPrivKey;
 137         EncryptedPrivateKeyInfo encrInfo;
 138         byte[] plain;
 139         try {
 140             encrInfo = new EncryptedPrivateKeyInfo(encrBytes);
 141         } catch (IOException ioe) {
 142             throw new UnrecoverableKeyException("Private key not stored as "
 143                                                 + "PKCS #8 "
 144                                                 + "EncryptedPrivateKeyInfo");
 145         }
 146         return keyProtector.recover(encrInfo);
 147     }
 148 
 149     /**
 150      * Returns the certificate chain associated with the given alias.
 151      *
 152      * @param alias the alias name
 153      *
 154      * @return the certificate chain (ordered with the user's certificate first
 155      * and the root certificate authority last), or null if the given alias
 156      * does not exist or does not contain a certificate chain (i.e., the given
 157      * alias identifies either a <i>trusted certificate entry</i> or a
 158      * <i>key entry</i> without a certificate chain).
 159      */
 160     public Certificate[] engineGetCertificateChain(String alias) {
 161         Object entry = entries.get(convertAlias(alias));
 162 
 163         if (entry != null && entry instanceof KeyEntry) {
 164             if (((KeyEntry)entry).chain == null) {
 165                 return null;
 166             } else {
 167                 return ((KeyEntry)entry).chain.clone();
 168             }
 169         } else {
 170             return null;
 171         }
 172     }
 173 
 174     /**
 175      * Returns the certificate associated with the given alias.
 176      *
 177      * <p>If the given alias name identifies a
 178      * <i>trusted certificate entry</i>, the certificate associated with that
 179      * entry is returned. If the given alias name identifies a
 180      * <i>key entry</i>, the first element of the certificate chain of that
 181      * entry is returned, or null if that entry does not have a certificate
 182      * chain.
 183      *
 184      * @param alias the alias name
 185      *
 186      * @return the certificate, or null if the given alias does not exist or
 187      * does not contain a certificate.
 188      */
 189     public Certificate engineGetCertificate(String alias) {
 190         Object entry = entries.get(convertAlias(alias));
 191 
 192         if (entry != null) {
 193             if (entry instanceof TrustedCertEntry) {
 194                 return ((TrustedCertEntry)entry).cert;
 195             } else {
 196                 if (((KeyEntry)entry).chain == null) {
 197                     return null;
 198                 } else {
 199                     return ((KeyEntry)entry).chain[0];
 200                 }
 201             }
 202         } else {
 203             return null;
 204         }
 205     }
 206 
 207     /**
 208      * Returns the creation date of the entry identified by the given alias.
 209      *
 210      * @param alias the alias name
 211      *
 212      * @return the creation date of this entry, or null if the given alias does
 213      * not exist
 214      */
 215     public Date engineGetCreationDate(String alias) {
 216         Object entry = entries.get(convertAlias(alias));
 217 
 218         if (entry != null) {
 219             if (entry instanceof TrustedCertEntry) {
 220                 return new Date(((TrustedCertEntry)entry).date.getTime());
 221             } else {
 222                 return new Date(((KeyEntry)entry).date.getTime());
 223             }
 224         } else {
 225             return null;
 226         }
 227     }
 228 
 229     /**
 230      * Assigns the given private key to the given alias, protecting
 231      * it with the given password as defined in PKCS8.
 232      *
 233      * <p>The given java.security.PrivateKey <code>key</code> must
 234      * be accompanied by a certificate chain certifying the
 235      * corresponding public key.
 236      *
 237      * <p>If the given alias already exists, the keystore information
 238      * associated with it is overridden by the given key and certificate
 239      * chain.
 240      *
 241      * @param alias the alias name
 242      * @param key the private key to be associated with the alias
 243      * @param password the password to protect the key
 244      * @param chain the certificate chain for the corresponding public
 245      * key (only required if the given key is of type
 246      * <code>java.security.PrivateKey</code>).
 247      *
 248      * @exception KeyStoreException if the given key is not a private key,
 249      * cannot be protected, or this operation fails for some other reason
 250      */
 251     public void engineSetKeyEntry(String alias, Key key, char[] password,
 252                                   Certificate[] chain)
 253         throws KeyStoreException
 254     {
 255         KeyProtector keyProtector = null;
 256 
 257         if (!(key instanceof java.security.PrivateKey)) {
 258             throw new KeyStoreException("Cannot store non-PrivateKeys");
 259         }
 260         try {
 261             synchronized(entries) {
 262                 KeyEntry entry = new KeyEntry();
 263                 entry.date = new Date();
 264 
 265                 // Protect the encoding of the key
 266                 keyProtector = new KeyProtector(password);
 267                 entry.protectedPrivKey = keyProtector.protect(key);
 268 
 269                 // clone the chain
 270                 if ((chain != null) &&
 271                     (chain.length != 0)) {
 272                     entry.chain = chain.clone();
 273                 } else {
 274                     entry.chain = null;
 275                 }
 276 
 277                 entries.put(convertAlias(alias), entry);
 278             }
 279         } catch (NoSuchAlgorithmException nsae) {
 280             throw new KeyStoreException("Key protection algorithm not found");
 281         } finally {
 282             keyProtector = null;
 283         }
 284     }
 285 
 286     /**
 287      * Assigns the given key (that has already been protected) to the given
 288      * alias.
 289      *
 290      * <p>If the protected key is of type
 291      * <code>java.security.PrivateKey</code>, it must be accompanied by a
 292      * certificate chain certifying the corresponding public key. If the
 293      * underlying keystore implementation is of type <code>jks</code>,
 294      * <code>key</code> must be encoded as an
 295      * <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard.
 296      *
 297      * <p>If the given alias already exists, the keystore information
 298      * associated with it is overridden by the given key (and possibly
 299      * certificate chain).
 300      *
 301      * @param alias the alias name
 302      * @param key the key (in protected format) to be associated with the alias
 303      * @param chain the certificate chain for the corresponding public
 304      * key (only useful if the protected key is of type
 305      * <code>java.security.PrivateKey</code>).
 306      *
 307      * @exception KeyStoreException if this operation fails.
 308      */
 309     public void engineSetKeyEntry(String alias, byte[] key,
 310                                   Certificate[] chain)
 311         throws KeyStoreException
 312     {
 313         synchronized(entries) {
 314             // key must be encoded as EncryptedPrivateKeyInfo as defined in
 315             // PKCS#8
 316             try {
 317                 new EncryptedPrivateKeyInfo(key);
 318             } catch (IOException ioe) {
 319                 throw new KeyStoreException("key is not encoded as "
 320                                             + "EncryptedPrivateKeyInfo");
 321             }
 322 
 323             KeyEntry entry = new KeyEntry();
 324             entry.date = new Date();
 325 
 326             entry.protectedPrivKey = key.clone();
 327             if ((chain != null) &&
 328                 (chain.length != 0)) {
 329                 entry.chain = chain.clone();
 330             } else {
 331                 entry.chain = null;
 332             }
 333 
 334             entries.put(convertAlias(alias), entry);
 335         }
 336     }
 337 
 338     /**
 339      * Assigns the given certificate to the given alias.
 340      *
 341      * <p>If the given alias already exists in this keystore and identifies a
 342      * <i>trusted certificate entry</i>, the certificate associated with it is
 343      * overridden by the given certificate.
 344      *
 345      * @param alias the alias name
 346      * @param cert the certificate
 347      *
 348      * @exception KeyStoreException if the given alias already exists and does
 349      * not identify a <i>trusted certificate entry</i>, or this operation
 350      * fails for some other reason.
 351      */
 352     public void engineSetCertificateEntry(String alias, Certificate cert)
 353         throws KeyStoreException
 354     {
 355         synchronized(entries) {
 356 
 357             Object entry = entries.get(convertAlias(alias));
 358             if ((entry != null) && (entry instanceof KeyEntry)) {
 359                 throw new KeyStoreException
 360                     ("Cannot overwrite own certificate");
 361             }
 362 
 363             TrustedCertEntry trustedCertEntry = new TrustedCertEntry();
 364             trustedCertEntry.cert = cert;
 365             trustedCertEntry.date = new Date();
 366             entries.put(convertAlias(alias), trustedCertEntry);
 367         }
 368     }
 369 
 370     /**
 371      * Deletes the entry identified by the given alias from this keystore.
 372      *
 373      * @param alias the alias name
 374      *
 375      * @exception KeyStoreException if the entry cannot be removed.
 376      */
 377     public void engineDeleteEntry(String alias)
 378         throws KeyStoreException
 379     {
 380         synchronized(entries) {
 381             entries.remove(convertAlias(alias));
 382         }
 383     }
 384 
 385     /**
 386      * Lists all the alias names of this keystore.
 387      *
 388      * @return enumeration of the alias names
 389      */
 390     public Enumeration<String> engineAliases() {
 391         return entries.keys();
 392     }
 393 
 394     /**
 395      * Checks if the given alias exists in this keystore.
 396      *
 397      * @param alias the alias name
 398      *
 399      * @return true if the alias exists, false otherwise
 400      */
 401     public boolean engineContainsAlias(String alias) {
 402         return entries.containsKey(convertAlias(alias));
 403     }
 404 
 405     /**
 406      * Retrieves the number of entries in this keystore.
 407      *
 408      * @return the number of entries in this keystore
 409      */
 410     public int engineSize() {
 411         return entries.size();
 412     }
 413 
 414     /**
 415      * Returns true if the entry identified by the given alias is a
 416      * <i>key entry</i>, and false otherwise.
 417      *
 418      * @return true if the entry identified by the given alias is a
 419      * <i>key entry</i>, false otherwise.
 420      */
 421     public boolean engineIsKeyEntry(String alias) {
 422         Object entry = entries.get(convertAlias(alias));
 423         if ((entry != null) && (entry instanceof KeyEntry)) {
 424             return true;
 425         } else {
 426             return false;
 427         }
 428     }
 429 
 430     /**
 431      * Returns true if the entry identified by the given alias is a
 432      * <i>trusted certificate entry</i>, and false otherwise.
 433      *
 434      * @return true if the entry identified by the given alias is a
 435      * <i>trusted certificate entry</i>, false otherwise.
 436      */
 437     public boolean engineIsCertificateEntry(String alias) {
 438         Object entry = entries.get(convertAlias(alias));
 439         if ((entry != null) && (entry instanceof TrustedCertEntry)) {
 440             return true;
 441         } else {
 442             return false;
 443         }
 444     }
 445 
 446     /**
 447      * Returns the (alias) name of the first keystore entry whose certificate
 448      * matches the given certificate.
 449      *
 450      * <p>This method attempts to match the given certificate with each
 451      * keystore entry. If the entry being considered
 452      * is a <i>trusted certificate entry</i>, the given certificate is
 453      * compared to that entry's certificate. If the entry being considered is
 454      * a <i>key entry</i>, the given certificate is compared to the first
 455      * element of that entry's certificate chain (if a chain exists).
 456      *
 457      * @param cert the certificate to match with.
 458      *
 459      * @return the (alias) name of the first entry with matching certificate,
 460      * or null if no such entry exists in this keystore.
 461      */
 462     public String engineGetCertificateAlias(Certificate cert) {
 463         Certificate certElem;
 464 
 465         for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) {
 466             String alias = e.nextElement();
 467             Object entry = entries.get(alias);
 468             if (entry instanceof TrustedCertEntry) {
 469                 certElem = ((TrustedCertEntry)entry).cert;
 470             } else if (((KeyEntry)entry).chain != null) {
 471                 certElem = ((KeyEntry)entry).chain[0];
 472             } else {
 473                 continue;
 474             }
 475             if (certElem.equals(cert)) {
 476                 return alias;
 477             }
 478         }
 479         return null;
 480     }
 481 
 482     /**
 483      * Stores this keystore to the given output stream, and protects its
 484      * integrity with the given password.
 485      *
 486      * @param stream the output stream to which this keystore is written.
 487      * @param password the password to generate the keystore integrity check
 488      *
 489      * @exception IOException if there was an I/O problem with data
 490      * @exception NoSuchAlgorithmException if the appropriate data integrity
 491      * algorithm could not be found
 492      * @exception CertificateException if any of the certificates included in
 493      * the keystore data could not be stored
 494      */
 495     public void engineStore(OutputStream stream, char[] password)
 496         throws IOException, NoSuchAlgorithmException, CertificateException
 497     {
 498         synchronized(entries) {
 499             /*
 500              * KEYSTORE FORMAT:
 501              *
 502              * Magic number (big-endian integer),
 503              * Version of this file format (big-endian integer),
 504              *
 505              * Count (big-endian integer),
 506              * followed by "count" instances of either:
 507              *
 508              *     {
 509              *      tag=1 (big-endian integer),
 510              *      alias (UTF string)
 511              *      timestamp
 512              *      encrypted private-key info according to PKCS #8
 513              *          (integer length followed by encoding)
 514              *      cert chain (integer count, then certs; for each cert,
 515              *          integer length followed by encoding)
 516              *     }
 517              *
 518              * or:
 519              *
 520              *     {
 521              *      tag=2 (big-endian integer)
 522              *      alias (UTF string)
 523              *      timestamp
 524              *      cert (integer length followed by encoding)
 525              *     }
 526              *
 527              * ended by a keyed SHA1 hash (bytes only) of
 528              *     { password + whitener + preceding body }
 529              */
 530 
 531             // password is mandatory when storing
 532             if (password == null) {
 533                 throw new IllegalArgumentException("password can't be null");
 534             }
 535 
 536             byte[] encoded; // the certificate encoding
 537 
 538             MessageDigest md = getPreKeyedHash(password);
 539             DataOutputStream dos
 540                 = new DataOutputStream(new DigestOutputStream(stream, md));
 541 
 542             dos.writeInt(MAGIC);
 543             // always write the latest version
 544             dos.writeInt(VERSION_2);
 545 
 546             dos.writeInt(entries.size());
 547 
 548             for (Enumeration<String> e = entries.keys(); e.hasMoreElements();) {
 549 
 550                 String alias = e.nextElement();
 551                 Object entry = entries.get(alias);
 552 
 553                 if (entry instanceof KeyEntry) {
 554 
 555                     // Store this entry as a KeyEntry
 556                     dos.writeInt(1);
 557 
 558                     // Write the alias
 559                     dos.writeUTF(alias);
 560 
 561                     // Write the (entry creation) date
 562                     dos.writeLong(((KeyEntry)entry).date.getTime());
 563 
 564                     // Write the protected private key
 565                     dos.writeInt(((KeyEntry)entry).protectedPrivKey.length);
 566                     dos.write(((KeyEntry)entry).protectedPrivKey);
 567 
 568                     // Write the certificate chain
 569                     int chainLen;
 570                     if (((KeyEntry)entry).chain == null) {
 571                         chainLen = 0;
 572                     } else {
 573                         chainLen = ((KeyEntry)entry).chain.length;
 574                     }
 575                     dos.writeInt(chainLen);
 576                     for (int i = 0; i < chainLen; i++) {
 577                         encoded = ((KeyEntry)entry).chain[i].getEncoded();
 578                         dos.writeUTF(((KeyEntry)entry).chain[i].getType());
 579                         dos.writeInt(encoded.length);
 580                         dos.write(encoded);
 581                     }
 582                 } else {
 583 
 584                     // Store this entry as a certificate
 585                     dos.writeInt(2);
 586 
 587                     // Write the alias
 588                     dos.writeUTF(alias);
 589 
 590                     // Write the (entry creation) date
 591                     dos.writeLong(((TrustedCertEntry)entry).date.getTime());
 592 
 593                     // Write the trusted certificate
 594                     encoded = ((TrustedCertEntry)entry).cert.getEncoded();
 595                     dos.writeUTF(((TrustedCertEntry)entry).cert.getType());
 596                     dos.writeInt(encoded.length);
 597                     dos.write(encoded);
 598                 }
 599             }
 600 
 601             /*
 602              * Write the keyed hash which is used to detect tampering with
 603              * the keystore (such as deleting or modifying key or
 604              * certificate entries).
 605              */
 606             byte digest[] = md.digest();
 607 
 608             dos.write(digest);
 609             dos.flush();
 610         }
 611     }
 612 
 613     /**
 614      * Loads the keystore from the given input stream.
 615      *
 616      * <p>If a password is given, it is used to check the integrity of the
 617      * keystore data. Otherwise, the integrity of the keystore is not checked.
 618      *
 619      * @param stream the input stream from which the keystore is loaded
 620      * @param password the (optional) password used to check the integrity of
 621      * the keystore.
 622      *
 623      * @exception IOException if there is an I/O or format problem with the
 624      * keystore data
 625      * @exception NoSuchAlgorithmException if the algorithm used to check
 626      * the integrity of the keystore cannot be found
 627      * @exception CertificateException if any of the certificates in the
 628      * keystore could not be loaded
 629      */
 630     public void engineLoad(InputStream stream, char[] password)
 631         throws IOException, NoSuchAlgorithmException, CertificateException
 632     {
 633         synchronized(entries) {
 634             DataInputStream dis;
 635             MessageDigest md = null;
 636             CertificateFactory cf = null;
 637             Hashtable<String, CertificateFactory> cfs = null;
 638             ByteArrayInputStream bais = null;
 639             byte[] encoded = null;
 640 
 641             if (stream == null)
 642                 return;
 643 
 644             if (password != null) {
 645                 md = getPreKeyedHash(password);
 646                 dis = new DataInputStream(new DigestInputStream(stream, md));
 647             } else {
 648                 dis = new DataInputStream(stream);
 649             }
 650 
 651             // Body format: see store method
 652 
 653             int xMagic = dis.readInt();
 654             int xVersion = dis.readInt();
 655 
 656             if (xMagic!=MAGIC ||
 657                 (xVersion!=VERSION_1 && xVersion!=VERSION_2)) {
 658                 throw new IOException("Invalid keystore format");
 659             }
 660 
 661             if (xVersion == VERSION_1) {
 662                 cf = CertificateFactory.getInstance("X509");
 663             } else {
 664                 // version 2
 665                 cfs = new Hashtable<String, CertificateFactory>(3);
 666             }
 667 
 668             entries.clear();
 669             int count = dis.readInt();
 670 
 671             for (int i = 0; i < count; i++) {
 672                 int tag;
 673                 String alias;
 674 
 675                 tag = dis.readInt();
 676 
 677                 if (tag == 1) { // private key entry
 678 
 679                     KeyEntry entry = new KeyEntry();
 680 
 681                     // Read the alias
 682                     alias = dis.readUTF();
 683 
 684                     // Read the (entry creation) date
 685                     entry.date = new Date(dis.readLong());
 686 
 687                     // Read the private key
 688                     entry.protectedPrivKey =
 689                             IOUtils.readFully(dis, dis.readInt(), true);
 690 
 691                     // Read the certificate chain
 692                     int numOfCerts = dis.readInt();
 693                     if (numOfCerts > 0) {
 694                         List<Certificate> certs = new ArrayList<>(
 695                                 numOfCerts > 10 ? 10 : numOfCerts);
 696                         for (int j = 0; j < numOfCerts; j++) {
 697                             if (xVersion == 2) {
 698                                 // read the certificate type, and instantiate a
 699                                 // certificate factory of that type (reuse
 700                                 // existing factory if possible)
 701                                 String certType = dis.readUTF();
 702                                 if (cfs.containsKey(certType)) {
 703                                     // reuse certificate factory
 704                                     cf = cfs.get(certType);
 705                                 } else {
 706                                     // create new certificate factory
 707                                     cf = CertificateFactory.getInstance(certType);
 708                                     // store the certificate factory so we can
 709                                     // reuse it later
 710                                     cfs.put(certType, cf);
 711                                 }
 712                             }
 713                             // instantiate the certificate
 714                             encoded = IOUtils.readFully(dis, dis.readInt(), true);
 715                             bais = new ByteArrayInputStream(encoded);
 716                             certs.add(cf.generateCertificate(bais));
 717                             bais.close();
 718                         }
 719                         // We can be sure now that numOfCerts of certs are read
 720                         entry.chain = certs.toArray(new Certificate[numOfCerts]);
 721                     }
 722 
 723                     // Add the entry to the list
 724                     entries.put(alias, entry);
 725 
 726                 } else if (tag == 2) { // trusted certificate entry
 727 
 728                     TrustedCertEntry entry = new TrustedCertEntry();
 729 
 730                     // Read the alias
 731                     alias = dis.readUTF();
 732 
 733                     // Read the (entry creation) date
 734                     entry.date = new Date(dis.readLong());
 735 
 736                     // Read the trusted certificate
 737                     if (xVersion == 2) {
 738                         // read the certificate type, and instantiate a
 739                         // certificate factory of that type (reuse
 740                         // existing factory if possible)
 741                         String certType = dis.readUTF();
 742                         if (cfs.containsKey(certType)) {
 743                             // reuse certificate factory
 744                             cf = cfs.get(certType);
 745                         } else {
 746                             // create new certificate factory
 747                             cf = CertificateFactory.getInstance(certType);
 748                             // store the certificate factory so we can
 749                             // reuse it later
 750                             cfs.put(certType, cf);
 751                         }
 752                     }
 753                     encoded = IOUtils.readFully(dis, dis.readInt(), true);
 754                     bais = new ByteArrayInputStream(encoded);
 755                     entry.cert = cf.generateCertificate(bais);
 756                     bais.close();
 757 
 758                     // Add the entry to the list
 759                     entries.put(alias, entry);
 760 
 761                 } else {
 762                     throw new IOException("Unrecognized keystore entry");
 763                 }
 764             }
 765 
 766             /*
 767              * If a password has been provided, we check the keyed digest
 768              * at the end. If this check fails, the store has been tampered
 769              * with
 770              */
 771             if (password != null) {
 772                 byte computed[], actual[];
 773                 computed = md.digest();
 774                 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 }