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    /*
155     * Put defaults into holder class to avoid class preloading creating an
156     * instance of the classes referenced.
157     */
158    private static class NoPreloadHolder {
159        /**
160         * The factory using the default JVM settings for secure connections.
161         */
162        private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory();
163    }
164
165    /**
166     * Gets an singleton instance of the SSLProtocolSocketFactory.
167     * @return a SSLProtocolSocketFactory
168     */
169    public static SSLSocketFactory getSocketFactory() {
170        return NoPreloadHolder.DEFAULT_FACTORY;
171    }
172
173    private final SSLContext sslcontext;
174    private final javax.net.ssl.SSLSocketFactory socketfactory;
175    private final HostNameResolver nameResolver;
176    private X509HostnameVerifier hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
177
178    public SSLSocketFactory(
179        String algorithm,
180        final KeyStore keystore,
181        final String keystorePassword,
182        final KeyStore truststore,
183        final SecureRandom random,
184        final HostNameResolver nameResolver)
185        throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
186    {
187        super();
188        if (algorithm == null) {
189            algorithm = TLS;
190        }
191        KeyManager[] keymanagers = null;
192        if (keystore != null) {
193            keymanagers = createKeyManagers(keystore, keystorePassword);
194        }
195        TrustManager[] trustmanagers = null;
196        if (truststore != null) {
197            trustmanagers = createTrustManagers(truststore);
198        }
199        this.sslcontext = SSLContext.getInstance(algorithm);
200        this.sslcontext.init(keymanagers, trustmanagers, random);
201        this.socketfactory = this.sslcontext.getSocketFactory();
202        this.nameResolver = nameResolver;
203    }
204
205    public SSLSocketFactory(
206            final KeyStore keystore,
207            final String keystorePassword,
208            final KeyStore truststore)
209            throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
210    {
211        this(TLS, keystore, keystorePassword, truststore, null, null);
212    }
213
214    public SSLSocketFactory(final KeyStore keystore, final String keystorePassword)
215            throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
216    {
217        this(TLS, keystore, keystorePassword, null, null, null);
218    }
219
220    public SSLSocketFactory(final KeyStore truststore)
221            throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
222    {
223        this(TLS, null, null, truststore, null, null);
224    }
225
226    /**
227     * Constructs an HttpClient SSLSocketFactory backed by the given JSSE
228     * SSLSocketFactory.
229     *
230     * @hide
231     */
232    public SSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory) {
233        super();
234        this.sslcontext = null;
235        this.socketfactory = socketfactory;
236        this.nameResolver = null;
237    }
238
239    /**
240     * Creates the default SSL socket factory.
241     * This constructor is used exclusively to instantiate the factory for
242     * {@link #getSocketFactory getSocketFactory}.
243     */
244    private SSLSocketFactory() {
245        super();
246        this.sslcontext = null;
247        this.socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory();
248        this.nameResolver = null;
249    }
250
251    private static KeyManager[] createKeyManagers(final KeyStore keystore, final String password)
252        throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
253        if (keystore == null) {
254            throw new IllegalArgumentException("Keystore may not be null");
255        }
256        KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
257            KeyManagerFactory.getDefaultAlgorithm());
258        kmfactory.init(keystore, password != null ? password.toCharArray(): null);
259        return kmfactory.getKeyManagers();
260    }
261
262    private static TrustManager[] createTrustManagers(final KeyStore keystore)
263        throws KeyStoreException, NoSuchAlgorithmException {
264        if (keystore == null) {
265            throw new IllegalArgumentException("Keystore may not be null");
266        }
267        TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
268            TrustManagerFactory.getDefaultAlgorithm());
269        tmfactory.init(keystore);
270        return tmfactory.getTrustManagers();
271    }
272
273
274    // non-javadoc, see interface org.apache.http.conn.SocketFactory
275    public Socket createSocket()
276        throws IOException {
277
278        // the cast makes sure that the factory is working as expected
279        return (SSLSocket) this.socketfactory.createSocket();
280    }
281
282
283    // non-javadoc, see interface org.apache.http.conn.SocketFactory
284    public Socket connectSocket(
285        final Socket sock,
286        final String host,
287        final int port,
288        final InetAddress localAddress,
289        int localPort,
290        final HttpParams params
291    ) throws IOException {
292
293        if (host == null) {
294            throw new IllegalArgumentException("Target host may not be null.");
295        }
296        if (params == null) {
297            throw new IllegalArgumentException("Parameters may not be null.");
298        }
299
300        SSLSocket sslsock = (SSLSocket)
301            ((sock != null) ? sock : createSocket());
302
303        if ((localAddress != null) || (localPort > 0)) {
304
305            // we need to bind explicitly
306            if (localPort < 0)
307                localPort = 0; // indicates "any"
308
309            InetSocketAddress isa =
310                new InetSocketAddress(localAddress, localPort);
311            sslsock.bind(isa);
312        }
313
314        int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
315        int soTimeout = HttpConnectionParams.getSoTimeout(params);
316
317        InetSocketAddress remoteAddress;
318        if (this.nameResolver != null) {
319            remoteAddress = new InetSocketAddress(this.nameResolver.resolve(host), port);
320        } else {
321            remoteAddress = new InetSocketAddress(host, port);
322        }
323
324        sslsock.connect(remoteAddress, connTimeout);
325
326        sslsock.setSoTimeout(soTimeout);
327        try {
328            hostnameVerifier.verify(host, sslsock);
329            // verifyHostName() didn't blowup - good!
330        } catch (IOException iox) {
331            // close the socket before re-throwing the exception
332            try { sslsock.close(); } catch (Exception x) { /*ignore*/ }
333            throw iox;
334        }
335
336        return sslsock;
337    }
338
339
340    /**
341     * Checks whether a socket connection is secure.
342     * This factory creates TLS/SSL socket connections
343     * which, by default, are considered secure.
344     * <br/>
345     * Derived classes may override this method to perform
346     * runtime checks, for example based on the cypher suite.
347     *
348     * @param sock      the connected socket
349     *
350     * @return  <code>true</code>
351     *
352     * @throws IllegalArgumentException if the argument is invalid
353     */
354    public boolean isSecure(Socket sock)
355        throws IllegalArgumentException {
356
357        if (sock == null) {
358            throw new IllegalArgumentException("Socket may not be null.");
359        }
360        // This instanceof check is in line with createSocket() above.
361        if (!(sock instanceof SSLSocket)) {
362            throw new IllegalArgumentException
363                ("Socket not created by this factory.");
364        }
365        // This check is performed last since it calls the argument object.
366        if (sock.isClosed()) {
367            throw new IllegalArgumentException("Socket is closed.");
368        }
369
370        return true;
371
372    } // isSecure
373
374
375    // non-javadoc, see interface LayeredSocketFactory
376    public Socket createSocket(
377        final Socket socket,
378        final String host,
379        final int port,
380        final boolean autoClose
381    ) throws IOException, UnknownHostException {
382        SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
383              socket,
384              host,
385              port,
386              autoClose
387        );
388        hostnameVerifier.verify(host, sslSocket);
389        // verifyHostName() didn't blowup - good!
390        return sslSocket;
391    }
392
393    public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) {
394        if ( hostnameVerifier == null ) {
395            throw new IllegalArgumentException("Hostname verifier may not be null");
396        }
397        this.hostnameVerifier = hostnameVerifier;
398    }
399
400    public X509HostnameVerifier getHostnameVerifier() {
401        return hostnameVerifier;
402    }
403
404}
405