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