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