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 }