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 }