1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java $
3 * $Revision: 659194 $
4 * $Date: 2008-05-22 11:33:47 -0700 (Thu, 22 May 2008) $
5 *
6 * ====================================================================
7 * Licensed to the Apache Software Foundation (ASF) under one
8 * or more contributor license agreements.  See the NOTICE file
9 * distributed with this work for additional information
10 * regarding copyright ownership.  The ASF licenses this file
11 * to you under the Apache License, Version 2.0 (the
12 * "License"); you may not use this file except in compliance
13 * with the License.  You may obtain a copy of the License at
14 *
15 *   http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing,
18 * software distributed under the License is distributed on an
19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 * KIND, either express or implied.  See the License for the
21 * specific language governing permissions and limitations
22 * under the License.
23 * ====================================================================
24 *
25 * This software consists of voluntary contributions made by many
26 * individuals on behalf of the Apache Software Foundation.  For more
27 * information on the Apache Software Foundation, please see
28 * <http://www.apache.org/>.
29 *
30 */
31
32package org.apache.http.conn.ssl;
33
34import org.apache.http.conn.scheme.HostNameResolver;
35import org.apache.http.conn.scheme.LayeredSocketFactory;
36import org.apache.http.params.HttpConnectionParams;
37import org.apache.http.params.HttpParams;
38
39import javax.net.ssl.HttpsURLConnection;
40import javax.net.ssl.KeyManager;
41import javax.net.ssl.KeyManagerFactory;
42import javax.net.ssl.SSLContext;
43import javax.net.ssl.SSLSocket;
44import javax.net.ssl.TrustManager;
45import javax.net.ssl.TrustManagerFactory;
46import java.io.IOException;
47import java.net.InetAddress;
48import java.net.InetSocketAddress;
49import java.net.Socket;
50import java.net.UnknownHostException;
51import java.security.KeyManagementException;
52import java.security.KeyStore;
53import java.security.KeyStoreException;
54import java.security.NoSuchAlgorithmException;
55import java.security.SecureRandom;
56import java.security.UnrecoverableKeyException;
57
58/**
59 * Layered socket factory for TLS/SSL connections, based on JSSE.
60 *.
61 * <p>
62 * SSLSocketFactory can be used to validate the identity of the HTTPS
63 * server against a list of trusted certificates and to authenticate to
64 * the HTTPS server using a private key.
65 * </p>
66 *
67 * <p>
68 * SSLSocketFactory will enable server authentication when supplied with
69 * a {@link KeyStore truststore} file containg one or several trusted
70 * certificates. The client secure socket will reject the connection during
71 * the SSL session handshake if the target HTTPS server attempts to
72 * authenticate itself with a non-trusted certificate.
73 * </p>
74 *
75 * <p>
76 * Use JDK keytool utility to import a trusted certificate and generate a truststore file:
77 *    <pre>
78 *     keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
79 *    </pre>
80 * </p>
81 *
82 * <p>
83 * SSLSocketFactory will enable client authentication when supplied with
84 * a {@link KeyStore keystore} file containg a private key/public certificate
85 * pair. The client secure socket will use the private key to authenticate
86 * itself to the target HTTPS server during the SSL session handshake if
87 * requested to do so by the server.
88 * The target HTTPS server will in its turn verify the certificate presented
89 * by the client in order to establish client's authenticity
90 * </p>
91 *
92 * <p>
93 * Use the following sequence of actions to generate a keystore file
94 * </p>
95 *   <ul>
96 *     <li>
97 *      <p>
98 *      Use JDK keytool utility to generate a new key
99 *      <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
100 *      For simplicity use the same password for the key as that of the keystore
101 *      </p>
102 *     </li>
103 *     <li>
104 *      <p>
105 *      Issue a certificate signing request (CSR)
106 *      <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
107 *     </p>
108 *     </li>
109 *     <li>
110 *      <p>
111 *      Send the certificate request to the trusted Certificate Authority for signature.
112 *      One may choose to act as her own CA and sign the certificate request using a PKI
113 *      tool, such as OpenSSL.
114 *      </p>
115 *     </li>
116 *     <li>
117 *      <p>
118 *       Import the trusted CA root certificate
119 *       <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
120 *      </p>
121 *     </li>
122 *     <li>
123 *      <p>
124 *       Import the PKCS#7 file containg the complete certificate chain
125 *       <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
126 *      </p>
127 *     </li>
128 *     <li>
129 *      <p>
130 *       Verify the content the resultant keystore file
131 *       <pre>keytool -list -v -keystore my.keystore</pre>
132 *      </p>
133 *     </li>
134 *   </ul>
135 * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
136 * @author Julius Davies
137 */
138
139public class SSLSocketFactory implements LayeredSocketFactory {
140
141    public static final String TLS   = "TLS";
142    public static final String SSL   = "SSL";
143    public static final String SSLV2 = "SSLv2";
144
145    public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER
146        = new AllowAllHostnameVerifier();
147
148    public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
149        = new BrowserCompatHostnameVerifier();
150
151    public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
152        = new StrictHostnameVerifier();
153    /**
154     * The factory using the default JVM settings for secure connections.
155     */
156    private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory();
157
158    /**
159     * Gets an singleton instance of the SSLProtocolSocketFactory.
160     * @return a SSLProtocolSocketFactory
161     */
162    public static SSLSocketFactory getSocketFactory() {
163        return DEFAULT_FACTORY;
164    }
165
166    private final SSLContext sslcontext;
167    private final javax.net.ssl.SSLSocketFactory socketfactory;
168    private final HostNameResolver nameResolver;
169    private X509HostnameVerifier hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
170
171    public SSLSocketFactory(
172        String algorithm,
173        final KeyStore keystore,
174        final String keystorePassword,
175        final KeyStore truststore,
176        final SecureRandom random,
177        final HostNameResolver nameResolver)
178        throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
179    {
180        super();
181        if (algorithm == null) {
182            algorithm = TLS;
183        }
184        KeyManager[] keymanagers = null;
185        if (keystore != null) {
186            keymanagers = createKeyManagers(keystore, keystorePassword);
187        }
188        TrustManager[] trustmanagers = null;
189        if (truststore != null) {
190            trustmanagers = createTrustManagers(truststore);
191        }
192        this.sslcontext = SSLContext.getInstance(algorithm);
193        this.sslcontext.init(keymanagers, trustmanagers, random);
194        this.socketfactory = this.sslcontext.getSocketFactory();
195        this.nameResolver = nameResolver;
196    }
197
198    public SSLSocketFactory(
199            final KeyStore keystore,
200            final String keystorePassword,
201            final KeyStore truststore)
202            throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
203    {
204        this(TLS, keystore, keystorePassword, truststore, null, null);
205    }
206
207    public SSLSocketFactory(final KeyStore keystore, final String keystorePassword)
208            throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
209    {
210        this(TLS, keystore, keystorePassword, null, null, null);
211    }
212
213    public SSLSocketFactory(final KeyStore truststore)
214            throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
215    {
216        this(TLS, null, null, truststore, null, null);
217    }
218
219    /**
220     * Constructs an HttpClient SSLSocketFactory backed by the given JSSE
221     * SSLSocketFactory.
222     *
223     * @hide
224     */
225    public SSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory) {
226        super();
227        this.sslcontext = null;
228        this.socketfactory = socketfactory;
229        this.nameResolver = null;
230    }
231
232    /**
233     * Creates the default SSL socket factory.
234     * This constructor is used exclusively to instantiate the factory for
235     * {@link #getSocketFactory getSocketFactory}.
236     */
237    private SSLSocketFactory() {
238        super();
239        this.sslcontext = null;
240        this.socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory();
241        this.nameResolver = null;
242    }
243
244    private static KeyManager[] createKeyManagers(final KeyStore keystore, final String password)
245        throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
246        if (keystore == null) {
247            throw new IllegalArgumentException("Keystore may not be null");
248        }
249        KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
250            KeyManagerFactory.getDefaultAlgorithm());
251        kmfactory.init(keystore, password != null ? password.toCharArray(): null);
252        return kmfactory.getKeyManagers();
253    }
254
255    private static TrustManager[] createTrustManagers(final KeyStore keystore)
256        throws KeyStoreException, NoSuchAlgorithmException {
257        if (keystore == null) {
258            throw new IllegalArgumentException("Keystore may not be null");
259        }
260        TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
261            TrustManagerFactory.getDefaultAlgorithm());
262        tmfactory.init(keystore);
263        return tmfactory.getTrustManagers();
264    }
265
266
267    // non-javadoc, see interface org.apache.http.conn.SocketFactory
268    public Socket createSocket()
269        throws IOException {
270
271        // the cast makes sure that the factory is working as expected
272        return (SSLSocket) this.socketfactory.createSocket();
273    }
274
275
276    // non-javadoc, see interface org.apache.http.conn.SocketFactory
277    public Socket connectSocket(
278        final Socket sock,
279        final String host,
280        final int port,
281        final InetAddress localAddress,
282        int localPort,
283        final HttpParams params
284    ) throws IOException {
285
286        if (host == null) {
287            throw new IllegalArgumentException("Target host may not be null.");
288        }
289        if (params == null) {
290            throw new IllegalArgumentException("Parameters may not be null.");
291        }
292
293        SSLSocket sslsock = (SSLSocket)
294            ((sock != null) ? sock : createSocket());
295
296        if ((localAddress != null) || (localPort > 0)) {
297
298            // we need to bind explicitly
299            if (localPort < 0)
300                localPort = 0; // indicates "any"
301
302            InetSocketAddress isa =
303                new InetSocketAddress(localAddress, localPort);
304            sslsock.bind(isa);
305        }
306
307        int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
308        int soTimeout = HttpConnectionParams.getSoTimeout(params);
309
310        InetSocketAddress remoteAddress;
311        if (this.nameResolver != null) {
312            remoteAddress = new InetSocketAddress(this.nameResolver.resolve(host), port);
313        } else {
314            remoteAddress = new InetSocketAddress(host, port);
315        }
316
317        sslsock.connect(remoteAddress, connTimeout);
318
319        sslsock.setSoTimeout(soTimeout);
320        try {
321            hostnameVerifier.verify(host, sslsock);
322            // verifyHostName() didn't blowup - good!
323        } catch (IOException iox) {
324            // close the socket before re-throwing the exception
325            try { sslsock.close(); } catch (Exception x) { /*ignore*/ }
326            throw iox;
327        }
328
329        return sslsock;
330    }
331
332
333    /**
334     * Checks whether a socket connection is secure.
335     * This factory creates TLS/SSL socket connections
336     * which, by default, are considered secure.
337     * <br/>
338     * Derived classes may override this method to perform
339     * runtime checks, for example based on the cypher suite.
340     *
341     * @param sock      the connected socket
342     *
343     * @return  <code>true</code>
344     *
345     * @throws IllegalArgumentException if the argument is invalid
346     */
347    public boolean isSecure(Socket sock)
348        throws IllegalArgumentException {
349
350        if (sock == null) {
351            throw new IllegalArgumentException("Socket may not be null.");
352        }
353        // This instanceof check is in line with createSocket() above.
354        if (!(sock instanceof SSLSocket)) {
355            throw new IllegalArgumentException
356                ("Socket not created by this factory.");
357        }
358        // This check is performed last since it calls the argument object.
359        if (sock.isClosed()) {
360            throw new IllegalArgumentException("Socket is closed.");
361        }
362
363        return true;
364
365    } // isSecure
366
367
368    // non-javadoc, see interface LayeredSocketFactory
369    public Socket createSocket(
370        final Socket socket,
371        final String host,
372        final int port,
373        final boolean autoClose
374    ) throws IOException, UnknownHostException {
375        SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
376              socket,
377              host,
378              port,
379              autoClose
380        );
381        hostnameVerifier.verify(host, sslSocket);
382        // verifyHostName() didn't blowup - good!
383        return sslSocket;
384    }
385
386    public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) {
387        if ( hostnameVerifier == null ) {
388            throw new IllegalArgumentException("Hostname verifier may not be null");
389        }
390        this.hostnameVerifier = hostnameVerifier;
391    }
392
393    public X509HostnameVerifier getHostnameVerifier() {
394        return hostnameVerifier;
395    }
396
397}
398