1 /*
   2  * Copyright (c) 2011, 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 apple.security;
  27 
  28 import java.io.*;
  29 import java.security.*;
  30 import java.security.cert.*;
  31 import java.security.cert.Certificate;
  32 import java.security.spec.*;
  33 import java.util.*;
  34 
  35 import javax.crypto.*;
  36 import javax.crypto.spec.*;
  37 import javax.security.auth.x500.*;
  38 
  39 import sun.security.pkcs.*;
  40 import sun.security.pkcs.EncryptedPrivateKeyInfo;
  41 import sun.security.util.*;
  42 import sun.security.x509.*;
  43 
  44 /**
  45  * This class provides the keystore implementation referred to as "KeychainStore".
  46  * It uses the current user's keychain as its backing storage, and does NOT support
  47  * a file-based implementation.
  48  */
  49 
  50 public final class KeychainStore extends KeyStoreSpi {
  51 
  52     // Private keys and their supporting certificate chains
  53     // If a key came from the keychain it has a SecKeyRef and one or more
  54     // SecCertificateRef.  When we delete the key we have to delete all of the corresponding
  55     // native objects.
  56     class KeyEntry {
  57         Date date; // the creation date of this entry
  58         byte[] protectedPrivKey;
  59         char[] password;
  60         long keyRef;  // SecKeyRef for this key
  61         Certificate chain[];
  62         long chainRefs[];  // SecCertificateRefs for this key's chain.
  63     };
  64 
  65     // Trusted certificates
  66     class TrustedCertEntry {
  67         Date date; // the creation date of this entry
  68 
  69         Certificate cert;
  70         long certRef;  // SecCertificateRef for this key
  71     };
  72 
  73     /**
  74      * Entries that have been deleted.  When something calls engineStore we'll
  75      * remove them from the keychain.
  76      */
  77     private Hashtable deletedEntries = new Hashtable();
  78 
  79     /**
  80      * Entries that have been added.  When something calls engineStore we'll
  81      * add them to the keychain.
  82      */
  83     private Hashtable addedEntries = new Hashtable();
  84 
  85     /**
  86      * Private keys and certificates are stored in a hashtable.
  87      * Hash entries are keyed by alias names.
  88      */
  89     private Hashtable entries = new Hashtable();
  90 
  91     /**
  92      * Algorithm identifiers and corresponding OIDs for the contents of the PKCS12 bag we get from the Keychain.
  93      */
  94     private static final int keyBag[]  = {1, 2, 840, 113549, 1, 12, 10, 1, 2};
  95     private static final int pbeWithSHAAnd3KeyTripleDESCBC[] =     {1, 2, 840, 113549, 1, 12, 1, 3};
  96     private static ObjectIdentifier PKCS8ShroudedKeyBag_OID;
  97     private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID;
  98 
  99     /**
 100      * Constnats used in PBE decryption.
 101      */
 102     private static final int iterationCount = 1024;
 103     private static final int SALT_LEN = 20;
 104 
 105     static {
 106         AccessController.doPrivileged(
 107             new PrivilegedAction<Void>() {
 108                 public Void run() {
 109                     System.loadLibrary("osx");
 110                     return null;
 111                 }
 112             });
 113         try {
 114             PKCS8ShroudedKeyBag_OID = new ObjectIdentifier(keyBag);
 115             pbeWithSHAAnd3KeyTripleDESCBC_OID = new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC);
 116         } catch (IOException ioe) {
 117             // should not happen
 118         }
 119     }
 120 
 121     private static void permissionCheck() {
 122         SecurityManager sec = System.getSecurityManager();
 123 
 124         if (sec != null) {
 125             sec.checkPermission(new RuntimePermission("useKeychainStore"));
 126         }
 127     }
 128 
 129 
 130     /**
 131      * Verify the Apple provider in the constructor.
 132      *
 133      * @exception SecurityException if fails to verify
 134      * its own integrity
 135      */
 136     public KeychainStore() { }
 137 
 138     /**
 139         * Returns the key associated with the given alias, using the given
 140      * password to recover it.
 141      *
 142      * @param alias the alias name
 143      * @param password the password for recovering the key
 144      *
 145      * @return the requested key, or null if the given alias does not exist
 146      * or does not identify a <i>key entry</i>.
 147      *
 148      * @exception NoSuchAlgorithmException if the algorithm for recovering the
 149      * key cannot be found
 150      * @exception UnrecoverableKeyException if the key cannot be recovered
 151      * (e.g., the given password is wrong).
 152      */
 153     public Key engineGetKey(String alias, char[] password)
 154         throws NoSuchAlgorithmException, UnrecoverableKeyException
 155     {
 156         permissionCheck();
 157 
 158         Object entry = entries.get(alias.toLowerCase());
 159 
 160         if (entry == null || !(entry instanceof KeyEntry)) {
 161             return null;
 162         }
 163 
 164         // This call gives us a PKCS12 bag, with the key inside it.
 165         byte[] exportedKeyInfo = _getEncodedKeyData(((KeyEntry)entry).keyRef, password);
 166         if (exportedKeyInfo == null) {
 167             return null;
 168         }
 169 
 170         PrivateKey returnValue = null;
 171 
 172         try {
 173             byte[] pkcs8KeyData = fetchPrivateKeyFromBag(exportedKeyInfo);
 174             byte[] encryptedKey;
 175             AlgorithmParameters algParams;
 176             ObjectIdentifier algOid;
 177             try {
 178                 // get the encrypted private key
 179                 EncryptedPrivateKeyInfo encrInfo = new EncryptedPrivateKeyInfo(pkcs8KeyData);
 180                 encryptedKey = encrInfo.getEncryptedData();
 181 
 182                 // parse Algorithm parameters
 183                 DerValue val = new DerValue(encrInfo.getAlgorithm().encode());
 184                 DerInputStream in = val.toDerInputStream();
 185                 algOid = in.getOID();
 186                 algParams = parseAlgParameters(in);
 187 
 188             } catch (IOException ioe) {
 189                 UnrecoverableKeyException uke =
 190                 new UnrecoverableKeyException("Private key not stored as "
 191                                               + "PKCS#8 EncryptedPrivateKeyInfo: " + ioe);
 192                 uke.initCause(ioe);
 193                 throw uke;
 194             }
 195 
 196             // Use JCE to decrypt the data using the supplied password.
 197             SecretKey skey = getPBEKey(password);
 198             Cipher cipher = Cipher.getInstance(algOid.toString());
 199             cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
 200             byte[] decryptedPrivateKey = cipher.doFinal(encryptedKey);
 201             PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(decryptedPrivateKey);
 202 
 203              // Parse the key algorithm and then use a JCA key factory to create the private key.
 204             DerValue val = new DerValue(decryptedPrivateKey);
 205             DerInputStream in = val.toDerInputStream();
 206 
 207             // Ignore this -- version should be 0.
 208             int i = in.getInteger();
 209 
 210             // Get the Algorithm ID next
 211             DerValue[] value = in.getSequence(2);
 212             AlgorithmId algId = new AlgorithmId(value[0].getOID());
 213             String algName = algId.getName();
 214 
 215             // Get a key factory for this algorithm.  It's likely to be 'RSA'.
 216             KeyFactory kfac = KeyFactory.getInstance(algName);
 217             returnValue = kfac.generatePrivate(kspec);
 218         } catch (Exception e) {
 219             UnrecoverableKeyException uke =
 220             new UnrecoverableKeyException("Get Key failed: " +
 221                                           e.getMessage());
 222             uke.initCause(e);
 223             throw uke;
 224         }
 225 
 226         return returnValue;
 227     }
 228 
 229     private native byte[] _getEncodedKeyData(long secKeyRef, char[] password);
 230 
 231     /**
 232      * Returns the certificate chain associated with the given alias.
 233      *
 234      * @param alias the alias name
 235      *
 236      * @return the certificate chain (ordered with the user's certificate first
 237                                       * and the root certificate authority last), or null if the given alias
 238      * does not exist or does not contain a certificate chain (i.e., the given
 239                                                                * alias identifies either a <i>trusted certificate entry</i> or a
 240                                                                * <i>key entry</i> without a certificate chain).
 241      */
 242     public Certificate[] engineGetCertificateChain(String alias) {
 243         permissionCheck();
 244 
 245         Object entry = entries.get(alias.toLowerCase());
 246 
 247         if (entry != null && entry instanceof KeyEntry) {
 248             if (((KeyEntry)entry).chain == null) {
 249                 return null;
 250             } else {
 251                 return (Certificate[])((KeyEntry)entry).chain.clone();
 252             }
 253         } else {
 254             return null;
 255         }
 256     }
 257 
 258     /**
 259      * Returns the certificate associated with the given alias.
 260      *
 261      * <p>If the given alias name identifies a
 262      * <i>trusted certificate entry</i>, the certificate associated with that
 263      * entry is returned. If the given alias name identifies a
 264      * <i>key entry</i>, the first element of the certificate chain of that
 265      * entry is returned, or null if that entry does not have a certificate
 266      * chain.
 267      *
 268      * @param alias the alias name
 269      *
 270      * @return the certificate, or null if the given alias does not exist or
 271      * does not contain a certificate.
 272      */
 273     public Certificate engineGetCertificate(String alias) {
 274         permissionCheck();
 275 
 276         Object entry = entries.get(alias.toLowerCase());
 277 
 278         if (entry != null) {
 279             if (entry instanceof TrustedCertEntry) {
 280                 return ((TrustedCertEntry)entry).cert;
 281             } else {
 282                 if (((KeyEntry)entry).chain == null) {
 283                     return null;
 284                 } else {
 285                     return ((KeyEntry)entry).chain[0];
 286                 }
 287             }
 288         } else {
 289             return null;
 290         }
 291     }
 292 
 293     /**
 294         * Returns the creation date of the entry identified by the given alias.
 295      *
 296      * @param alias the alias name
 297      *
 298      * @return the creation date of this entry, or null if the given alias does
 299      * not exist
 300      */
 301     public Date engineGetCreationDate(String alias) {
 302         permissionCheck();
 303 
 304         Object entry = entries.get(alias.toLowerCase());
 305 
 306         if (entry != null) {
 307             if (entry instanceof TrustedCertEntry) {
 308                 return new Date(((TrustedCertEntry)entry).date.getTime());
 309             } else {
 310                 return new Date(((KeyEntry)entry).date.getTime());
 311             }
 312         } else {
 313             return null;
 314         }
 315     }
 316 
 317     /**
 318         * Assigns the given key to the given alias, protecting it with the given
 319      * password.
 320      *
 321      * <p>If the given key is of type <code>java.security.PrivateKey</code>,
 322      * it must be accompanied by a certificate chain certifying the
 323      * corresponding public key.
 324      *
 325      * <p>If the given alias already exists, the keystore information
 326      * associated with it is overridden by the given key (and possibly
 327                                                           * certificate chain).
 328      *
 329      * @param alias the alias name
 330      * @param key the key to be associated with the alias
 331      * @param password the password to protect the key
 332      * @param chain the certificate chain for the corresponding public
 333      * key (only required if the given key is of type
 334             * <code>java.security.PrivateKey</code>).
 335      *
 336      * @exception KeyStoreException if the given key cannot be protected, or
 337      * this operation fails for some other reason
 338      */
 339     public void engineSetKeyEntry(String alias, Key key, char[] password,
 340                                   Certificate[] chain)
 341         throws KeyStoreException
 342     {
 343         permissionCheck();
 344 
 345         synchronized(entries) {
 346             try {
 347                 KeyEntry entry = new KeyEntry();
 348                 entry.date = new Date();
 349 
 350                 if (key instanceof PrivateKey) {
 351                     if ((key.getFormat().equals("PKCS#8")) ||
 352                         (key.getFormat().equals("PKCS8"))) {
 353                         entry.protectedPrivKey = encryptPrivateKey(key.getEncoded(), password);
 354                         entry.password = password.clone();
 355                     } else {
 356                         throw new KeyStoreException("Private key is not encoded as PKCS#8");
 357                     }
 358                 } else {
 359                     throw new KeyStoreException("Key is not a PrivateKey");
 360                 }
 361 
 362                 // clone the chain
 363                 if (chain != null) {
 364                     if ((chain.length > 1) && !validateChain(chain)) {
 365                         throw new KeyStoreException("Certificate chain does not validate");
 366                     }
 367 
 368                     entry.chain = (Certificate[])chain.clone();
 369                     entry.chainRefs = new long[entry.chain.length];
 370                 }
 371 
 372                 String lowerAlias = alias.toLowerCase();
 373                 if (entries.get(lowerAlias) != null) {
 374                     deletedEntries.put(lowerAlias, entries.get(lowerAlias));
 375                 }
 376 
 377                 entries.put(lowerAlias, entry);
 378                 addedEntries.put(lowerAlias, entry);
 379             } catch (Exception nsae) {
 380                 KeyStoreException ke = new KeyStoreException("Key protection algorithm not found: " + nsae);
 381                 ke.initCause(nsae);
 382                 throw ke;
 383             }
 384         }
 385     }
 386 
 387     /**
 388         * Assigns the given key (that has already been protected) to the given
 389      * alias.
 390      *
 391      * <p>If the protected key is of type
 392      * <code>java.security.PrivateKey</code>, it must be accompanied by a
 393      * certificate chain certifying the corresponding public key. If the
 394      * underlying keystore implementation is of type <code>jks</code>,
 395      * <code>key</code> must be encoded as an
 396      * <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard.
 397      *
 398      * <p>If the given alias already exists, the keystore information
 399      * associated with it is overridden by the given key (and possibly
 400                                                           * certificate chain).
 401      *
 402      * @param alias the alias name
 403      * @param key the key (in protected format) to be associated with the alias
 404      * @param chain the certificate chain for the corresponding public
 405      * key (only useful if the protected key is of type
 406             * <code>java.security.PrivateKey</code>).
 407      *
 408      * @exception KeyStoreException if this operation fails.
 409      */
 410     public void engineSetKeyEntry(String alias, byte[] key,
 411                                   Certificate[] chain)
 412         throws KeyStoreException
 413     {
 414         permissionCheck();
 415 
 416         synchronized(entries) {
 417             // key must be encoded as EncryptedPrivateKeyInfo as defined in
 418             // PKCS#8
 419             KeyEntry entry = new KeyEntry();
 420             try {
 421                 EncryptedPrivateKeyInfo privateKey = new EncryptedPrivateKeyInfo(key);
 422                 entry.protectedPrivKey = privateKey.getEncoded();
 423             } catch (IOException ioe) {
 424                 throw new KeyStoreException("key is not encoded as "
 425                                             + "EncryptedPrivateKeyInfo");
 426             }
 427 
 428             entry.date = new Date();
 429 
 430             if ((chain != null) &&
 431                 (chain.length != 0)) {
 432                 entry.chain = (Certificate[])chain.clone();
 433                 entry.chainRefs = new long[entry.chain.length];
 434             }
 435 
 436             String lowerAlias = alias.toLowerCase();
 437             if (entries.get(lowerAlias) != null) {
 438                 deletedEntries.put(lowerAlias, entries.get(alias));
 439             }
 440             entries.put(lowerAlias, entry);
 441             addedEntries.put(lowerAlias, entry);
 442         }
 443     }
 444 
 445     /**
 446         * Assigns the given certificate to the given alias.
 447      *
 448      * <p>If the given alias already exists in this keystore and identifies a
 449      * <i>trusted certificate entry</i>, the certificate associated with it is
 450      * overridden by the given certificate.
 451      *
 452      * @param alias the alias name
 453      * @param cert the certificate
 454      *
 455      * @exception KeyStoreException if the given alias already exists and does
 456      * not identify a <i>trusted certificate entry</i>, or this operation
 457      * fails for some other reason.
 458      */
 459     public void engineSetCertificateEntry(String alias, Certificate cert)
 460         throws KeyStoreException
 461     {
 462         permissionCheck();
 463 
 464         synchronized(entries) {
 465 
 466             Object entry = entries.get(alias.toLowerCase());
 467             if ((entry != null) && (entry instanceof KeyEntry)) {
 468                 throw new KeyStoreException
 469                 ("Cannot overwrite key entry with certificate");
 470             }
 471 
 472             // This will be slow, but necessary.  Enumerate the values and then see if the cert matches the one in the trusted cert entry.
 473             // Security framework doesn't support the same certificate twice in a keychain.
 474             Collection allValues = entries.values();
 475 
 476             for (Object value : allValues) {
 477                 if (value instanceof TrustedCertEntry) {
 478                     TrustedCertEntry tce = (TrustedCertEntry)value;
 479                     if (tce.cert.equals(cert)) {
 480                         throw new KeyStoreException("Keychain does not support mulitple copies of same certificate.");
 481                     }
 482                 }
 483             }
 484 
 485             TrustedCertEntry trustedCertEntry = new TrustedCertEntry();
 486             trustedCertEntry.cert = cert;
 487             trustedCertEntry.date = new Date();
 488             String lowerAlias = alias.toLowerCase();
 489             if (entries.get(lowerAlias) != null) {
 490                 deletedEntries.put(lowerAlias, entries.get(lowerAlias));
 491             }
 492             entries.put(lowerAlias, trustedCertEntry);
 493             addedEntries.put(lowerAlias, trustedCertEntry);
 494         }
 495     }
 496 
 497     /**
 498         * Deletes the entry identified by the given alias from this keystore.
 499      *
 500      * @param alias the alias name
 501      *
 502      * @exception KeyStoreException if the entry cannot be removed.
 503      */
 504     public void engineDeleteEntry(String alias)
 505         throws KeyStoreException
 506     {
 507         permissionCheck();
 508 
 509         synchronized(entries) {
 510             Object entry = entries.remove(alias.toLowerCase());
 511             deletedEntries.put(alias.toLowerCase(), entry);
 512         }
 513     }
 514 
 515     /**
 516         * Lists all the alias names of this keystore.
 517      *
 518      * @return enumeration of the alias names
 519      */
 520     public Enumeration engineAliases() {
 521         permissionCheck();
 522         return entries.keys();
 523     }
 524 
 525     /**
 526         * Checks if the given alias exists in this keystore.
 527      *
 528      * @param alias the alias name
 529      *
 530      * @return true if the alias exists, false otherwise
 531      */
 532     public boolean engineContainsAlias(String alias) {
 533         permissionCheck();
 534         return entries.containsKey(alias.toLowerCase());
 535     }
 536 
 537     /**
 538         * Retrieves the number of entries in this keystore.
 539      *
 540      * @return the number of entries in this keystore
 541      */
 542     public int engineSize() {
 543         permissionCheck();
 544         return entries.size();
 545     }
 546 
 547     /**
 548         * Returns true if the entry identified by the given alias is a
 549      * <i>key entry</i>, and false otherwise.
 550      *
 551      * @return true if the entry identified by the given alias is a
 552      * <i>key entry</i>, false otherwise.
 553      */
 554     public boolean engineIsKeyEntry(String alias) {
 555         permissionCheck();
 556         Object entry = entries.get(alias.toLowerCase());
 557         if ((entry != null) && (entry instanceof KeyEntry)) {
 558             return true;
 559         } else {
 560             return false;
 561         }
 562     }
 563 
 564     /**
 565         * Returns true if the entry identified by the given alias is a
 566      * <i>trusted certificate entry</i>, and false otherwise.
 567      *
 568      * @return true if the entry identified by the given alias is a
 569      * <i>trusted certificate entry</i>, false otherwise.
 570      */
 571     public boolean engineIsCertificateEntry(String alias) {
 572         permissionCheck();
 573         Object entry = entries.get(alias.toLowerCase());
 574         if ((entry != null) && (entry instanceof TrustedCertEntry)) {
 575             return true;
 576         } else {
 577             return false;
 578         }
 579     }
 580 
 581     /**
 582         * Returns the (alias) name of the first keystore entry whose certificate
 583      * matches the given certificate.
 584      *
 585      * <p>This method attempts to match the given certificate with each
 586      * keystore entry. If the entry being considered
 587      * is a <i>trusted certificate entry</i>, the given certificate is
 588      * compared to that entry's certificate. If the entry being considered is
 589      * a <i>key entry</i>, the given certificate is compared to the first
 590      * element of that entry's certificate chain (if a chain exists).
 591      *
 592      * @param cert the certificate to match with.
 593      *
 594      * @return the (alias) name of the first entry with matching certificate,
 595      * or null if no such entry exists in this keystore.
 596      */
 597     public String engineGetCertificateAlias(Certificate cert) {
 598         permissionCheck();
 599         Certificate certElem;
 600 
 601         for (Enumeration e = entries.keys(); e.hasMoreElements(); ) {
 602             String alias = (String)e.nextElement();
 603             Object entry = entries.get(alias);
 604             if (entry instanceof TrustedCertEntry) {
 605                 certElem = ((TrustedCertEntry)entry).cert;
 606             } else if (((KeyEntry)entry).chain != null) {
 607                 certElem = ((KeyEntry)entry).chain[0];
 608             } else {
 609                 continue;
 610             }
 611             if (certElem.equals(cert)) {
 612                 return alias;
 613             }
 614         }
 615         return null;
 616     }
 617 
 618     /**
 619         * Stores this keystore to the given output stream, and protects its
 620      * integrity with the given password.
 621      *
 622      * @param stream Ignored. the output stream to which this keystore is written.
 623      * @param password the password to generate the keystore integrity check
 624      *
 625      * @exception IOException if there was an I/O problem with data
 626      * @exception NoSuchAlgorithmException if the appropriate data integrity
 627      * algorithm could not be found
 628      * @exception CertificateException if any of the certificates included in
 629      * the keystore data could not be stored
 630      */
 631     public void engineStore(OutputStream stream, char[] password)
 632         throws IOException, NoSuchAlgorithmException, CertificateException
 633     {
 634         permissionCheck();
 635 
 636         // Delete items that do have a keychain item ref.
 637         for (Enumeration e = deletedEntries.keys(); e.hasMoreElements(); ) {
 638             String alias = (String)e.nextElement();
 639             Object entry = deletedEntries.get(alias);
 640             if (entry instanceof TrustedCertEntry) {
 641                 if (((TrustedCertEntry)entry).certRef != 0) {
 642                     _removeItemFromKeychain(((TrustedCertEntry)entry).certRef);
 643                     _releaseKeychainItemRef(((TrustedCertEntry)entry).certRef);
 644                 }
 645             } else {
 646                 Certificate certElem;
 647                 KeyEntry keyEntry = (KeyEntry)entry;
 648 
 649                 if (keyEntry.chain != null) {
 650                     for (int i = 0; i < keyEntry.chain.length; i++) {
 651                         if (keyEntry.chainRefs[i] != 0) {
 652                             _removeItemFromKeychain(keyEntry.chainRefs[i]);
 653                             _releaseKeychainItemRef(keyEntry.chainRefs[i]);
 654                         }
 655                     }
 656 
 657                     if (keyEntry.keyRef != 0) {
 658                         _removeItemFromKeychain(keyEntry.keyRef);
 659                         _releaseKeychainItemRef(keyEntry.keyRef);
 660                     }
 661                 }
 662             }
 663         }
 664 
 665         // Add all of the certs or keys in the added entries.
 666         // No need to check for 0 refs, as they are in the added list.
 667         for (Enumeration e = addedEntries.keys(); e.hasMoreElements(); ) {
 668             String alias = (String)e.nextElement();
 669             Object entry = addedEntries.get(alias);
 670             if (entry instanceof TrustedCertEntry) {
 671                 TrustedCertEntry tce = (TrustedCertEntry)entry;
 672                 Certificate certElem;
 673                 certElem = tce.cert;
 674                 tce.certRef = addCertificateToKeychain(alias, certElem);
 675             } else {
 676                 KeyEntry keyEntry = (KeyEntry)entry;
 677 
 678                 if (keyEntry.chain != null) {
 679                     for (int i = 0; i < keyEntry.chain.length; i++) {
 680                         keyEntry.chainRefs[i] = addCertificateToKeychain(alias, keyEntry.chain[i]);
 681                     }
 682 
 683                     keyEntry.keyRef = _addItemToKeychain(alias, false, keyEntry.protectedPrivKey, keyEntry.password);
 684                 }
 685             }
 686         }
 687 
 688         // Clear the added and deletedEntries hashtables here, now that we're done with the updates.
 689         // For the deleted entries, we freed up the native references above.
 690         deletedEntries.clear();
 691         addedEntries.clear();
 692     }
 693 
 694     private long addCertificateToKeychain(String alias, Certificate cert) {
 695         byte[] certblob = null;
 696         long returnValue = 0;
 697 
 698         try {
 699             certblob = cert.getEncoded();
 700             returnValue = _addItemToKeychain(alias, true, certblob, null);
 701         } catch (Exception e) {
 702             e.printStackTrace();
 703         }
 704 
 705         return returnValue;
 706     }
 707 
 708     private native long _addItemToKeychain(String alias, boolean isCertificate, byte[] datablob, char[] password);
 709     private native int _removeItemFromKeychain(long certRef);
 710     private native void _releaseKeychainItemRef(long keychainItemRef);
 711 
 712     /**
 713       * Loads the keystore from the Keychain.
 714      *
 715      * @param stream Ignored - here for API compatibility.
 716      * @param password Ignored - if user needs to unlock keychain Security
 717      * framework will post any dialogs.
 718      *
 719      * @exception IOException if there is an I/O or format problem with the
 720      * keystore data
 721      * @exception NoSuchAlgorithmException if the algorithm used to check
 722      * the integrity of the keystore cannot be found
 723      * @exception CertificateException if any of the certificates in the
 724      * keystore could not be loaded
 725      */
 726     public void engineLoad(InputStream stream, char[] password)
 727         throws IOException, NoSuchAlgorithmException, CertificateException
 728     {
 729         permissionCheck();
 730 
 731         // Release any stray keychain references before clearing out the entries.
 732         synchronized(entries) {
 733             for (Enumeration e = entries.keys(); e.hasMoreElements(); ) {
 734                 String alias = (String)e.nextElement();
 735                 Object entry = entries.get(alias);
 736                 if (entry instanceof TrustedCertEntry) {
 737                     if (((TrustedCertEntry)entry).certRef != 0) {
 738                         _releaseKeychainItemRef(((TrustedCertEntry)entry).certRef);
 739                     }
 740                 } else {
 741                     KeyEntry keyEntry = (KeyEntry)entry;
 742 
 743                     if (keyEntry.chain != null) {
 744                         for (int i = 0; i < keyEntry.chain.length; i++) {
 745                             if (keyEntry.chainRefs[i] != 0) {
 746                                 _releaseKeychainItemRef(keyEntry.chainRefs[i]);
 747                             }
 748                         }
 749 
 750                         if (keyEntry.keyRef != 0) {
 751                             _releaseKeychainItemRef(keyEntry.keyRef);
 752                         }
 753                     }
 754                 }
 755             }
 756 
 757             entries.clear();
 758             _scanKeychain();
 759         }
 760     }
 761 
 762     private native void _scanKeychain();
 763 
 764     /**
 765      * Callback method from _scanKeychain.  If a trusted certificate is found, this method will be called.
 766      */
 767     private void createTrustedCertEntry(String alias, long keychainItemRef, long creationDate, byte[] derStream) {
 768         TrustedCertEntry tce = new TrustedCertEntry();
 769 
 770         try {
 771             CertificateFactory cf = CertificateFactory.getInstance("X.509");
 772             InputStream input = new ByteArrayInputStream(derStream);
 773             X509Certificate cert = (X509Certificate) cf.generateCertificate(input);
 774             input.close();
 775             tce.cert = cert;
 776             tce.certRef = keychainItemRef;
 777 
 778             // Make a creation date.
 779             if (creationDate != 0)
 780                 tce.date = new Date(creationDate);
 781             else
 782                 tce.date = new Date();
 783 
 784             int uniqueVal = 1;
 785             String originalAlias = alias;
 786 
 787             while (entries.containsKey(alias.toLowerCase())) {
 788                 alias = originalAlias + " " + uniqueVal;
 789                 uniqueVal++;
 790             }
 791 
 792             entries.put(alias.toLowerCase(), tce);
 793         } catch (Exception e) {
 794             // The certificate will be skipped.
 795             System.err.println("KeychainStore Ignored Exception: " + e);
 796         }
 797     }
 798 
 799     /**
 800      * Callback method from _scanKeychain.  If an identity is found, this method will be called to create Java certificate
 801      * and private key objects from the keychain data.
 802      */
 803     private void createKeyEntry(String alias, long creationDate, long secKeyRef, long[] secCertificateRefs, byte[][] rawCertData)
 804         throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
 805         KeyEntry ke = new KeyEntry();
 806 
 807         // First, store off the private key information.  This is the easy part.
 808         ke.protectedPrivKey = null;
 809         ke.keyRef = secKeyRef;
 810 
 811         // Make a creation date.
 812         if (creationDate != 0)
 813             ke.date = new Date(creationDate);
 814         else
 815             ke.date = new Date();
 816 
 817         // Next, create X.509 Certificate objects from the raw data.  This is complicated
 818         // because a certificate's public key may be too long for Java's default encryption strength.
 819         List createdCerts = new ArrayList();
 820 
 821         try {
 822             CertificateFactory cf = CertificateFactory.getInstance("X.509");
 823 
 824             for (int i = 0; i < rawCertData.length; i++) {
 825                 try {
 826                     InputStream input = new ByteArrayInputStream(rawCertData[i]);
 827                     X509Certificate cert = (X509Certificate) cf.generateCertificate(input);
 828                     input.close();
 829 
 830                     // We successfully created the certificate, so track it and its corresponding SecCertificateRef.
 831                     createdCerts.add(new CertKeychainItemPair(secCertificateRefs[i], cert));
 832                 } catch (CertificateException e) {
 833                     // The certificate will be skipped.
 834                     System.err.println("KeychainStore Ignored Exception: " + e);
 835                 }
 836             }
 837         } catch (CertificateException e) {
 838             e.printStackTrace();
 839         } catch (IOException ioe) {
 840             ioe.printStackTrace();  // How would this happen?
 841         }
 842 
 843         // We have our certificates in the List, so now extract them into an array of
 844         // Certificates and SecCertificateRefs.
 845         Object[] objArray = createdCerts.toArray();
 846         Certificate[] certArray = new Certificate[objArray.length];
 847         long[] certRefArray = new long[objArray.length];
 848 
 849         for (int i = 0; i < objArray.length; i++) {
 850             CertKeychainItemPair addedItem = (CertKeychainItemPair)objArray[i];
 851             certArray[i] = addedItem.mCert;
 852             certRefArray[i] = addedItem.mCertificateRef;
 853         }
 854 
 855         ke.chain = certArray;
 856         ke.chainRefs = certRefArray;
 857 
 858         // If we don't have already have an item with this item's alias
 859         // create a new one for it.
 860         int uniqueVal = 1;
 861         String originalAlias = alias;
 862 
 863         while (entries.containsKey(alias.toLowerCase())) {
 864             alias = originalAlias + " " + uniqueVal;
 865             uniqueVal++;
 866         }
 867 
 868         entries.put(alias.toLowerCase(), ke);
 869     }
 870 
 871     private class CertKeychainItemPair {
 872         long mCertificateRef;
 873         Certificate mCert;
 874 
 875         CertKeychainItemPair(long inCertRef, Certificate cert) {
 876             mCertificateRef = inCertRef;
 877             mCert = cert;
 878         }
 879     }
 880 
 881     /*
 882      * Validate Certificate Chain
 883      */
 884     private boolean validateChain(Certificate[] certChain)
 885     {
 886         for (int i = 0; i < certChain.length-1; i++) {
 887             X500Principal issuerDN =
 888             ((X509Certificate)certChain[i]).getIssuerX500Principal();
 889             X500Principal subjectDN =
 890                 ((X509Certificate)certChain[i+1]).getSubjectX500Principal();
 891             if (!(issuerDN.equals(subjectDN)))
 892                 return false;
 893         }
 894         return true;
 895     }
 896 
 897     private byte[] fetchPrivateKeyFromBag(byte[] privateKeyInfo) throws IOException, NoSuchAlgorithmException, CertificateException
 898     {
 899         byte[] returnValue = null;
 900         DerValue val = new DerValue(new ByteArrayInputStream(privateKeyInfo));
 901         DerInputStream s = val.toDerInputStream();
 902         int version = s.getInteger();
 903 
 904         if (version != 3) {
 905             throw new IOException("PKCS12 keystore not in version 3 format");
 906         }
 907 
 908         /*
 909             * Read the authSafe.
 910          */
 911         byte[] authSafeData;
 912         ContentInfo authSafe = new ContentInfo(s);
 913         ObjectIdentifier contentType = authSafe.getContentType();
 914 
 915         if (contentType.equals(ContentInfo.DATA_OID)) {
 916             authSafeData = authSafe.getData();
 917         } else /* signed data */ {
 918             throw new IOException("public key protected PKCS12 not supported");
 919         }
 920 
 921         DerInputStream as = new DerInputStream(authSafeData);
 922         DerValue[] safeContentsArray = as.getSequence(2);
 923         int count = safeContentsArray.length;
 924 
 925         /*
 926          * Spin over the ContentInfos.
 927          */
 928         for (int i = 0; i < count; i++) {
 929             byte[] safeContentsData;
 930             ContentInfo safeContents;
 931             DerInputStream sci;
 932             byte[] eAlgId = null;
 933 
 934             sci = new DerInputStream(safeContentsArray[i].toByteArray());
 935             safeContents = new ContentInfo(sci);
 936             contentType = safeContents.getContentType();
 937             safeContentsData = null;
 938 
 939             if (contentType.equals(ContentInfo.DATA_OID)) {
 940                 safeContentsData = safeContents.getData();
 941             } else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) {
 942                 // The password was used to export the private key from the keychain.
 943                 // The Keychain won't export the key with encrypted data, so we don't need
 944                 // to worry about it.
 945                 continue;
 946             } else {
 947                 throw new IOException("public key protected PKCS12" +
 948                                       " not supported");
 949             }
 950             DerInputStream sc = new DerInputStream(safeContentsData);
 951             returnValue = extractKeyData(sc);
 952         }
 953 
 954         return returnValue;
 955     }
 956 
 957     private byte[] extractKeyData(DerInputStream stream)
 958         throws IOException, NoSuchAlgorithmException, CertificateException
 959     {
 960         byte[] returnValue = null;
 961         DerValue[] safeBags = stream.getSequence(2);
 962         int count = safeBags.length;
 963 
 964         /*
 965          * Spin over the SafeBags.
 966          */
 967         for (int i = 0; i < count; i++) {
 968             ObjectIdentifier bagId;
 969             DerInputStream sbi;
 970             DerValue bagValue;
 971             Object bagItem = null;
 972 
 973             sbi = safeBags[i].toDerInputStream();
 974             bagId = sbi.getOID();
 975             bagValue = sbi.getDerValue();
 976             if (!bagValue.isContextSpecific((byte)0)) {
 977                 throw new IOException("unsupported PKCS12 bag value type "
 978                                       + bagValue.tag);
 979             }
 980             bagValue = bagValue.data.getDerValue();
 981             if (bagId.equals(PKCS8ShroudedKeyBag_OID)) {
 982                 // got what we were looking for.  Return it.
 983                 returnValue = bagValue.toByteArray();
 984             } else {
 985                 // log error message for "unsupported PKCS12 bag type"
 986                 System.out.println("Unsupported bag type '" + bagId + "'");
 987             }
 988         }
 989 
 990         return returnValue;
 991     }
 992 
 993     /*
 994         * Generate PBE Algorithm Parameters
 995      */
 996     private AlgorithmParameters getAlgorithmParameters(String algorithm)
 997         throws IOException
 998     {
 999         AlgorithmParameters algParams = null;
1000 
1001         // create PBE parameters from salt and iteration count
1002         PBEParameterSpec paramSpec =
1003             new PBEParameterSpec(getSalt(), iterationCount);
1004         try {
1005             algParams = AlgorithmParameters.getInstance(algorithm);
1006             algParams.init(paramSpec);
1007         } catch (Exception e) {
1008             IOException ioe =
1009             new IOException("getAlgorithmParameters failed: " +
1010                             e.getMessage());
1011             ioe.initCause(e);
1012             throw ioe;
1013         }
1014         return algParams;
1015     }
1016 
1017     // the source of randomness
1018     private SecureRandom random;
1019 
1020     /*
1021      * Generate random salt
1022      */
1023     private byte[] getSalt()
1024     {
1025         // Generate a random salt.
1026         byte[] salt = new byte[SALT_LEN];
1027         if (random == null) {
1028             random = new SecureRandom();
1029         }
1030         salt = random.generateSeed(SALT_LEN);
1031         return salt;
1032     }
1033 
1034     /*
1035      * parse Algorithm Parameters
1036      */
1037     private AlgorithmParameters parseAlgParameters(DerInputStream in)
1038         throws IOException
1039     {
1040         AlgorithmParameters algParams = null;
1041         try {
1042             DerValue params;
1043             if (in.available() == 0) {
1044                 params = null;
1045             } else {
1046                 params = in.getDerValue();
1047                 if (params.tag == DerValue.tag_Null) {
1048                     params = null;
1049                 }
1050             }
1051             if (params != null) {
1052                 algParams = AlgorithmParameters.getInstance("PBE");
1053                 algParams.init(params.toByteArray());
1054             }
1055         } catch (Exception e) {
1056             IOException ioe =
1057             new IOException("parseAlgParameters failed: " +
1058                             e.getMessage());
1059             ioe.initCause(e);
1060             throw ioe;
1061         }
1062         return algParams;
1063     }
1064 
1065     /*
1066      * Generate PBE key
1067      */
1068     private SecretKey getPBEKey(char[] password) throws IOException
1069     {
1070         SecretKey skey = null;
1071 
1072         try {
1073             PBEKeySpec keySpec = new PBEKeySpec(password);
1074             SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE");
1075             skey = skFac.generateSecret(keySpec);
1076         } catch (Exception e) {
1077             IOException ioe = new IOException("getSecretKey failed: " +
1078                                               e.getMessage());
1079             ioe.initCause(e);
1080             throw ioe;
1081         }
1082         return skey;
1083     }
1084 
1085     /*
1086      * Encrypt private key using Password-based encryption (PBE)
1087      * as defined in PKCS#5.
1088      *
1089      * NOTE: Currently pbeWithSHAAnd3-KeyTripleDES-CBC algorithmID is
1090      *       used to derive the key and IV.
1091      *
1092      * @return encrypted private key encoded as EncryptedPrivateKeyInfo
1093      */
1094     private byte[] encryptPrivateKey(byte[] data, char[] password)
1095         throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException
1096     {
1097         byte[] key = null;
1098 
1099         try {
1100             // create AlgorithmParameters
1101             AlgorithmParameters algParams =
1102             getAlgorithmParameters("PBEWithSHA1AndDESede");
1103 
1104             // Use JCE
1105             SecretKey skey = getPBEKey(password);
1106             Cipher cipher = Cipher.getInstance("PBEWithSHA1AndDESede");
1107             cipher.init(Cipher.ENCRYPT_MODE, skey, algParams);
1108             byte[] encryptedKey = cipher.doFinal(data);
1109 
1110             // wrap encrypted private key in EncryptedPrivateKeyInfo
1111             // as defined in PKCS#8
1112             AlgorithmId algid =
1113                 new AlgorithmId(pbeWithSHAAnd3KeyTripleDESCBC_OID, algParams);
1114             EncryptedPrivateKeyInfo encrInfo =
1115                 new EncryptedPrivateKeyInfo(algid, encryptedKey);
1116             key = encrInfo.getEncoded();
1117         } catch (Exception e) {
1118             UnrecoverableKeyException uke =
1119             new UnrecoverableKeyException("Encrypt Private Key failed: "
1120                                           + e.getMessage());
1121             uke.initCause(e);
1122             throw uke;
1123         }
1124 
1125         return ((byte[])key);
1126     }
1127 
1128 
1129 }
1130