--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/macosx/classes/apple/security/KeychainStore.java Tue Mar 06 20:34:38 2012 +0000
@@ -0,0 +1,1124 @@
+/*
+ * Copyright (c) 2011, 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 deletedEntries = new Hashtable();
+
+ /**
+ * Entries that have been added. When something calls engineStore we'll
+ * add them to the keychain.
+ */
+ private Hashtable addedEntries = new Hashtable();
+
+ /**
+ * Private keys and certificates are stored in a hashtable.
+ * Hash entries are keyed by alias names.
+ */
+ private Hashtable 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 {
+ java.security.AccessController.doPrivileged((PrivilegedAction<?>)new sun.security.action.LoadLibraryAction("osx"));
+ 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 (Certificate[])((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 = (Certificate[])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 = (Certificate[])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 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 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 e = entries.keys(); e.hasMoreElements(); ) {
+ String alias = (String)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 e = deletedEntries.keys(); e.hasMoreElements(); ) {
+ String alias = (String)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 e = addedEntries.keys(); e.hasMoreElements(); ) {
+ String alias = (String)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 e = entries.keys(); e.hasMoreElements(); ) {
+ String alias = (String)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 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.
+ Object[] objArray = createdCerts.toArray();
+ Certificate[] certArray = new Certificate[objArray.length];
+ long[] certRefArray = new long[objArray.length];
+
+ for (int i = 0; i < objArray.length; i++) {
+ CertKeychainItemPair addedItem = (CertKeychainItemPair)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 ((byte[])key);
+ }
+
+
+}
+