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