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