1/*
2 * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.crypto.provider;
27
28import java.io.*;
29import java.util.*;
30import java.security.DigestInputStream;
31import java.security.DigestOutputStream;
32import java.security.MessageDigest;
33import java.security.NoSuchAlgorithmException;
34import java.security.Key;
35import java.security.PrivateKey;
36import java.security.KeyStoreSpi;
37import java.security.KeyStoreException;
38import java.security.UnrecoverableKeyException;
39import java.security.cert.Certificate;
40import java.security.cert.CertificateFactory;
41import java.security.cert.CertificateException;
42import javax.crypto.SealedObject;
43
44/**
45 * This class provides the keystore implementation referred to as "jceks".
46 * This implementation strongly protects the keystore private keys using
47 * triple-DES, where the triple-DES encryption/decryption key is derived from
48 * the user's password.
49 * The encrypted private keys are stored in the keystore in a standard format,
50 * namely the <code>EncryptedPrivateKeyInfo</code> format defined in PKCS #8.
51 *
52 * @author Jan Luehe
53 *
54 *
55 * @see java.security.KeyStoreSpi
56 */
57
58public final class JceKeyStore extends KeyStoreSpi {
59
60    private static final int JCEKS_MAGIC = 0xcececece;
61    private static final int JKS_MAGIC = 0xfeedfeed;
62    private static final int VERSION_1 = 0x01;
63    private static final int VERSION_2 = 0x02;
64
65    // Private key and supporting certificate chain
66    private static final class PrivateKeyEntry {
67        Date date; // the creation date of this entry
68        byte[] protectedKey;
69        Certificate chain[];
70    };
71
72    // Secret key
73    private static final class SecretKeyEntry {
74        Date date; // the creation date of this entry
75        SealedObject sealedKey;
76    }
77
78    // Trusted certificate
79    private static final class TrustedCertEntry {
80        Date date; // the creation date of this entry
81        Certificate cert;
82    };
83
84    /**
85     * Private keys and certificates are stored in a hashtable.
86     * Hash entries are keyed by alias names.
87     */
88    private Hashtable<String, Object> entries = new Hashtable<String, Object>();
89
90    /**
91     * Returns the key associated with the given alias, using the given
92     * password to recover it.
93     *
94     * @param alias the alias name
95     * @param password the password for recovering the key
96     *
97     * @return the requested key, or null if the given alias does not exist
98     * or does not identify a <i>key entry</i>.
99     *
100     * @exception NoSuchAlgorithmException if the algorithm for recovering the
101     * key cannot be found
102     * @exception UnrecoverableKeyException if the key cannot be recovered
103     * (e.g., the given password is wrong).
104     */
105    public Key engineGetKey(String alias, char[] password)
106        throws NoSuchAlgorithmException, UnrecoverableKeyException
107    {
108        Key key = null;
109
110        Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
111
112        if (!((entry instanceof PrivateKeyEntry) ||
113              (entry instanceof SecretKeyEntry))) {
114            return null;
115        }
116
117        KeyProtector keyProtector = new KeyProtector(password);
118
119        if (entry instanceof PrivateKeyEntry) {
120            byte[] encrBytes = ((PrivateKeyEntry)entry).protectedKey;
121            EncryptedPrivateKeyInfo encrInfo;
122            try {
123                encrInfo = new EncryptedPrivateKeyInfo(encrBytes);
124            } catch (IOException ioe) {
125                throw new UnrecoverableKeyException("Private key not stored "
126                                                    + "as PKCS #8 " +
127                                                    "EncryptedPrivateKeyInfo");
128            }
129            key = keyProtector.recover(encrInfo);
130        } else {
131            key =
132                keyProtector.unseal(((SecretKeyEntry)entry).sealedKey);
133        }
134
135        return key;
136    }
137
138    /**
139     * Returns the certificate chain associated with the given alias.
140     *
141     * @param alias the alias name
142     *
143     * @return the certificate chain (ordered with the user's certificate first
144     * and the root certificate authority last), or null if the given alias
145     * does not exist or does not contain a certificate chain (i.e., the given
146     * alias identifies either a <i>trusted certificate entry</i> or a
147     * <i>key entry</i> without a certificate chain).
148     */
149    public Certificate[] engineGetCertificateChain(String alias)
150    {
151        Certificate[] chain = null;
152
153        Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
154
155        if ((entry instanceof PrivateKeyEntry)
156            && (((PrivateKeyEntry)entry).chain != null)) {
157            chain = ((PrivateKeyEntry)entry).chain.clone();
158        }
159
160        return chain;
161    }
162
163    /**
164     * Returns the certificate associated with the given alias.
165     *
166     * <p>If the given alias name identifies a
167     * <i>trusted certificate entry</i>, the certificate associated with that
168     * entry is returned. If the given alias name identifies a
169     * <i>key entry</i>, the first element of the certificate chain of that
170     * entry is returned, or null if that entry does not have a certificate
171     * chain.
172     *
173     * @param alias the alias name
174     *
175     * @return the certificate, or null if the given alias does not exist or
176     * does not contain a certificate.
177     */
178    public Certificate engineGetCertificate(String alias) {
179        Certificate cert = null;
180
181        Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
182
183        if (entry != null) {
184            if (entry instanceof TrustedCertEntry) {
185                cert = ((TrustedCertEntry)entry).cert;
186            } else if ((entry instanceof PrivateKeyEntry) &&
187                       (((PrivateKeyEntry)entry).chain != null)) {
188                cert = ((PrivateKeyEntry)entry).chain[0];
189            }
190        }
191
192        return cert;
193    }
194
195    /**
196     * Returns the creation date of the entry identified by the given alias.
197     *
198     * @param alias the alias name
199     *
200     * @return the creation date of this entry, or null if the given alias does
201     * not exist
202     */
203    public Date engineGetCreationDate(String alias) {
204        Date date = null;
205
206        Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
207
208        if (entry != null) {
209            // We have to create a new instance of java.util.Date because
210            // dates are not immutable
211            if (entry instanceof TrustedCertEntry) {
212                date = new Date(((TrustedCertEntry)entry).date.getTime());
213            } else if (entry instanceof PrivateKeyEntry) {
214                date = new Date(((PrivateKeyEntry)entry).date.getTime());
215            } else {
216                date = new Date(((SecretKeyEntry)entry).date.getTime());
217            }
218        }
219
220        return date;
221    }
222
223    /**
224     * Assigns the given key to the given alias, protecting it with the given
225     * password.
226     *
227     * <p>If the given key is of type <code>java.security.PrivateKey</code>,
228     * it must be accompanied by a certificate chain certifying the
229     * corresponding public key.
230     *
231     * <p>If the given alias already exists, the keystore information
232     * associated with it is overridden by the given key (and possibly
233     * certificate chain).
234     *
235     * @param alias the alias name
236     * @param key the key to be associated with the alias
237     * @param password the password to protect the key
238     * @param chain the certificate chain for the corresponding public
239     * key (only required if the given key is of type
240     * <code>java.security.PrivateKey</code>).
241     *
242     * @exception KeyStoreException if the given key cannot be protected, or
243     * this operation fails for some other reason
244     */
245    public void engineSetKeyEntry(String alias, Key key, char[] password,
246                                  Certificate[] chain)
247        throws KeyStoreException
248    {
249        synchronized(entries) {
250            try {
251                KeyProtector keyProtector = new KeyProtector(password);
252
253                if (key instanceof PrivateKey) {
254                    PrivateKeyEntry entry = new PrivateKeyEntry();
255                    entry.date = new Date();
256
257                    // protect the private key
258                    entry.protectedKey = keyProtector.protect((PrivateKey)key);
259
260                    // clone the chain
261                    if ((chain != null) &&
262                        (chain.length !=0)) {
263                        entry.chain = chain.clone();
264                    } else {
265                        entry.chain = null;
266                    }
267
268                    // store the entry
269                    entries.put(alias.toLowerCase(Locale.ENGLISH), entry);
270
271                } else {
272                    SecretKeyEntry entry = new SecretKeyEntry();
273                    entry.date = new Date();
274
275                    // seal and store the key
276                    entry.sealedKey = keyProtector.seal(key);
277                    entries.put(alias.toLowerCase(Locale.ENGLISH), entry);
278                }
279
280            } catch (Exception e) {
281                throw new KeyStoreException(e.getMessage());
282            }
283        }
284    }
285
286    /**
287     * Assigns the given key (that has already been protected) to the given
288     * alias.
289     *
290     * <p>If the protected key is of type
291     * <code>java.security.PrivateKey</code>,
292     * it must be accompanied by a certificate chain certifying the
293     * corresponding public key.
294     *
295     * <p>If the given alias already exists, the keystore information
296     * associated with it is overridden by the given key (and possibly
297     * certificate chain).
298     *
299     * @param alias the alias name
300     * @param key the key (in protected format) to be associated with the alias
301     * @param chain the certificate chain for the corresponding public
302     * key (only useful if the protected key is of type
303     * <code>java.security.PrivateKey</code>).
304     *
305     * @exception KeyStoreException if this operation fails.
306     */
307    public void engineSetKeyEntry(String alias, byte[] key,
308                                  Certificate[] chain)
309        throws KeyStoreException
310    {
311        synchronized(entries) {
312            // We assume it's a private key, because there is no standard
313            // (ASN.1) encoding format for wrapped secret keys
314            PrivateKeyEntry entry = new PrivateKeyEntry();
315            entry.date = new Date();
316
317            entry.protectedKey = key.clone();
318            if ((chain != null) &&
319                (chain.length != 0)) {
320                entry.chain = chain.clone();
321            } else {
322                entry.chain = null;
323            }
324
325            entries.put(alias.toLowerCase(Locale.ENGLISH), entry);
326        }
327    }
328
329    /**
330     * Assigns the given certificate to the given alias.
331     *
332     * <p>If the given alias already exists in this keystore and identifies a
333     * <i>trusted certificate entry</i>, the certificate associated with it is
334     * overridden by the given certificate.
335     *
336     * @param alias the alias name
337     * @param cert the certificate
338     *
339     * @exception KeyStoreException if the given alias already exists and does
340     * not identify a <i>trusted certificate entry</i>, or this operation
341     * fails for some other reason.
342     */
343    public void engineSetCertificateEntry(String alias, Certificate cert)
344        throws KeyStoreException
345    {
346        synchronized(entries) {
347
348            Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
349            if (entry != null) {
350                if (entry instanceof PrivateKeyEntry) {
351                    throw new KeyStoreException("Cannot overwrite own "
352                                                + "certificate");
353                } else if (entry instanceof SecretKeyEntry) {
354                    throw new KeyStoreException("Cannot overwrite secret key");
355                }
356            }
357
358            TrustedCertEntry trustedCertEntry = new TrustedCertEntry();
359            trustedCertEntry.cert = cert;
360            trustedCertEntry.date = new Date();
361            entries.put(alias.toLowerCase(Locale.ENGLISH), trustedCertEntry);
362        }
363    }
364
365    /**
366     * Deletes the entry identified by the given alias from this keystore.
367     *
368     * @param alias the alias name
369     *
370     * @exception KeyStoreException if the entry cannot be removed.
371     */
372    public void engineDeleteEntry(String alias)
373        throws KeyStoreException
374    {
375        synchronized(entries) {
376            entries.remove(alias.toLowerCase(Locale.ENGLISH));
377        }
378    }
379
380    /**
381     * Lists all the alias names of this keystore.
382     *
383     * @return enumeration of the alias names
384     */
385    public Enumeration<String> engineAliases() {
386        return entries.keys();
387    }
388
389    /**
390     * Checks if the given alias exists in this keystore.
391     *
392     * @param alias the alias name
393     *
394     * @return true if the alias exists, false otherwise
395     */
396    public boolean engineContainsAlias(String alias) {
397        return entries.containsKey(alias.toLowerCase(Locale.ENGLISH));
398    }
399
400    /**
401     * Retrieves the number of entries in this keystore.
402     *
403     * @return the number of entries in this keystore
404     */
405    public int engineSize() {
406        return entries.size();
407    }
408
409    /**
410     * Returns true if the entry identified by the given alias is a
411     * <i>key entry</i>, and false otherwise.
412     *
413     * @return true if the entry identified by the given alias is a
414     * <i>key entry</i>, false otherwise.
415     */
416    public boolean engineIsKeyEntry(String alias) {
417        boolean isKey = false;
418
419        Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
420        if ((entry instanceof PrivateKeyEntry)
421            || (entry instanceof SecretKeyEntry)) {
422            isKey = true;
423        }
424
425        return isKey;
426    }
427
428    /**
429     * Returns true if the entry identified by the given alias is a
430     * <i>trusted certificate entry</i>, and false otherwise.
431     *
432     * @return true if the entry identified by the given alias is a
433     * <i>trusted certificate entry</i>, false otherwise.
434     */
435    public boolean engineIsCertificateEntry(String alias) {
436        boolean isCert = false;
437        Object entry = entries.get(alias.toLowerCase(Locale.ENGLISH));
438        if (entry instanceof TrustedCertEntry) {
439            isCert = true;
440        }
441        return isCert;
442    }
443
444    /**
445     * Returns the (alias) name of the first keystore entry whose certificate
446     * matches the given certificate.
447     *
448     * <p>This method attempts to match the given certificate with each
449     * keystore entry. If the entry being considered
450     * is a <i>trusted certificate entry</i>, the given certificate is
451     * compared to that entry's certificate. If the entry being considered is
452     * a <i>key entry</i>, the given certificate is compared to the first
453     * element of that entry's certificate chain (if a chain exists).
454     *
455     * @param cert the certificate to match with.
456     *
457     * @return the (alias) name of the first entry with matching certificate,
458     * or null if no such entry exists in this keystore.
459     */
460    public String engineGetCertificateAlias(Certificate cert) {
461        Certificate certElem;
462
463        Enumeration<String> e = entries.keys();
464        while (e.hasMoreElements()) {
465            String alias = e.nextElement();
466            Object entry = entries.get(alias);
467            if (entry instanceof TrustedCertEntry) {
468                certElem = ((TrustedCertEntry)entry).cert;
469            } else if ((entry instanceof PrivateKeyEntry) &&
470                       (((PrivateKeyEntry)entry).chain != null)) {
471                certElem = ((PrivateKeyEntry)entry).chain[0];
472            } else {
473                continue;
474            }
475            if (certElem.equals(cert)) {
476                return alias;
477            }
478        }
479        return null;
480    }
481
482    /**
483     * Stores this keystore to the given output stream, and protects its
484     * integrity with the given password.
485     *
486     * @param stream the output stream to which this keystore is written.
487     * @param password the password to generate the keystore integrity check
488     *
489     * @exception IOException if there was an I/O problem with data
490     * @exception NoSuchAlgorithmException if the appropriate data integrity
491     * algorithm could not be found
492     * @exception CertificateException if any of the certificates included in
493     * the keystore data could not be stored
494     */
495    public void engineStore(OutputStream stream, char[] password)
496        throws IOException, NoSuchAlgorithmException, CertificateException
497    {
498        synchronized(entries) {
499            /*
500             * KEYSTORE FORMAT:
501             *
502             * Magic number (big-endian integer),
503             * Version of this file format (big-endian integer),
504             *
505             * Count (big-endian integer),
506             * followed by "count" instances of either:
507             *
508             *     {
509             *      tag=1 (big-endian integer)
510             *      alias (UTF string)
511             *      timestamp
512             *      encrypted private-key info according to PKCS #8
513             *          (integer length followed by encoding)
514             *      cert chain (integer count followed by certs;
515             *          for each cert: type UTF string, followed by integer
516             *              length, followed by encoding)
517             *     }
518             *
519             * or:
520             *
521             *     {
522             *      tag=2 (big-endian integer)
523             *      alias (UTF string)
524             *      timestamp
525             *      cert (type UTF string, followed by integer length,
526             *          followed by encoding)
527             *     }
528             *
529             * or:
530             *
531             *     {
532             *      tag=3 (big-endian integer)
533             *      alias (UTF string)
534             *      timestamp
535             *      sealed secret key (in serialized form)
536             *     }
537             *
538             * ended by a keyed SHA1 hash (bytes only) of
539             *     { password + whitener + preceding body }
540             */
541
542            // password is mandatory when storing
543            if (password == null) {
544                throw new IllegalArgumentException("password can't be null");
545            }
546
547            byte[] encoded; // the certificate encoding
548
549            MessageDigest md = getPreKeyedHash(password);
550            DataOutputStream dos
551                = new DataOutputStream(new DigestOutputStream(stream, md));
552            // NOTE: don't pass dos to oos at this point or it'll corrupt
553            // the keystore!!!
554            ObjectOutputStream oos = null;
555            try {
556                dos.writeInt(JCEKS_MAGIC);
557                dos.writeInt(VERSION_2); // always write the latest version
558
559                dos.writeInt(entries.size());
560
561                Enumeration<String> e = entries.keys();
562                while (e.hasMoreElements()) {
563
564                    String alias = e.nextElement();
565                    Object entry = entries.get(alias);
566
567                    if (entry instanceof PrivateKeyEntry) {
568
569                        PrivateKeyEntry pentry = (PrivateKeyEntry)entry;
570
571                        // write PrivateKeyEntry tag
572                        dos.writeInt(1);
573
574                        // write the alias
575                        dos.writeUTF(alias);
576
577                        // write the (entry creation) date
578                        dos.writeLong(pentry.date.getTime());
579
580                        // write the protected private key
581                        dos.writeInt(pentry.protectedKey.length);
582                        dos.write(pentry.protectedKey);
583
584                        // write the certificate chain
585                        int chainLen;
586                        if (pentry.chain == null) {
587                            chainLen = 0;
588                        } else {
589                            chainLen = pentry.chain.length;
590                        }
591                        dos.writeInt(chainLen);
592                        for (int i = 0; i < chainLen; i++) {
593                            encoded = pentry.chain[i].getEncoded();
594                            dos.writeUTF(pentry.chain[i].getType());
595                            dos.writeInt(encoded.length);
596                            dos.write(encoded);
597                        }
598
599                    } else if (entry instanceof TrustedCertEntry) {
600
601                        // write TrustedCertEntry tag
602                        dos.writeInt(2);
603
604                        // write the alias
605                        dos.writeUTF(alias);
606
607                        // write the (entry creation) date
608                        dos.writeLong(((TrustedCertEntry)entry).date.getTime());
609
610                        // write the trusted certificate
611                        encoded = ((TrustedCertEntry)entry).cert.getEncoded();
612                        dos.writeUTF(((TrustedCertEntry)entry).cert.getType());
613                        dos.writeInt(encoded.length);
614                        dos.write(encoded);
615
616                    } else {
617
618                        // write SecretKeyEntry tag
619                        dos.writeInt(3);
620
621                        // write the alias
622                        dos.writeUTF(alias);
623
624                        // write the (entry creation) date
625                        dos.writeLong(((SecretKeyEntry)entry).date.getTime());
626
627                        // write the sealed key
628                        oos = new ObjectOutputStream(dos);
629                        oos.writeObject(((SecretKeyEntry)entry).sealedKey);
630                        // NOTE: don't close oos here since we are still
631                        // using dos!!!
632                    }
633                }
634
635                /*
636                 * Write the keyed hash which is used to detect tampering with
637                 * the keystore (such as deleting or modifying key or
638                 * certificate entries).
639                 */
640                byte digest[] = md.digest();
641
642                dos.write(digest);
643                dos.flush();
644            } finally {
645                if (oos != null) {
646                    oos.close();
647                } else {
648                    dos.close();
649                }
650            }
651        }
652    }
653
654    /**
655     * Loads the keystore from the given input stream.
656     *
657     * <p>If a password is given, it is used to check the integrity of the
658     * keystore data. Otherwise, the integrity of the keystore is not checked.
659     *
660     * @param stream the input stream from which the keystore is loaded
661     * @param password the (optional) password used to check the integrity of
662     * the keystore.
663     *
664     * @exception IOException if there is an I/O or format problem with the
665     * keystore data
666     * @exception NoSuchAlgorithmException if the algorithm used to check
667     * the integrity of the keystore cannot be found
668     * @exception CertificateException if any of the certificates in the
669     * keystore could not be loaded
670     */
671    public void engineLoad(InputStream stream, char[] password)
672        throws IOException, NoSuchAlgorithmException, CertificateException
673    {
674        synchronized(entries) {
675            DataInputStream dis;
676            MessageDigest md = null;
677            CertificateFactory cf = null;
678            Hashtable<String, CertificateFactory> cfs = null;
679            ByteArrayInputStream bais = null;
680            byte[] encoded = null;
681
682            if (stream == null)
683                return;
684
685            if (password != null) {
686                md = getPreKeyedHash(password);
687                dis = new DataInputStream(new DigestInputStream(stream, md));
688            } else {
689                dis = new DataInputStream(stream);
690            }
691            // NOTE: don't pass dis to ois at this point or it'll fail to load
692            // the keystore!!!
693            ObjectInputStream ois = null;
694
695            try {
696                // Body format: see store method
697
698                int xMagic = dis.readInt();
699                int xVersion = dis.readInt();
700
701                // Accept the following keystore implementations:
702                // - JCEKS (this implementation), versions 1 and 2
703                // - JKS (Sun's keystore implementation in JDK 1.2),
704                //   versions 1 and 2
705                if (((xMagic != JCEKS_MAGIC) && (xMagic != JKS_MAGIC)) ||
706                    ((xVersion != VERSION_1) && (xVersion != VERSION_2))) {
707                    throw new IOException("Invalid keystore format");
708                }
709
710                if (xVersion == VERSION_1) {
711                    cf = CertificateFactory.getInstance("X509");
712                } else {
713                    // version 2
714                    cfs = new Hashtable<>(3);
715                }
716
717                entries.clear();
718                int count = dis.readInt();
719
720                for (int i = 0; i < count; i++) {
721                    int tag;
722                    String alias;
723
724                    tag = dis.readInt();
725
726                    if (tag == 1) { // private-key entry
727
728                        PrivateKeyEntry entry = new PrivateKeyEntry();
729
730                        // read the alias
731                        alias = dis.readUTF();
732
733                        // read the (entry creation) date
734                        entry.date = new Date(dis.readLong());
735
736                        // read the private key
737                        try {
738                            entry.protectedKey = new byte[dis.readInt()];
739                        } catch (OutOfMemoryError e) {
740                            throw new IOException("Keysize too big");
741                        }
742                        dis.readFully(entry.protectedKey);
743
744                        // read the certificate chain
745                        int numOfCerts = dis.readInt();
746                        try {
747                            if (numOfCerts > 0) {
748                                entry.chain = new Certificate[numOfCerts];
749                            }
750                        } catch (OutOfMemoryError e) {
751                            throw new IOException("Too many certificates in "
752                                                  + "chain");
753                        }
754                        for (int j = 0; j < numOfCerts; j++) {
755                            if (xVersion == 2) {
756                                // read the certificate type, and instantiate a
757                                // certificate factory of that type (reuse
758                                // existing factory if possible)
759                                String certType = dis.readUTF();
760                                if (cfs.containsKey(certType)) {
761                                // reuse certificate factory
762                                    cf = cfs.get(certType);
763                                } else {
764                                // create new certificate factory
765                                    cf = CertificateFactory.getInstance(
766                                        certType);
767                                // store the certificate factory so we can
768                                // reuse it later
769                                    cfs.put(certType, cf);
770                                }
771                            }
772                            // instantiate the certificate
773                            try {
774                                encoded = new byte[dis.readInt()];
775                            } catch (OutOfMemoryError e) {
776                                throw new IOException("Certificate too big");
777                            }
778                            dis.readFully(encoded);
779                            bais = new ByteArrayInputStream(encoded);
780                            entry.chain[j] = cf.generateCertificate(bais);
781                        }
782
783                        // Add the entry to the list
784                        entries.put(alias, entry);
785
786                    } else if (tag == 2) { // trusted certificate entry
787
788                        TrustedCertEntry entry = new TrustedCertEntry();
789
790                        // read the alias
791                        alias = dis.readUTF();
792
793                        // read the (entry creation) date
794                        entry.date = new Date(dis.readLong());
795
796                        // read the trusted certificate
797                        if (xVersion == 2) {
798                            // read the certificate type, and instantiate a
799                            // certificate factory of that type (reuse
800                            // existing factory if possible)
801                            String certType = dis.readUTF();
802                            if (cfs.containsKey(certType)) {
803                                // reuse certificate factory
804                                cf = cfs.get(certType);
805                            } else {
806                                // create new certificate factory
807                                cf = CertificateFactory.getInstance(certType);
808                                // store the certificate factory so we can
809                                // reuse it later
810                                cfs.put(certType, cf);
811                            }
812                        }
813                        try {
814                            encoded = new byte[dis.readInt()];
815                        } catch (OutOfMemoryError e) {
816                            throw new IOException("Certificate too big");
817                        }
818                        dis.readFully(encoded);
819                        bais = new ByteArrayInputStream(encoded);
820                        entry.cert = cf.generateCertificate(bais);
821
822                        // Add the entry to the list
823                        entries.put(alias, entry);
824
825                    } else if (tag == 3) { // secret-key entry
826
827                        SecretKeyEntry entry = new SecretKeyEntry();
828
829                        // read the alias
830                        alias = dis.readUTF();
831
832                        // read the (entry creation) date
833                        entry.date = new Date(dis.readLong());
834
835                        // read the sealed key
836                        try {
837                            ois = new ObjectInputStream(dis);
838                            entry.sealedKey = (SealedObject)ois.readObject();
839                            // NOTE: don't close ois here since we are still
840                            // using dis!!!
841                        } catch (ClassNotFoundException cnfe) {
842                            throw new IOException(cnfe.getMessage());
843                        }
844
845                        // Add the entry to the list
846                        entries.put(alias, entry);
847
848                    } else {
849                        throw new IOException("Unrecognized keystore entry");
850                    }
851                }
852
853                /*
854                 * If a password has been provided, we check the keyed digest
855                 * at the end. If this check fails, the store has been tampered
856                 * with
857                 */
858                if (password != null) {
859                    byte computed[], actual[];
860                    computed = md.digest();
861                    actual = new byte[computed.length];
862                    dis.readFully(actual);
863                    for (int i = 0; i < computed.length; i++) {
864                        if (computed[i] != actual[i]) {
865                            throw new IOException(
866                                "Keystore was tampered with, or "
867                                        + "password was incorrect",
868                                    new UnrecoverableKeyException(
869                                            "Password verification failed"));
870                        }
871                    }
872                }
873            }  finally {
874                if (ois != null) {
875                    ois.close();
876                } else {
877                    dis.close();
878                }
879            }
880        }
881    }
882
883    /**
884     * To guard against tampering with the keystore, we append a keyed
885     * hash with a bit of whitener.
886     */
887    private MessageDigest getPreKeyedHash(char[] password)
888    throws NoSuchAlgorithmException, UnsupportedEncodingException {
889        int i, j;
890
891        MessageDigest md = MessageDigest.getInstance("SHA");
892        byte[] passwdBytes = new byte[password.length * 2];
893        for (i=0, j=0; i<password.length; i++) {
894            passwdBytes[j++] = (byte)(password[i] >> 8);
895            passwdBytes[j++] = (byte)password[i];
896        }
897        md.update(passwdBytes);
898        for (i=0; i<passwdBytes.length; i++)
899            passwdBytes[i] = 0;
900        md.update("Mighty Aphrodite".getBytes("UTF8"));
901        return md;
902    }
903
904    /**
905     * Probe the first few bytes of the keystore data stream for a valid
906     * JCEKS keystore encoding.
907     */
908    @Override
909    public boolean engineProbe(InputStream stream) throws IOException {
910        DataInputStream dataStream;
911        if (stream instanceof DataInputStream) {
912            dataStream = (DataInputStream)stream;
913        } else {
914            dataStream = new DataInputStream(stream);
915        }
916
917        return JCEKS_MAGIC == dataStream.readInt();
918    }
919}
920