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