1/*
2 * Copyright (c) 2011, 2016, 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
26package apple.security;
27
28import java.io.*;
29import java.security.*;
30import java.security.cert.*;
31import java.security.cert.Certificate;
32import java.security.spec.*;
33import java.util.*;
34
35import javax.crypto.*;
36import javax.crypto.spec.*;
37import javax.security.auth.x500.*;
38
39import sun.security.pkcs.*;
40import sun.security.pkcs.EncryptedPrivateKeyInfo;
41import sun.security.util.*;
42import 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
50public 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("osxsecurity");
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    private byte[] fetchPrivateKeyFromBag(byte[] privateKeyInfo) throws IOException, NoSuchAlgorithmException, CertificateException
915    {
916        byte[] returnValue = null;
917        DerValue val = new DerValue(new ByteArrayInputStream(privateKeyInfo));
918        DerInputStream s = val.toDerInputStream();
919        int version = s.getInteger();
920
921        if (version != 3) {
922            throw new IOException("PKCS12 keystore not in version 3 format");
923        }
924
925        /*
926            * Read the authSafe.
927         */
928        byte[] authSafeData;
929        ContentInfo authSafe = new ContentInfo(s);
930        ObjectIdentifier contentType = authSafe.getContentType();
931
932        if (contentType.equals(ContentInfo.DATA_OID)) {
933            authSafeData = authSafe.getData();
934        } else /* signed data */ {
935            throw new IOException("public key protected PKCS12 not supported");
936        }
937
938        DerInputStream as = new DerInputStream(authSafeData);
939        DerValue[] safeContentsArray = as.getSequence(2);
940        int count = safeContentsArray.length;
941
942        /*
943         * Spin over the ContentInfos.
944         */
945        for (int i = 0; i < count; i++) {
946            byte[] safeContentsData;
947            ContentInfo safeContents;
948            DerInputStream sci;
949            byte[] eAlgId = null;
950
951            sci = new DerInputStream(safeContentsArray[i].toByteArray());
952            safeContents = new ContentInfo(sci);
953            contentType = safeContents.getContentType();
954            safeContentsData = null;
955
956            if (contentType.equals(ContentInfo.DATA_OID)) {
957                safeContentsData = safeContents.getData();
958            } else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) {
959                // The password was used to export the private key from the keychain.
960                // The Keychain won't export the key with encrypted data, so we don't need
961                // to worry about it.
962                continue;
963            } else {
964                throw new IOException("public key protected PKCS12" +
965                                      " not supported");
966            }
967            DerInputStream sc = new DerInputStream(safeContentsData);
968            returnValue = extractKeyData(sc);
969        }
970
971        return returnValue;
972    }
973
974    private byte[] extractKeyData(DerInputStream stream)
975        throws IOException, NoSuchAlgorithmException, CertificateException
976    {
977        byte[] returnValue = null;
978        DerValue[] safeBags = stream.getSequence(2);
979        int count = safeBags.length;
980
981        /*
982         * Spin over the SafeBags.
983         */
984        for (int i = 0; i < count; i++) {
985            ObjectIdentifier bagId;
986            DerInputStream sbi;
987            DerValue bagValue;
988            Object bagItem = null;
989
990            sbi = safeBags[i].toDerInputStream();
991            bagId = sbi.getOID();
992            bagValue = sbi.getDerValue();
993            if (!bagValue.isContextSpecific((byte)0)) {
994                throw new IOException("unsupported PKCS12 bag value type "
995                                      + bagValue.tag);
996            }
997            bagValue = bagValue.data.getDerValue();
998            if (bagId.equals(PKCS8ShroudedKeyBag_OID)) {
999                // got what we were looking for.  Return it.
1000                returnValue = bagValue.toByteArray();
1001            } else {
1002                // log error message for "unsupported PKCS12 bag type"
1003                System.out.println("Unsupported bag type '" + bagId + "'");
1004            }
1005        }
1006
1007        return returnValue;
1008    }
1009
1010    /*
1011        * Generate PBE Algorithm Parameters
1012     */
1013    private AlgorithmParameters getAlgorithmParameters(String algorithm)
1014        throws IOException
1015    {
1016        AlgorithmParameters algParams = null;
1017
1018        // create PBE parameters from salt and iteration count
1019        PBEParameterSpec paramSpec =
1020            new PBEParameterSpec(getSalt(), iterationCount);
1021        try {
1022            algParams = AlgorithmParameters.getInstance(algorithm);
1023            algParams.init(paramSpec);
1024        } catch (Exception e) {
1025            IOException ioe =
1026            new IOException("getAlgorithmParameters failed: " +
1027                            e.getMessage());
1028            ioe.initCause(e);
1029            throw ioe;
1030        }
1031        return algParams;
1032    }
1033
1034    // the source of randomness
1035    private SecureRandom random;
1036
1037    /*
1038     * Generate random salt
1039     */
1040    private byte[] getSalt()
1041    {
1042        // Generate a random salt.
1043        byte[] salt = new byte[SALT_LEN];
1044        if (random == null) {
1045            random = new SecureRandom();
1046        }
1047        salt = random.generateSeed(SALT_LEN);
1048        return salt;
1049    }
1050
1051    /*
1052     * parse Algorithm Parameters
1053     */
1054    private AlgorithmParameters parseAlgParameters(DerInputStream in)
1055        throws IOException
1056    {
1057        AlgorithmParameters algParams = null;
1058        try {
1059            DerValue params;
1060            if (in.available() == 0) {
1061                params = null;
1062            } else {
1063                params = in.getDerValue();
1064                if (params.tag == DerValue.tag_Null) {
1065                    params = null;
1066                }
1067            }
1068            if (params != null) {
1069                algParams = AlgorithmParameters.getInstance("PBE");
1070                algParams.init(params.toByteArray());
1071            }
1072        } catch (Exception e) {
1073            IOException ioe =
1074            new IOException("parseAlgParameters failed: " +
1075                            e.getMessage());
1076            ioe.initCause(e);
1077            throw ioe;
1078        }
1079        return algParams;
1080    }
1081
1082    /*
1083     * Generate PBE key
1084     */
1085    private SecretKey getPBEKey(char[] password) throws IOException
1086    {
1087        SecretKey skey = null;
1088
1089        try {
1090            PBEKeySpec keySpec = new PBEKeySpec(password);
1091            SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE");
1092            skey = skFac.generateSecret(keySpec);
1093        } catch (Exception e) {
1094            IOException ioe = new IOException("getSecretKey failed: " +
1095                                              e.getMessage());
1096            ioe.initCause(e);
1097            throw ioe;
1098        }
1099        return skey;
1100    }
1101
1102    /*
1103     * Encrypt private key using Password-based encryption (PBE)
1104     * as defined in PKCS#5.
1105     *
1106     * NOTE: Currently pbeWithSHAAnd3-KeyTripleDES-CBC algorithmID is
1107     *       used to derive the key and IV.
1108     *
1109     * @return encrypted private key encoded as EncryptedPrivateKeyInfo
1110     */
1111    private byte[] encryptPrivateKey(byte[] data, char[] password)
1112        throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException
1113    {
1114        byte[] key = null;
1115
1116        try {
1117            // create AlgorithmParameters
1118            AlgorithmParameters algParams =
1119            getAlgorithmParameters("PBEWithSHA1AndDESede");
1120
1121            // Use JCE
1122            SecretKey skey = getPBEKey(password);
1123            Cipher cipher = Cipher.getInstance("PBEWithSHA1AndDESede");
1124            cipher.init(Cipher.ENCRYPT_MODE, skey, algParams);
1125            byte[] encryptedKey = cipher.doFinal(data);
1126
1127            // wrap encrypted private key in EncryptedPrivateKeyInfo
1128            // as defined in PKCS#8
1129            AlgorithmId algid =
1130                new AlgorithmId(pbeWithSHAAnd3KeyTripleDESCBC_OID, algParams);
1131            EncryptedPrivateKeyInfo encrInfo =
1132                new EncryptedPrivateKeyInfo(algid, encryptedKey);
1133            key = encrInfo.getEncoded();
1134        } catch (Exception e) {
1135            UnrecoverableKeyException uke =
1136            new UnrecoverableKeyException("Encrypt Private Key failed: "
1137                                          + e.getMessage());
1138            uke.initCause(e);
1139            throw uke;
1140        }
1141
1142        return key;
1143    }
1144
1145
1146}
1147
1148