001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.geronimo.security.keystore;
018
019 import java.io.BufferedInputStream;
020 import java.io.BufferedOutputStream;
021 import java.io.ByteArrayInputStream;
022 import java.io.ByteArrayOutputStream;
023 import java.io.File;
024 import java.io.FileInputStream;
025 import java.io.FileNotFoundException;
026 import java.io.FileOutputStream;
027 import java.io.IOException;
028 import java.io.InputStream;
029 import java.math.BigInteger;
030 import java.net.URI;
031 import java.security.InvalidKeyException;
032 import java.security.KeyPair;
033 import java.security.KeyPairGenerator;
034 import java.security.KeyStore;
035 import java.security.KeyStoreException;
036 import java.security.NoSuchAlgorithmException;
037 import java.security.NoSuchProviderException;
038 import java.security.PrivateKey;
039 import java.security.PublicKey;
040 import java.security.SignatureException;
041 import java.security.UnrecoverableKeyException;
042 import java.security.cert.Certificate;
043 import java.security.cert.CertificateEncodingException;
044 import java.security.cert.CertificateException;
045 import java.security.cert.CertificateFactory;
046 import java.security.cert.X509Certificate;
047 import java.util.ArrayList;
048 import java.util.Arrays;
049 import java.util.Collection;
050 import java.util.Date;
051 import java.util.Enumeration;
052 import java.util.HashMap;
053 import java.util.Hashtable;
054 import java.util.Iterator;
055 import java.util.List;
056 import java.util.Map;
057 import java.util.Vector;
058 import javax.net.ssl.KeyManager;
059 import javax.net.ssl.KeyManagerFactory;
060 import javax.net.ssl.TrustManager;
061 import javax.net.ssl.TrustManagerFactory;
062 import org.apache.commons.logging.Log;
063 import org.apache.commons.logging.LogFactory;
064 import org.apache.geronimo.gbean.GBeanInfo;
065 import org.apache.geronimo.gbean.GBeanInfoBuilder;
066 import org.apache.geronimo.gbean.GBeanLifecycle;
067 import org.apache.geronimo.gbean.AbstractName;
068 import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
069 import org.apache.geronimo.kernel.Kernel;
070 import org.apache.geronimo.management.geronimo.KeyNotFoundException;
071 import org.apache.geronimo.management.geronimo.KeystoreException;
072 import org.apache.geronimo.management.geronimo.KeystoreInstance;
073 import org.apache.geronimo.management.geronimo.KeystoreIsLocked;
074 import org.apache.geronimo.system.serverinfo.ServerInfo;
075 import org.apache.geronimo.crypto.asn1.ASN1InputStream;
076 import org.apache.geronimo.crypto.asn1.ASN1Sequence;
077 import org.apache.geronimo.crypto.asn1.ASN1Set;
078 import org.apache.geronimo.crypto.asn1.DEROutputStream;
079 import org.apache.geronimo.crypto.asn1.x509.X509CertificateStructure;
080 import org.apache.geronimo.crypto.asn1.x509.X509Name;
081 import org.apache.geronimo.crypto.encoders.Base64;
082 import org.apache.geronimo.crypto.jce.PKCS10CertificationRequest;
083 import org.apache.geronimo.crypto.jce.X509Principal;
084 import org.apache.geronimo.crypto.jce.X509V1CertificateGenerator;
085
086 /**
087 * Implementation of KeystoreInstance that accesses a keystore file on the
088 * local filesystem, identified by the file's name (the last component of
089 * the name only, not the full path).
090 *
091 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
092 */
093 public class FileKeystoreInstance implements KeystoreInstance, GBeanLifecycle {
094 private static final Log log = LogFactory.getLog(FileKeystoreInstance.class);
095 final static String JKS = "JKS";
096 private URI keystorePath; // relative path
097 private ServerInfo serverInfo; // used to decode relative path
098 private File keystoreFile; // Only valid after startup
099 private String keystoreName;
100 private String keystoreType;
101 private char[] keystorePassword; // Used to "unlock" the keystore for other services
102 private Map<String, char[]> keyPasswords = new HashMap<String, char[]>();
103 private Kernel kernel;
104 private AbstractName abstractName;
105 private char[] openPassword; // The password last used to open the keystore for editing
106 // The following variables are the state of the keystore, which should be chucked if the file on disk changes
107 private List privateKeys = new ArrayList();
108 private List trustCerts = new ArrayList();
109 private KeyStore keystore;
110 private long keystoreReadDate = Long.MIN_VALUE;
111
112 public FileKeystoreInstance(ServerInfo serverInfo, URI keystorePath, String keystoreName, String keystorePassword, String keystoreType, String keyPasswords, Kernel kernel, AbstractName abstractName) {
113 this.serverInfo = serverInfo;
114 this.keystorePath = keystorePath;
115 this.keystoreName = keystoreName;
116 this.keystoreType = keystoreType;
117 this.kernel = kernel;
118 this.abstractName = abstractName;
119 this.keystorePassword = keystorePassword == null ? null : keystorePassword.toCharArray();
120 if(keyPasswords != null) {
121 String[] keys = keyPasswords.split("\\]\\!\\[");
122 for (int i = 0; i < keys.length; i++) {
123 String key = keys[i];
124 int pos = key.indexOf('=');
125 this.keyPasswords.put(key.substring(0, pos), key.substring(pos+1).toCharArray());
126 }
127 }
128 }
129
130 public void doStart() throws Exception {
131 keystoreFile = new File(serverInfo.resolveServer(keystorePath));
132 if(!keystoreFile.exists() || !keystoreFile.canRead()) {
133 throw new IllegalArgumentException("Invalid keystore file ("+keystorePath+" = "+keystoreFile.getAbsolutePath()+")");
134 }
135 }
136
137 public void doStop() throws Exception {
138 }
139
140 public void doFail() {
141 }
142
143 public static final GBeanInfo GBEAN_INFO;
144
145 static {
146 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(FileKeystoreInstance.class, NameFactory.KEYSTORE_INSTANCE);
147 infoFactory.addAttribute("keystorePath", URI.class, true, false);
148 infoFactory.addAttribute("keystoreName", String.class, true, false);
149 infoFactory.addAttribute("keystorePassword", String.class, true, true);
150 infoFactory.addAttribute("keystoreType", String.class, true, false);
151 infoFactory.addAttribute("keyPasswords", String.class, true, true);
152 infoFactory.addAttribute("kernel", Kernel.class, false);
153 infoFactory.addAttribute("abstractName", AbstractName.class, false);
154 infoFactory.addReference("ServerInfo", ServerInfo.class, NameFactory.GERONIMO_SERVICE);
155 infoFactory.addInterface(KeystoreInstance.class);
156 infoFactory.setConstructor(new String[]{"ServerInfo","keystorePath", "keystoreName", "keystorePassword", "keystoreType", "keyPasswords", "kernel", "abstractName"});
157
158 GBEAN_INFO = infoFactory.getBeanInfo();
159 }
160
161 public static GBeanInfo getGBeanInfo() {
162 return GBEAN_INFO;
163 }
164
165
166 // KeystoreInstnace interface
167
168 public String getKeystoreName() {
169 return keystoreName;
170 }
171
172 public String getKeystoreType() {
173 return keystoreType;
174 }
175
176 public void unlockKeystore(char[] password) throws KeystoreException {
177 if (password == null) {
178 throw new NullPointerException("password is null");
179 }
180 ensureLoaded(password);
181 try {
182 kernel.setAttribute(abstractName, "keystorePassword", new String(password));
183 } catch (Exception e) {
184 throw new KeystoreException("Unable to set attribute keystorePassword on myself!", e);
185 }
186 }
187
188 public void setKeystorePassword(String password) {
189 keystorePassword = password == null ? null : password.toCharArray();
190 }
191
192 public void lockKeystore(char[] password) throws KeystoreException {
193 try {
194 kernel.setAttribute(abstractName, "keystorePassword", null);
195 keyPasswords.clear();
196 storePasswords();
197 } catch (Exception e) {
198 throw new KeystoreException("Unable to set attribute keystorePassword on myself!", e);
199 }
200 }
201
202 public boolean isKeystoreLocked() {
203 return keystorePassword == null;
204 }
205
206 public String[] listPrivateKeys(char[] storePassword) throws KeystoreException {
207 ensureLoaded(storePassword);
208 return (String[]) privateKeys.toArray(new String[privateKeys.size()]);
209 }
210
211 public void unlockPrivateKey(String alias, char[] storePassword, char[] password) throws KeystoreException {
212 if (storePassword == null && keystorePassword == null) {
213 throw new KeystoreException("storePassword is null and keystore is locked for availability.");
214 }
215 if(storePassword != null)
216 getPrivateKey(alias, storePassword, password);
217 else
218 getPrivateKey(alias, keystorePassword, password);
219 keyPasswords.put(alias, password);
220 storePasswords();
221 }
222
223 public String[] getUnlockedKeys(char[] storePassword) throws KeystoreException {
224 ensureLoaded(storePassword);
225 return (String[]) keyPasswords.keySet().toArray(new String[keyPasswords.size()]);
226 }
227
228 public boolean isTrustStore(char[] storePassword) throws KeystoreException {
229 ensureLoaded(storePassword);
230 return trustCerts.size() > 0;
231 }
232
233 public void lockPrivateKey(String alias, char[] storePassword) throws KeystoreException {
234 if (storePassword == null) {
235 throw new NullPointerException("storePassword is null");
236 }
237 ensureLoaded(storePassword);
238 keyPasswords.remove(alias);
239 storePasswords();
240 }
241
242 private void storePasswords() throws KeystoreException {
243 StringBuffer buf = new StringBuffer();
244 for (Iterator it = keyPasswords.entrySet().iterator(); it.hasNext();) {
245 if(buf.length() > 0) {
246 buf.append("]![");
247 }
248 Map.Entry entry = (Map.Entry) it.next();
249 buf.append(entry.getKey()).append("=").append((char[])entry.getValue());
250 }
251 try {
252 kernel.setAttribute(abstractName, "keyPasswords", buf.length() == 0 ? null : buf.toString());
253 } catch (Exception e) {
254 throw new KeystoreException("Unable to save key passwords in keystore '"+keystoreName+"'", e);
255 }
256 }
257
258 public void setKeyPasswords(String passwords) {} // Just so the kernel sees the new value
259
260 /**
261 * Checks whether the specified private key is locked, which is to say,
262 * available for other components to use to generate socket factories.
263 * Does not check whether the unlock password is actually correct.
264 */
265 public boolean isKeyLocked(String alias) {
266 return keyPasswords.get(alias) == null;
267 }
268
269 public String[] listTrustCertificates(char[] storePassword) throws KeystoreException {
270 ensureLoaded(storePassword);
271 return (String[]) trustCerts.toArray(new String[trustCerts.size()]);
272 }
273
274 public void importTrustCertificate(Certificate cert, String alias, char[] storePassword) throws KeystoreException {
275 if (storePassword == null) {
276 throw new NullPointerException("storePassword is null");
277 }
278 ensureLoaded(storePassword);
279 try {
280 keystore.setCertificateEntry(alias, cert);
281 } catch (KeyStoreException e) {
282 throw new KeystoreException("Unable to set certificate entry in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
283 }
284 trustCerts.add(alias);
285 saveKeystore(storePassword);
286 }
287
288 public void generateKeyPair(String alias, char[] storePassword, char[] keyPassword, String keyAlgorithm, int keySize, String signatureAlgorithm, int validity, String commonName, String orgUnit, String organization, String locality, String state, String country) throws KeystoreException {
289 if (storePassword == null) {
290 throw new NullPointerException("storePassword is null");
291 }
292 ensureLoaded(storePassword);
293 try {
294 KeyPairGenerator kpgen = KeyPairGenerator.getInstance(keyAlgorithm);
295 kpgen.initialize(keySize);
296 KeyPair keyPair = kpgen.generateKeyPair();
297 X509Certificate cert = generateCertificate(keyPair.getPublic(), keyPair.getPrivate(), signatureAlgorithm,
298 validity, commonName, orgUnit, organization, locality, state, country);
299
300 keystore.setKeyEntry(alias, keyPair.getPrivate(), keyPassword, new Certificate[] { cert });
301 privateKeys.add(alias);
302 } catch (KeyStoreException e) {
303 throw new KeystoreException("Unable to generate key pair in keystore '" + keystoreName + "'", e);
304 } catch (InvalidKeyException e) {
305 throw new KeystoreException("Unable to generate key pair in keystore '" + keystoreName + "'", e);
306 } catch (SignatureException e) {
307 throw new KeystoreException("Unable to generate key pair in keystore '" + keystoreName + "'", e);
308 } catch (NoSuchAlgorithmException e) {
309 throw new KeystoreException("Unable to generate key pair in keystore '" + keystoreName + "'", e);
310 }
311 saveKeystore(storePassword);
312 }
313
314
315 public String generateCSR(String alias, char[] storePassword) throws KeystoreException {
316 ensureLoaded(storePassword);
317 try {
318 // find certificate by alias
319 X509Certificate cert = (X509Certificate) keystore.getCertificate(alias);
320 // find private key by alias
321 PrivateKey key = (PrivateKey) keystore.getKey(alias, (char[])keyPasswords.get(alias));
322 // generate csr
323 String csr = generateCSR(cert, key);
324 return csr;
325 } catch (KeyStoreException e) {
326 throw new KeystoreException("Unable to generate CSR in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
327 } catch (NoSuchAlgorithmException e) {
328 throw new KeystoreException("Unable to generate CSR in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
329 } catch (UnrecoverableKeyException e) {
330 throw new KeystoreException("Unable to generate CSR in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
331 } catch (InvalidKeyException e) {
332 throw new KeystoreException("Unable to generate CSR in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
333 } catch (NoSuchProviderException e) {
334 throw new KeystoreException("Unable to generate CSR in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
335 } catch (SignatureException e) {
336 throw new KeystoreException("Unable to generate CSR in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
337 } catch (IOException e) {
338 throw new KeystoreException("Unable to generate CSR in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
339 }
340 }
341
342 private String generateCSR(X509Certificate cert, PrivateKey signingKey) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, KeyStoreException, IOException {
343 String sigalg = cert.getSigAlgName();
344 X509Name subject;
345 try{
346 ASN1InputStream ais = new ASN1InputStream(cert.getEncoded());
347 X509CertificateStructure x509Struct = new X509CertificateStructure((ASN1Sequence)ais.readObject());
348 ais.close();
349 subject = x509Struct.getSubject();
350 } catch(CertificateEncodingException e) {
351 log.warn(e.toString()+" while retrieving subject from certificate to create CSR. Using subjectDN instead.");
352 subject = new X509Name(cert.getSubjectDN().toString());
353 }
354 PublicKey publicKey = cert.getPublicKey();
355 ASN1Set attributes = null;
356
357 PKCS10CertificationRequest csr = new PKCS10CertificationRequest(sigalg,
358 subject, publicKey, attributes, signingKey);
359
360 if (!csr.verify()) {
361 throw new KeyStoreException("CSR verification failed");
362 }
363
364 ByteArrayOutputStream os = new ByteArrayOutputStream();
365 DEROutputStream deros = new DEROutputStream(os);
366 deros.writeObject(csr.getDERObject());
367 String b64 = new String(Base64.encode(os.toByteArray()));
368
369 final String BEGIN_CERT_REQ = "-----BEGIN CERTIFICATE REQUEST-----";
370 final String END_CERT_REQ = "-----END CERTIFICATE REQUEST-----";
371 final int CERT_REQ_LINE_LENGTH = 70;
372
373 StringBuffer sbuf = new StringBuffer(BEGIN_CERT_REQ).append('\n');
374
375 int idx = 0;
376 while (idx < b64.length()) {
377
378 int len = (idx + CERT_REQ_LINE_LENGTH > b64.length()) ? b64
379 .length()
380 - idx : CERT_REQ_LINE_LENGTH;
381
382 String chunk = b64.substring(idx, idx + len);
383
384 sbuf.append(chunk).append('\n');
385 idx += len;
386 }
387
388 sbuf.append(END_CERT_REQ);
389 return sbuf.toString();
390 }
391
392 public void importPKCS7Certificate(String alias, String certbuf, char[] storePassword) throws KeystoreException {
393 if (storePassword == null) {
394 throw new NullPointerException("storePassword is null");
395 }
396 ensureLoaded(storePassword);
397 InputStream is = null;
398 try {
399 is = new ByteArrayInputStream(certbuf.getBytes());
400 CertificateFactory cf = CertificateFactory.getInstance("X.509");
401 Collection certcoll = cf.generateCertificates(is);
402 Certificate[] chain = new Certificate[certcoll.size()];
403 Iterator iter = certcoll.iterator();
404 for (int i = 0; iter.hasNext(); i++) {
405 chain[i] = (Certificate) iter.next();
406 }
407 if(keystore.getCertificate(alias).getPublicKey().equals(chain[0].getPublicKey())) {
408 char[] keyPassword = (char[])keyPasswords.get(alias);
409 keystore.setKeyEntry(alias, keystore.getKey(alias, keyPassword), keyPassword, chain);
410 saveKeystore(keystorePassword);
411 } else {
412 log.error("Error in importPKCS7Certificate. PublicKey in the certificate received is not related to the PrivateKey in the keystore. keystore = "+keystoreName+", alias = "+alias);
413 }
414 } catch (CertificateException e) {
415 throw new KeystoreException("Unable to import PKCS7 certificat in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
416 } catch (KeyStoreException e) {
417 throw new KeystoreException("Unable to import PKCS7 certificat in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
418 } catch (NoSuchAlgorithmException e) {
419 throw new KeystoreException("Unable to import PKCS7 certificat in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
420 } catch (UnrecoverableKeyException e) {
421 throw new KeystoreException("Unable to import PKCS7 certificat in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
422 } finally {
423 if (is != null) {
424 try {
425 is.close();
426 } catch (Exception e) {
427 }
428 }
429 }
430 }
431
432 public void deleteEntry(String alias, char[] storePassword) throws KeystoreException {
433 if (storePassword == null) {
434 throw new NullPointerException("storePassword is null");
435 }
436 ensureLoaded(storePassword);
437 try {
438 keystore.deleteEntry(alias);
439 } catch (KeyStoreException e) {
440 throw new KeystoreException("Unable to delete key in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
441 }
442 privateKeys.remove(alias);
443 trustCerts.remove(alias);
444 if (keyPasswords.containsKey(alias)) {
445 keyPasswords.remove(alias);
446 storePasswords();
447 }
448 saveKeystore(storePassword);
449 }
450
451 public KeyManager[] getKeyManager(String algorithm, String alias, char[] storePassword) throws KeystoreException {
452 ensureLoaded(storePassword);
453 try {
454 KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(algorithm);
455 if(privateKeys.size() == 1) {
456 keyFactory.init(keystore, (char[]) keyPasswords.get(alias));
457 } else {
458 // When there is more than one private key in the keystore, we create a temporary "sub keystore"
459 // with only one entry of our interest and use it
460 KeyStore subKeystore = KeyStore.getInstance(keystore.getType(), keystore.getProvider());
461 try {
462 subKeystore.load(null, null);
463 } catch (NoSuchAlgorithmException e) {
464 // should not occur
465 } catch (CertificateException e) {
466 // should not occur
467 } catch (IOException e) {
468 // should not occur
469 }
470 subKeystore.setKeyEntry(alias, keystore.getKey(alias, (char[]) keyPasswords.get(alias)),
471 (char[]) keyPasswords.get(alias), keystore.getCertificateChain(alias));
472 keyFactory.init(subKeystore, (char[]) keyPasswords.get(alias));
473 }
474 return keyFactory.getKeyManagers();
475 } catch (KeyStoreException e) {
476 throw new KeystoreException("Unable to retrieve key manager in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
477 } catch (NoSuchAlgorithmException e) {
478 throw new KeystoreException("Unable to retrieve key manager in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
479 } catch (UnrecoverableKeyException e) {
480 throw new KeystoreException("Unable to retrieve key manager in keystore '" + keystoreName + "' for alias '" + alias + "'", e);
481 }
482 }
483
484 public TrustManager[] getTrustManager(String algorithm, char[] storePassword) throws KeystoreException {
485 ensureLoaded(storePassword);
486 try {
487 TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(algorithm);
488 trustFactory.init(keystore);
489 return trustFactory.getTrustManagers();
490 } catch (KeyStoreException e) {
491 throw new KeystoreException("Unable to retrieve trust manager in keystore '" + keystoreName + "'", e);
492 } catch (NoSuchAlgorithmException e) {
493 throw new KeystoreException("Unable to retrieve trust manager in keystore '" + keystoreName + "'", e);
494 }
495 }
496
497 /**
498 * Gets the private key with the specified alias.
499 * @param alias The alias of the private key to be retrieved
500 * @param storePassword The password used to access the keystore
501 * @param keyPassword The password to use to protect the new key
502 * @return PrivateKey with the alias specified
503 */
504 public PrivateKey getPrivateKey(String alias, char[] storePassword, char[] keyPassword) throws KeyNotFoundException, KeystoreException, KeystoreIsLocked {
505 ensureLoaded(storePassword);
506 try {
507 PrivateKey key = (PrivateKey) keystore.getKey(alias, keyPassword);
508 if (key == null) {
509 throw new KeyNotFoundException("Keystore '"+keystoreName+"' does not contain a private key with alias'"+alias+"'.");
510 }
511 return key;
512 } catch (KeyStoreException e) {
513 throw new KeystoreException("Unable to retrieve private key from keystore", e);
514 } catch (NoSuchAlgorithmException e) {
515 throw new KeystoreException("Unable to retrieve private key from keystore", e);
516 } catch (UnrecoverableKeyException e) {
517 throw new KeystoreException("Unable to retrieve private key from keystore", e);
518 }
519 }
520
521 /**
522 * Gets a particular certificate from the keystore. This may be a trust
523 * certificate or the certificate corresponding to a particular private
524 * key.
525 * This only works if the keystore is unlocked.
526 * @param alias The certificate to look at
527 * @throws KeyNotFoundException
528 * @throws KeyStoreException
529 */
530 public Certificate getCertificate(String alias, char[] storePassword) throws KeystoreIsLocked, KeyNotFoundException, KeystoreException {
531 ensureLoaded(storePassword);
532 try {
533 Certificate cert = keystore.getCertificate(alias);
534 if (cert == null) {
535 throw new KeyNotFoundException("Keystore '"+keystoreName+"' does not contain a certificate with alias'"+alias+"'.");
536 }
537 return cert;
538 } catch (KeyStoreException e) {
539 throw new KeystoreException("Unable to retrieve certificate from keystore", e);
540 }
541 }
542
543 public String getCertificateAlias(Certificate cert, char[] storePassword) throws KeystoreException {
544 ensureLoaded(storePassword);
545 try {
546 String alias = keystore.getCertificateAlias(cert);
547 if (alias == null) {
548 throw new KeyNotFoundException("Keystore '"+keystoreName+"' does not contain an alias corresponding to the given certificate.");
549 }
550 return alias;
551 } catch (KeyStoreException e) {
552 throw new KeystoreException("Unable to read certificate alias from keystore", e);
553 }
554 }
555
556 public Certificate[] getCertificateChain(String alias, char[] storePassword) throws KeystoreException {
557 ensureLoaded(storePassword);
558 try {
559 Certificate[] certs = keystore.getCertificateChain(alias);
560 if (certs == null) {
561 throw new KeyNotFoundException("Keystore '"+keystoreName+"' does not contain a certificate chain with alias'"+alias+"'.");
562 }
563 return certs;
564 } catch (KeyStoreException e) {
565 throw new KeystoreException("Unable to read certificate chain from keystore", e);
566 }
567 }
568
569 /**
570 * Gets a particular certificate from the keystore. This may be a trust
571 * certificate or the certificate corresponding to a particular private
572 * key.
573 * This only works if the keystore is unlocked.
574 * @param alias The certificate to look at
575 */
576 public Certificate getCertificate(String alias) {
577 if(isKeystoreLocked()) {
578 return null;
579 }
580 try {
581 return keystore.getCertificate(alias);
582 } catch (KeyStoreException e) {
583 log.error("Unable to read certificate from keystore", e);
584 }
585 return null;
586 }
587
588 /**
589 * Changes the keystore password.
590 * @param storePassword Current password for the keystore
591 * @param newPassword New password for the keystore
592 * @throws KeystoreException
593 */
594 public void changeKeystorePassword(char[] storePassword, char[] newPassword) throws KeystoreException {
595 ensureLoaded(storePassword);
596 saveKeystore(newPassword);
597 log.info("Password changed for keystore "+keystoreName);
598 openPassword = newPassword;
599 if(!isKeystoreLocked()) {
600 unlockKeystore(newPassword);
601 }
602 }
603
604 /**
605 * Changes the password for a private key entry in the keystore.
606 * @param storePassword Password for the keystore
607 * @param keyPassword Current password for the private key
608 * @param newKeyPassword New password for the private key
609 * @throws KeystoreException
610 */
611 public void changeKeyPassword(String alias, char[] storePassword, char[] keyPassword, char[] newKeyPassword) throws KeystoreException {
612 ensureLoaded(storePassword);
613 if(!privateKeys.contains(alias)) {
614 throw new KeystoreException("No private key entry "+alias+" exists in the keystore "+keystoreName);
615 }
616 if(keyPasswords.containsKey(alias)) {
617 if(!Arrays.equals(keyPasswords.get(alias), keyPassword)) {
618 throw new KeystoreException("Incorrect password provided for private key entry "+alias);
619 }
620 keyPasswords.put(alias, newKeyPassword);
621 }
622 PrivateKey key = getPrivateKey(alias, storePassword, keyPassword);
623 Certificate[] chain = getCertificateChain(alias, storePassword);
624 try {
625 keystore.setKeyEntry(alias, key, newKeyPassword, chain);
626 saveKeystore(storePassword);
627 log.info("Password changed for private key entry "+alias+" in keystore "+keystoreName+".");
628 if(keyPasswords.containsKey(alias)) {
629 storePasswords();
630 }
631 } catch(KeyStoreException e) {
632 throw new KeystoreException("Could not change password for private key entry "+alias, e);
633 }
634 }
635
636 // ==================== Internals =====================
637
638 private void loadKeystoreData(char[] password) throws KeystoreException {
639 InputStream in = null;
640 try {
641 // Make sure the keystore is loadable using the provided password before resetting the instance variables.
642 KeyStore tempKeystore = KeyStore.getInstance(keystoreType);
643 in = new BufferedInputStream(new FileInputStream(keystoreFile));
644 long readDate = System.currentTimeMillis();
645 tempKeystore.load(in, password);
646 // Keystore could be loaded successfully. Initialize the instance variables to reflect the new keystore.
647 keystore = tempKeystore;
648 keystoreReadDate = readDate;
649 privateKeys.clear();
650 trustCerts.clear();
651 openPassword = password;
652 Enumeration aliases = keystore.aliases();
653 while (aliases.hasMoreElements()) {
654 String alias = (String) aliases.nextElement();
655 if(keystore.isKeyEntry(alias)) {
656 privateKeys.add(alias);
657 } else if(keystore.isCertificateEntry(alias)) {
658 trustCerts.add(alias);
659 }
660 }
661 } catch (KeyStoreException e) {
662 throw new KeystoreException("Unable to open keystore with provided password", e);
663 } catch (IOException e) {
664 throw new KeystoreException("Unable to open keystore with provided password", e);
665 } catch (NoSuchAlgorithmException e) {
666 throw new KeystoreException("Unable to open keystore with provided password", e);
667 } catch (CertificateException e) {
668 throw new KeystoreException("Unable to open keystore with provided password", e);
669 } finally {
670 if(in != null) {
671 try {
672 in.close();
673 } catch (IOException e) {
674 log.error("Error while closing keystore file "+keystoreFile.getAbsolutePath(), e);
675 }
676 }
677 }
678 }
679
680 private boolean isLoaded(char[] password) {
681 if(openPassword == null || openPassword.length != password.length) {
682 return false;
683 }
684 if(keystoreReadDate < keystoreFile.lastModified()) {
685 return false;
686 }
687 for (int i = 0; i < password.length; i++) {
688 if(password[i] != openPassword[i]) {
689 return false;
690 }
691 }
692 return true;
693 }
694
695 private void ensureLoaded(char[] storePassword) throws KeystoreException {
696 char[] password;
697 if (storePassword == null) {
698 if (isKeystoreLocked()) {
699 throw new KeystoreIsLocked("Keystore '"+keystoreName+"' is locked; please unlock it in the console.");
700 }
701 password = keystorePassword;
702 } else {
703 password = storePassword;
704 }
705 if (!isLoaded(password)) {
706 loadKeystoreData(password);
707 }
708 }
709
710 private X509Certificate generateCertificate(PublicKey publicKey, PrivateKey privateKey, String algorithm, int validity, String commonName, String orgUnit, String organization, String locality, String state, String country) throws SignatureException, InvalidKeyException {
711 X509V1CertificateGenerator certgen = new X509V1CertificateGenerator();
712 Vector order = new Vector();
713 Hashtable attrmap = new Hashtable();
714
715 if (commonName != null) {
716 attrmap.put(X509Principal.CN, commonName);
717 order.add(X509Principal.CN);
718 }
719
720 if (orgUnit != null) {
721 attrmap.put(X509Principal.OU, orgUnit);
722 order.add(X509Principal.OU);
723 }
724
725 if (organization != null) {
726 attrmap.put(X509Principal.O, organization);
727 order.add(X509Principal.O);
728 }
729
730 if (locality != null) {
731 attrmap.put(X509Principal.L, locality);
732 order.add(X509Principal.L);
733 }
734
735 if (state != null) {
736 attrmap.put(X509Principal.ST, state);
737 order.add(X509Principal.ST);
738 }
739
740 if (country != null) {
741 attrmap.put(X509Principal.C, country);
742 order.add(X509Principal.C);
743 }
744
745 X509Principal issuerDN = new X509Principal(order, attrmap);
746
747 // validity
748 long curr = System.currentTimeMillis();
749 long untill = curr + (long) validity * 24 * 60 * 60 * 1000;
750
751 certgen.setNotBefore(new Date(curr));
752 certgen.setNotAfter(new Date(untill));
753 certgen.setIssuerDN(issuerDN);
754 certgen.setSubjectDN(issuerDN);
755 certgen.setPublicKey(publicKey);
756 certgen.setSignatureAlgorithm(algorithm);
757 certgen.setSerialNumber(new BigInteger(String.valueOf(curr)));
758
759 // make certificate
760 return certgen.generateX509Certificate(privateKey);
761 }
762
763 private void saveKeystore(char[] password) throws KeystoreException {
764 try {
765 BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(keystoreFile));
766 keystore.store(out, password);
767 out.flush();
768 out.close();
769 keystoreReadDate = System.currentTimeMillis();
770 } catch (KeyStoreException e) {
771 throw new KeystoreException("Unable to save keystore '" + keystoreName + "'", e);
772 } catch (FileNotFoundException e) {
773 throw new KeystoreException("Unable to save keystore '" + keystoreName + "'", e);
774 } catch (IOException e) {
775 throw new KeystoreException("Unable to save keystore '" + keystoreName + "'", e);
776 } catch (NoSuchAlgorithmException e) {
777 throw new KeystoreException("Unable to save keystore '" + keystoreName + "'", e);
778 } catch (CertificateException e) {
779 throw new KeystoreException("Unable to save keystore '" + keystoreName + "'", e);
780 }
781 }
782
783 }