View Javadoc

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