View Javadoc

1   /**
2    *
3    *  Licensed to the Apache Software Foundation (ASF) under one or more
4    *  contributor license agreements.  See the NOTICE file distributed with
5    *  this work for additional information regarding copyright ownership.
6    *  The ASF licenses this file to You under the Apache License, Version 2.0
7    *  (the "License"); you may not use this file except in compliance with
8    *  the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing, software
13   *  distributed under the License is distributed on an "AS IS" BASIS,
14   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *  See the License for the specific language governing permissions and
16   *  limitations under the License.
17   */
18  package org.apache.geronimo.security.keystore;
19  
20  import java.io.BufferedOutputStream;
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.OutputStream;
25  import java.math.BigInteger;
26  import java.net.URI;
27  import java.net.URISyntaxException;
28  import java.security.KeyManagementException;
29  import java.security.KeyStore;
30  import java.security.KeyStoreException;
31  import java.security.NoSuchAlgorithmException;
32  import java.security.NoSuchProviderException;
33  import java.security.PrivateKey;
34  import java.security.PublicKey;
35  import java.security.UnrecoverableKeyException;
36  import java.security.cert.CertificateException;
37  import java.security.cert.X509Certificate;
38  import java.util.ArrayList;
39  import java.util.Collection;
40  import java.util.Date;
41  import java.util.Hashtable;
42  import java.util.Iterator;
43  import java.util.List;
44  import java.util.Vector;
45  import javax.net.ssl.SSLServerSocketFactory;
46  import javax.net.ssl.SSLSocketFactory;
47  import org.apache.commons.logging.Log;
48  import org.apache.commons.logging.LogFactory;
49  import org.apache.geronimo.gbean.AbstractName;
50  import org.apache.geronimo.gbean.GBeanData;
51  import org.apache.geronimo.gbean.GBeanInfo;
52  import org.apache.geronimo.gbean.GBeanInfoBuilder;
53  import org.apache.geronimo.gbean.GBeanLifecycle;
54  import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
55  import org.apache.geronimo.kernel.Kernel;
56  import org.apache.geronimo.kernel.config.ConfigurationUtil;
57  import org.apache.geronimo.kernel.config.EditableConfigurationManager;
58  import org.apache.geronimo.kernel.config.InvalidConfigException;
59  import org.apache.geronimo.management.geronimo.KeyIsLocked;
60  import org.apache.geronimo.management.geronimo.KeystoreException;
61  import org.apache.geronimo.management.geronimo.KeystoreInstance;
62  import org.apache.geronimo.management.geronimo.KeystoreIsLocked;
63  import org.apache.geronimo.management.geronimo.KeystoreManager;
64  import org.apache.geronimo.system.serverinfo.ServerInfo;
65  import org.apache.geronimo.util.jce.X509Principal;
66  import org.apache.geronimo.util.jce.X509V1CertificateGenerator;
67  
68  /**
69   * An implementation of KeystoreManager that assumes every file in a specified
70   * directory is a keystore.
71   *
72   * @version $Rev: 470597 $ $Date: 2006-11-02 15:30:55 -0800 (Thu, 02 Nov 2006) $
73   */
74  public class FileKeystoreManager implements KeystoreManager, GBeanLifecycle {
75      private static final Log log = LogFactory.getLog(FileKeystoreManager.class);
76      private File directory;
77      private ServerInfo serverInfo;
78      private URI configuredDir;
79      private Collection keystores;
80      private Kernel kernel;
81  
82      public FileKeystoreManager(URI keystoreDir, ServerInfo serverInfo, Collection keystores, Kernel kernel) {
83          configuredDir = keystoreDir;
84          this.serverInfo = serverInfo;
85          this.keystores = keystores;
86          this.kernel = kernel;
87      }
88  
89      public void doStart() throws Exception {
90          URI rootURI;
91          if (serverInfo != null) {
92              rootURI = serverInfo.resolve(configuredDir);
93          } else {
94              rootURI = configuredDir;
95          }
96          if (!rootURI.getScheme().equals("file")) {
97              throw new IllegalStateException("FileKeystoreManager must have a root that's a local directory (not " + rootURI + ")");
98          }
99          directory = new File(rootURI);
100         if (!directory.exists() || !directory.isDirectory() || !directory.canRead()) {
101             throw new IllegalStateException("FileKeystoreManager must have a root that's a valid readable directory (not " + directory.getAbsolutePath() + ")");
102         }
103         log.debug("Keystore directory is " + directory.getAbsolutePath());
104     }
105 
106     public void doStop() throws Exception {
107     }
108 
109     public void doFail() {
110     }
111 
112     public String[] listKeystoreFiles() {
113         File[] files = directory.listFiles();
114         List list = new ArrayList();
115         for (int i = 0; i < files.length; i++) {
116             File file = files[i];
117             if(file.canRead() && !file.isDirectory()) {
118                 list.add(file.getName());
119             }
120         }
121         return (String[]) list.toArray(new String[list.size()]);
122     }
123 
124     public KeystoreInstance[] getKeystores() {
125         String[] names = listKeystoreFiles();
126         KeystoreInstance[] result = new KeystoreInstance[names.length];
127         for (int i = 0; i < result.length; i++) {
128             result[i] = getKeystore(names[i]);
129             if(result[i] == null) {
130                 return null;
131             }
132         }
133         return result;
134     }
135 
136     public KeystoreInstance getKeystore(String name) {
137         for (Iterator it = keystores.iterator(); it.hasNext();) {
138             KeystoreInstance instance = (KeystoreInstance) it.next();
139             if(instance.getKeystoreName().equals(name)) {
140                 return instance;
141             }
142         }
143         File test = new File(directory, name);
144         if(!test.exists() || !test.canRead()) {
145             throw new IllegalArgumentException("Cannot access keystore "+test.getAbsolutePath()+"!");
146         }
147         AbstractName aName;
148         AbstractName myName = kernel.getAbstractNameFor(this);
149         aName = kernel.getNaming().createSiblingName(myName, name, NameFactory.KEYSTORE_INSTANCE);
150         GBeanData data = new GBeanData(aName, FileKeystoreInstance.getGBeanInfo());
151         try {
152             String path = configuredDir.toString();
153             if(!path.endsWith("/")) {
154                 path += "/";
155             }
156             data.setAttribute("keystorePath", new URI(path +name));
157         } catch (URISyntaxException e) {
158             throw new IllegalStateException("Can't resolve keystore path: "+e.getMessage());
159         }
160         data.setReferencePattern("ServerInfo", kernel.getAbstractNameFor(serverInfo));
161         data.setAttribute("keystoreName", name);
162         EditableConfigurationManager mgr = ConfigurationUtil.getEditableConfigurationManager(kernel);
163         if(mgr != null) {
164             try {
165                 mgr.addGBeanToConfiguration(myName.getArtifact(), data, true);
166                 return (KeystoreInstance) kernel.getProxyManager().createProxy(aName, KeystoreInstance.class);
167             } catch (InvalidConfigException e) {
168                 log.error("Should never happen", e);
169                 throw new IllegalStateException("Unable to add Keystore GBean ("+e.getMessage()+")");
170             } finally {
171                 ConfigurationUtil.releaseConfigurationManager(kernel, mgr);
172             }
173         } else {
174             log.warn("The ConfigurationManager in the kernel does not allow changes at runtime");
175             return null;
176         }
177     }
178 
179     /**
180      * Gets a SocketFactory using one Keystore to access the private key
181      * and another to provide the list of trusted certificate authorities.
182      *
183      * @param provider   The SSL provider to use, or null for the default
184      * @param protocol   The SSL protocol to use
185      * @param algorithm  The SSL algorithm to use
186      * @param trustStore The trust keystore name as provided by listKeystores.
187      *                   The KeystoreInstance for this keystore must have
188      *                   unlocked this key.
189      * @param loader     The class loader used to resolve factory classes.
190      *
191      * @return A created SSLSocketFactory item created from the KeystoreManager.
192      * @throws KeystoreIsLocked
193      *                Occurs when the requested key keystore cannot
194      *                be used because it has not been unlocked.
195      * @throws KeyIsLocked
196      *                Occurs when the requested private key in the key
197      *                keystore cannot be used because it has not been
198      *                unlocked.
199      * @throws NoSuchAlgorithmException
200      * @throws UnrecoverableKeyException
201      * @throws KeyStoreException
202      * @throws KeyManagementException
203      * @throws NoSuchProviderException
204      */
205     public SSLSocketFactory createSSLFactory(String provider, String protocol, String algorithm, String trustStore, ClassLoader loader) throws KeystoreException {
206         // typically, the keyStore and the keyAlias are not required if authentication is also not required.
207         return createSSLFactory(provider, protocol, algorithm, null, null, trustStore, loader);
208     }
209 
210     /**
211      * Gets a SocketFactory using one Keystore to access the private key
212      * and another to provide the list of trusted certificate authorities.
213      *
214      * @param provider   The SSL provider to use, or null for the default
215      * @param protocol   The SSL protocol to use
216      * @param algorithm  The SSL algorithm to use
217      * @param keyStore   The key keystore name as provided by listKeystores.  The
218      *                   KeystoreInstance for this keystore must be unlocked.
219      * @param keyAlias   The name of the private key in the keystore.  The
220      *                   KeystoreInstance for this keystore must have unlocked
221      *                   this key.
222      * @param trustStore The trust keystore name as provided by listKeystores.
223      *                   The KeystoreInstance for this keystore must have
224      *                   unlocked this key.
225      * @param loader     The class loader used to resolve factory classes.
226      *
227      * @return A created SSLSocketFactory item created from the KeystoreManager.
228      * @throws KeystoreIsLocked
229      *                Occurs when the requested key keystore cannot
230      *                be used because it has not been unlocked.
231      * @throws KeyIsLocked
232      *                Occurs when the requested private key in the key
233      *                keystore cannot be used because it has not been
234      *                unlocked.
235      * @throws KeystoreException
236      */
237     public SSLSocketFactory createSSLFactory(String provider, String protocol, String algorithm, String keyStore, String keyAlias, String trustStore, ClassLoader loader) throws KeystoreException {
238         // the keyStore is optional.
239         KeystoreInstance keyInstance = null;
240         if (keyStore != null) {
241             keyInstance = getKeystore(keyStore);
242             if(keyInstance.isKeystoreLocked()) {
243                 throw new KeystoreIsLocked("Keystore '"+keyStore+"' is locked; please use the keystore page in the admin console to unlock it");
244             }
245             if(keyInstance.isKeyLocked(keyAlias)) {
246                 throw new KeystoreIsLocked("Key '"+keyAlias+"' in keystore '"+keyStore+"' is locked; please use the keystore page in the admin console to unlock it");
247             }
248         }
249         KeystoreInstance trustInstance = trustStore == null ? null : getKeystore(trustStore);
250         if(trustInstance != null && trustInstance.isKeystoreLocked()) {
251             throw new KeystoreIsLocked("Keystore '"+trustStore+"' is locked; please use the keystore page in the admin console to unlock it");
252         }
253 
254         // OMG this hurts, but it causes ClassCastExceptions elsewhere unless done this way!
255         try {
256             Class cls = loader.loadClass("javax.net.ssl.SSLContext");
257             Object ctx = cls.getMethod("getInstance", new Class[] {String.class}).invoke(null, new Object[]{protocol});
258             Class kmc = loader.loadClass("[Ljavax.net.ssl.KeyManager;");
259             Class tmc = loader.loadClass("[Ljavax.net.ssl.TrustManager;");
260             Class src = loader.loadClass("java.security.SecureRandom");
261             cls.getMethod("init", new Class[]{kmc, tmc, src}).invoke(ctx, new Object[]{
262                                                                             keyInstance == null ? null : keyInstance.getKeyManager(algorithm, keyAlias, null),
263                                                                             trustInstance == null ? null : trustInstance.getTrustManager(algorithm, null),
264                                                                             new java.security.SecureRandom()});
265             Object result = cls.getMethod("getSocketFactory", new Class[0]).invoke(ctx, new Object[0]);
266             return (SSLSocketFactory) result;
267         } catch (Exception e) {
268             throw new KeystoreException("Unable to create SSL Factory", e);
269         }
270     }
271 
272     /**
273      * Gets a ServerSocketFactory using one Keystore to access the private key
274      * and another to provide the list of trusted certificate authorities.
275      * @param provider The SSL provider to use, or null for the default
276      * @param protocol The SSL protocol to use
277      * @param algorithm The SSL algorithm to use
278      * @param keyStore The key keystore name as provided by listKeystores.  The
279      *                 KeystoreInstance for this keystore must be unlocked.
280      * @param keyAlias The name of the private key in the keystore.  The
281      *                 KeystoreInstance for this keystore must have unlocked
282      *                 this key.
283      * @param trustStore The trust keystore name as provided by listKeystores.
284      *                   The KeystoreInstance for this keystore must have
285      *                   unlocked this key.
286      * @param loader     The class loader used to resolve factory classes.
287      *
288      * @throws KeystoreIsLocked Occurs when the requested key keystore cannot
289      *                          be used because it has not been unlocked.
290      * @throws KeyIsLocked Occurs when the requested private key in the key
291      *                     keystore cannot be used because it has not been
292      *                     unlocked.
293      */
294     public SSLServerSocketFactory createSSLServerFactory(String provider, String protocol, String algorithm, String keyStore, String keyAlias, String trustStore, ClassLoader loader) throws KeystoreException {
295         KeystoreInstance keyInstance = getKeystore(keyStore);
296         if(keyInstance.isKeystoreLocked()) {
297             throw new KeystoreIsLocked("Keystore '"+keyStore+"' is locked; please use the keystore page in the admin console to unlock it");
298         }
299         if(keyInstance.isKeyLocked(keyAlias)) {
300             throw new KeystoreIsLocked("Key '"+keyAlias+"' in keystore '"+keyStore+"' is locked; please use the keystore page in the admin console to unlock it");
301         }
302         KeystoreInstance trustInstance = trustStore == null ? null : getKeystore(trustStore);
303         if(trustInstance != null && trustInstance.isKeystoreLocked()) {
304             throw new KeystoreIsLocked("Keystore '"+trustStore+"' is locked; please use the keystore page in the admin console to unlock it");
305         }
306 
307         // OMG this hurts, but it causes ClassCastExceptions elsewhere unless done this way!
308         try {
309             Class cls = loader.loadClass("javax.net.ssl.SSLContext");
310             Object ctx = cls.getMethod("getInstance", new Class[] {String.class}).invoke(null, new Object[]{protocol});
311             Class kmc = loader.loadClass("[Ljavax.net.ssl.KeyManager;");
312             Class tmc = loader.loadClass("[Ljavax.net.ssl.TrustManager;");
313             Class src = loader.loadClass("java.security.SecureRandom");
314             cls.getMethod("init", new Class[]{kmc, tmc, src}).invoke(ctx, new Object[]{keyInstance.getKeyManager(algorithm, keyAlias, null),
315                                                                             trustInstance == null ? null : trustInstance.getTrustManager(algorithm, null),
316                                                                             new java.security.SecureRandom()});
317             Object result = cls.getMethod("getServerSocketFactory", new Class[0]).invoke(ctx, new Object[0]);
318             return (SSLServerSocketFactory) result;
319         } catch (Exception e) {
320             throw new KeystoreException("Unable to create SSL Server Factory", e);
321         }
322     }
323 
324     public KeystoreInstance createKeystore(String name, char[] password) throws KeystoreException {
325         File test = new File(directory, name);
326         if(test.exists()) {
327             throw new IllegalArgumentException("Keystore already exists "+test.getAbsolutePath()+"!");
328         }
329         try {
330             KeyStore keystore = KeyStore.getInstance(FileKeystoreInstance.JKS);
331             keystore.load(null, password);
332             OutputStream out = new BufferedOutputStream(new FileOutputStream(test));
333             keystore.store(out, password);
334             out.flush();
335             out.close();
336             return getKeystore(name);
337         } catch (KeyStoreException e) {
338             throw new KeystoreException("Unable to create keystore", e);
339         } catch (IOException e) {
340             throw new KeystoreException("Unable to create keystore", e);
341         } catch (NoSuchAlgorithmException e) {
342             throw new KeystoreException("Unable to create keystore", e);
343         } catch (CertificateException e) {
344             throw new KeystoreException("Unable to create keystore", e);
345         }
346     }
347 
348     public KeystoreInstance[] getUnlockedKeyStores() {
349         List results = new ArrayList();
350         for (Iterator it = keystores.iterator(); it.hasNext();) {
351             KeystoreInstance instance = (KeystoreInstance) it.next();
352             try {
353                 if(!instance.isKeystoreLocked() && instance.getUnlockedKeys(null).length > 0) {
354                     results.add(instance);
355                 }
356             } catch (KeystoreException e) {}
357         }
358         return (KeystoreInstance[]) results.toArray(new KeystoreInstance[results.size()]);
359     }
360 
361     public KeystoreInstance[] getUnlockedTrustStores() {
362         List results = new ArrayList();
363         for (Iterator it = keystores.iterator(); it.hasNext();) {
364             KeystoreInstance instance = (KeystoreInstance) it.next();
365             try {
366                 if(!instance.isKeystoreLocked() && instance.isTrustStore(null)) {
367                     results.add(instance);
368                 }
369             } catch (KeystoreException e) {}
370         }
371         return (KeystoreInstance[]) results.toArray(new KeystoreInstance[results.size()]);
372     }
373 
374     public static final GBeanInfo GBEAN_INFO;
375 
376     static {
377         GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(FileKeystoreManager.class);
378         infoFactory.addAttribute("keystoreDir", URI.class, true);
379         infoFactory.addAttribute("kernel", Kernel.class, false);
380         infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
381         infoFactory.addReference("KeystoreInstances", KeystoreInstance.class, NameFactory.KEYSTORE_INSTANCE);
382         infoFactory.addInterface(KeystoreManager.class);
383         infoFactory.setConstructor(new String[]{"keystoreDir", "ServerInfo", "KeystoreInstances", "kernel"});
384 
385         GBEAN_INFO = infoFactory.getBeanInfo();
386     }
387 
388     public static GBeanInfo getGBeanInfo() {
389         return GBEAN_INFO;
390     }
391 
392     // ===================== Move this to a unitiy class or something ====================
393 
394     public X509Certificate generateCert(PublicKey publicKey,
395                                         PrivateKey privateKey, String sigalg, int validity, String cn,
396                                         String ou, String o, String l, String st, String c)
397             throws java.security.SignatureException,
398             java.security.InvalidKeyException {
399         X509V1CertificateGenerator certgen = new X509V1CertificateGenerator();
400 
401         // issuer dn
402         Vector order = new Vector();
403         Hashtable attrmap = new Hashtable();
404 
405         if (cn != null) {
406             attrmap.put(X509Principal.CN, cn);
407             order.add(X509Principal.CN);
408         }
409 
410         if (ou != null) {
411             attrmap.put(X509Principal.OU, ou);
412             order.add(X509Principal.OU);
413         }
414 
415         if (o != null) {
416             attrmap.put(X509Principal.O, o);
417             order.add(X509Principal.O);
418         }
419 
420         if (l != null) {
421             attrmap.put(X509Principal.L, l);
422             order.add(X509Principal.L);
423         }
424 
425         if (st != null) {
426             attrmap.put(X509Principal.ST, st);
427             order.add(X509Principal.ST);
428         }
429 
430         if (c != null) {
431             attrmap.put(X509Principal.C, c);
432             order.add(X509Principal.C);
433         }
434 
435         X509Principal issuerDN = new X509Principal(order, attrmap);
436         certgen.setIssuerDN(issuerDN);
437 
438         // validity
439         long curr = System.currentTimeMillis();
440         long untill = curr + (long) validity * 24 * 60 * 60 * 1000;
441 
442         certgen.setNotBefore(new Date(curr));
443         certgen.setNotAfter(new Date(untill));
444 
445         // subject dn
446         certgen.setSubjectDN(issuerDN);
447 
448         // public key
449         certgen.setPublicKey(publicKey);
450 
451         // signature alg
452         certgen.setSignatureAlgorithm(sigalg);
453 
454         // serial number
455         certgen.setSerialNumber(new BigInteger(String.valueOf(curr)));
456 
457         // make certificate
458         return certgen.generateX509Certificate(privateKey);
459     }
460 }