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