001    /*
002     * ====================================================================
003     *
004     *  Licensed to the Apache Software Foundation (ASF) under one or more
005     *  contributor license agreements.  See the NOTICE file distributed with
006     *  this work for additional information regarding copyright ownership.
007     *  The ASF licenses this file to You under the Apache License, Version 2.0
008     *  (the "License"); you may not use this file except in compliance with
009     *  the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     *  Unless required by applicable law or agreed to in writing, software
014     *  distributed under the License is distributed on an "AS IS" BASIS,
015     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     *  See the License for the specific language governing permissions and
017     *  limitations under the License.
018     * ====================================================================
019     *
020     * This software consists of voluntary contributions made by many
021     * individuals on behalf of the Apache Software Foundation.  For more
022     * information on the Apache Software Foundation, please see
023     * <http://www.apache.org/>.
024     *
025     */
026    
027    package org.apache.commons.httpclient.contrib.ssl;
028    
029    import java.io.IOException;
030    import java.net.InetAddress;
031    import java.net.InetSocketAddress;
032    import java.net.Socket;
033    import java.net.SocketAddress;
034    import java.net.UnknownHostException;
035    
036    import javax.net.SocketFactory;
037    import javax.net.ssl.SSLContext;
038    import javax.net.ssl.TrustManager;
039    
040    import org.apache.commons.httpclient.ConnectTimeoutException;
041    import org.apache.commons.httpclient.HttpClientError;
042    import org.apache.commons.httpclient.params.HttpConnectionParams;
043    import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
044    import org.apache.commons.logging.Log;
045    import org.apache.commons.logging.LogFactory;
046    
047    /**
048     * <p>
049     * EasySSLProtocolSocketFactory can be used to creats SSL {@link Socket}s 
050     * that accept self-signed certificates. 
051     * </p>
052     * <p>
053     * This socket factory SHOULD NOT be used for productive systems 
054     * due to security reasons, unless it is a concious decision and 
055     * you are perfectly aware of security implications of accepting 
056     * self-signed certificates
057     * </p>
058     *
059     * <p>
060     * Example of using custom protocol socket factory for a specific host:
061     *     <pre>
062     *     Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
063     *
064     *     URI uri = new URI("https://localhost/", true);
065     *     // use relative url only
066     *     GetMethod httpget = new GetMethod(uri.getPathQuery());
067     *     HostConfiguration hc = new HostConfiguration();
068     *     hc.setHost(uri.getHost(), uri.getPort(), easyhttps);
069     *     HttpClient client = new HttpClient();
070     *     client.executeMethod(hc, httpget);
071     *     </pre>
072     * </p>
073     * <p>
074     * Example of using custom protocol socket factory per default instead of the standard one:
075     *     <pre>
076     *     Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
077     *     Protocol.registerProtocol("https", easyhttps);
078     *
079     *     HttpClient client = new HttpClient();
080     *     GetMethod httpget = new GetMethod("https://localhost/");
081     *     client.executeMethod(httpget);
082     *     </pre>
083     * </p>
084     * 
085     * @author <a href="mailto:oleg -at- ural.ru">Oleg Kalnichevski</a>
086     * 
087     * <p>
088     * DISCLAIMER: HttpClient developers DO NOT actively support this component.
089     * The component is provided as a reference material, which may be inappropriate
090     * for use without additional customization.
091     * </p>
092     */
093    
094    public class EasySSLProtocolSocketFactory implements SecureProtocolSocketFactory {
095    
096        /** Log object for this class. */
097        private static final Log LOG = LogFactory.getLog(EasySSLProtocolSocketFactory.class);
098    
099        private SSLContext sslcontext = null;
100    
101        /**
102         * Constructor for EasySSLProtocolSocketFactory.
103         */
104        public EasySSLProtocolSocketFactory() {
105            super();
106        }
107    
108        private static SSLContext createEasySSLContext() {
109            try {
110                SSLContext context = SSLContext.getInstance("SSL");
111                context.init(
112                  null, 
113                  new TrustManager[] {new EasyX509TrustManager(null)}, 
114                  null);
115                return context;
116            } catch (Exception e) {
117                LOG.error(e.getMessage(), e);
118                throw new HttpClientError(e.toString());
119            }
120        }
121    
122        private SSLContext getSSLContext() {
123            if (this.sslcontext == null) {
124                this.sslcontext = createEasySSLContext();
125            }
126            return this.sslcontext;
127        }
128    
129        /**
130         * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
131         */
132        public Socket createSocket(
133            String host,
134            int port,
135            InetAddress clientHost,
136            int clientPort)
137            throws IOException, UnknownHostException {
138    
139            return getSSLContext().getSocketFactory().createSocket(
140                host,
141                port,
142                clientHost,
143                clientPort
144            );
145        }
146    
147        /**
148         * Attempts to get a new socket connection to the given host within the given time limit.
149         * <p>
150         * To circumvent the limitations of older JREs that do not support connect timeout a 
151         * controller thread is executed. The controller thread attempts to create a new socket 
152         * within the given limit of time. If socket constructor does not return until the 
153         * timeout expires, the controller terminates and throws an {@link ConnectTimeoutException}
154         * </p>
155         *  
156         * @param host the host name/IP
157         * @param port the port on the host
158         * @param clientHost the local host name/IP to bind the socket to
159         * @param clientPort the port on the local machine
160         * @param params {@link HttpConnectionParams Http connection parameters}
161         * 
162         * @return Socket a new socket
163         * 
164         * @throws IOException if an I/O error occurs while creating the socket
165         * @throws UnknownHostException if the IP address of the host cannot be
166         * determined
167         */
168        public Socket createSocket(
169            final String host,
170            final int port,
171            final InetAddress localAddress,
172            final int localPort,
173            final HttpConnectionParams params
174        ) throws IOException, UnknownHostException, ConnectTimeoutException {
175            if (params == null) {
176                throw new IllegalArgumentException("Parameters may not be null");
177            }
178            int timeout = params.getConnectionTimeout();
179            SocketFactory socketfactory = getSSLContext().getSocketFactory();
180            if (timeout == 0) {
181                return socketfactory.createSocket(host, port, localAddress, localPort);
182            } else {
183                Socket socket = socketfactory.createSocket();
184                SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
185                SocketAddress remoteaddr = new InetSocketAddress(host, port);
186                socket.bind(localaddr);
187                socket.connect(remoteaddr, timeout);
188                return socket;
189            }
190        }
191    
192        /**
193         * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
194         */
195        public Socket createSocket(String host, int port)
196            throws IOException, UnknownHostException {
197            return getSSLContext().getSocketFactory().createSocket(
198                host,
199                port
200            );
201        }
202    
203        /**
204         * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
205         */
206        public Socket createSocket(
207            Socket socket,
208            String host,
209            int port,
210            boolean autoClose)
211            throws IOException, UnknownHostException {
212            return getSSLContext().getSocketFactory().createSocket(
213                socket,
214                host,
215                port,
216                autoClose
217            );
218        }
219    
220        public boolean equals(Object obj) {
221            return ((obj != null) && obj.getClass().equals(EasySSLProtocolSocketFactory.class));
222        }
223    
224        public int hashCode() {
225            return EasySSLProtocolSocketFactory.class.hashCode();
226        }
227    
228    }