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    }