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.UnknownHostException;
35  import java.security.KeyManagementException;
36  import java.security.KeyStore;
37  import java.security.KeyStoreException;
38  import java.security.NoSuchAlgorithmException;
39  import java.security.SecureRandom;
40  import java.security.UnrecoverableKeyException;
41  
42  import javax.net.ssl.SSLContext;
43  import javax.net.ssl.SSLSocket;
44  
45  import org.apache.http.HttpHost;
46  import org.apache.http.annotation.ThreadSafe;
47  import org.apache.http.conn.ConnectTimeoutException;
48  import org.apache.http.conn.HttpInetSocketAddress;
49  import org.apache.http.conn.scheme.HostNameResolver;
50  import org.apache.http.conn.scheme.LayeredSchemeSocketFactory;
51  import org.apache.http.conn.scheme.LayeredSocketFactory;
52  import org.apache.http.conn.scheme.SchemeLayeredSocketFactory;
53  import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
54  import org.apache.http.params.HttpConnectionParams;
55  import org.apache.http.params.HttpParams;
56  import org.apache.http.protocol.HttpContext;
57  import org.apache.http.util.Args;
58  import org.apache.http.util.Asserts;
59  import org.apache.http.util.TextUtils;
60  
61  /**
62   * Layered socket factory for TLS/SSL connections.
63   * <p>
64   * SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of
65   * trusted certificates and to authenticate to the HTTPS server using a private key.
66   * <p>
67   * SSLSocketFactory will enable server authentication when supplied with
68   * a {@link KeyStore trust-store} file containing one or several trusted certificates. The client
69   * secure socket will reject the connection during the SSL session handshake if the target HTTPS
70   * server attempts to authenticate itself with a non-trusted certificate.
71   * <p>
72   * Use JDK keytool utility to import a trusted certificate and generate a trust-store file:
73   *    <pre>
74   *     keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
75   *    </pre>
76   * <p>
77   * In special cases the standard trust verification process can be bypassed by using a custom
78   * {@link TrustStrategy}. This interface is primarily intended for allowing self-signed
79   * certificates to be accepted as trusted without having to add them to the trust-store file.
80   * <p>
81   * SSLSocketFactory will enable client authentication when supplied with
82   * a {@link KeyStore key-store} file containing a private key/public certificate
83   * pair. The client secure socket will use the private key to authenticate
84   * itself to the target HTTPS server during the SSL session handshake if
85   * requested to do so by the server.
86   * The target HTTPS server will in its turn verify the certificate presented
87   * by the client in order to establish client's authenticity.
88   * <p>
89   * Use the following sequence of actions to generate a key-store file
90   * </p>
91   *   <ul>
92   *     <li>
93   *      <p>
94   *      Use JDK keytool utility to generate a new key
95   *      <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
96   *      For simplicity use the same password for the key as that of the key-store
97   *      </p>
98   *     </li>
99   *     <li>
100  *      <p>
101  *      Issue a certificate signing request (CSR)
102  *      <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
103  *     </p>
104  *     </li>
105  *     <li>
106  *      <p>
107  *      Send the certificate request to the trusted Certificate Authority for signature.
108  *      One may choose to act as her own CA and sign the certificate request using a PKI
109  *      tool, such as OpenSSL.
110  *      </p>
111  *     </li>
112  *     <li>
113  *      <p>
114  *       Import the trusted CA root certificate
115  *       <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
116  *      </p>
117  *     </li>
118  *     <li>
119  *      <p>
120  *       Import the PKCS#7 file containg the complete certificate chain
121  *       <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
122  *      </p>
123  *     </li>
124  *     <li>
125  *      <p>
126  *       Verify the content the resultant keystore file
127  *       <pre>keytool -list -v -keystore my.keystore</pre>
128  *      </p>
129  *     </li>
130  *   </ul>
131  *
132  * @since 4.0
133  *
134  * @deprecated (4.3) use {@link SSLConnectionSocketFactory}.
135  */
136 @ThreadSafe
137 @Deprecated
138 public class SSLSocketFactory implements LayeredConnectionSocketFactory, SchemeLayeredSocketFactory,
139                                          LayeredSchemeSocketFactory, 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      * Obtains default SSL socket factory with an SSL context based on the standard JSSE
156      * trust material (<code>cacerts</code> file in the security properties directory).
157      * System properties are not taken into consideration.
158      *
159      * @return default SSL socket factory
160      */
161     public static SSLSocketFactory getSocketFactory() throws SSLInitializationException {
162         return new SSLSocketFactory(
163             SSLContexts.createDefault(),
164             BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
165     }
166 
167     private static String[] split(final String s) {
168         if (TextUtils.isBlank(s)) {
169             return null;
170         }
171         return s.split(" *, *");
172     }
173 
174     /**
175      * Obtains default SSL socket factory with an SSL context based on system properties
176      * as described in
177      * <a href="http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html">
178      * "JavaTM Secure Socket Extension (JSSE) Reference Guide for the JavaTM 2 Platform
179      * Standard Edition 5</a>
180      *
181      * @return default system SSL socket factory
182      */
183     public static SSLSocketFactory getSystemSocketFactory() throws SSLInitializationException {
184         return new SSLSocketFactory(
185             (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(),
186             split(System.getProperty("https.protocols")),
187             split(System.getProperty("https.cipherSuites")),
188             BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
189     }
190 
191     private final javax.net.ssl.SSLSocketFactory socketfactory;
192     private final HostNameResolver nameResolver;
193     // TODO: make final
194     private volatile X509HostnameVerifier hostnameVerifier;
195     private final String[] supportedProtocols;
196     private final String[] supportedCipherSuites;
197 
198     public SSLSocketFactory(
199             final String algorithm,
200             final KeyStore keystore,
201             final String keyPassword,
202             final KeyStore truststore,
203             final SecureRandom random,
204             final HostNameResolver nameResolver)
205                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
206         this(SSLContexts.custom()
207                 .useProtocol(algorithm)
208                 .setSecureRandom(random)
209                 .loadKeyMaterial(keystore, keyPassword != null ? keyPassword.toCharArray() : null)
210                 .loadTrustMaterial(truststore)
211                 .build(),
212                 nameResolver);
213     }
214 
215     /**
216      * @since 4.1
217      */
218     public SSLSocketFactory(
219             final String algorithm,
220             final KeyStore keystore,
221             final String keyPassword,
222             final KeyStore truststore,
223             final SecureRandom random,
224             final TrustStrategy trustStrategy,
225             final X509HostnameVerifier hostnameVerifier)
226                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
227         this(SSLContexts.custom()
228                 .useProtocol(algorithm)
229                 .setSecureRandom(random)
230                 .loadKeyMaterial(keystore, keyPassword != null ? keyPassword.toCharArray() : null)
231                 .loadTrustMaterial(truststore, trustStrategy)
232                 .build(),
233                 hostnameVerifier);
234     }
235 
236     /**
237      * @since 4.1
238      */
239     public SSLSocketFactory(
240             final String algorithm,
241             final KeyStore keystore,
242             final String keyPassword,
243             final KeyStore truststore,
244             final SecureRandom random,
245             final X509HostnameVerifier hostnameVerifier)
246                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
247         this(SSLContexts.custom()
248                 .useProtocol(algorithm)
249                 .setSecureRandom(random)
250                 .loadKeyMaterial(keystore, keyPassword != null ? keyPassword.toCharArray() : null)
251                 .loadTrustMaterial(truststore)
252                 .build(),
253                 hostnameVerifier);
254     }
255 
256     public SSLSocketFactory(
257             final KeyStore keystore,
258             final String keystorePassword,
259             final KeyStore truststore)
260                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
261         this(SSLContexts.custom()
262                 .loadKeyMaterial(keystore, keystorePassword != null ? keystorePassword.toCharArray() : null)
263                 .loadTrustMaterial(truststore)
264                 .build(),
265                 BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
266     }
267 
268     public SSLSocketFactory(
269             final KeyStore keystore,
270             final String keystorePassword)
271                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException{
272         this(SSLContexts.custom()
273                 .loadKeyMaterial(keystore, keystorePassword != null ? keystorePassword.toCharArray() : null)
274                 .build(),
275                 BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
276     }
277 
278     public SSLSocketFactory(
279             final KeyStore truststore)
280                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
281         this(SSLContexts.custom()
282                 .loadTrustMaterial(truststore)
283                 .build(),
284                 BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
285     }
286 
287     /**
288      * @since 4.1
289      */
290     public SSLSocketFactory(
291             final TrustStrategy trustStrategy,
292             final X509HostnameVerifier hostnameVerifier)
293                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
294         this(SSLContexts.custom()
295                 .loadTrustMaterial(null, trustStrategy)
296                 .build(),
297                 hostnameVerifier);
298     }
299 
300     /**
301      * @since 4.1
302      */
303     public SSLSocketFactory(
304             final TrustStrategy trustStrategy)
305                 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
306         this(SSLContexts.custom()
307                 .loadTrustMaterial(null, trustStrategy)
308                 .build(),
309                 BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
310     }
311 
312     public SSLSocketFactory(final SSLContext sslContext) {
313         this(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
314     }
315 
316     public SSLSocketFactory(
317             final SSLContext sslContext, final HostNameResolver nameResolver) {
318         super();
319         this.socketfactory = sslContext.getSocketFactory();
320         this.hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
321         this.nameResolver = nameResolver;
322         this.supportedProtocols = null;
323         this.supportedCipherSuites = null;
324     }
325 
326     /**
327      * @since 4.1
328      */
329     public SSLSocketFactory(
330             final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) {
331         this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
332                 null, null, hostnameVerifier);
333     }
334 
335     /**
336      * @since 4.3
337      */
338     public SSLSocketFactory(
339             final SSLContext sslContext,
340             final String[] supportedProtocols,
341             final String[] supportedCipherSuites,
342             final X509HostnameVerifier hostnameVerifier) {
343         this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
344                 supportedProtocols, supportedCipherSuites, hostnameVerifier);
345     }
346 
347     /**
348      * @since 4.2
349      */
350     public SSLSocketFactory(
351             final javax.net.ssl.SSLSocketFactory socketfactory,
352             final X509HostnameVerifier hostnameVerifier) {
353         this(socketfactory, null, null, hostnameVerifier);
354     }
355 
356     /**
357      * @since 4.3
358      */
359     public SSLSocketFactory(
360             final javax.net.ssl.SSLSocketFactory socketfactory,
361             final String[] supportedProtocols,
362             final String[] supportedCipherSuites,
363             final X509HostnameVerifier hostnameVerifier) {
364         this.socketfactory = Args.notNull(socketfactory, "SSL socket factory");
365         this.supportedProtocols = supportedProtocols;
366         this.supportedCipherSuites = supportedCipherSuites;
367         this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
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         return createSocket((HttpContext) null);
378     }
379 
380     public Socket createSocket() throws IOException {
381         return createSocket((HttpContext) null);
382     }
383 
384     /**
385      * @since 4.1
386      */
387     public Socket connectSocket(
388             final Socket socket,
389             final InetSocketAddress remoteAddress,
390             final InetSocketAddress localAddress,
391             final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
392         Args.notNull(remoteAddress, "Remote address");
393         Args.notNull(params, "HTTP parameters");
394         final HttpHost host;
395         if (remoteAddress instanceof HttpInetSocketAddress) {
396             host = ((HttpInetSocketAddress) remoteAddress).getHttpHost();
397         } else {
398             host = new HttpHost(remoteAddress.getHostName(), remoteAddress.getPort(), "https");
399         }
400         final int connectTimeout = HttpConnectionParams.getConnectionTimeout(params);
401         return connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, null);
402     }
403 
404     /**
405      * Checks whether a socket connection is secure.
406      * This factory creates TLS/SSL socket connections
407      * which, by default, are considered secure.
408      * <br/>
409      * Derived classes may override this method to perform
410      * runtime checks, for example based on the cypher suite.
411      *
412      * @param sock      the connected socket
413      *
414      * @return  <code>true</code>
415      *
416      * @throws IllegalArgumentException if the argument is invalid
417      */
418     public boolean isSecure(final Socket sock) throws IllegalArgumentException {
419         Args.notNull(sock, "Socket");
420         Asserts.check(sock instanceof SSLSocket, "Socket not created by this factory");
421         Asserts.check(!sock.isClosed(), "Socket is closed");
422         return true;
423     }
424 
425     /**
426      * @since 4.2
427      */
428     public Socket createLayeredSocket(
429         final Socket socket,
430         final String host,
431         final int port,
432         final HttpParams params) throws IOException, UnknownHostException {
433         return createLayeredSocket(socket, host, port, (HttpContext) null);
434     }
435 
436     public Socket createLayeredSocket(
437         final Socket socket,
438         final String host,
439         final int port,
440         final boolean autoClose) throws IOException, UnknownHostException {
441         return createLayeredSocket(socket, host, port, (HttpContext) null);
442     }
443 
444     public void setHostnameVerifier(final X509HostnameVerifier hostnameVerifier) {
445         Args.notNull(hostnameVerifier, "Hostname verifier");
446         this.hostnameVerifier = hostnameVerifier;
447     }
448 
449     public X509HostnameVerifier getHostnameVerifier() {
450         return this.hostnameVerifier;
451     }
452 
453     public Socket connectSocket(
454             final Socket socket,
455             final String host, final int port,
456             final InetAddress local, final int localPort,
457             final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
458         final InetAddress remote;
459         if (this.nameResolver != null) {
460             remote = this.nameResolver.resolve(host);
461         } else {
462             remote = InetAddress.getByName(host);
463         }
464         InetSocketAddress localAddress = null;
465         if (local != null || localPort > 0) {
466             localAddress = new InetSocketAddress(local, localPort > 0 ? localPort : 0);
467         }
468         final InetSocketAddress remoteAddress = new HttpInetSocketAddress(
469                 new HttpHost(host, port), remote, port);
470         return connectSocket(socket, remoteAddress, localAddress, params);
471     }
472 
473     public Socket createSocket(
474             final Socket socket,
475             final String host, final int port,
476             final boolean autoClose) throws IOException, UnknownHostException {
477         return createLayeredSocket(socket, host, port, autoClose);
478     }
479 
480     /**
481      * Performs any custom initialization for a newly created SSLSocket
482      * (before the SSL handshake happens).
483      *
484      * The default implementation is a no-op, but could be overridden to, e.g.,
485      * call {@link SSLSocket#setEnabledCipherSuites(java.lang.String[])}.
486      *
487      * @since 4.2
488      */
489     protected void prepareSocket(final SSLSocket socket) throws IOException {
490     }
491 
492     private void internalPrepareSocket(final SSLSocket socket) throws IOException {
493         if (supportedProtocols != null) {
494             socket.setEnabledProtocols(supportedProtocols);
495         }
496         if (supportedCipherSuites != null) {
497             socket.setEnabledCipherSuites(supportedCipherSuites);
498         }
499         prepareSocket(socket);
500     }
501 
502     public Socket createSocket(final HttpContext context) throws IOException {
503         final SSLSocket sock = (SSLSocket) this.socketfactory.createSocket();
504         internalPrepareSocket(sock);
505         return sock;
506     }
507 
508     public Socket connectSocket(
509             final int connectTimeout,
510             final Socket socket,
511             final HttpHost host,
512             final InetSocketAddress remoteAddress,
513             final InetSocketAddress localAddress,
514             final HttpContext context) throws IOException {
515         Args.notNull(host, "HTTP host");
516         Args.notNull(remoteAddress, "Remote address");
517         final Socket sock = socket != null ? socket : createSocket(context);
518         if (localAddress != null) {
519             sock.bind(localAddress);
520         }
521         try {
522             sock.connect(remoteAddress, connectTimeout);
523         } catch (final IOException ex) {
524             try {
525                 sock.close();
526             } catch (final IOException ignore) {
527             }
528             throw ex;
529         }
530         // Setup SSL layering if necessary
531         if (sock instanceof SSLSocket) {
532             final SSLSocket sslsock = (SSLSocket) sock;
533             sslsock.startHandshake();
534             verifyHostname(sslsock, host.getHostName());
535             return sock;
536         } else {
537             return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), context);
538         }
539     }
540 
541     public Socket createLayeredSocket(
542             final Socket socket,
543             final String target,
544             final int port,
545             final HttpContext context) throws IOException {
546         final SSLSocket sslsock = (SSLSocket) this.socketfactory.createSocket(
547                 socket,
548                 target,
549                 port,
550                 true);
551         internalPrepareSocket(sslsock);
552         sslsock.startHandshake();
553         verifyHostname(sslsock, target);
554         return sslsock;
555     }
556 
557     private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException {
558         try {
559             this.hostnameVerifier.verify(hostname, sslsock);
560             // verifyHostName() didn't blowup - good!
561         } catch (final IOException iox) {
562             // close the socket before re-throwing the exception
563             try { sslsock.close(); } catch (final Exception x) { /*ignore*/ }
564             throw iox;
565         }
566     }
567 
568 }