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