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