Source code: hk/hku/cecid/phoenix/pki/CompositeKeyStore.java
1 /*
2 * Academic Free License
3 * Version 1.0
4 *
5 * This Academic Free License applies to any software and associated
6 * documentation (the "Software") whose owner (the "Licensor") has placed the
7 * statement "Licensed under the Academic Free License Version 1.0" immediately
8 * after the copyright notice that applies to the Software.
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a copy
11 * of the Software (1) to use, copy, modify, merge, publish, perform,
12 * distribute, sublicense, and/or sell copies of the Software, and to permit
13 * persons to whom the Software is furnished to do so, and (2) under patent
14 * claims owned or controlled by the Licensor that are embodied in the Software
15 * as furnished by the Licensor, to make, use, sell and offer for sale the
16 * Software and derivative works thereof, subject to the following conditions:
17 *
18 * - Redistributions of the Software in source code form must retain all
19 * copyright notices in the Software as furnished by the Licensor, this list
20 * of conditions, and the following disclaimers.
21 * - Redistributions of the Software in executable form must reproduce all
22 * copyright notices in the Software as furnished by the Licensor, this list
23 * of conditions, and the following disclaimers in the documentation and/or
24 * other materials provided with the distribution.
25 * - Neither the names of Licensor, nor the names of any contributors to the
26 * Software, nor any of their trademarks or service marks, may be used to
27 * endorse or promote products derived from this Software without express
28 * prior written permission of the Licensor.
29 *
30 * DISCLAIMERS: LICENSOR WARRANTS THAT THE COPYRIGHT IN AND TO THE SOFTWARE IS
31 * OWNED BY THE LICENSOR OR THAT THE SOFTWARE IS DISTRIBUTED BY LICENSOR UNDER
32 * A VALID CURRENT LICENSE. EXCEPT AS EXPRESSLY STATED IN THE IMMEDIATELY
33 * PRECEDING SENTENCE, THE SOFTWARE IS PROVIDED BY THE LICENSOR, CONTRIBUTORS
34 * AND COPYRIGHT OWNERS "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
37 * LICENSOR, CONTRIBUTORS OR COPYRIGHT OWNERS BE LIABLE FOR ANY CLAIM, DAMAGES
38 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
39 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE.
40 *
41 * This license is Copyright (C) 2002 Lawrence E. Rosen. All rights reserved.
42 * Permission is hereby granted to copy and distribute this license without
43 * modification. This license may not be modified without the express written
44 * permission of its copyright owner.
45 */
46
47 /* =====
48 *
49 * $Header: /ebxml/staff/cecid/cvs_repository/pki/src/hk/hku/cecid/phoenix/pki/CompositeKeyStore.java,v 1.13 2002/12/13 03:59:13 kcyee Exp $
50 *
51 * Code authored by:
52 *
53 * kcyee [2002-05-02]
54 *
55 * Code reviewed by:
56 *
57 * username [YYYY-MM-DD]
58 *
59 * Remarks:
60 *
61 * =====
62 */
63
64 package hk.hku.cecid.phoenix.pki;
65
66 import java.io.File;
67 import java.io.FileInputStream;
68 import java.io.FileOutputStream;
69 import java.io.FileNotFoundException;
70 import java.io.IOException;
71 import java.io.ObjectInputStream;
72 import java.io.ObjectOutputStream;
73 import java.lang.reflect.Constructor;
74 import java.lang.reflect.InvocationTargetException;
75 import java.security.Key;
76 import java.security.KeyStore;
77 import java.security.KeyStoreException;
78 import java.security.NoSuchAlgorithmException;
79 import java.security.NoSuchProviderException;
80 import java.security.Provider;
81 import java.security.Security;
82 import java.security.UnrecoverableKeyException;
83 import java.security.cert.Certificate;
84 import java.security.cert.CertificateException;
85 import java.util.Date;
86 import java.util.Enumeration;
87 import java.util.Hashtable;
88 import java.util.Vector;
89 import hk.hku.cecid.phoenix.common.util.Logger;
90 import hk.hku.cecid.phoenix.common.util.Property;
91 import hk.hku.cecid.phoenix.common.util.Version;
92
93 /**
94 * Composite keystore which manages keystores of different types. A typical
95 * Java keystore supports only one keystore type per file. That will be
96 * inconvenient for applications to manage several types of keystore. Also,
97 * this composite keystore supports managing multiple keystore files. This
98 * can be viewed as a keystore registry, that is, this object manages a pool
99 * of keystore files.
100 *
101 * @author kcyee
102 * @version $Revision: 1.13 $
103 */
104 public class CompositeKeyStore {
105
106 /**
107 * Logger
108 */
109 protected static Logger logger =
110 Logger.getLogger(CompositeKeyStore.class.getName());
111
112 /**
113 * Internal storage of the keystore file information
114 */
115 protected Hashtable storage;
116
117 /**
118 * Internal storage of the aliases inside the keystore file
119 */
120 protected Hashtable cache;
121
122 /**
123 * Internal storage of the keystore object
124 */
125 protected Vector keystores;
126
127 /**
128 * Default constructor. The internal variables are being initialized.
129 */
130 public CompositeKeyStore() {
131 storage = new Hashtable();
132 cache = null;
133 keystores = new Vector();
134 }
135
136 /**
137 * Adds a keystore file to the keystore management pool.
138 *
139 * @param keyFile the name of the keystore file
140 * @param type the type of the keystore
141 * @param password the password for accessing the keystore
142 */
143 public void addKeyStoreFile(String keyFile, String type, char[] password) {
144 if (keyFile == null) {
145 logger.debug("Keystore file cannot be <null>!");
146 }
147 else {
148 File f = new File(keyFile);
149 if (f == null || !f.exists()) {
150 logger.debug("Keystore file " + keyFile + " not found!");
151 }
152 else {
153 addKeyStoreFile(f, type, password);
154 }
155 }
156 }
157
158 /**
159 * Adds a keystore file to the keystore management pool.
160 *
161 * @param keyFile the keystore file
162 * @param type the type of the keystore
163 * @param password the password for accessing the keystore
164 */
165 protected void addKeyStoreFile(File keyFile, String type, char[] password) {
166 KeyStoreFileProp ksp = new KeyStoreFileProp(type, password);
167 try {
168 storage.put(keyFile.getCanonicalPath(), ksp);
169 }
170 catch (IOException e) {}
171 }
172
173 /**
174 * Gets the first KeyStore object from the keystore management pool.
175 *
176 * @return the first KeyStore object from the keystore management pool
177 */
178 public KeyStore getKeyStore() {
179 if (cache == null) {
180 loadCache();
181 }
182 if (keystores.size() > 0) {
183 return (KeyStore) keystores.get(0);
184 }
185 else {
186 return null;
187 }
188 }
189
190 /**
191 * Removes a keystore file from the keystore management pool.
192 *
193 * @param keyFile the name of the keystore file
194 */
195 public void removeKeyStoreFile(String keyFile) {
196 removeKeyStoreFile(new File(keyFile));
197 }
198
199 /**
200 * Removes a keystore file from the keystore management pool.
201 *
202 * @param keyFile the keystore file
203 */
204 protected void removeKeyStoreFile(File keyFile) {
205 try {
206 storage.remove(keyFile.getCanonicalPath());
207 }
208 catch (IOException e) {}
209 }
210
211 /**
212 * Gets an instance of the keystore of correct type. This function
213 * will consider the Java version and determine whether to use
214 * JSSE or not. For Java version 1.4 or above, JSSE is built in.
215 * So, no need to call an external provider to create an instance
216 * of PKCS#12 formatted keystore. Otherwise, JSSE should be used, and
217 * we make use of dynamic binding to load the JSSE library.
218 *
219 * @param fileName the keystore file name to load
220 * @param ksp other keystore parameters for loading
221 * @return keystore instance of the correct type
222 */
223 protected KeyStore loadKeyStore(String fileName, KeyStoreFileProp ksp) {
224 KeyStore ks = null;
225 if (ksp.getType() == null) {
226 KeyStoreFileProp ksp_new =
227 new KeyStoreFileProp("JKS", ksp.getPassword());
228 ks = loadKeyStore(fileName, ksp_new);
229 if (ks == null) {
230 ksp_new = new KeyStoreFileProp("PKCS12", ksp.getPassword());
231 ks = loadKeyStore(fileName, ksp_new);
232 }
233 return ks;
234 }
235 else if (ksp.getType().toUpperCase().equals("JKS")) {
236 try {
237 ks = KeyStore.getInstance("JKS");
238 }
239 catch (KeyStoreException e) {}
240 }
241 else if (ksp.getType().toUpperCase().equals("PKCS12")) {
242 if (isUsingJSSE()) {
243 try {
244 Class clsProv = Class.forName(
245 "com.sun.net.ssl.internal.ssl.Provider");
246 Constructor c = clsProv.getConstructor(null);
247 Provider provider = (Provider) c.newInstance(null);
248 if (Security.getProvider(provider.getName()) == null) {
249 Security.addProvider(provider);
250 }
251 }
252 catch (ClassNotFoundException e) {}
253 catch (NoSuchMethodException e) {}
254 catch (InstantiationException e) {}
255 catch (IllegalAccessException e) {}
256 catch (InvocationTargetException e) {}
257
258 try {
259 ks = KeyStore.getInstance("PKCS12", "SunJSSE");
260 }
261 catch (NoSuchProviderException e) {}
262 catch (KeyStoreException e) {}
263 }
264 else {
265 try {
266 ks = KeyStore.getInstance("PKCS12");
267 }
268 catch (KeyStoreException e) {}
269 }
270 }
271 if (ks != null) {
272 try {
273 ks.load(new FileInputStream(fileName), ksp.getPassword());
274 return ks;
275 }
276 catch (IOException e) {}
277 catch (NoSuchAlgorithmException e) {}
278 catch (CertificateException e) {}
279 }
280 return null;
281 }
282
283 /**
284 * Loads the keystores pointed by this composite keystore into memory
285 * and create a caching of aliases.
286 */
287 protected void loadCache() {
288 cache = new Hashtable();
289
290 Enumeration fileNames = storage.keys();
291 while (fileNames.hasMoreElements()) {
292 String fileName = (String) fileNames.nextElement();
293 KeyStoreFileProp ksp = (KeyStoreFileProp) storage.get(fileName);
294
295 KeyStore ks = loadKeyStore(fileName, ksp);
296 keystores.add(ks);
297 if (ks != null) {
298 try {
299 Enumeration ks_alias = ks.aliases();
300 while (ks_alias.hasMoreElements()) {
301 cache.put(ks_alias.nextElement(), ks);
302 }
303 }
304 catch (KeyStoreException e) {}
305 }
306 }
307 }
308
309 /**
310 * Gets all the aliases of the keystores pointed by this composite
311 * keystore.
312 *
313 * @return an enumeration of string, holding the aliases of the keys
314 */
315 public Enumeration aliases() {
316 if (cache == null) {
317 loadCache();
318 }
319 return cache.keys();
320 }
321
322 /**
323 * Determines whether a given alias exists in one of the keystores
324 * pointed by this composite keystore or not.
325 *
326 * @param alias the alias of the key/certificate
327 * @return true if the alias exists in one of the keystores, false
328 & otherwise
329 */
330 public boolean containsAlias(String alias) {
331 if (cache == null) {
332 loadCache();
333 }
334 return (cache.get(alias) != null);
335 }
336
337 /**
338 * Gets the certificate named by the given alias, from the collection
339 * of keystores pointed by this composite keystore.
340 *
341 * @param alias the alias of the key/certificate
342 * @return the certificate named by the given alias, null if not found
343 * @throws KeyStoreException the keystore is corrupted
344 */
345 public Certificate getCertificate(String alias) throws KeyStoreException {
346 if (cache == null) {
347 loadCache();
348 }
349 KeyStore ks = (KeyStore) cache.get(alias);
350 if (ks != null) {
351 return ks.getCertificate(alias);
352 }
353 return null;
354 }
355
356 /**
357 * Gets the alias of the specified certificate.
358 *
359 * @param cert the certificate
360 * @return the alias of the certificate, if the certificate can be found
361 * in the collection of keystores pointed by this composite
362 * keystore. Otherwise, null will be returned
363 */
364 public String getCertificateAlias(Certificate cert) {
365 if (cache == null) {
366 loadCache();
367 }
368 Enumeration keyStores = cache.elements();
369 while (keyStores.hasMoreElements()) {
370 KeyStore ks = (KeyStore) keyStores.nextElement();
371 try {
372 String alias = ks.getCertificateAlias(cert);
373 if (alias != null) {
374 return alias;
375 }
376 }
377 catch (KeyStoreException e) {}
378 }
379 return null;
380 }
381
382 /**
383 * Gets the certificate chain by the specified alias.
384 *
385 * @param alias the alias of the key/certificate
386 * @return the certificate chain by the specified alias, null if not found
387 * @throws KeyStoreException the keystore is corrupted
388 */
389 public Certificate[] getCertificateChain(String alias)
390 throws KeyStoreException {
391 if (cache == null) {
392 loadCache();
393 }
394 KeyStore ks = (KeyStore) cache.get(alias);
395 if (ks != null) {
396 return ks.getCertificateChain(alias);
397 }
398 return null;
399 }
400
401 /**
402 * Gets the creation date of the key/certificate by the specified alias.
403 *
404 * @param alias the alias of the key/certificate
405 * @return the creation date of the key/certificate by the specified alias,
406 * null if not found
407 * @throws KeyStoreException the keystore is corrupted
408 */
409 public Date getCreationDate(String alias) throws KeyStoreException {
410 if (cache == null) {
411 loadCache();
412 }
413 KeyStore ks = (KeyStore) cache.get(alias);
414 if (ks != null) {
415 return ks.getCreationDate(alias);
416 }
417 return null;
418 }
419
420 /**
421 * Gets the key by the specified alias. A password should be given also
422 * to retrieve the key.
423 *
424 * @param alias the alias of the key/certificate
425 * @param password the password to retrieve the key
426 * @return the key specified by the alias, null if not found
427 * @throws KeyStoreException the keystore is corrupted
428 * @throws NoSuchAlgorithmException the keystore cannot be read
429 * @throws UnrecoverableKeyException the keystore cannot be read
430 */
431 public Key getKey(String alias, char[] password) throws
432 KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
433 if (cache == null) {
434 loadCache();
435 }
436 KeyStore ks = (KeyStore) cache.get(alias);
437 if (ks != null) {
438 return ks.getKey(alias, password);
439 }
440 return null;
441 }
442
443 /**
444 * Determines whether the specified alias is specifying a certificate
445 * or not.
446 *
447 * @param alias the alias of the key/certificate
448 * @throws KeyStoreException the keystore is corrupted
449 */
450 public boolean isCertificateEntry(String alias) throws KeyStoreException {
451 if (cache == null) {
452 loadCache();
453 }
454 KeyStore ks = (KeyStore) cache.get(alias);
455 if (ks != null) {
456 return ks.isCertificateEntry(alias);
457 }
458 return false;
459 }
460
461 /**
462 * Determines whether the specified alias is specifying a key
463 * or not.
464 *
465 * @param alias the alias of the key/certificate
466 * @throws KeyStoreException the keystore is corrupted
467 */
468 public boolean isKeyEntry(String alias) throws KeyStoreException {
469 if (cache == null) {
470 loadCache();
471 }
472 KeyStore ks = (KeyStore) cache.get(alias);
473 if (ks != null) {
474 return ks.isKeyEntry(alias);
475 }
476 return false;
477 }
478
479 /**
480 * Gets the total number of keys/certificates in all the keystores
481 * pointed by this composite keystore.
482 *
483 * @return the total number of keys/certificates
484 */
485 public int size() {
486 if (cache == null) {
487 loadCache();
488 }
489 return cache.size();
490 }
491
492 /**
493 * Loads the composite keystore from a persistent file in the file
494 * system.
495 *
496 * @param storeFileName the name of the composite keystore persistent file
497 * @throws InitializationException the persistent file is corrupted
498 */
499 public void load(String storeFileName) throws InitializationException {
500 load(new File(storeFileName));
501 }
502
503 /**
504 * Loads the composite keystore from a persistent file in the file
505 * system.
506 *
507 * @param storeFile the composite keystore persistent file
508 * @throws InitializationException the persistent file is corrupted
509 */
510 public void load(File storeFile) throws InitializationException {
511 try {
512 ObjectInputStream ois = new ObjectInputStream(
513 new FileInputStream(storeFile));
514 storage = (Hashtable) ois.readObject();
515 ois.close();
516 }
517 catch (FileNotFoundException e) {
518 storage = null;
519 throw new InitializationException("FileNotFound Exception\n"
520 + e.getMessage());
521 }
522 catch (IOException e) {
523 storage = null;
524 throw new InitializationException("IO Exception\n"
525 + e.getMessage());
526 }
527 catch (ClassNotFoundException e) {
528 storage = null;
529 throw new InitializationException("ClassNotFound Exception\n"
530 + e.getMessage());
531 }
532 }
533
534 /**
535 * Stores the composite keystore to a persistent file in the file
536 * system.
537 *
538 * @param storeFileName the name of the composite keystore persistent file
539 * @throws StoreException the composite keystore is not successfully stored
540 */
541 public void store(String storeFileName) throws StoreException {
542 store(new File(storeFileName));
543 }
544
545 /**
546 * Stores the composite keystore to a persistent file in the file
547 * system.
548 *
549 * @param storeFile the composite keystore persistent file
550 * @throws StoreException the composite keystore is not successfully stored
551 */
552 public void store(File storeFile) throws StoreException {
553 try {
554 ObjectOutputStream oos = new ObjectOutputStream(
555 new FileOutputStream(storeFile));
556 oos.writeObject(storage);
557 oos.close();
558 }
559 catch (FileNotFoundException e) {
560 throw new StoreException("FileNotFound Exception\n"
561 + e.getMessage());
562 }
563 catch (IOException e) {
564 throw new StoreException("IO Exception\n"
565 + e.getMessage());
566 }
567 }
568
569 /**
570 * Determines whether JSSE should be used to access PKCS#12 formatted
571 * keystore. For JDK1.4 or above, JSSE is built into the JDK. Therefore,
572 * no additional provider have to be added.
573 *
574 * @return true if JSSE should be used; false if otherwise
575 */
576 protected static boolean isUsingJSSE() {
577 double javaVersion = Version.getJDKVersion();
578 return (javaVersion < 1.4);
579 }
580 }