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.io.InputStream;
32  import java.net.InetSocketAddress;
33  import java.net.Socket;
34  import java.security.cert.Certificate;
35  import java.security.cert.X509Certificate;
36  
37  import javax.net.SocketFactory;
38  import javax.net.ssl.HostnameVerifier;
39  import javax.net.ssl.SSLContext;
40  import javax.net.ssl.SSLHandshakeException;
41  import javax.net.ssl.SSLPeerUnverifiedException;
42  import javax.net.ssl.SSLSession;
43  import javax.net.ssl.SSLSocket;
44  import javax.security.auth.x500.X500Principal;
45  
46  import org.apache.http.HttpHost;
47  import org.apache.http.annotation.ThreadSafe;
48  import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
49  import org.apache.http.conn.util.PublicSuffixMatcherLoader;
50  import org.apache.http.protocol.HttpContext;
51  import org.apache.http.util.Args;
52  import org.apache.http.util.TextUtils;
53  
54  /**
55   * Layered socket factory for TLS/SSL connections.
56   * <p>
57   * SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of
58   * trusted certificates and to authenticate to the HTTPS server using a private key.
59   * <p>
60   * SSLSocketFactory will enable server authentication when supplied with
61   * a {@link java.security.KeyStore trust-store} file containing one or several trusted certificates. The client
62   * secure socket will reject the connection during the SSL session handshake if the target HTTPS
63   * server attempts to authenticate itself with a non-trusted certificate.
64   * <p>
65   * Use JDK keytool utility to import a trusted certificate and generate a trust-store file:
66   *    <pre>
67   *     keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
68   *    </pre>
69   * <p>
70   * In special cases the standard trust verification process can be bypassed by using a custom
71   * {@link org.apache.http.conn.ssl.TrustStrategy}. This interface is primarily intended for allowing self-signed
72   * certificates to be accepted as trusted without having to add them to the trust-store file.
73   * <p>
74   * SSLSocketFactory will enable client authentication when supplied with
75   * a {@link java.security.KeyStore key-store} file containing a private key/public certificate
76   * pair. The client secure socket will use the private key to authenticate
77   * itself to the target HTTPS server during the SSL session handshake if
78   * requested to do so by the server.
79   * The target HTTPS server will in its turn verify the certificate presented
80   * by the client in order to establish client's authenticity.
81   * <p>
82   * Use the following sequence of actions to generate a key-store file
83   * </p>
84   *   <ul>
85   *     <li>
86   *      <p>
87   *      Use JDK keytool utility to generate a new key
88   *      </p>
89   *      <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
90   *      <p>
91   *      For simplicity use the same password for the key as that of the key-store
92   *      </p>
93   *     </li>
94   *     <li>
95   *      <p>
96   *      Issue a certificate signing request (CSR)
97   *      </p>
98   *      <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
99   *     </li>
100  *     <li>
101  *      <p>
102  *      Send the certificate request to the trusted Certificate Authority for signature.
103  *      One may choose to act as her own CA and sign the certificate request using a PKI
104  *      tool, such as OpenSSL.
105  *      </p>
106  *     </li>
107  *     <li>
108  *      <p>
109  *       Import the trusted CA root certificate
110  *      </p>
111  *       <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
112  *     </li>
113  *     <li>
114  *       <p>
115  *       Import the PKCS#7 file containing the complete certificate chain
116  *       </p>
117  *       <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
118  *     </li>
119  *     <li>
120  *       <p>
121  *       Verify the content of the resultant keystore file
122  *       </p>
123  *       <pre>keytool -list -v -keystore my.keystore</pre>
124  *     </li>
125  *   </ul>
126  *
127  * @since 4.3
128  */
129 @ThreadSafe @SuppressWarnings("deprecation")
130 public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactory {
131 
132     public static final String TLS   = "TLS";
133     public static final String SSL   = "SSL";
134     public static final String SSLV2 = "SSLv2";
135 
136     @Deprecated
137     public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER
138         = AllowAllHostnameVerifier.INSTANCE;
139 
140     @Deprecated
141     public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
142         = BrowserCompatHostnameVerifier.INSTANCE;
143 
144     @Deprecated
145     public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
146         = StrictHostnameVerifier.INSTANCE;
147 
148     /**
149      * @since 4.4
150      */
151     public static HostnameVerifier getDefaultHostnameVerifier() {
152         return new DefaultHostnameVerifier(PublicSuffixMatcherLoader.getDefault());
153     }
154 
155     /**
156      * Obtains default SSL socket factory with an SSL context based on the standard JSSE
157      * trust material ({@code cacerts} file in the security properties directory).
158      * System properties are not taken into consideration.
159      *
160      * @return default SSL socket factory
161      */
162     public static SSLConnectionSocketFactory getSocketFactory() throws SSLInitializationException {
163         return new SSLConnectionSocketFactory(SSLContexts.createDefault(), getDefaultHostnameVerifier());
164     }
165 
166     private static String[] split(final String s) {
167         if (TextUtils.isBlank(s)) {
168             return null;
169         }
170         return s.split(" *, *");
171     }
172 
173     /**
174      * Obtains default SSL socket factory with an SSL context based on system properties
175      * as described in
176      * <a href="http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html">
177      * "JavaTM Secure Socket Extension (JSSE) Reference Guide for the JavaTM 2 Platform
178      * Standard Edition 5</a>
179      *
180      * @return default system SSL socket factory
181      */
182     public static SSLConnectionSocketFactory getSystemSocketFactory() throws SSLInitializationException {
183         return new SSLConnectionSocketFactory(
184             (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(),
185             split(System.getProperty("https.protocols")),
186             split(System.getProperty("https.cipherSuites")),
187             getDefaultHostnameVerifier());
188     }
189 
190     private final javax.net.ssl.SSLSocketFactory socketfactory;
191     private final HostnameVerifier hostnameVerifier;
192     private final String[] supportedProtocols;
193     private final String[] supportedCipherSuites;
194 
195     public SSLConnectionSocketFactory(final SSLContext sslContext) {
196         this(sslContext, getDefaultHostnameVerifier());
197     }
198 
199     /**
200      * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLContext,
201      *   javax.net.ssl.HostnameVerifier)}
202      */
203     @Deprecated
204     public SSLConnectionSocketFactory(
205             final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) {
206         this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
207                 null, null, hostnameVerifier);
208     }
209 
210     /**
211      * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLContext,
212      *   String[], String[], javax.net.ssl.HostnameVerifier)}
213      */
214     @Deprecated
215     public SSLConnectionSocketFactory(
216             final SSLContext sslContext,
217             final String[] supportedProtocols,
218             final String[] supportedCipherSuites,
219             final X509HostnameVerifier hostnameVerifier) {
220         this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
221                 supportedProtocols, supportedCipherSuites, hostnameVerifier);
222     }
223 
224     /**
225      * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLSocketFactory,
226      *   javax.net.ssl.HostnameVerifier)}
227      */
228     @Deprecated
229     public SSLConnectionSocketFactory(
230             final javax.net.ssl.SSLSocketFactory socketfactory,
231             final X509HostnameVerifier hostnameVerifier) {
232         this(socketfactory, null, null, hostnameVerifier);
233     }
234 
235     /**
236      * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLSocketFactory,
237      *   String[], String[], javax.net.ssl.HostnameVerifier)}
238      */
239     @Deprecated
240     public SSLConnectionSocketFactory(
241             final javax.net.ssl.SSLSocketFactory socketfactory,
242             final String[] supportedProtocols,
243             final String[] supportedCipherSuites,
244             final X509HostnameVerifier hostnameVerifier) {
245         this(socketfactory, supportedProtocols, supportedCipherSuites, (HostnameVerifier) hostnameVerifier);
246     }
247 
248     /**
249      * @since 4.4
250      */
251     public SSLConnectionSocketFactory(
252             final SSLContext sslContext, final HostnameVerifier hostnameVerifier) {
253         this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
254                 null, null, hostnameVerifier);
255     }
256 
257     /**
258      * @since 4.4
259      */
260     public SSLConnectionSocketFactory(
261             final SSLContext sslContext,
262             final String[] supportedProtocols,
263             final String[] supportedCipherSuites,
264             final HostnameVerifier hostnameVerifier) {
265         this(Args.notNull(sslContext, "SSL context").getSocketFactory(),
266                 supportedProtocols, supportedCipherSuites, hostnameVerifier);
267     }
268 
269     /**
270      * @since 4.4
271      */
272     public SSLConnectionSocketFactory(
273             final javax.net.ssl.SSLSocketFactory socketfactory,
274             final HostnameVerifier hostnameVerifier) {
275         this(socketfactory, null, null, hostnameVerifier);
276     }
277 
278     /**
279      * @since 4.4
280      */
281     public SSLConnectionSocketFactory(
282             final javax.net.ssl.SSLSocketFactory socketfactory,
283             final String[] supportedProtocols,
284             final String[] supportedCipherSuites,
285             final HostnameVerifier hostnameVerifier) {
286         this.socketfactory = Args.notNull(socketfactory, "SSL socket factory");
287         this.supportedProtocols = supportedProtocols;
288         this.supportedCipherSuites = supportedCipherSuites;
289         this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : getDefaultHostnameVerifier();
290     }
291 
292     /**
293      * Performs any custom initialization for a newly created SSLSocket
294      * (before the SSL handshake happens).
295      *
296      * The default implementation is a no-op, but could be overridden to, e.g.,
297      * call {@link javax.net.ssl.SSLSocket#setEnabledCipherSuites(String[])}.
298      * @throws IOException may be thrown if overridden
299      */
300     protected void prepareSocket(final SSLSocket socket) throws IOException {
301     }
302 
303     @Override
304     public Socket createSocket(final HttpContext context) throws IOException {
305         return SocketFactory.getDefault().createSocket();
306     }
307 
308     @Override
309     public Socket connectSocket(
310             final int connectTimeout,
311             final Socket socket,
312             final HttpHost host,
313             final InetSocketAddress remoteAddress,
314             final InetSocketAddress localAddress,
315             final HttpContext context) throws IOException {
316         Args.notNull(host, "HTTP host");
317         Args.notNull(remoteAddress, "Remote address");
318         final Socket sock = socket != null ? socket : createSocket(context);
319         if (localAddress != null) {
320             sock.bind(localAddress);
321         }
322         try {
323             if (connectTimeout > 0 && sock.getSoTimeout() == 0) {
324                 sock.setSoTimeout(connectTimeout);
325             }
326             sock.connect(remoteAddress, connectTimeout);
327         } catch (final IOException ex) {
328             try {
329                 sock.close();
330             } catch (final IOException ignore) {
331             }
332             throw ex;
333         }
334         // Setup SSL layering if necessary
335         if (sock instanceof SSLSocket) {
336             final SSLSocket sslsock = (SSLSocket) sock;
337             sslsock.startHandshake();
338             verifyHostname(sslsock, host.getHostName());
339             return sock;
340         } else {
341             return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), context);
342         }
343     }
344 
345     @Override
346     public Socket createLayeredSocket(
347             final Socket socket,
348             final String target,
349             final int port,
350             final HttpContext context) throws IOException {
351         final SSLSocket sslsock = (SSLSocket) this.socketfactory.createSocket(
352                 socket,
353                 target,
354                 port,
355                 true);
356         if (supportedProtocols != null) {
357             sslsock.setEnabledProtocols(supportedProtocols);
358         }
359         if (supportedCipherSuites != null) {
360             sslsock.setEnabledCipherSuites(supportedCipherSuites);
361         }
362         prepareSocket(sslsock);
363         sslsock.startHandshake();
364         verifyHostname(sslsock, target);
365         return sslsock;
366     }
367 
368     private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException {
369         try {
370             SSLSession session = sslsock.getSession();
371             if (session == null) {
372                 // In our experience this only happens under IBM 1.4.x when
373                 // spurious (unrelated) certificates show up in the server'
374                 // chain.  Hopefully this will unearth the real problem:
375                 final InputStream in = sslsock.getInputStream();
376                 in.available();
377                 // If ssl.getInputStream().available() didn't cause an
378                 // exception, maybe at least now the session is available?
379                 session = sslsock.getSession();
380                 if (session == null) {
381                     // If it's still null, probably a startHandshake() will
382                     // unearth the real problem.
383                     sslsock.startHandshake();
384                     session = sslsock.getSession();
385                 }
386             }
387             if (session == null) {
388                 throw new SSLHandshakeException("SSL session not available");
389             }
390             if (!this.hostnameVerifier.verify(hostname, session)) {
391                 final Certificate[] certs = session.getPeerCertificates();
392                 final X509Certificate x509 = (X509Certificate) certs[0];
393                 final X500Principal x500Principal = x509.getSubjectX500Principal();
394                 throw new SSLPeerUnverifiedException("Host name '" + hostname + "' does not match " +
395                         "the certificate subject provided by the peer (" + x500Principal.toString() + ")");
396             }
397             // verifyHostName() didn't blowup - good!
398         } catch (final IOException iox) {
399             // close the socket before re-throwing the exception
400             try { sslsock.close(); } catch (final Exception x) { /*ignore*/ }
401             throw iox;
402         }
403     }
404 
405 }