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