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