View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.conn.ssl;
29  
30  import java.io.IOException;
31  import java.net.InetAddress;
32  import java.net.InetSocketAddress;
33  import java.net.Socket;
34  import java.net.SocketTimeoutException;
35  import java.net.UnknownHostException;
36  import java.security.KeyManagementException;
37  import java.security.KeyStore;
38  import java.security.KeyStoreException;
39  import java.security.NoSuchAlgorithmException;
40  import java.security.SecureRandom;
41  import java.security.UnrecoverableKeyException;
42  
43  import javax.net.ssl.KeyManager;
44  import javax.net.ssl.KeyManagerFactory;
45  import javax.net.ssl.SSLContext;
46  import javax.net.ssl.SSLSocket;
47  import javax.net.ssl.TrustManager;
48  import javax.net.ssl.TrustManagerFactory;
49  import javax.net.ssl.X509TrustManager;
50  
51  import org.apache.http.HttpHost;
52  import org.apache.http.annotation.ThreadSafe;
53  import org.apache.http.conn.ConnectTimeoutException;
54  import org.apache.http.conn.HttpInetSocketAddress;
55  import org.apache.http.conn.scheme.HostNameResolver;
56  import org.apache.http.conn.scheme.LayeredSchemeSocketFactory;
57  import org.apache.http.conn.scheme.LayeredSocketFactory;
58  import org.apache.http.conn.scheme.SchemeLayeredSocketFactory;
59  import org.apache.http.params.HttpConnectionParams;
60  import org.apache.http.params.HttpParams;
61  
62  /**
63   * Layered socket factory for TLS/SSL connections.
64   * <p>
65   * SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of
66   * trusted certificates and to authenticate to the HTTPS server using a private key.
67   * <p>
68   * SSLSocketFactory will enable server authentication when supplied with
69   * a {@link KeyStore trust-store} file containing one or several trusted certificates. The client
70   * secure socket will reject the connection during the SSL session handshake if the target HTTPS
71   * server attempts to authenticate itself with a non-trusted certificate.
72   * <p>
73   * Use JDK keytool utility to import a trusted certificate and generate a trust-store file:
74   *    <pre>
75   *     keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
76   *    </pre>
77   * <p>
78   * In special cases the standard trust verification process can be bypassed by using a custom
79   * {@link TrustStrategy}. This interface is primarily intended for allowing self-signed
80   * certificates to be accepted as trusted without having to add them to the trust-store file.
81   * <p>
82   * The following parameters can be used to customize the behavior of this
83   * class:
84   * <ul>
85   *  <li>{@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
86   *  <li>{@link org.apache.http.params.CoreConnectionPNames#SO_TIMEOUT}</li>
87   *  <li>{@link org.apache.http.params.CoreConnectionPNames#SO_REUSEADDR}</li>
88   * </ul>
89   * <p>
90   * SSLSocketFactory will enable client authentication when supplied with
91   * a {@link KeyStore key-store} file containing a private key/public certificate
92   * pair. The client secure socket will use the private key to authenticate
93   * itself to the target HTTPS server during the SSL session handshake if
94   * requested to do so by the server.
95   * The target HTTPS server will in its turn verify the certificate presented
96   * by the client in order to establish client's authenticity
97   * <p>
98   * Use the following sequence of actions to generate a key-store file
99   * </p>
100  *   <ul>
101  *     <li>
102  *      <p>
103  *      Use JDK keytool utility to generate a new key
104  *      <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
105  *      For simplicity use the same password for the key as that of the key-store
106  *      </p>
107  *     </li>
108  *     <li>
109  *      <p>
110  *      Issue a certificate signing request (CSR)
111  *      <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
112  *     </p>
113  *     </li>
114  *     <li>
115  *      <p>
116  *      Send the certificate request to the trusted Certificate Authority for signature.
117  *      One may choose to act as her own CA and sign the certificate request using a PKI
118  *      tool, such as OpenSSL.
119  *      </p>
120  *     </li>
121  *     <li>
122  *      <p>
123  *       Import the trusted CA root certificate
124  *       <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
125  *      </p>
126  *     </li>
127  *     <li>
128  *      <p>
129  *       Import the PKCS#7 file containg the complete certificate chain
130  *       <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
131  *      </p>
132  *     </li>
133  *     <li>
134  *      <p>
135  *       Verify the content the resultant keystore file
136  *       <pre>keytool -list -v -keystore my.keystore</pre>
137  *      </p>
138  *     </li>
139  *   </ul>
140  *
141  * @since 4.0
142  */
143 @SuppressWarnings("deprecation")
144 @ThreadSafe
145 public class SSLSocketFactory implements SchemeLayeredSocketFactory,
146                                          LayeredSchemeSocketFactory, LayeredSocketFactory {
147 
148     public static final String TLS   = "TLS";
149     public static final String SSL   = "SSL";
150     public static final String SSLV2 = "SSLv2";
151 
152     public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER
153         = new AllowAllHostnameVerifier();
154 
155     public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
156         = new BrowserCompatHostnameVerifier();
157 
158     public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
159         = new StrictHostnameVerifier();
160 
161     /**
162      * Obtains default SSL socket factory with an SSL context based on the standard JSSE
163      * trust material (<code>cacerts</code> file in the security properties directory).
164      * System properties are not taken into consideration.
165      *
166      * @return default SSL socket factory
167      */
168     public static SSLSocketFactory getSocketFactory() throws SSLInitializationException {
169         SSLContext sslcontext;
170         try {
171             sslcontext = SSLContext.getInstance("TLS");
172             sslcontext.init(null, null, null);
173             return new SSLSocketFactory(
174                 sslcontext,
175                 BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
176         } catch (NoSuchAlgorithmException ex) {
177             throw new SSLInitializationException(ex.getMessage(), ex);
178         } catch (KeyManagementException ex) {
179             throw new SSLInitializationException(ex.getMessage(), ex);
180         }
181     }
182 
183     /**
184      * Obtains default SSL socket factory with an SSL context based on system properties
185      * as described in
186      * <a href="http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html">
187      * "JavaTM Secure Socket Extension (JSSE) Reference Guide for the JavaTM 2 Platform
188      * Standard Edition 5</a>
189      *
190      * @return default system SSL socket factory
191      */
192     public static SSLSocketFactory getSystemSocketFactory() throws SSLInitializationException {
193         return new SSLSocketFactory(
194             (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(),
195             BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
196     }
197 
198     private final javax.net.ssl.SSLSocketFactory socketfactory;
199     private final HostNameResolver nameResolver;
200     // TODO: make final
201     private volatile X509HostnameVerifier hostnameVerifier;
202 
203     private static SSLContext createSSLContext(
204             String algorithm,
205             final KeyStore keystore,
206             final String keystorePassword,
207             final KeyStore truststore,
208             final SecureRandom random,
209             final TrustStrategy trustStrategy)
210                 throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {
211         if (algorithm == null) {
212             algorithm = TLS;
213         }
214         KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
215                 KeyManagerFactory.getDefaultAlgorithm());
216         kmfactory.init(keystore, keystorePassword != null ? keystorePassword.toCharArray(): null);
217         KeyManager[] keymanagers =  kmfactory.getKeyManagers();
218         TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
219                 TrustManagerFactory.getDefaultAlgorithm());
220         tmfactory.init(truststore);
221         TrustManager[] trustmanagers = tmfactory.getTrustManagers();
222         if (trustmanagers != null && trustStrategy != null) {
223             for (int i = 0; i < trustmanagers.length; i++) {
224                 TrustManager tm = trustmanagers[i];
225                 if (tm instanceof X509TrustManager) {
226                     trustmanagers[i] = new TrustManagerDecorator(
227                             (X509TrustManager) tm, trustStrategy);
228                 }
229             }
230         }
231 
232         SSLContext sslcontext = SSLContext.getInstance(algorithm);
233         sslcontext.init(keymanagers, trustmanagers, random);
234         return sslcontext;
235     }
236     
237     /**
238      * @deprecated Use {@link #SSLSocketFactory(String, KeyStore, String, KeyStore, SecureRandom, X509HostnameVerifier)}
239      */
240     @Deprecated
241     public SSLSocketFactory(
242             final String algorithm,
243             final KeyStore keystore,
244             final String keystorePassword,
245             final KeyStore truststore,
246             final SecureRandom random,
247             final HostNameResolver nameResolver)
248                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
249         this(createSSLContext(
250                 algorithm, keystore, keystorePassword, truststore, random, null),
251                 nameResolver);
252     }
253 
254     /**
255      * @since 4.1
256      */
257     public SSLSocketFactory(
258             String algorithm,
259             final KeyStore keystore,
260             final String keystorePassword,
261             final KeyStore truststore,
262             final SecureRandom random,
263             final X509HostnameVerifier hostnameVerifier)
264                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
265         this(createSSLContext(
266                 algorithm, keystore, keystorePassword, truststore, random, null),
267                 hostnameVerifier);
268     }
269 
270     /**
271      * @since 4.1
272      */
273     public SSLSocketFactory(
274             String algorithm,
275             final KeyStore keystore,
276             final String keystorePassword,
277             final KeyStore truststore,
278             final SecureRandom random,
279             final TrustStrategy trustStrategy,
280             final X509HostnameVerifier hostnameVerifier)
281                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
282         this(createSSLContext(
283                 algorithm, keystore, keystorePassword, truststore, random, trustStrategy),
284                 hostnameVerifier);
285     }
286 
287     public SSLSocketFactory(
288             final KeyStore keystore,
289             final String keystorePassword,
290             final KeyStore truststore)
291                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
292         this(TLS, keystore, keystorePassword, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
293     }
294 
295     public SSLSocketFactory(
296             final KeyStore keystore,
297             final String keystorePassword)
298                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException{
299         this(TLS, keystore, keystorePassword, null, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
300     }
301 
302     public SSLSocketFactory(
303             final KeyStore truststore)
304                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
305         this(TLS, null, null, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
306     }
307 
308     /**
309      * @since 4.1
310      */
311     public SSLSocketFactory(
312             final TrustStrategy trustStrategy,
313             final X509HostnameVerifier hostnameVerifier)
314                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
315         this(TLS, null, null, null, null, trustStrategy, hostnameVerifier);
316     }
317 
318     /**
319      * @since 4.1
320      */
321     public SSLSocketFactory(
322             final TrustStrategy trustStrategy)
323                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
324         this(TLS, null, null, null, null, trustStrategy, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
325     }
326 
327     public SSLSocketFactory(final SSLContext sslContext) {
328         this(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
329     }
330 
331     /**
332      * @deprecated Use {@link #SSLSocketFactory(SSLContext)}
333      */
334     @Deprecated
335     public SSLSocketFactory(
336             final SSLContext sslContext, final HostNameResolver nameResolver) {
337         super();
338         this.socketfactory = sslContext.getSocketFactory();
339         this.hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
340         this.nameResolver = nameResolver;
341     }
342 
343     /**
344      * @since 4.1
345      */
346     public SSLSocketFactory(
347             final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) {
348         super();
349         if (sslContext == null) {
350             throw new IllegalArgumentException("SSL context may not be null");
351         }
352         this.socketfactory = sslContext.getSocketFactory();
353         this.hostnameVerifier = hostnameVerifier;
354         this.nameResolver = null;
355     }
356 
357     /**
358      * @since 4.2
359      */
360     public SSLSocketFactory(
361             final javax.net.ssl.SSLSocketFactory socketfactory, 
362             final X509HostnameVerifier hostnameVerifier) {
363         if (socketfactory == null) {
364             throw new IllegalArgumentException("SSL socket factory may not be null");
365         }
366         this.socketfactory = socketfactory;
367         this.hostnameVerifier = hostnameVerifier;
368         this.nameResolver = null;
369     }
370 
371     /**
372      * @param params Optional parameters. Parameters passed to this method will have no effect.
373      *               This method will create a unconnected instance of {@link Socket} class.
374      * @since 4.1
375      */
376     public Socket createSocket(final HttpParams params) throws IOException {
377         SSLSocket sock = (SSLSocket) this.socketfactory.createSocket();
378         prepareSocket(sock);
379         return sock;
380     }
381 
382     @Deprecated
383     public Socket createSocket() throws IOException {
384         SSLSocket sock = (SSLSocket) this.socketfactory.createSocket();
385         prepareSocket(sock);
386         return sock;
387     }
388 
389     /**
390      * @since 4.1
391      */
392     public Socket connectSocket(
393             final Socket socket,
394             final InetSocketAddress remoteAddress,
395             final InetSocketAddress localAddress,
396             final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
397         if (remoteAddress == null) {
398             throw new IllegalArgumentException("Remote address may not be null");
399         }
400         if (params == null) {
401             throw new IllegalArgumentException("HTTP parameters may not be null");
402         }
403         Socket sock = socket != null ? socket : this.socketfactory.createSocket();
404         if (localAddress != null) {
405             sock.setReuseAddress(HttpConnectionParams.getSoReuseaddr(params));
406             sock.bind(localAddress);
407         }
408 
409         int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
410         int soTimeout = HttpConnectionParams.getSoTimeout(params);
411 
412         try {
413             sock.setSoTimeout(soTimeout);
414             sock.connect(remoteAddress, connTimeout);
415         } catch (SocketTimeoutException ex) {
416             throw new ConnectTimeoutException("Connect to " + remoteAddress + " timed out");
417         }
418 
419         String hostname;
420         if (remoteAddress instanceof HttpInetSocketAddress) {
421             hostname = ((HttpInetSocketAddress) remoteAddress).getHttpHost().getHostName();
422         } else {
423             hostname = remoteAddress.getHostName();
424         }
425 
426         SSLSocket sslsock;
427         // Setup SSL layering if necessary
428         if (sock instanceof SSLSocket) {
429             sslsock = (SSLSocket) sock;
430         } else {
431             int port = remoteAddress.getPort();
432             sslsock = (SSLSocket) this.socketfactory.createSocket(sock, hostname, port, true);
433             prepareSocket(sslsock);
434         }
435         if (this.hostnameVerifier != null) {
436             try {
437                 this.hostnameVerifier.verify(hostname, sslsock);
438                 // verifyHostName() didn't blowup - good!
439             } catch (IOException iox) {
440                 // close the socket before re-throwing the exception
441                 try { sslsock.close(); } catch (Exception x) { /*ignore*/ }
442                 throw iox;
443             }
444         }
445         return sslsock;
446     }
447 
448 
449     /**
450      * Checks whether a socket connection is secure.
451      * This factory creates TLS/SSL socket connections
452      * which, by default, are considered secure.
453      * <br/>
454      * Derived classes may override this method to perform
455      * runtime checks, for example based on the cypher suite.
456      *
457      * @param sock      the connected socket
458      *
459      * @return  <code>true</code>
460      *
461      * @throws IllegalArgumentException if the argument is invalid
462      */
463     public boolean isSecure(final Socket sock) throws IllegalArgumentException {
464         if (sock == null) {
465             throw new IllegalArgumentException("Socket may not be null");
466         }
467         // This instanceof check is in line with createSocket() above.
468         if (!(sock instanceof SSLSocket)) {
469             throw new IllegalArgumentException("Socket not created by this factory");
470         }
471         // This check is performed last since it calls the argument object.
472         if (sock.isClosed()) {
473             throw new IllegalArgumentException("Socket is closed");
474         }
475         return true;
476     }
477 
478     /**
479      * @since 4.2
480      */
481     public Socket createLayeredSocket(
482         final Socket socket,
483         final String host,
484         final int port,
485         final HttpParams params) throws IOException, UnknownHostException {
486         SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
487               socket,
488               host,
489               port,
490               true);
491         prepareSocket(sslSocket);
492         if (this.hostnameVerifier != null) {
493             this.hostnameVerifier.verify(host, sslSocket);
494         }
495         // verifyHostName() didn't blowup - good!
496         return sslSocket;
497     }
498 
499     /**
500      * @deprecated use {@link #createLayeredSocket(Socket, String, int, HttpParams)}
501      */
502     public Socket createLayeredSocket(
503         final Socket socket,
504         final String host,
505         final int port,
506         final boolean autoClose) throws IOException, UnknownHostException {
507         SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
508               socket,
509               host,
510               port,
511               autoClose
512         );
513         prepareSocket(sslSocket);
514         if (this.hostnameVerifier != null) {
515             this.hostnameVerifier.verify(host, sslSocket);
516         }
517         // verifyHostName() didn't blowup - good!
518         return sslSocket;
519     }
520 
521     @Deprecated
522     public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) {
523         if ( hostnameVerifier == null ) {
524             throw new IllegalArgumentException("Hostname verifier may not be null");
525         }
526         this.hostnameVerifier = hostnameVerifier;
527     }
528 
529     public X509HostnameVerifier getHostnameVerifier() {
530         return this.hostnameVerifier;
531     }
532 
533     /**
534      * @deprecated Use {@link #connectSocket(Socket, InetSocketAddress, InetSocketAddress, HttpParams)}
535      */
536     @Deprecated
537     public Socket connectSocket(
538             final Socket socket,
539             final String host, int port,
540             final InetAddress localAddress, int localPort,
541             final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
542         InetSocketAddress local = null;
543         if (localAddress != null || localPort > 0) {
544             // we need to bind explicitly
545             if (localPort < 0) {
546                 localPort = 0; // indicates "any"
547             }
548             local = new InetSocketAddress(localAddress, localPort);
549         }
550         InetAddress remoteAddress;
551         if (this.nameResolver != null) {
552             remoteAddress = this.nameResolver.resolve(host);
553         } else {
554             remoteAddress = InetAddress.getByName(host);
555         }
556         InetSocketAddress remote = new HttpInetSocketAddress(new HttpHost(host, port), remoteAddress, port);
557         return connectSocket(socket, remote, local, params);
558     }
559 
560     /**
561      * @deprecated Use {@link #createLayeredSocket(Socket, String, int, boolean)}
562      */
563     @Deprecated
564     public Socket createSocket(
565             final Socket socket,
566             final String host, int port,
567             boolean autoClose) throws IOException, UnknownHostException {
568         return createLayeredSocket(socket, host, port, autoClose);
569     }
570 
571     /**
572      * Performs any custom initialization for a newly created SSLSocket
573      * (before the SSL handshake happens).
574      *
575      * The default implementation is a no-op, but could be overriden to, e.g.,
576      * call {@link SSLSocket#setEnabledCipherSuites(java.lang.String[])}.
577      *
578      * @since 4.2
579      */
580     protected void prepareSocket(final SSLSocket socket) throws IOException {
581     }
582 }