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