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