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.yoko; 018 019 import java.io.IOException; 020 import java.net.ConnectException; 021 import java.net.InetAddress; 022 import java.net.ServerSocket; 023 import java.net.Socket; 024 import java.security.cert.Certificate; 025 import java.util.Arrays; 026 027 import javax.net.ssl.HandshakeCompletedEvent; 028 import javax.net.ssl.HandshakeCompletedListener; 029 import javax.net.ssl.SSLServerSocket; 030 import javax.net.ssl.SSLServerSocketFactory; 031 import javax.net.ssl.SSLSocket; 032 import javax.net.ssl.SSLSocketFactory; 033 034 import org.apache.commons.logging.Log; 035 import org.apache.commons.logging.LogFactory; 036 import org.apache.geronimo.corba.ORBConfiguration; 037 import org.apache.geronimo.corba.security.config.ConfigUtil; 038 import org.apache.geronimo.corba.security.config.ssl.SSLCipherSuiteDatabase; 039 import org.apache.geronimo.corba.security.config.ssl.SSLConfig; 040 import org.apache.geronimo.corba.security.config.tss.TSSCompoundSecMechListConfig; 041 import org.apache.geronimo.corba.security.config.tss.TSSConfig; 042 import org.apache.geronimo.corba.security.config.tss.TSSSSLTransportConfig; 043 import org.apache.geronimo.corba.security.config.tss.TSSTransportMechConfig; 044 import org.apache.geronimo.corba.util.Util; 045 import org.apache.yoko.orb.OB.IORDump; 046 import org.apache.yoko.orb.OCI.IIOP.ConnectionHelper; 047 import org.apache.yoko.orb.OCI.ProfileInfo; 048 import org.apache.yoko.orb.OCI.ProfileInfoHolder; 049 import org.omg.CORBA.ORB; 050 import org.omg.CORBA.Policy; 051 import org.omg.CSIIOP.EstablishTrustInClient; 052 import org.omg.CSIIOP.EstablishTrustInTarget; 053 import org.omg.CSIIOP.NoProtection; 054 import org.omg.CSIIOP.TAG_CSI_SEC_MECH_LIST; 055 import org.omg.IIOP.ProfileBody_1_0; 056 import org.omg.IIOP.ProfileBody_1_0Helper; 057 import org.omg.IOP.IOR; 058 059 060 /** 061 * Socket factory instance used to interface openejb2 062 * with the Yoko ORB. Also enables the ORB for 063 * SSL-type connections. 064 * @version $Revision: 505035 $ $Date: 2007-02-08 16:01:06 -0500 (Thu, 08 Feb 2007) $ 065 */ 066 public class SocketFactory implements ConnectionHelper { 067 068 private final static Log log = LogFactory.getLog(SocketFactory.class); 069 070 // The initialized SSLSocketFactory obtained from the Geronimo KeystoreManager. 071 private SSLSocketFactory socketFactory = null; 072 // The initialized SSLServerSocketFactory obtained from the Geronimo KeystoreManager. 073 private SSLServerSocketFactory serverSocketFactory = null; 074 // The initialized SSLConfig we use to retrieve the SSL socket factories. 075 private SSLConfig sslConfig = null; 076 // The set of cypher suites we use with the SSL connection. 077 private String[] cipherSuites; 078 // indicates whether client authentication is supported by this transport. 079 private boolean clientAuthSupported; 080 // indicates whether client authentication is required by this transport. 081 private boolean clientAuthRequired; 082 // supports and requires values used to retrieve the cipher suites. 083 int supports = NoProtection.value; 084 int requires = NoProtection.value; 085 // the orb we're attached to 086 private ORB orb; 087 088 public SocketFactory() { 089 } 090 091 /** 092 * Initialize the socket factory instance. 093 * 094 * @param orb The hosting ORB. 095 * @param configName The initialization parameter passed to the socket factor. 096 * This contains the abstract name of our configurator, 097 * which we retrieve from a registry. 098 */ 099 public void init(ORB orb, String configName) { 100 this.orb = orb; 101 clientAuthSupported = false; 102 clientAuthRequired = false; 103 104 // retrieve the configuration from the config adapter registry. 105 ORBConfiguration config = Util.getRegisteredORB(configName); 106 if (config == null) { 107 throw new RuntimeException("Unable to resolve ORB configuration " + configName); 108 } 109 // get the configuration from the hosting bean and decode what needs to be implemented. 110 sslConfig = config.getSslConfig(); 111 TSSConfig tssConfig = config.getTssConfig(); 112 113 TSSTransportMechConfig transportMech = tssConfig.getTransport_mech(); 114 // if we have a transport mech defined, this is the configuration for any listeners we end up 115 // creating. 116 if (transportMech != null) { 117 if (transportMech instanceof TSSSSLTransportConfig) { 118 TSSSSLTransportConfig transportConfig = (TSSSSLTransportConfig) transportMech; 119 supports = transportConfig.getSupports(); 120 requires = transportConfig.getRequires(); 121 } 122 } 123 124 // now set our listener creation flags based on the supports and requires values from the 125 // TSS config. 126 if ((supports & EstablishTrustInClient.value) != 0) { 127 clientAuthSupported = true; 128 129 if ((requires & EstablishTrustInClient.value) != 0) { 130 clientAuthRequired = true; 131 } 132 } 133 134 if ((supports & EstablishTrustInTarget.value) != 0) { 135 clientAuthSupported = true; 136 137 if ((requires & EstablishTrustInTarget.value) != 0) { 138 clientAuthRequired = true; 139 } 140 } 141 142 if (log.isDebugEnabled()) { 143 log.debug("Creating Yoko SocketFactor for GBean " + configName); 144 log.debug(" SUPPORTS: " + ConfigUtil.flags(supports)); 145 log.debug(" REQUIRES: " + ConfigUtil.flags(requires)); 146 } 147 } 148 149 /** 150 * Create a client socket of the appropriate 151 * type using the provided IOR and Policy information. 152 * 153 * @param ior The target IOR of the connection. 154 * @param policies Policies in effect for this ORB. 155 * @param address The target address of the connection. 156 * @param port The connection port. 157 * 158 * @return A Socket (either plain or SSL) configured for connection 159 * to the target. 160 * @exception IOException 161 * @exception ConnectException 162 */ 163 public Socket createSocket(IOR ior, Policy[] policies, InetAddress address, int port) throws IOException { 164 if (log.isDebugEnabled()) { 165 log.debug("SocketFactory attempting to create socket for address: " + address + " port: " + port); 166 log.debug("Policies: " + Arrays.asList(policies)); 167 log.debug(IORDump.PrintObjref(orb, ior)); 168 } 169 170 try { 171 ProfileInfoHolder holder = new ProfileInfoHolder(); 172 // we need to extract the profile information from the IOR to see if this connection has 173 // any transport-level security defined. 174 if (org.apache.yoko.orb.OCI.IIOP.Util.extractProfileInfo(ior, holder)) { 175 ProfileInfo profileInfo = holder.value; 176 for (int i = 0; i < profileInfo.components.length; i++) { 177 // we're lookoing for the security mechanism items. 178 if (profileInfo.components[i].tag == TAG_CSI_SEC_MECH_LIST.value) { 179 try { 180 // decode and pull the transport information. 181 TSSCompoundSecMechListConfig config = TSSCompoundSecMechListConfig.decodeIOR(Util.getCodec(), profileInfo.components[i]); 182 if (log.isDebugEnabled()) { 183 log.debug("looking at tss: " + config); 184 } 185 for (int j = 0; j < config.size(); j++) { 186 TSSTransportMechConfig transport_mech = config.mechAt(j).getTransport_mech(); 187 if (transport_mech instanceof TSSSSLTransportConfig) { 188 TSSSSLTransportConfig transportConfig = (TSSSSLTransportConfig) transport_mech; 189 190 int supports = transportConfig.getSupports(); 191 int requires = transportConfig.getRequires(); 192 // override the port and hostname with what's configured here. 193 int sslPort = transportConfig.getPort(); 194 String sslHost = transportConfig.getHostname(); 195 196 if (log.isDebugEnabled()) { 197 log.debug("IOR to target " + sslHost + ":" + sslPort); 198 log.debug(" SUPPORTS: " + ConfigUtil.flags(supports)); 199 log.debug(" REQUIRES: " + ConfigUtil.flags(requires)); 200 } 201 202 // TLS is configured. If this is explicitly noprotection, then 203 // just go create a plain socket using the configured port. 204 if ((NoProtection.value & requires) == NoProtection.value) { 205 break; 206 } 207 // we need SSL, so create an SSLSocket for this connection. 208 return createSSLSocket(sslHost, sslPort, requires, supports); 209 } 210 } 211 } catch (Exception e) { 212 // do nothing 213 } 214 } 215 } 216 } 217 218 //SSL not needed, look in the profile for host/port 219 String host = address.getHostName(); 220 221 // the Yoko ORB will use both the primary and secondary targets for connetions, which 222 // sometimes gets us into trouble, forcing us to use an SSL target when we really need to 223 // use the plain socket connection. Therefore, we will ignore what's passed to us, 224 // and extract the primary port information directly from the profile. 225 for (int i = 0; i < ior.profiles.length; i++) { 226 if (ior.profiles[i].tag == org.omg.IOP.TAG_INTERNET_IOP.value) { 227 try { 228 // 229 // Get the IIOP profile body 230 // 231 byte[] data = ior.profiles[i].profile_data; 232 ProfileBody_1_0 body = ProfileBody_1_0Helper.extract(Util.getCodec().decode_value(data, ProfileBody_1_0Helper.type())); 233 234 // 235 // Create new connector for this profile 236 // 237 if (body.port < 0) { 238 port = 0xffff + (int) body.port + 1; 239 } else { 240 port = (int) body.port; 241 } 242 log.debug("set port: " + port); 243 } catch (org.omg.IOP.CodecPackage.FormatMismatch e) { 244 // just keep the original port. 245 log.debug("could not set port: ", e); 246 break; 247 } catch (org.omg.IOP.CodecPackage.TypeMismatch e) { 248 // just keep the original port. 249 log.debug("could not set port: ", e); 250 break; 251 } 252 253 } 254 } 255 256 257 // if security is not required, just create a plain Socket. 258 if (log.isDebugEnabled()) log.debug("Created plain endpoint to " + host + ":" + port); 259 return new Socket(host, port); 260 261 } catch (IOException ex) { 262 log.error("Exception creating a client socket to " + address.getHostName() + ":" + port, ex); 263 throw ex; 264 } 265 } 266 267 /** 268 * Create a loopback connection to the hosting 269 * ORB. 270 * 271 * @param address The address information for the server. 272 * @param port The target port. 273 * 274 * @return An appropriately configured socket based on the 275 * listener characteristics. 276 * @exception IOException 277 * @exception ConnectException 278 */ 279 public Socket createSelfConnection(InetAddress address, int port) throws IOException { 280 try { 281 // the requires information tells us whether we created a plain or SSL listener. We need to create one 282 // of the matching type. 283 284 if ((NoProtection.value & requires) == NoProtection.value) { 285 if (log.isDebugEnabled()) log.debug("Created plain endpoint to " + address.getHostName() + ":" + port); 286 return new Socket(address, port); 287 } 288 else { 289 return createSSLSocket(address.getHostName(), port, requires, supports); 290 } 291 } catch (IOException ex) { 292 log.error("Exception creating a client socket to " + address.getHostName() + ":" + port, ex); 293 throw ex; 294 } 295 } 296 297 /** 298 * Create a server socket listening on the given port. 299 * 300 * @param port The target listening port. 301 * @param backlog The desired backlog value. 302 * 303 * @return An appropriate server socket for this connection. 304 * @exception IOException 305 * @exception ConnectException 306 */ 307 public ServerSocket createServerSocket(int port, int backlog) throws IOException { 308 try { 309 // if no protection is required, just create a plain socket. 310 if ((NoProtection.value & requires) == NoProtection.value) { 311 if (log.isDebugEnabled()) log.debug("Created plain server socket for port " + port); 312 return new ServerSocket(port, backlog); 313 } 314 else { 315 // SSL is required. Create one from the SSLServerFactory retrieved from the config. This will 316 // require additional QOS configuration after creation. 317 SSLServerSocket serverSocket = (SSLServerSocket)getServerSocketFactory().createServerSocket(port, backlog); 318 configureServerSocket(serverSocket); 319 return serverSocket; 320 } 321 } catch (IOException ex) { 322 log.error("Exception creating a server socket for port " + port, ex); 323 throw ex; 324 } 325 } 326 327 /** 328 * Create a server socket for this connection. 329 * 330 * @param port The target listener port. 331 * @param backlog The requested backlog value for the connection. 332 * @param address The host address information we're publishing under. 333 * 334 * @return An appropriately configured ServerSocket for this 335 * connection. 336 * @exception IOException 337 * @exception ConnectException 338 */ 339 public ServerSocket createServerSocket(int port, int backlog, InetAddress address) throws IOException { 340 try { 341 // if no protection is required, just create a plain socket. 342 if ((NoProtection.value & requires) == NoProtection.value) { 343 if (log.isDebugEnabled()) log.debug("Created plain server socket for port " + port); 344 return new ServerSocket(port, backlog, address); 345 } 346 else { 347 // SSL is required. Create one from the SSLServerFactory retrieved from the config. This will 348 // require additional QOS configuration after creation. 349 SSLServerSocket serverSocket = (SSLServerSocket)getServerSocketFactory().createServerSocket(port, backlog, address); 350 configureServerSocket(serverSocket); 351 return serverSocket; 352 } 353 } catch (IOException ex) { 354 log.error("Exception creating a client socket to " + address.getHostName() + ":" + port, ex); 355 throw ex; 356 } 357 } 358 359 /** 360 * On-demand creation of an SSL socket factory, using the provided 361 * Geronimo SSLConfig information. 362 * 363 * @return The SSLSocketFactory this connection should be using to create 364 * secure connections. 365 * @throws java.io.IOException if we can't get a socket factory 366 */ 367 private SSLSocketFactory getSocketFactory() throws IOException { 368 // first use? 369 if (socketFactory == null) { 370 // the SSLConfig is optional, so if it's not there, use the default SSLSocketFactory. 371 if (sslConfig == null) { 372 socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 373 } 374 else { 375 // ask the SSLConfig bean to create a factory for us. 376 try { 377 socketFactory = sslConfig.createSSLFactory(Thread.currentThread().getContextClassLoader()); 378 } catch (Exception e) { 379 log.error("Unable to create client SSL socket factory", e); 380 throw (IOException)new IOException("Unable to create client SSL socket factory: " + e.getMessage()).initCause(e); 381 } 382 } 383 } 384 return socketFactory; 385 } 386 387 /** 388 * On-demand creation of an SSL server socket factory, using the provided 389 * Geronimo SSLConfig information. 390 * 391 * @return The SSLServerSocketFactory this connection should be using to create 392 * secure connections. 393 * @throws java.io.IOException if we can't get a server socket factory 394 */ 395 private SSLServerSocketFactory getServerSocketFactory() throws IOException { 396 // first use? 397 if (serverSocketFactory == null) { 398 // the SSLConfig is optional, so if it's not there, use the default SSLSocketFactory. 399 if (sslConfig == null) { 400 serverSocketFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); 401 } 402 else { 403 try { 404 serverSocketFactory = sslConfig.createSSLServerFactory(Thread.currentThread().getContextClassLoader()); 405 } catch (Exception e) { 406 log.error("Unable to create server SSL socket factory", e); 407 throw (IOException)new IOException("Unable to create server SSL socket factory: " + e.getMessage()).initCause(e); 408 } 409 } 410 // we have a socket factory....now get our cipher suite set based on our requirements and what's 411 // available from the factory. 412 if (cipherSuites == null) { 413 cipherSuites = SSLCipherSuiteDatabase.getCipherSuites(requires, supports, serverSocketFactory.getSupportedCipherSuites()); 414 } 415 // There's a bit of a timing problem with server-side ORBs. Part of the ORB shutdown is to 416 // establish a self-connection to shutdown the acceptor threads. This requires a client 417 // SSL socket factory. Unfortunately, if this is occurring during server shutdown, the 418 // FileKeystoreManager will get a NullPointerException because some name queries fail because 419 // things are getting shutdown. Therefore, if we need the server factory, assume we'll also 420 // need the client factory to shutdown, and request it now. 421 getSocketFactory(); 422 } 423 return serverSocketFactory; 424 } 425 426 427 /** 428 * Set the server socket configuration to our required 429 * QOS values. 430 * 431 * A small experiment shows that setting either (want, need) parameter to either true or false sets the 432 * other parameter to false. 433 * 434 * @param serverSocket 435 * The newly created SSLServerSocket. 436 * 437 * @throws IOException if server socket can't be configured 438 */ 439 private void configureServerSocket(SSLServerSocket serverSocket) throws IOException { 440 // set the authentication value and cipher suite info. 441 serverSocket.setEnabledCipherSuites(cipherSuites); 442 if (clientAuthRequired) { 443 serverSocket.setNeedClientAuth(true); 444 } else if (clientAuthSupported) { 445 serverSocket.setWantClientAuth(true); 446 } else { 447 serverSocket.setNeedClientAuth(false); //could set want with the same effect 448 } 449 serverSocket.setSoTimeout(60 * 1000); 450 451 if (log.isDebugEnabled()) { 452 log.debug("Created SSL server socket on port " + serverSocket.getLocalPort()); 453 log.debug(" client authentication " + (clientAuthSupported ? "SUPPORTED" : "UNSUPPORTED")); 454 log.debug(" client authentication " + (clientAuthRequired ? "REQUIRED" : "OPTIONAL")); 455 log.debug(" cipher suites:"); 456 457 for (int i = 0; i < cipherSuites.length; i++) { 458 log.debug(" " + cipherSuites[i]); 459 } 460 } 461 } 462 463 /** 464 * Create an SSL client socket using the IOR-encoded 465 * security characteristics. 466 * Setting want/need client auth on a client socket has no effect so all we can do is use the right host, port, ciphers 467 * 468 * @param host The target host name. 469 * @param port The target connection port. 470 * 471 * @return An appropriately configured client SSLSocket. 472 * @exception IOException if ssl socket can't be obtained and configured. 473 */ 474 private Socket createSSLSocket(String host, int port, int requires, int supports) throws IOException { 475 SSLSocketFactory factory = getSocketFactory(); 476 SSLSocket socket = (SSLSocket) factory.createSocket(host, port); 477 478 socket.setSoTimeout(60 * 1000); 479 480 // get a set of cipher suites appropriate for this connections requirements. 481 // We request this for each connection, since the outgoing IOR's requirements may be different from 482 // our server listener requirements. 483 String[] iorSuites = SSLCipherSuiteDatabase.getCipherSuites(requires, supports, factory.getSupportedCipherSuites()); 484 socket.setEnabledCipherSuites(iorSuites); 485 if (log.isDebugEnabled()) { 486 log.debug("Created SSL socket to " + host + ":" + port); 487 log.debug(" cipher suites:"); 488 489 for (int i = 0; i < iorSuites.length; i++) { 490 log.debug(" " + iorSuites[i]); 491 } 492 socket.addHandshakeCompletedListener(new HandshakeCompletedListener() { 493 494 public void handshakeCompleted(HandshakeCompletedEvent handshakeCompletedEvent) { 495 Certificate[] certs = handshakeCompletedEvent.getLocalCertificates(); 496 if (certs != null) { 497 log.debug("handshake returned local certs count: " + certs.length); 498 for (int i = 0; i < certs.length; i++) { 499 Certificate cert = certs[i]; 500 log.debug("cert: " + cert.toString()); 501 } 502 } else { 503 log.debug("handshake returned no local certs"); 504 } 505 } 506 }); 507 } 508 return socket; 509 } 510 }