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 org.apache.http.HttpHost;
31  import org.apache.http.annotation.ThreadSafe;
32  import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
33  import org.apache.http.protocol.HttpContext;
34  import org.apache.http.util.Args;
35  import org.apache.http.util.TextUtils;
36  
37  import javax.net.SocketFactory;
38  import javax.net.ssl.SSLContext;
39  import javax.net.ssl.SSLSocket;
40  import java.io.IOException;
41  import java.net.InetSocketAddress;
42  import java.net.Socket;
43  import java.util.ArrayList;
44  import java.util.List;
45  
46  /**
47   * Layered socket factory for TLS/SSL connections.
48   * <p>
49   * SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of
50   * trusted certificates and to authenticate to the HTTPS server using a private key.
51   * <p>
52   * SSLSocketFactory will enable server authentication when supplied with
53   * a {@link java.security.KeyStore trust-store} file containing one or several trusted certificates. The client
54   * secure socket will reject the connection during the SSL session handshake if the target HTTPS
55   * server attempts to authenticate itself with a non-trusted certificate.
56   * <p>
57   * Use JDK keytool utility to import a trusted certificate and generate a trust-store file:
58   *    <pre>
59   *     keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
60   *    </pre>
61   * <p>
62   * In special cases the standard trust verification process can be bypassed by using a custom
63   * {@link org.apache.http.conn.ssl.TrustStrategy}. This interface is primarily intended for allowing self-signed
64   * certificates to be accepted as trusted without having to add them to the trust-store file.
65   * <p>
66   * SSLSocketFactory will enable client authentication when supplied with
67   * a {@link java.security.KeyStore key-store} file containing a private key/public certificate
68   * pair. The client secure socket will use the private key to authenticate
69   * itself to the target HTTPS server during the SSL session handshake if
70   * requested to do so by the server.
71   * The target HTTPS server will in its turn verify the certificate presented
72   * by the client in order to establish client's authenticity.
73   * <p>
74   * Use the following sequence of actions to generate a key-store file
75   * </p>
76   *   <ul>
77   *     <li>
78   *      <p>
79   *      Use JDK keytool utility to generate a new key
80   *      <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
81   *      For simplicity use the same password for the key as that of the key-store
82   *      </p>
83   *     </li>
84   *     <li>
85   *      <p>
86   *      Issue a certificate signing request (CSR)
87   *      <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
88   *     </p>
89   *     </li>
90   *     <li>
91   *      <p>
92   *      Send the certificate request to the trusted Certificate Authority for signature.
93   *      One may choose to act as her own CA and sign the certificate request using a PKI
94   *      tool, such as OpenSSL.
95   *      </p>
96   *     </li>
97   *     <li>
98   *      <p>
99   *       Import the trusted CA root certificate
100  *       <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
101  *      </p>
102  *     </li>
103  *     <li>
104  *      <p>
105  *       Import the PKCS#7 file containg the complete certificate chain
106  *       <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
107  *      </p>
108  *     </li>
109  *     <li>
110  *      <p>
111  *       Verify the content the resultant keystore file
112  *       <pre>keytool -list -v -keystore my.keystore</pre>
113  *      </p>
114  *     </li>
115  *   </ul>
116  *
117  * @since 4.0
118  */
119 @ThreadSafe
120 public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactory {
121 
122     public static final String TLS   = "TLS";
123     public static final String SSL   = "SSL";
124     public static final String SSLV2 = "SSLv2";
125 
126     public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER
127         = new AllowAllHostnameVerifier();
128 
129     public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
130         = new BrowserCompatHostnameVerifier();
131 
132     public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
133         = new StrictHostnameVerifier();
134 
135     /**
136      * Obtains default SSL socket factory with an SSL context based on the standard JSSE
137      * trust material (<code>cacerts</code> file in the security properties directory).
138      * System properties are not taken into consideration.
139      *
140      * @return default SSL socket factory
141      */
142     public static SSLConnectionSocketFactory getSocketFactory() throws SSLInitializationException {
143         return new SSLConnectionSocketFactory(
144             SSLContexts.createDefault(),
145             BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
146     }
147 
148     private static String[] split(final String s) {
149         if (TextUtils.isBlank(s)) {
150             return null;
151         }
152         return s.split(" *, *");
153     }
154 
155     /**
156      * Obtains default SSL socket factory with an SSL context based on system properties
157      * as described in
158      * <a href="http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html">
159      * "JavaTM Secure Socket Extension (JSSE) Reference Guide for the JavaTM 2 Platform
160      * Standard Edition 5</a>
161      *
162      * @return default system SSL socket factory
163      */
164     public static SSLConnectionSocketFactory getSystemSocketFactory() throws SSLInitializationException {
165         return new SSLConnectionSocketFactory(
166             (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(),
167             split(System.getProperty("https.protocols")),
168             split(System.getProperty("https.cipherSuites")),
169             BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
170     }
171 
172     private final javax.net.ssl.SSLSocketFactory socketfactory;
173     private final X509HostnameVerifier hostnameVerifier;
174     private final String[] supportedProtocols;
175     private final String[] supportedCipherSuites;
176 
177     public SSLConnectionSocketFactory(final SSLContext sslContext) {
178         this(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
179     }
180 
181     public SSLConnectionSocketFactory(
182             final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) {
183         this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
184                 null, null, hostnameVerifier);
185     }
186 
187     public SSLConnectionSocketFactory(
188             final SSLContext sslContext,
189             final String[] supportedProtocols,
190             final String[] supportedCipherSuites,
191             final X509HostnameVerifier hostnameVerifier) {
192         this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
193                 supportedProtocols, supportedCipherSuites, hostnameVerifier);
194     }
195 
196     public SSLConnectionSocketFactory(
197             final javax.net.ssl.SSLSocketFactory socketfactory,
198             final X509HostnameVerifier hostnameVerifier) {
199         this(socketfactory, null, null, hostnameVerifier);
200     }
201 
202     public SSLConnectionSocketFactory(
203             final javax.net.ssl.SSLSocketFactory socketfactory,
204             final String[] supportedProtocols,
205             final String[] supportedCipherSuites,
206             final X509HostnameVerifier hostnameVerifier) {
207         this.socketfactory = Args.notNull(socketfactory, "SSL socket factory");
208         this.supportedProtocols = supportedProtocols;
209         this.supportedCipherSuites = supportedCipherSuites;
210         this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
211     }
212 
213     /**
214      * Performs any custom initialization for a newly created SSLSocket
215      * (before the SSL handshake happens).
216      *
217      * The default implementation is a no-op, but could be overridden to, e.g.,
218      * call {@link javax.net.ssl.SSLSocket#setEnabledCipherSuites(String[])}.
219      */
220     protected void prepareSocket(final SSLSocket socket) throws IOException {
221     }
222 
223     public Socket createSocket(final HttpContext context) throws IOException {
224         return SocketFactory.getDefault().createSocket();
225     }
226 
227     public Socket connectSocket(
228             final int connectTimeout,
229             final Socket socket,
230             final HttpHost host,
231             final InetSocketAddress remoteAddress,
232             final InetSocketAddress localAddress,
233             final HttpContext context) throws IOException {
234         Args.notNull(host, "HTTP host");
235         Args.notNull(remoteAddress, "Remote address");
236         final Socket sock = socket != null ? socket : createSocket(context);
237         if (localAddress != null) {
238             sock.bind(localAddress);
239         }
240         try {
241             if (connectTimeout > 0 && sock.getSoTimeout() == 0) {
242                 sock.setSoTimeout(connectTimeout);
243             }
244             sock.connect(remoteAddress, connectTimeout);
245         } catch (final IOException ex) {
246             try {
247                 sock.close();
248             } catch (final IOException ignore) {
249             }
250             throw ex;
251         }
252         // Setup SSL layering if necessary
253         if (sock instanceof SSLSocket) {
254             final SSLSocket sslsock = (SSLSocket) sock;
255             sslsock.startHandshake();
256             verifyHostname(sslsock, host.getHostName());
257             return sock;
258         } else {
259             return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), context);
260         }
261     }
262 
263     public Socket createLayeredSocket(
264             final Socket socket,
265             final String target,
266             final int port,
267             final HttpContext context) throws IOException {
268         final SSLSocket sslsock = (SSLSocket) this.socketfactory.createSocket(
269                 socket,
270                 target,
271                 port,
272                 true);
273         if (supportedProtocols != null) {
274             sslsock.setEnabledProtocols(supportedProtocols);
275         } else {
276             // If supported protocols are not explicitly set, remove all SSL protocol versions
277             final String[] allProtocols = sslsock.getSupportedProtocols();
278             final List<String> enabledProtocols = new ArrayList<String>(allProtocols.length);
279             for (String protocol: allProtocols) {
280                 if (!protocol.startsWith("SSL")) {
281                     enabledProtocols.add(protocol);
282                 }
283             }
284             sslsock.setEnabledProtocols(enabledProtocols.toArray(new String[enabledProtocols.size()]));
285         }
286         if (supportedCipherSuites != null) {
287             sslsock.setEnabledCipherSuites(supportedCipherSuites);
288         }
289         prepareSocket(sslsock);
290         sslsock.startHandshake();
291         verifyHostname(sslsock, target);
292         return sslsock;
293     }
294 
295     X509HostnameVerifier getHostnameVerifier() {
296         return this.hostnameVerifier;
297     }
298 
299     private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException {
300         try {
301             this.hostnameVerifier.verify(hostname, sslsock);
302             // verifyHostName() didn't blowup - good!
303         } catch (final IOException iox) {
304             // close the socket before re-throwing the exception
305             try { sslsock.close(); } catch (final Exception x) { /*ignore*/ }
306             throw iox;
307         }
308     }
309 
310 }