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