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