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.BufferedOutputStream;
020    import java.io.File;
021    import java.io.FileOutputStream;
022    import java.io.IOException;
023    import java.io.OutputStream;
024    import java.math.BigInteger;
025    import java.net.URI;
026    import java.net.URISyntaxException;
027    import java.security.KeyManagementException;
028    import java.security.KeyStore;
029    import java.security.KeyStoreException;
030    import java.security.NoSuchAlgorithmException;
031    import java.security.NoSuchProviderException;
032    import java.security.PrivateKey;
033    import java.security.PublicKey;
034    import java.security.UnrecoverableKeyException;
035    import java.security.cert.CertificateException;
036    import java.security.cert.X509Certificate;
037    import java.util.ArrayList;
038    import java.util.Collection;
039    import java.util.Date;
040    import java.util.Hashtable;
041    import java.util.Iterator;
042    import java.util.List;
043    import java.util.Vector;
044    import javax.net.ssl.SSLServerSocketFactory;
045    import javax.net.ssl.SSLSocketFactory;
046    import javax.net.ssl.SSLEngine;
047    import javax.net.ssl.SSLContext;
048    
049    import org.apache.commons.logging.Log;
050    import org.apache.commons.logging.LogFactory;
051    import org.apache.geronimo.gbean.AbstractName;
052    import org.apache.geronimo.gbean.GBeanData;
053    import org.apache.geronimo.gbean.GBeanInfo;
054    import org.apache.geronimo.gbean.GBeanInfoBuilder;
055    import org.apache.geronimo.gbean.GBeanLifecycle;
056    import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
057    import org.apache.geronimo.kernel.Kernel;
058    import org.apache.geronimo.kernel.config.ConfigurationUtil;
059    import org.apache.geronimo.kernel.config.EditableConfigurationManager;
060    import org.apache.geronimo.kernel.config.InvalidConfigException;
061    import org.apache.geronimo.management.geronimo.KeyIsLocked;
062    import org.apache.geronimo.management.geronimo.KeystoreException;
063    import org.apache.geronimo.management.geronimo.KeystoreInstance;
064    import org.apache.geronimo.management.geronimo.KeystoreIsLocked;
065    import org.apache.geronimo.management.geronimo.KeystoreManager;
066    import org.apache.geronimo.system.serverinfo.ServerInfo;
067    import org.apache.geronimo.crypto.KeystoreUtil;
068    import org.apache.geronimo.crypto.jce.X509Principal;
069    import org.apache.geronimo.crypto.jce.X509V1CertificateGenerator;
070    
071    /**
072     * An implementation of KeystoreManager that assumes every file in a specified
073     * directory is a keystore.
074     *
075     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
076     */
077    public class FileKeystoreManager implements KeystoreManager, GBeanLifecycle {
078        private static final Log log = LogFactory.getLog(FileKeystoreManager.class);
079        private File directory;
080        private ServerInfo serverInfo;
081        private URI configuredDir;
082        private Collection keystores;
083        private Kernel kernel;
084    
085        public FileKeystoreManager(URI keystoreDir, ServerInfo serverInfo, Collection keystores, Kernel kernel) {
086            configuredDir = keystoreDir;
087            this.serverInfo = serverInfo;
088            this.keystores = keystores;
089            this.kernel = kernel;
090        }
091    
092        public void doStart() throws Exception {
093            URI rootURI;
094            if (serverInfo != null) {
095                rootURI = serverInfo.resolveServer(configuredDir);
096            } else {
097                rootURI = configuredDir;
098            }
099            if (!rootURI.getScheme().equals("file")) {
100                throw new IllegalStateException("FileKeystoreManager must have a root that's a local directory (not " + rootURI + ")");
101            }
102            directory = new File(rootURI);
103            if (!directory.exists() || !directory.isDirectory() || !directory.canRead()) {
104                throw new IllegalStateException("FileKeystoreManager must have a root that's a valid readable directory (not " + directory.getAbsolutePath() + ")");
105            }
106            log.debug("Keystore directory is " + directory.getAbsolutePath());
107        }
108    
109        public void doStop() throws Exception {
110        }
111    
112        public void doFail() {
113        }
114    
115        public String[] listKeystoreFiles() {
116            File[] files = directory.listFiles();
117            List list = new ArrayList();
118            for (int i = 0; i < files.length; i++) {
119                File file = files[i];
120                if(file.canRead() && !file.isDirectory()) {
121                    list.add(file.getName());
122                }
123            }
124            return (String[]) list.toArray(new String[list.size()]);
125        }
126    
127        public KeystoreInstance[] getKeystores() {
128            String[] names = listKeystoreFiles();
129            KeystoreInstance[] result = new KeystoreInstance[names.length];
130            for (int i = 0; i < result.length; i++) {
131                result[i] = getKeystore(names[i], null);
132                if(result[i] == null) {
133                    return null;
134                }
135            }
136            return result;
137        }
138    
139        public KeystoreInstance getKeystore(String name, String type) {
140            for (Iterator it = keystores.iterator(); it.hasNext();) {
141                KeystoreInstance instance = (KeystoreInstance) it.next();
142                if(instance.getKeystoreName().equals(name)) {
143                    return instance;
144                }
145            }
146            File test = new File(directory, name);
147            if(!test.exists() || !test.canRead()) {
148                throw new IllegalArgumentException("Cannot access keystore "+test.getAbsolutePath()+"!");
149            }
150            AbstractName aName;
151            AbstractName myName = kernel.getAbstractNameFor(this);
152            aName = kernel.getNaming().createSiblingName(myName, name, NameFactory.KEYSTORE_INSTANCE);
153            GBeanData data = new GBeanData(aName, FileKeystoreInstance.getGBeanInfo());
154            try {
155                String path = configuredDir.toString();
156                if(!path.endsWith("/")) {
157                    path += "/";
158                }
159                data.setAttribute("keystorePath", new URI(path +name));
160            } catch (URISyntaxException e) {
161                throw new IllegalStateException("Can't resolve keystore path: "+e.getMessage(), e);
162            }
163            data.setReferencePattern("ServerInfo", kernel.getAbstractNameFor(serverInfo));
164            data.setAttribute("keystoreName", name);
165            if(type == null) {
166                if(name.lastIndexOf(".") == -1) {
167                    type = KeystoreUtil.defaultType;
168                    log.warn("keystoreType for new keystore \""+name+"\" set to default type \""+type+"\".");
169                } else {
170                    type = name.substring(name.lastIndexOf(".")+1);
171                    log.warn("keystoreType for new keystore \""+name+"\" set to \""+type+"\" based on file extension.");
172                }
173            }
174            data.setAttribute("keystoreType", type);
175            EditableConfigurationManager mgr = ConfigurationUtil.getEditableConfigurationManager(kernel);
176            if(mgr != null) {
177                try {
178                    mgr.addGBeanToConfiguration(myName.getArtifact(), data, true);
179                    return (KeystoreInstance) kernel.getProxyManager().createProxy(aName, KeystoreInstance.class);
180                } catch (InvalidConfigException e) {
181                    log.error("Should never happen", e);
182                    throw new IllegalStateException("Unable to add Keystore GBean ("+e.getMessage()+")", e);
183                } finally {
184                    ConfigurationUtil.releaseConfigurationManager(kernel, mgr);
185                }
186            } else {
187                log.warn("The ConfigurationManager in the kernel does not allow changes at runtime");
188                return null;
189            }
190        }
191    
192        /**
193         * Gets a SocketFactory using one Keystore to access the private key
194         * and another to provide the list of trusted certificate authorities.
195         *
196         * @param provider   The SSL provider to use, or null for the default
197         * @param protocol   The SSL protocol to use
198         * @param algorithm  The SSL algorithm to use
199         * @param trustStore The trust keystore name as provided by listKeystores.
200         *                   The KeystoreInstance for this keystore must have
201         *                   unlocked this key.
202         * @param loader     The class loader used to resolve factory classes.
203         *
204         * @return A created SSLSocketFactory item created from the KeystoreManager.
205         * @throws KeystoreIsLocked
206         *                Occurs when the requested key keystore cannot
207         *                be used because it has not been unlocked.
208         * @throws KeyIsLocked
209         *                Occurs when the requested private key in the key
210         *                keystore cannot be used because it has not been
211         *                unlocked.
212         * @throws NoSuchAlgorithmException
213         * @throws UnrecoverableKeyException
214         * @throws KeyStoreException
215         * @throws KeyManagementException
216         * @throws NoSuchProviderException
217         */
218        public SSLSocketFactory createSSLFactory(String provider, String protocol, String algorithm, String trustStore, ClassLoader loader) throws KeystoreException {
219            // typically, the keyStore and the keyAlias are not required if authentication is also not required.
220            return createSSLFactory(provider, protocol, algorithm, null, null, trustStore, loader);
221        }
222    
223        /**
224         * Gets a SocketFactory using one Keystore to access the private key
225         * and another to provide the list of trusted certificate authorities.
226         *
227         * @param provider   The SSL provider to use, or null for the default
228         * @param protocol   The SSL protocol to use
229         * @param algorithm  The SSL algorithm to use
230         * @param keyStore   The key keystore name as provided by listKeystores.  The
231         *                   KeystoreInstance for this keystore must be unlocked.
232         * @param keyAlias   The name of the private key in the keystore.  The
233         *                   KeystoreInstance for this keystore must have unlocked
234         *                   this key.
235         * @param trustStore The trust keystore name as provided by listKeystores.
236         *                   The KeystoreInstance for this keystore must have
237         *                   unlocked this key.
238         * @param loader     The class loader used to resolve factory classes.
239         *
240         * @return A created SSLSocketFactory item created from the KeystoreManager.
241         * @throws KeystoreIsLocked
242         *                Occurs when the requested key keystore cannot
243         *                be used because it has not been unlocked.
244         * @throws KeyIsLocked
245         *                Occurs when the requested private key in the key
246         *                keystore cannot be used because it has not been
247         *                unlocked.
248         * @throws KeystoreException
249         */
250        public SSLSocketFactory createSSLFactory(String provider, String protocol, String algorithm, String keyStore, String keyAlias, String trustStore, ClassLoader loader) throws KeystoreException {
251            // the keyStore is optional.
252            KeystoreInstance keyInstance = null;
253            if (keyStore != null) {
254                keyInstance = getKeystore(keyStore, null);
255                if(keyInstance.isKeystoreLocked()) {
256                    throw new KeystoreIsLocked("Keystore '"+keyStore+"' is locked; please use the keystore page in the admin console to unlock it");
257                }
258                if(keyInstance.isKeyLocked(keyAlias)) {
259                    throw new KeystoreIsLocked("Key '"+keyAlias+"' in keystore '"+keyStore+"' is locked; please use the keystore page in the admin console to unlock it");
260                }
261            }
262            KeystoreInstance trustInstance = trustStore == null ? null : getKeystore(trustStore, null);
263            if(trustInstance != null && trustInstance.isKeystoreLocked()) {
264                throw new KeystoreIsLocked("Keystore '"+trustStore+"' is locked; please use the keystore page in the admin console to unlock it");
265            }
266    
267            // OMG this hurts, but it causes ClassCastExceptions elsewhere unless done this way!
268            try {
269                Class cls = loader.loadClass("javax.net.ssl.SSLContext");
270                Object ctx = cls.getMethod("getInstance", new Class[] {String.class}).invoke(null, new Object[]{protocol});
271                Class kmc = Class.forName("[Ljavax.net.ssl.KeyManager;", false, loader);
272                Class tmc = Class.forName("[Ljavax.net.ssl.TrustManager;", false, loader);            Class src = loader.loadClass("java.security.SecureRandom");
273                cls.getMethod("init", new Class[]{kmc, tmc, src}).invoke(ctx, new Object[]{
274                                                                                keyInstance == null ? null : keyInstance.getKeyManager(algorithm, keyAlias, null),
275                                                                                trustInstance == null ? null : trustInstance.getTrustManager(algorithm, null),
276                                                                                new java.security.SecureRandom()});
277                Object result = cls.getMethod("getSocketFactory", new Class[0]).invoke(ctx, new Object[0]);
278                return (SSLSocketFactory) result;
279            } catch (Exception e) {
280                throw new KeystoreException("Unable to create SSL Factory", e);
281            }
282        }
283    
284        /**
285         * Gets a ServerSocketFactory using one Keystore to access the private key
286         * and another to provide the list of trusted certificate authorities.
287         * @param provider The SSL provider to use, or null for the default
288         * @param protocol The SSL protocol to use
289         * @param algorithm The SSL algorithm to use
290         * @param keyStore The key keystore name as provided by listKeystores.  The
291         *                 KeystoreInstance for this keystore must be unlocked.
292         * @param keyAlias The name of the private key in the keystore.  The
293         *                 KeystoreInstance for this keystore must have unlocked
294         *                 this key.
295         * @param trustStore The trust keystore name as provided by listKeystores.
296         *                   The KeystoreInstance for this keystore must have
297         *                   unlocked this key.
298         * @param loader     The class loader used to resolve factory classes.
299         *
300         * @throws KeystoreIsLocked Occurs when the requested key keystore cannot
301         *                          be used because it has not been unlocked.
302         * @throws KeyIsLocked Occurs when the requested private key in the key
303         *                     keystore cannot be used because it has not been
304         *                     unlocked.
305         */
306        public SSLServerSocketFactory createSSLServerFactory(String provider, String protocol, String algorithm, String keyStore, String keyAlias, String trustStore, ClassLoader loader) throws KeystoreException {
307            SSLContext sslContext = createSSLContext(provider, protocol, algorithm, keyStore, keyAlias, trustStore, loader);
308            // OMG this hurts, but it causes ClassCastExceptions elsewhere unless done this way!
309            try {
310                Object result = sslContext.getClass().getMethod("getServerSocketFactory", new Class[0]).invoke(sslContext, new Object[0]);
311                return (SSLServerSocketFactory) result;
312            } catch (Exception e) {
313                throw new KeystoreException("Unable to create SSL Server Factory", e);
314            }
315        }
316    
317        /**
318         * Gets a ServerSocketFactory using one Keystore to access the private key
319         * and another to provide the list of trusted certificate authorities.
320         * @param provider The SSL provider to use, or null for the default
321         * @param protocol The SSL protocol to use
322         * @param algorithm The SSL algorithm to use
323         * @param keyStore The key keystore name as provided by listKeystores.  The
324         *                 KeystoreInstance for this keystore must be unlocked.
325         * @param keyAlias The name of the private key in the keystore.  The
326         *                 KeystoreInstance for this keystore must have unlocked
327         *                 this key.
328         * @param trustStore The trust keystore name as provided by listKeystores.
329         *                   The KeystoreInstance for this keystore must have
330         *                   unlocked this key.
331         * @param loader     The class loader used to resolve factory classes.
332         *
333         * @return SSLContext using the security info provided
334         * @throws KeystoreIsLocked Occurs when the requested key keystore cannot
335         *                          be used because it has not been unlocked.
336         * @throws KeyIsLocked Occurs when the requested private key in the key
337         *                     keystore cannot be used because it has not been
338         *                     unlocked.
339         */
340        public SSLContext createSSLContext(String provider, String protocol, String algorithm, String keyStore, String keyAlias, String trustStore, ClassLoader loader) throws KeystoreException {
341            KeystoreInstance keyInstance = getKeystore(keyStore, null);
342            if(keyInstance.isKeystoreLocked()) {
343                throw new KeystoreIsLocked("Keystore '"+keyStore+"' is locked; please use the keystore page in the admin console to unlock it");
344            }
345            if(keyInstance.isKeyLocked(keyAlias)) {
346                throw new KeystoreIsLocked("Key '"+keyAlias+"' in keystore '"+keyStore+"' is locked; please use the keystore page in the admin console to unlock it");
347            }
348            KeystoreInstance trustInstance = trustStore == null ? null : getKeystore(trustStore, null);
349            if(trustInstance != null && trustInstance.isKeystoreLocked()) {
350                throw new KeystoreIsLocked("Keystore '"+trustStore+"' is locked; please use the keystore page in the admin console to unlock it");
351            }
352    
353            // OMG this hurts, but it causes ClassCastExceptions elsewhere unless done this way!
354            try {
355                Class cls = loader.loadClass("javax.net.ssl.SSLContext");
356                Object ctx = cls.getMethod("getInstance", new Class[] {String.class}).invoke(null, new Object[]{protocol});
357                Class kmc = Class.forName("[Ljavax.net.ssl.KeyManager;", false, loader);
358                Class tmc = Class.forName("[Ljavax.net.ssl.TrustManager;", false, loader);
359                Class src = loader.loadClass("java.security.SecureRandom");
360                cls.getMethod("init", new Class[]{kmc, tmc, src}).invoke(ctx, new Object[]{keyInstance.getKeyManager(algorithm, keyAlias, null),
361                                                                                trustInstance == null ? null : trustInstance.getTrustManager(algorithm, null),
362                                                                                new java.security.SecureRandom()});
363                return (SSLContext) ctx;
364            } catch (Exception e) {
365                throw new KeystoreException("Unable to create SSL Context", e);
366            }
367        }
368    
369        public KeystoreInstance createKeystore(String name, char[] password, String keystoreType) throws KeystoreException {
370            File test = new File(directory, name);
371            if(test.exists()) {
372                throw new IllegalArgumentException("Keystore already exists "+test.getAbsolutePath()+"!");
373            }
374            try {
375                KeyStore keystore = KeyStore.getInstance(keystoreType);
376                keystore.load(null, password);
377                OutputStream out = new BufferedOutputStream(new FileOutputStream(test));
378                keystore.store(out, password);
379                out.flush();
380                out.close();
381                return getKeystore(name, keystoreType);
382            } catch (KeyStoreException e) {
383                throw new KeystoreException("Unable to create keystore", e);
384            } catch (IOException e) {
385                throw new KeystoreException("Unable to create keystore", e);
386            } catch (NoSuchAlgorithmException e) {
387                throw new KeystoreException("Unable to create keystore", e);
388            } catch (CertificateException e) {
389                throw new KeystoreException("Unable to create keystore", e);
390            }
391        }
392    
393        public KeystoreInstance[] getUnlockedKeyStores() {
394            List results = new ArrayList();
395            for (Iterator it = keystores.iterator(); it.hasNext();) {
396                KeystoreInstance instance = (KeystoreInstance) it.next();
397                try {
398                    if(!instance.isKeystoreLocked() && instance.getUnlockedKeys(null).length > 0) {
399                        results.add(instance);
400                    }
401                } catch (KeystoreException e) {}
402            }
403            return (KeystoreInstance[]) results.toArray(new KeystoreInstance[results.size()]);
404        }
405    
406        public KeystoreInstance[] getUnlockedTrustStores() {
407            List results = new ArrayList();
408            for (Iterator it = keystores.iterator(); it.hasNext();) {
409                KeystoreInstance instance = (KeystoreInstance) it.next();
410                try {
411                    if(!instance.isKeystoreLocked() && instance.isTrustStore(null)) {
412                        results.add(instance);
413                    }
414                } catch (KeystoreException e) {}
415            }
416            return (KeystoreInstance[]) results.toArray(new KeystoreInstance[results.size()]);
417        }
418    
419        public static final GBeanInfo GBEAN_INFO;
420    
421        static {
422            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(FileKeystoreManager.class);
423            infoFactory.addAttribute("keystoreDir", URI.class, true);
424            infoFactory.addAttribute("kernel", Kernel.class, false);
425            infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
426            infoFactory.addReference("KeystoreInstances", KeystoreInstance.class, NameFactory.KEYSTORE_INSTANCE);
427            infoFactory.addInterface(KeystoreManager.class);
428            infoFactory.setConstructor(new String[]{"keystoreDir", "ServerInfo", "KeystoreInstances", "kernel"});
429    
430            GBEAN_INFO = infoFactory.getBeanInfo();
431        }
432    
433        public static GBeanInfo getGBeanInfo() {
434            return GBEAN_INFO;
435        }
436    
437        // ===================== Move this to a unitiy class or something ====================
438    
439        public X509Certificate generateCert(PublicKey publicKey,
440                                            PrivateKey privateKey, String sigalg, int validity, String cn,
441                                            String ou, String o, String l, String st, String c)
442                throws java.security.SignatureException,
443                java.security.InvalidKeyException {
444            X509V1CertificateGenerator certgen = new X509V1CertificateGenerator();
445    
446            // issuer dn
447            Vector order = new Vector();
448            Hashtable attrmap = new Hashtable();
449    
450            if (cn != null) {
451                attrmap.put(X509Principal.CN, cn);
452                order.add(X509Principal.CN);
453            }
454    
455            if (ou != null) {
456                attrmap.put(X509Principal.OU, ou);
457                order.add(X509Principal.OU);
458            }
459    
460            if (o != null) {
461                attrmap.put(X509Principal.O, o);
462                order.add(X509Principal.O);
463            }
464    
465            if (l != null) {
466                attrmap.put(X509Principal.L, l);
467                order.add(X509Principal.L);
468            }
469    
470            if (st != null) {
471                attrmap.put(X509Principal.ST, st);
472                order.add(X509Principal.ST);
473            }
474    
475            if (c != null) {
476                attrmap.put(X509Principal.C, c);
477                order.add(X509Principal.C);
478            }
479    
480            X509Principal issuerDN = new X509Principal(order, attrmap);
481            certgen.setIssuerDN(issuerDN);
482    
483            // validity
484            long curr = System.currentTimeMillis();
485            long untill = curr + (long) validity * 24 * 60 * 60 * 1000;
486    
487            certgen.setNotBefore(new Date(curr));
488            certgen.setNotAfter(new Date(untill));
489    
490            // subject dn
491            certgen.setSubjectDN(issuerDN);
492    
493            // public key
494            certgen.setPublicKey(publicKey);
495    
496            // signature alg
497            certgen.setSignatureAlgorithm(sigalg);
498    
499            // serial number
500            certgen.setSerialNumber(new BigInteger(String.valueOf(curr)));
501    
502            // make certificate
503            return certgen.generateX509Certificate(privateKey);
504        }
505    }