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