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 }