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    }