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