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     @Override
383     public Socket createSocket(final HttpParams params) throws IOException {
384         return createSocket((HttpContext) null);
385     }
386 
387     @Override
388     public Socket createSocket() throws IOException {
389         return createSocket((HttpContext) null);
390     }
391 
392     /**
393      * @since 4.1
394      */
395     @Override
396     public Socket connectSocket(
397             final Socket socket,
398             final InetSocketAddress remoteAddress,
399             final InetSocketAddress localAddress,
400             final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
401         Args.notNull(remoteAddress, "Remote address");
402         Args.notNull(params, "HTTP parameters");
403         final HttpHost host;
404         if (remoteAddress instanceof HttpInetSocketAddress) {
405             host = ((HttpInetSocketAddress) remoteAddress).getHttpHost();
406         } else {
407             host = new HttpHost(remoteAddress.getHostName(), remoteAddress.getPort(), "https");
408         }
409         final int socketTimeout = HttpConnectionParams.getSoTimeout(params);
410         final int connectTimeout = HttpConnectionParams.getConnectionTimeout(params);
411         socket.setSoTimeout(socketTimeout);
412         return connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, null);
413     }
414 
415     /**
416      * Checks whether a socket connection is secure.
417      * This factory creates TLS/SSL socket connections
418      * which, by default, are considered secure.
419      * <p>
420      * Derived classes may override this method to perform
421      * runtime checks, for example based on the cypher suite.
422      * </p>
423      *
424      * @param sock      the connected socket
425      *
426      * @return  {@code true}
427      *
428      * @throws IllegalArgumentException if the argument is invalid
429      */
430     @Override
431     public boolean isSecure(final Socket sock) throws IllegalArgumentException {
432         Args.notNull(sock, "Socket");
433         Asserts.check(sock instanceof SSLSocket, "Socket not created by this factory");
434         Asserts.check(!sock.isClosed(), "Socket is closed");
435         return true;
436     }
437 
438     /**
439      * @since 4.2
440      */
441     @Override
442     public Socket createLayeredSocket(
443         final Socket socket,
444         final String host,
445         final int port,
446         final HttpParams params) throws IOException, UnknownHostException {
447         return createLayeredSocket(socket, host, port, (HttpContext) null);
448     }
449 
450     @Override
451     public Socket createLayeredSocket(
452         final Socket socket,
453         final String host,
454         final int port,
455         final boolean autoClose) throws IOException, UnknownHostException {
456         return createLayeredSocket(socket, host, port, (HttpContext) null);
457     }
458 
459     public void setHostnameVerifier(final X509HostnameVerifier hostnameVerifier) {
460         Args.notNull(hostnameVerifier, "Hostname verifier");
461         this.hostnameVerifier = hostnameVerifier;
462     }
463 
464     public X509HostnameVerifier getHostnameVerifier() {
465         return this.hostnameVerifier;
466     }
467 
468     @Override
469     public Socket connectSocket(
470             final Socket socket,
471             final String host, final int port,
472             final InetAddress local, final int localPort,
473             final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
474         final InetAddress remote;
475         if (this.nameResolver != null) {
476             remote = this.nameResolver.resolve(host);
477         } else {
478             remote = InetAddress.getByName(host);
479         }
480         InetSocketAddress localAddress = null;
481         if (local != null || localPort > 0) {
482             localAddress = new InetSocketAddress(local, localPort > 0 ? localPort : 0);
483         }
484         final InetSocketAddress remoteAddress = new HttpInetSocketAddress(
485                 new HttpHost(host, port), remote, port);
486         return connectSocket(socket, remoteAddress, localAddress, params);
487     }
488 
489     @Override
490     public Socket createSocket(
491             final Socket socket,
492             final String host, final int port,
493             final boolean autoClose) throws IOException, UnknownHostException {
494         return createLayeredSocket(socket, host, port, autoClose);
495     }
496 
497     /**
498      * Performs any custom initialization for a newly created SSLSocket
499      * (before the SSL handshake happens).
500      *
501      * The default implementation is a no-op, but could be overridden to, e.g.,
502      * call {@link SSLSocket#setEnabledCipherSuites(java.lang.String[])}.
503      * @throws IOException (only if overridden)
504      *
505      * @since 4.2
506      */
507     protected void prepareSocket(final SSLSocket socket) throws IOException {
508     }
509 
510     private void internalPrepareSocket(final SSLSocket socket) throws IOException {
511         if (supportedProtocols != null) {
512             socket.setEnabledProtocols(supportedProtocols);
513         }
514         if (supportedCipherSuites != null) {
515             socket.setEnabledCipherSuites(supportedCipherSuites);
516         }
517         prepareSocket(socket);
518     }
519 
520     @Override
521     public Socket createSocket(final HttpContext context) throws IOException {
522         final SSLSocket sock = (SSLSocket) this.socketfactory.createSocket();
523         internalPrepareSocket(sock);
524         return sock;
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 }