jdk/src/jdk.deploy.osx/macosx/classes/apple/security/KeychainStore.java
changeset 25859 3317bb8137f4
parent 25762 c4a3548120c6
child 27291 42769f9ca831
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.deploy.osx/macosx/classes/apple/security/KeychainStore.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,1130 @@
+/*
+ * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package apple.security;
+
+import java.io.*;
+import java.security.*;
+import java.security.cert.*;
+import java.security.cert.Certificate;
+import java.security.spec.*;
+import java.util.*;
+
+import javax.crypto.*;
+import javax.crypto.spec.*;
+import javax.security.auth.x500.*;
+
+import sun.security.pkcs.*;
+import sun.security.pkcs.EncryptedPrivateKeyInfo;
+import sun.security.util.*;
+import sun.security.x509.*;
+
+/**
+ * This class provides the keystore implementation referred to as "KeychainStore".
+ * It uses the current user's keychain as its backing storage, and does NOT support
+ * a file-based implementation.
+ */
+
+public final class KeychainStore extends KeyStoreSpi {
+
+    // Private keys and their supporting certificate chains
+    // If a key came from the keychain it has a SecKeyRef and one or more
+    // SecCertificateRef.  When we delete the key we have to delete all of the corresponding
+    // native objects.
+    class KeyEntry {
+        Date date; // the creation date of this entry
+        byte[] protectedPrivKey;
+        char[] password;
+        long keyRef;  // SecKeyRef for this key
+        Certificate chain[];
+        long chainRefs[];  // SecCertificateRefs for this key's chain.
+    };
+
+    // Trusted certificates
+    class TrustedCertEntry {
+        Date date; // the creation date of this entry
+
+        Certificate cert;
+        long certRef;  // SecCertificateRef for this key
+    };
+
+    /**
+     * Entries that have been deleted.  When something calls engineStore we'll
+     * remove them from the keychain.
+     */
+    private Hashtable<String, Object> deletedEntries = new Hashtable<>();
+
+    /**
+     * Entries that have been added.  When something calls engineStore we'll
+     * add them to the keychain.
+     */
+    private Hashtable<String, Object> addedEntries = new Hashtable<>();
+
+    /**
+     * Private keys and certificates are stored in a hashtable.
+     * Hash entries are keyed by alias names.
+     */
+    private Hashtable<String, Object> entries = new Hashtable<>();
+
+    /**
+     * Algorithm identifiers and corresponding OIDs for the contents of the PKCS12 bag we get from the Keychain.
+     */
+    private static final int keyBag[]  = {1, 2, 840, 113549, 1, 12, 10, 1, 2};
+    private static final int pbeWithSHAAnd3KeyTripleDESCBC[] =     {1, 2, 840, 113549, 1, 12, 1, 3};
+    private static ObjectIdentifier PKCS8ShroudedKeyBag_OID;
+    private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID;
+
+    /**
+     * Constnats used in PBE decryption.
+     */
+    private static final int iterationCount = 1024;
+    private static final int SALT_LEN = 20;
+
+    static {
+        AccessController.doPrivileged(
+            new PrivilegedAction<Void>() {
+                public Void run() {
+                    System.loadLibrary("osx");
+                    return null;
+                }
+            });
+        try {
+            PKCS8ShroudedKeyBag_OID = new ObjectIdentifier(keyBag);
+            pbeWithSHAAnd3KeyTripleDESCBC_OID = new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC);
+        } catch (IOException ioe) {
+            // should not happen
+        }
+    }
+
+    private static void permissionCheck() {
+        SecurityManager sec = System.getSecurityManager();
+
+        if (sec != null) {
+            sec.checkPermission(new RuntimePermission("useKeychainStore"));
+        }
+    }
+
+
+    /**
+     * Verify the Apple provider in the constructor.
+     *
+     * @exception SecurityException if fails to verify
+     * its own integrity
+     */
+    public KeychainStore() { }
+
+    /**
+        * Returns the key associated with the given alias, using the given
+     * password to recover it.
+     *
+     * @param alias the alias name
+     * @param password the password for recovering the key
+     *
+     * @return the requested key, or null if the given alias does not exist
+     * or does not identify a <i>key entry</i>.
+     *
+     * @exception NoSuchAlgorithmException if the algorithm for recovering the
+     * key cannot be found
+     * @exception UnrecoverableKeyException if the key cannot be recovered
+     * (e.g., the given password is wrong).
+     */
+    public Key engineGetKey(String alias, char[] password)
+        throws NoSuchAlgorithmException, UnrecoverableKeyException
+    {
+        permissionCheck();
+
+        Object entry = entries.get(alias.toLowerCase());
+
+        if (entry == null || !(entry instanceof KeyEntry)) {
+            return null;
+        }
+
+        // This call gives us a PKCS12 bag, with the key inside it.
+        byte[] exportedKeyInfo = _getEncodedKeyData(((KeyEntry)entry).keyRef, password);
+        if (exportedKeyInfo == null) {
+            return null;
+        }
+
+        PrivateKey returnValue = null;
+
+        try {
+            byte[] pkcs8KeyData = fetchPrivateKeyFromBag(exportedKeyInfo);
+            byte[] encryptedKey;
+            AlgorithmParameters algParams;
+            ObjectIdentifier algOid;
+            try {
+                // get the encrypted private key
+                EncryptedPrivateKeyInfo encrInfo = new EncryptedPrivateKeyInfo(pkcs8KeyData);
+                encryptedKey = encrInfo.getEncryptedData();
+
+                // parse Algorithm parameters
+                DerValue val = new DerValue(encrInfo.getAlgorithm().encode());
+                DerInputStream in = val.toDerInputStream();
+                algOid = in.getOID();
+                algParams = parseAlgParameters(in);
+
+            } catch (IOException ioe) {
+                UnrecoverableKeyException uke =
+                new UnrecoverableKeyException("Private key not stored as "
+                                              + "PKCS#8 EncryptedPrivateKeyInfo: " + ioe);
+                uke.initCause(ioe);
+                throw uke;
+            }
+
+            // Use JCE to decrypt the data using the supplied password.
+            SecretKey skey = getPBEKey(password);
+            Cipher cipher = Cipher.getInstance(algOid.toString());
+            cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
+            byte[] decryptedPrivateKey = cipher.doFinal(encryptedKey);
+            PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(decryptedPrivateKey);
+
+             // Parse the key algorithm and then use a JCA key factory to create the private key.
+            DerValue val = new DerValue(decryptedPrivateKey);
+            DerInputStream in = val.toDerInputStream();
+
+            // Ignore this -- version should be 0.
+            int i = in.getInteger();
+
+            // Get the Algorithm ID next
+            DerValue[] value = in.getSequence(2);
+            AlgorithmId algId = new AlgorithmId(value[0].getOID());
+            String algName = algId.getName();
+
+            // Get a key factory for this algorithm.  It's likely to be 'RSA'.
+            KeyFactory kfac = KeyFactory.getInstance(algName);
+            returnValue = kfac.generatePrivate(kspec);
+        } catch (Exception e) {
+            UnrecoverableKeyException uke =
+            new UnrecoverableKeyException("Get Key failed: " +
+                                          e.getMessage());
+            uke.initCause(e);
+            throw uke;
+        }
+
+        return returnValue;
+    }
+
+    private native byte[] _getEncodedKeyData(long secKeyRef, char[] password);
+
+    /**
+     * Returns the certificate chain associated with the given alias.
+     *
+     * @param alias the alias name
+     *
+     * @return the certificate chain (ordered with the user's certificate first
+                                      * and the root certificate authority last), or null if the given alias
+     * does not exist or does not contain a certificate chain (i.e., the given
+                                                               * alias identifies either a <i>trusted certificate entry</i> or a
+                                                               * <i>key entry</i> without a certificate chain).
+     */
+    public Certificate[] engineGetCertificateChain(String alias) {
+        permissionCheck();
+
+        Object entry = entries.get(alias.toLowerCase());
+
+        if (entry != null && entry instanceof KeyEntry) {
+            if (((KeyEntry)entry).chain == null) {
+                return null;
+            } else {
+                return ((KeyEntry)entry).chain.clone();
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the certificate associated with the given alias.
+     *
+     * <p>If the given alias name identifies a
+     * <i>trusted certificate entry</i>, the certificate associated with that
+     * entry is returned. If the given alias name identifies a
+     * <i>key entry</i>, the first element of the certificate chain of that
+     * entry is returned, or null if that entry does not have a certificate
+     * chain.
+     *
+     * @param alias the alias name
+     *
+     * @return the certificate, or null if the given alias does not exist or
+     * does not contain a certificate.
+     */
+    public Certificate engineGetCertificate(String alias) {
+        permissionCheck();
+
+        Object entry = entries.get(alias.toLowerCase());
+
+        if (entry != null) {
+            if (entry instanceof TrustedCertEntry) {
+                return ((TrustedCertEntry)entry).cert;
+            } else {
+                if (((KeyEntry)entry).chain == null) {
+                    return null;
+                } else {
+                    return ((KeyEntry)entry).chain[0];
+                }
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
+        * Returns the creation date of the entry identified by the given alias.
+     *
+     * @param alias the alias name
+     *
+     * @return the creation date of this entry, or null if the given alias does
+     * not exist
+     */
+    public Date engineGetCreationDate(String alias) {
+        permissionCheck();
+
+        Object entry = entries.get(alias.toLowerCase());
+
+        if (entry != null) {
+            if (entry instanceof TrustedCertEntry) {
+                return new Date(((TrustedCertEntry)entry).date.getTime());
+            } else {
+                return new Date(((KeyEntry)entry).date.getTime());
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
+        * Assigns the given key to the given alias, protecting it with the given
+     * password.
+     *
+     * <p>If the given key is of type <code>java.security.PrivateKey</code>,
+     * it must be accompanied by a certificate chain certifying the
+     * corresponding public key.
+     *
+     * <p>If the given alias already exists, the keystore information
+     * associated with it is overridden by the given key (and possibly
+                                                          * certificate chain).
+     *
+     * @param alias the alias name
+     * @param key the key to be associated with the alias
+     * @param password the password to protect the key
+     * @param chain the certificate chain for the corresponding public
+     * key (only required if the given key is of type
+            * <code>java.security.PrivateKey</code>).
+     *
+     * @exception KeyStoreException if the given key cannot be protected, or
+     * this operation fails for some other reason
+     */
+    public void engineSetKeyEntry(String alias, Key key, char[] password,
+                                  Certificate[] chain)
+        throws KeyStoreException
+    {
+        permissionCheck();
+
+        synchronized(entries) {
+            try {
+                KeyEntry entry = new KeyEntry();
+                entry.date = new Date();
+
+                if (key instanceof PrivateKey) {
+                    if ((key.getFormat().equals("PKCS#8")) ||
+                        (key.getFormat().equals("PKCS8"))) {
+                        entry.protectedPrivKey = encryptPrivateKey(key.getEncoded(), password);
+                        entry.password = password.clone();
+                    } else {
+                        throw new KeyStoreException("Private key is not encoded as PKCS#8");
+                    }
+                } else {
+                    throw new KeyStoreException("Key is not a PrivateKey");
+                }
+
+                // clone the chain
+                if (chain != null) {
+                    if ((chain.length > 1) && !validateChain(chain)) {
+                        throw new KeyStoreException("Certificate chain does not validate");
+                    }
+
+                    entry.chain = chain.clone();
+                    entry.chainRefs = new long[entry.chain.length];
+                }
+
+                String lowerAlias = alias.toLowerCase();
+                if (entries.get(lowerAlias) != null) {
+                    deletedEntries.put(lowerAlias, entries.get(lowerAlias));
+                }
+
+                entries.put(lowerAlias, entry);
+                addedEntries.put(lowerAlias, entry);
+            } catch (Exception nsae) {
+                KeyStoreException ke = new KeyStoreException("Key protection algorithm not found: " + nsae);
+                ke.initCause(nsae);
+                throw ke;
+            }
+        }
+    }
+
+    /**
+        * Assigns the given key (that has already been protected) to the given
+     * alias.
+     *
+     * <p>If the protected key is of type
+     * <code>java.security.PrivateKey</code>, it must be accompanied by a
+     * certificate chain certifying the corresponding public key. If the
+     * underlying keystore implementation is of type <code>jks</code>,
+     * <code>key</code> must be encoded as an
+     * <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard.
+     *
+     * <p>If the given alias already exists, the keystore information
+     * associated with it is overridden by the given key (and possibly
+                                                          * certificate chain).
+     *
+     * @param alias the alias name
+     * @param key the key (in protected format) to be associated with the alias
+     * @param chain the certificate chain for the corresponding public
+     * key (only useful if the protected key is of type
+            * <code>java.security.PrivateKey</code>).
+     *
+     * @exception KeyStoreException if this operation fails.
+     */
+    public void engineSetKeyEntry(String alias, byte[] key,
+                                  Certificate[] chain)
+        throws KeyStoreException
+    {
+        permissionCheck();
+
+        synchronized(entries) {
+            // key must be encoded as EncryptedPrivateKeyInfo as defined in
+            // PKCS#8
+            KeyEntry entry = new KeyEntry();
+            try {
+                EncryptedPrivateKeyInfo privateKey = new EncryptedPrivateKeyInfo(key);
+                entry.protectedPrivKey = privateKey.getEncoded();
+            } catch (IOException ioe) {
+                throw new KeyStoreException("key is not encoded as "
+                                            + "EncryptedPrivateKeyInfo");
+            }
+
+            entry.date = new Date();
+
+            if ((chain != null) &&
+                (chain.length != 0)) {
+                entry.chain = chain.clone();
+                entry.chainRefs = new long[entry.chain.length];
+            }
+
+            String lowerAlias = alias.toLowerCase();
+            if (entries.get(lowerAlias) != null) {
+                deletedEntries.put(lowerAlias, entries.get(alias));
+            }
+            entries.put(lowerAlias, entry);
+            addedEntries.put(lowerAlias, entry);
+        }
+    }
+
+    /**
+        * Assigns the given certificate to the given alias.
+     *
+     * <p>If the given alias already exists in this keystore and identifies a
+     * <i>trusted certificate entry</i>, the certificate associated with it is
+     * overridden by the given certificate.
+     *
+     * @param alias the alias name
+     * @param cert the certificate
+     *
+     * @exception KeyStoreException if the given alias already exists and does
+     * not identify a <i>trusted certificate entry</i>, or this operation
+     * fails for some other reason.
+     */
+    public void engineSetCertificateEntry(String alias, Certificate cert)
+        throws KeyStoreException
+    {
+        permissionCheck();
+
+        synchronized(entries) {
+
+            Object entry = entries.get(alias.toLowerCase());
+            if ((entry != null) && (entry instanceof KeyEntry)) {
+                throw new KeyStoreException
+                ("Cannot overwrite key entry with certificate");
+            }
+
+            // This will be slow, but necessary.  Enumerate the values and then see if the cert matches the one in the trusted cert entry.
+            // Security framework doesn't support the same certificate twice in a keychain.
+            Collection<Object> allValues = entries.values();
+
+            for (Object value : allValues) {
+                if (value instanceof TrustedCertEntry) {
+                    TrustedCertEntry tce = (TrustedCertEntry)value;
+                    if (tce.cert.equals(cert)) {
+                        throw new KeyStoreException("Keychain does not support mulitple copies of same certificate.");
+                    }
+                }
+            }
+
+            TrustedCertEntry trustedCertEntry = new TrustedCertEntry();
+            trustedCertEntry.cert = cert;
+            trustedCertEntry.date = new Date();
+            String lowerAlias = alias.toLowerCase();
+            if (entries.get(lowerAlias) != null) {
+                deletedEntries.put(lowerAlias, entries.get(lowerAlias));
+            }
+            entries.put(lowerAlias, trustedCertEntry);
+            addedEntries.put(lowerAlias, trustedCertEntry);
+        }
+    }
+
+    /**
+        * Deletes the entry identified by the given alias from this keystore.
+     *
+     * @param alias the alias name
+     *
+     * @exception KeyStoreException if the entry cannot be removed.
+     */
+    public void engineDeleteEntry(String alias)
+        throws KeyStoreException
+    {
+        permissionCheck();
+
+        synchronized(entries) {
+            Object entry = entries.remove(alias.toLowerCase());
+            deletedEntries.put(alias.toLowerCase(), entry);
+        }
+    }
+
+    /**
+        * Lists all the alias names of this keystore.
+     *
+     * @return enumeration of the alias names
+     */
+    public Enumeration<String> engineAliases() {
+        permissionCheck();
+        return entries.keys();
+    }
+
+    /**
+        * Checks if the given alias exists in this keystore.
+     *
+     * @param alias the alias name
+     *
+     * @return true if the alias exists, false otherwise
+     */
+    public boolean engineContainsAlias(String alias) {
+        permissionCheck();
+        return entries.containsKey(alias.toLowerCase());
+    }
+
+    /**
+        * Retrieves the number of entries in this keystore.
+     *
+     * @return the number of entries in this keystore
+     */
+    public int engineSize() {
+        permissionCheck();
+        return entries.size();
+    }
+
+    /**
+        * Returns true if the entry identified by the given alias is a
+     * <i>key entry</i>, and false otherwise.
+     *
+     * @return true if the entry identified by the given alias is a
+     * <i>key entry</i>, false otherwise.
+     */
+    public boolean engineIsKeyEntry(String alias) {
+        permissionCheck();
+        Object entry = entries.get(alias.toLowerCase());
+        if ((entry != null) && (entry instanceof KeyEntry)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+        * Returns true if the entry identified by the given alias is a
+     * <i>trusted certificate entry</i>, and false otherwise.
+     *
+     * @return true if the entry identified by the given alias is a
+     * <i>trusted certificate entry</i>, false otherwise.
+     */
+    public boolean engineIsCertificateEntry(String alias) {
+        permissionCheck();
+        Object entry = entries.get(alias.toLowerCase());
+        if ((entry != null) && (entry instanceof TrustedCertEntry)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+        * Returns the (alias) name of the first keystore entry whose certificate
+     * matches the given certificate.
+     *
+     * <p>This method attempts to match the given certificate with each
+     * keystore entry. If the entry being considered
+     * is a <i>trusted certificate entry</i>, the given certificate is
+     * compared to that entry's certificate. If the entry being considered is
+     * a <i>key entry</i>, the given certificate is compared to the first
+     * element of that entry's certificate chain (if a chain exists).
+     *
+     * @param cert the certificate to match with.
+     *
+     * @return the (alias) name of the first entry with matching certificate,
+     * or null if no such entry exists in this keystore.
+     */
+    public String engineGetCertificateAlias(Certificate cert) {
+        permissionCheck();
+        Certificate certElem;
+
+        for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) {
+            String alias = e.nextElement();
+            Object entry = entries.get(alias);
+            if (entry instanceof TrustedCertEntry) {
+                certElem = ((TrustedCertEntry)entry).cert;
+            } else if (((KeyEntry)entry).chain != null) {
+                certElem = ((KeyEntry)entry).chain[0];
+            } else {
+                continue;
+            }
+            if (certElem.equals(cert)) {
+                return alias;
+            }
+        }
+        return null;
+    }
+
+    /**
+        * Stores this keystore to the given output stream, and protects its
+     * integrity with the given password.
+     *
+     * @param stream Ignored. the output stream to which this keystore is written.
+     * @param password the password to generate the keystore integrity check
+     *
+     * @exception IOException if there was an I/O problem with data
+     * @exception NoSuchAlgorithmException if the appropriate data integrity
+     * algorithm could not be found
+     * @exception CertificateException if any of the certificates included in
+     * the keystore data could not be stored
+     */
+    public void engineStore(OutputStream stream, char[] password)
+        throws IOException, NoSuchAlgorithmException, CertificateException
+    {
+        permissionCheck();
+
+        // Delete items that do have a keychain item ref.
+        for (Enumeration<String> e = deletedEntries.keys(); e.hasMoreElements(); ) {
+            String alias = e.nextElement();
+            Object entry = deletedEntries.get(alias);
+            if (entry instanceof TrustedCertEntry) {
+                if (((TrustedCertEntry)entry).certRef != 0) {
+                    _removeItemFromKeychain(((TrustedCertEntry)entry).certRef);
+                    _releaseKeychainItemRef(((TrustedCertEntry)entry).certRef);
+                }
+            } else {
+                Certificate certElem;
+                KeyEntry keyEntry = (KeyEntry)entry;
+
+                if (keyEntry.chain != null) {
+                    for (int i = 0; i < keyEntry.chain.length; i++) {
+                        if (keyEntry.chainRefs[i] != 0) {
+                            _removeItemFromKeychain(keyEntry.chainRefs[i]);
+                            _releaseKeychainItemRef(keyEntry.chainRefs[i]);
+                        }
+                    }
+
+                    if (keyEntry.keyRef != 0) {
+                        _removeItemFromKeychain(keyEntry.keyRef);
+                        _releaseKeychainItemRef(keyEntry.keyRef);
+                    }
+                }
+            }
+        }
+
+        // Add all of the certs or keys in the added entries.
+        // No need to check for 0 refs, as they are in the added list.
+        for (Enumeration<String> e = addedEntries.keys(); e.hasMoreElements(); ) {
+            String alias = e.nextElement();
+            Object entry = addedEntries.get(alias);
+            if (entry instanceof TrustedCertEntry) {
+                TrustedCertEntry tce = (TrustedCertEntry)entry;
+                Certificate certElem;
+                certElem = tce.cert;
+                tce.certRef = addCertificateToKeychain(alias, certElem);
+            } else {
+                KeyEntry keyEntry = (KeyEntry)entry;
+
+                if (keyEntry.chain != null) {
+                    for (int i = 0; i < keyEntry.chain.length; i++) {
+                        keyEntry.chainRefs[i] = addCertificateToKeychain(alias, keyEntry.chain[i]);
+                    }
+
+                    keyEntry.keyRef = _addItemToKeychain(alias, false, keyEntry.protectedPrivKey, keyEntry.password);
+                }
+            }
+        }
+
+        // Clear the added and deletedEntries hashtables here, now that we're done with the updates.
+        // For the deleted entries, we freed up the native references above.
+        deletedEntries.clear();
+        addedEntries.clear();
+    }
+
+    private long addCertificateToKeychain(String alias, Certificate cert) {
+        byte[] certblob = null;
+        long returnValue = 0;
+
+        try {
+            certblob = cert.getEncoded();
+            returnValue = _addItemToKeychain(alias, true, certblob, null);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return returnValue;
+    }
+
+    private native long _addItemToKeychain(String alias, boolean isCertificate, byte[] datablob, char[] password);
+    private native int _removeItemFromKeychain(long certRef);
+    private native void _releaseKeychainItemRef(long keychainItemRef);
+
+    /**
+      * Loads the keystore from the Keychain.
+     *
+     * @param stream Ignored - here for API compatibility.
+     * @param password Ignored - if user needs to unlock keychain Security
+     * framework will post any dialogs.
+     *
+     * @exception IOException if there is an I/O or format problem with the
+     * keystore data
+     * @exception NoSuchAlgorithmException if the algorithm used to check
+     * the integrity of the keystore cannot be found
+     * @exception CertificateException if any of the certificates in the
+     * keystore could not be loaded
+     */
+    public void engineLoad(InputStream stream, char[] password)
+        throws IOException, NoSuchAlgorithmException, CertificateException
+    {
+        permissionCheck();
+
+        // Release any stray keychain references before clearing out the entries.
+        synchronized(entries) {
+            for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) {
+                String alias = e.nextElement();
+                Object entry = entries.get(alias);
+                if (entry instanceof TrustedCertEntry) {
+                    if (((TrustedCertEntry)entry).certRef != 0) {
+                        _releaseKeychainItemRef(((TrustedCertEntry)entry).certRef);
+                    }
+                } else {
+                    KeyEntry keyEntry = (KeyEntry)entry;
+
+                    if (keyEntry.chain != null) {
+                        for (int i = 0; i < keyEntry.chain.length; i++) {
+                            if (keyEntry.chainRefs[i] != 0) {
+                                _releaseKeychainItemRef(keyEntry.chainRefs[i]);
+                            }
+                        }
+
+                        if (keyEntry.keyRef != 0) {
+                            _releaseKeychainItemRef(keyEntry.keyRef);
+                        }
+                    }
+                }
+            }
+
+            entries.clear();
+            _scanKeychain();
+        }
+    }
+
+    private native void _scanKeychain();
+
+    /**
+     * Callback method from _scanKeychain.  If a trusted certificate is found, this method will be called.
+     */
+    private void createTrustedCertEntry(String alias, long keychainItemRef, long creationDate, byte[] derStream) {
+        TrustedCertEntry tce = new TrustedCertEntry();
+
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            InputStream input = new ByteArrayInputStream(derStream);
+            X509Certificate cert = (X509Certificate) cf.generateCertificate(input);
+            input.close();
+            tce.cert = cert;
+            tce.certRef = keychainItemRef;
+
+            // Make a creation date.
+            if (creationDate != 0)
+                tce.date = new Date(creationDate);
+            else
+                tce.date = new Date();
+
+            int uniqueVal = 1;
+            String originalAlias = alias;
+
+            while (entries.containsKey(alias.toLowerCase())) {
+                alias = originalAlias + " " + uniqueVal;
+                uniqueVal++;
+            }
+
+            entries.put(alias.toLowerCase(), tce);
+        } catch (Exception e) {
+            // The certificate will be skipped.
+            System.err.println("KeychainStore Ignored Exception: " + e);
+        }
+    }
+
+    /**
+     * Callback method from _scanKeychain.  If an identity is found, this method will be called to create Java certificate
+     * and private key objects from the keychain data.
+     */
+    private void createKeyEntry(String alias, long creationDate, long secKeyRef, long[] secCertificateRefs, byte[][] rawCertData)
+        throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
+        KeyEntry ke = new KeyEntry();
+
+        // First, store off the private key information.  This is the easy part.
+        ke.protectedPrivKey = null;
+        ke.keyRef = secKeyRef;
+
+        // Make a creation date.
+        if (creationDate != 0)
+            ke.date = new Date(creationDate);
+        else
+            ke.date = new Date();
+
+        // Next, create X.509 Certificate objects from the raw data.  This is complicated
+        // because a certificate's public key may be too long for Java's default encryption strength.
+        List<CertKeychainItemPair> createdCerts = new ArrayList<>();
+
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+            for (int i = 0; i < rawCertData.length; i++) {
+                try {
+                    InputStream input = new ByteArrayInputStream(rawCertData[i]);
+                    X509Certificate cert = (X509Certificate) cf.generateCertificate(input);
+                    input.close();
+
+                    // We successfully created the certificate, so track it and its corresponding SecCertificateRef.
+                    createdCerts.add(new CertKeychainItemPair(secCertificateRefs[i], cert));
+                } catch (CertificateException e) {
+                    // The certificate will be skipped.
+                    System.err.println("KeychainStore Ignored Exception: " + e);
+                }
+            }
+        } catch (CertificateException e) {
+            e.printStackTrace();
+        } catch (IOException ioe) {
+            ioe.printStackTrace();  // How would this happen?
+        }
+
+        // We have our certificates in the List, so now extract them into an array of
+        // Certificates and SecCertificateRefs.
+        CertKeychainItemPair[] objArray = createdCerts.toArray(new CertKeychainItemPair[0]);
+        Certificate[] certArray = new Certificate[objArray.length];
+        long[] certRefArray = new long[objArray.length];
+
+        for (int i = 0; i < objArray.length; i++) {
+            CertKeychainItemPair addedItem = objArray[i];
+            certArray[i] = addedItem.mCert;
+            certRefArray[i] = addedItem.mCertificateRef;
+        }
+
+        ke.chain = certArray;
+        ke.chainRefs = certRefArray;
+
+        // If we don't have already have an item with this item's alias
+        // create a new one for it.
+        int uniqueVal = 1;
+        String originalAlias = alias;
+
+        while (entries.containsKey(alias.toLowerCase())) {
+            alias = originalAlias + " " + uniqueVal;
+            uniqueVal++;
+        }
+
+        entries.put(alias.toLowerCase(), ke);
+    }
+
+    private class CertKeychainItemPair {
+        long mCertificateRef;
+        Certificate mCert;
+
+        CertKeychainItemPair(long inCertRef, Certificate cert) {
+            mCertificateRef = inCertRef;
+            mCert = cert;
+        }
+    }
+
+    /*
+     * Validate Certificate Chain
+     */
+    private boolean validateChain(Certificate[] certChain)
+    {
+        for (int i = 0; i < certChain.length-1; i++) {
+            X500Principal issuerDN =
+            ((X509Certificate)certChain[i]).getIssuerX500Principal();
+            X500Principal subjectDN =
+                ((X509Certificate)certChain[i+1]).getSubjectX500Principal();
+            if (!(issuerDN.equals(subjectDN)))
+                return false;
+        }
+        return true;
+    }
+
+    private byte[] fetchPrivateKeyFromBag(byte[] privateKeyInfo) throws IOException, NoSuchAlgorithmException, CertificateException
+    {
+        byte[] returnValue = null;
+        DerValue val = new DerValue(new ByteArrayInputStream(privateKeyInfo));
+        DerInputStream s = val.toDerInputStream();
+        int version = s.getInteger();
+
+        if (version != 3) {
+            throw new IOException("PKCS12 keystore not in version 3 format");
+        }
+
+        /*
+            * Read the authSafe.
+         */
+        byte[] authSafeData;
+        ContentInfo authSafe = new ContentInfo(s);
+        ObjectIdentifier contentType = authSafe.getContentType();
+
+        if (contentType.equals(ContentInfo.DATA_OID)) {
+            authSafeData = authSafe.getData();
+        } else /* signed data */ {
+            throw new IOException("public key protected PKCS12 not supported");
+        }
+
+        DerInputStream as = new DerInputStream(authSafeData);
+        DerValue[] safeContentsArray = as.getSequence(2);
+        int count = safeContentsArray.length;
+
+        /*
+         * Spin over the ContentInfos.
+         */
+        for (int i = 0; i < count; i++) {
+            byte[] safeContentsData;
+            ContentInfo safeContents;
+            DerInputStream sci;
+            byte[] eAlgId = null;
+
+            sci = new DerInputStream(safeContentsArray[i].toByteArray());
+            safeContents = new ContentInfo(sci);
+            contentType = safeContents.getContentType();
+            safeContentsData = null;
+
+            if (contentType.equals(ContentInfo.DATA_OID)) {
+                safeContentsData = safeContents.getData();
+            } else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) {
+                // The password was used to export the private key from the keychain.
+                // The Keychain won't export the key with encrypted data, so we don't need
+                // to worry about it.
+                continue;
+            } else {
+                throw new IOException("public key protected PKCS12" +
+                                      " not supported");
+            }
+            DerInputStream sc = new DerInputStream(safeContentsData);
+            returnValue = extractKeyData(sc);
+        }
+
+        return returnValue;
+    }
+
+    private byte[] extractKeyData(DerInputStream stream)
+        throws IOException, NoSuchAlgorithmException, CertificateException
+    {
+        byte[] returnValue = null;
+        DerValue[] safeBags = stream.getSequence(2);
+        int count = safeBags.length;
+
+        /*
+         * Spin over the SafeBags.
+         */
+        for (int i = 0; i < count; i++) {
+            ObjectIdentifier bagId;
+            DerInputStream sbi;
+            DerValue bagValue;
+            Object bagItem = null;
+
+            sbi = safeBags[i].toDerInputStream();
+            bagId = sbi.getOID();
+            bagValue = sbi.getDerValue();
+            if (!bagValue.isContextSpecific((byte)0)) {
+                throw new IOException("unsupported PKCS12 bag value type "
+                                      + bagValue.tag);
+            }
+            bagValue = bagValue.data.getDerValue();
+            if (bagId.equals(PKCS8ShroudedKeyBag_OID)) {
+                // got what we were looking for.  Return it.
+                returnValue = bagValue.toByteArray();
+            } else {
+                // log error message for "unsupported PKCS12 bag type"
+                System.out.println("Unsupported bag type '" + bagId + "'");
+            }
+        }
+
+        return returnValue;
+    }
+
+    /*
+        * Generate PBE Algorithm Parameters
+     */
+    private AlgorithmParameters getAlgorithmParameters(String algorithm)
+        throws IOException
+    {
+        AlgorithmParameters algParams = null;
+
+        // create PBE parameters from salt and iteration count
+        PBEParameterSpec paramSpec =
+            new PBEParameterSpec(getSalt(), iterationCount);
+        try {
+            algParams = AlgorithmParameters.getInstance(algorithm);
+            algParams.init(paramSpec);
+        } catch (Exception e) {
+            IOException ioe =
+            new IOException("getAlgorithmParameters failed: " +
+                            e.getMessage());
+            ioe.initCause(e);
+            throw ioe;
+        }
+        return algParams;
+    }
+
+    // the source of randomness
+    private SecureRandom random;
+
+    /*
+     * Generate random salt
+     */
+    private byte[] getSalt()
+    {
+        // Generate a random salt.
+        byte[] salt = new byte[SALT_LEN];
+        if (random == null) {
+            random = new SecureRandom();
+        }
+        salt = random.generateSeed(SALT_LEN);
+        return salt;
+    }
+
+    /*
+     * parse Algorithm Parameters
+     */
+    private AlgorithmParameters parseAlgParameters(DerInputStream in)
+        throws IOException
+    {
+        AlgorithmParameters algParams = null;
+        try {
+            DerValue params;
+            if (in.available() == 0) {
+                params = null;
+            } else {
+                params = in.getDerValue();
+                if (params.tag == DerValue.tag_Null) {
+                    params = null;
+                }
+            }
+            if (params != null) {
+                algParams = AlgorithmParameters.getInstance("PBE");
+                algParams.init(params.toByteArray());
+            }
+        } catch (Exception e) {
+            IOException ioe =
+            new IOException("parseAlgParameters failed: " +
+                            e.getMessage());
+            ioe.initCause(e);
+            throw ioe;
+        }
+        return algParams;
+    }
+
+    /*
+     * Generate PBE key
+     */
+    private SecretKey getPBEKey(char[] password) throws IOException
+    {
+        SecretKey skey = null;
+
+        try {
+            PBEKeySpec keySpec = new PBEKeySpec(password);
+            SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE");
+            skey = skFac.generateSecret(keySpec);
+        } catch (Exception e) {
+            IOException ioe = new IOException("getSecretKey failed: " +
+                                              e.getMessage());
+            ioe.initCause(e);
+            throw ioe;
+        }
+        return skey;
+    }
+
+    /*
+     * Encrypt private key using Password-based encryption (PBE)
+     * as defined in PKCS#5.
+     *
+     * NOTE: Currently pbeWithSHAAnd3-KeyTripleDES-CBC algorithmID is
+     *       used to derive the key and IV.
+     *
+     * @return encrypted private key encoded as EncryptedPrivateKeyInfo
+     */
+    private byte[] encryptPrivateKey(byte[] data, char[] password)
+        throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException
+    {
+        byte[] key = null;
+
+        try {
+            // create AlgorithmParameters
+            AlgorithmParameters algParams =
+            getAlgorithmParameters("PBEWithSHA1AndDESede");
+
+            // Use JCE
+            SecretKey skey = getPBEKey(password);
+            Cipher cipher = Cipher.getInstance("PBEWithSHA1AndDESede");
+            cipher.init(Cipher.ENCRYPT_MODE, skey, algParams);
+            byte[] encryptedKey = cipher.doFinal(data);
+
+            // wrap encrypted private key in EncryptedPrivateKeyInfo
+            // as defined in PKCS#8
+            AlgorithmId algid =
+                new AlgorithmId(pbeWithSHAAnd3KeyTripleDESCBC_OID, algParams);
+            EncryptedPrivateKeyInfo encrInfo =
+                new EncryptedPrivateKeyInfo(algid, encryptedKey);
+            key = encrInfo.getEncoded();
+        } catch (Exception e) {
+            UnrecoverableKeyException uke =
+            new UnrecoverableKeyException("Encrypt Private Key failed: "
+                                          + e.getMessage());
+            uke.initCause(e);
+            throw uke;
+        }
+
+        return key;
+    }
+
+
+}
+