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: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
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 }