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