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