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