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