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