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.net.SocketTimeoutException;
35  import java.security.KeyManagementException;
36  import java.security.KeyStoreException;
37  import java.security.NoSuchAlgorithmException;
38  import java.security.cert.CertificateException;
39  import java.security.cert.X509Certificate;
40  import java.util.concurrent.TimeUnit;
41  
42  import javax.net.ssl.HostnameVerifier;
43  import javax.net.ssl.SSLContext;
44  import javax.net.ssl.SSLException;
45  import javax.net.ssl.SSLServerSocket;
46  import javax.net.ssl.SSLSession;
47  import javax.net.ssl.SSLSocket;
48  
49  import org.apache.http.HttpHost;
50  import org.apache.http.impl.bootstrap.HttpServer;
51  import org.apache.http.impl.bootstrap.SSLServerSetupHandler;
52  import org.apache.http.impl.bootstrap.ServerBootstrap;
53  import org.apache.http.localserver.LocalServerTestBase;
54  import org.apache.http.localserver.SSLTestContexts;
55  import org.apache.http.protocol.BasicHttpContext;
56  import org.apache.http.protocol.HttpContext;
57  import org.apache.http.ssl.SSLContexts;
58  import org.hamcrest.CoreMatchers;
59  import org.junit.After;
60  import org.junit.Assert;
61  import org.junit.Test;
62  
63  /**
64   * Unit tests for {@link SSLConnectionSocketFactory}.
65   */
66  public class TestSSLSocketFactory {
67  
68      private HttpServer server;
69  
70      @After
71      public void shutDown() throws Exception {
72          if (this.server != null) {
73              this.server.shutdown(10, TimeUnit.SECONDS);
74          }
75      }
76  
77      static class TestX509HostnameVerifier implements HostnameVerifier {
78  
79          private boolean fired = false;
80  
81          @Override
82          public boolean verify(final String host, final SSLSession session) {
83              this.fired = true;
84              return true;
85          }
86  
87          public boolean isFired() {
88              return this.fired;
89          }
90  
91      }
92  
93      @Test
94      public void testBasicSSL() throws Exception {
95          // @formatter:off
96          this.server = ServerBootstrap.bootstrap()
97                  .setServerInfo(LocalServerTestBase.ORIGIN)
98                  .setSslContext(SSLTestContexts.createServerSSLContext())
99                  .create();
100         // @formatter:on
101         this.server.start();
102 
103         final HttpContext context = new BasicHttpContext();
104         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
105         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
106                 SSLTestContexts.createClientSSLContext(), hostVerifier);
107         final Socket socket = socketFactory.createSocket(context);
108         final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
109         final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
110         final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(0, socket, target, remoteAddress, null,
111                 context);
112         try {
113             final SSLSession sslsession = sslSocket.getSession();
114 
115             Assert.assertNotNull(sslsession);
116             Assert.assertTrue(hostVerifier.isFired());
117         } finally {
118             sslSocket.close();
119         }
120     }
121 
122     @Test
123     public void testBasicDefaultHostnameVerifier() throws Exception {
124         // @formatter:off
125         this.server = ServerBootstrap.bootstrap()
126                 .setServerInfo(LocalServerTestBase.ORIGIN)
127                 .setSslContext(SSLTestContexts.createServerSSLContext())
128                 .create();
129         // @formatter:on
130         this.server.start();
131 
132         final HttpContext context = new BasicHttpContext();
133         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
134                 SSLTestContexts.createClientSSLContext(), SSLConnectionSocketFactory.getDefaultHostnameVerifier());
135         final Socket socket = socketFactory.createSocket(context);
136         final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
137         final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
138         final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(0, socket, target, remoteAddress, null,
139                 context);
140         try {
141             final SSLSession sslsession = sslSocket.getSession();
142 
143             Assert.assertNotNull(sslsession);
144         } finally {
145             sslSocket.close();
146         }
147     }
148 
149     @Test
150     public void testClientAuthSSL() throws Exception {
151         // @formatter:off
152         this.server = ServerBootstrap.bootstrap()
153                 .setServerInfo(LocalServerTestBase.ORIGIN)
154                 .setSslContext(SSLTestContexts.createServerSSLContext())
155                 .create();
156         // @formatter:on
157         this.server.start();
158 
159         final HttpContext context = new BasicHttpContext();
160         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
161         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
162                 SSLTestContexts.createClientSSLContext(), hostVerifier);
163         final Socket socket = socketFactory.createSocket(context);
164         final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
165         final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
166         final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(0, socket, target, remoteAddress, null,
167                 context);
168         try {
169             final SSLSession sslsession = sslSocket.getSession();
170 
171             Assert.assertNotNull(sslsession);
172             Assert.assertTrue(hostVerifier.isFired());
173         } finally {
174             sslSocket.close();
175         }
176     }
177 
178     @Test(expected = IOException.class)
179     public void testClientAuthSSLFailure() throws Exception {
180         // @formatter:off
181         this.server = ServerBootstrap.bootstrap()
182                 .setServerInfo(LocalServerTestBase.ORIGIN)
183                 .setSslContext(SSLTestContexts.createServerSSLContext())
184                 .setSslSetupHandler(new SSLServerSetupHandler() {
185 
186                     @Override
187                     public void initialize(final SSLServerSocket socket) throws SSLException {
188                         socket.setNeedClientAuth(true);
189                     }
190 
191                 })
192                 .create();
193         // @formatter:on
194         this.server.start();
195 
196         final HttpContext context = new BasicHttpContext();
197         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
198         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
199                 SSLTestContexts.createClientSSLContext(), hostVerifier);
200         final Socket socket = socketFactory.createSocket(context);
201         final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
202         final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
203         final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(0, socket, target, remoteAddress, null,
204                 context);
205         try {
206             final InputStream inputStream = sslSocket.getInputStream();
207             Assert.assertEquals(-1, inputStream.read());
208 
209             final SSLSession sslsession = sslSocket.getSession();
210             Assert.assertNotNull(sslsession);
211             Assert.assertTrue(hostVerifier.isFired());
212         } finally {
213             sslSocket.close();
214         }
215     }
216 
217     @Test(expected = SSLException.class)
218     public void testSSLTrustVerification() throws Exception {
219         // @formatter:off
220         this.server = ServerBootstrap.bootstrap()
221                 .setServerInfo(LocalServerTestBase.ORIGIN)
222                 .setSslContext(SSLTestContexts.createServerSSLContext())
223                 .create();
224         // @formatter:on
225         this.server.start();
226 
227         final HttpContext context = new BasicHttpContext();
228         // Use default SSL context
229         final SSLContext defaultsslcontext = SSLContexts.createDefault();
230 
231         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(defaultsslcontext,
232                 NoopHostnameVerifier.INSTANCE);
233 
234         final Socket socket = socketFactory.createSocket(context);
235         final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
236         final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
237         final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(0, socket, target, remoteAddress, null,
238                 context);
239         sslSocket.close();
240     }
241 
242     @Test
243     public void testSSLTrustVerificationOverrideWithCustsom() throws Exception {
244         final TrustStrategy trustStrategy = new TrustStrategy() {
245 
246             @Override
247             public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
248                 return chain.length == 1;
249             }
250 
251         };
252         testSSLTrustVerificationOverride(trustStrategy);
253     }
254 
255     @Test
256     public void testSSLTrustVerificationOverrideWithTrustSelfSignedStrategy() throws Exception {
257         testSSLTrustVerificationOverride(TrustSelfSignedStrategy.INSTANCE);
258     }
259 
260     @Test
261     public void testSSLTrustVerificationOverrideWithTrustAllStrategy() throws Exception {
262         testSSLTrustVerificationOverride(TrustAllStrategy.INSTANCE);
263     }
264 
265     private void testSSLTrustVerificationOverride(final TrustStrategy trustStrategy)
266             throws Exception, IOException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
267         // @formatter:off
268         this.server = ServerBootstrap.bootstrap()
269                 .setServerInfo(LocalServerTestBase.ORIGIN)
270                 .setSslContext(SSLTestContexts.createServerSSLContext())
271                 .create();
272         // @formatter:on
273         this.server.start();
274 
275         final HttpContext context = new BasicHttpContext();
276 
277         // @formatter:off
278         final SSLContext sslcontext = SSLContexts.custom()
279             .loadTrustMaterial(null, trustStrategy)
280             .build();
281         // @formatter:on
282         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslcontext,
283                 NoopHostnameVerifier.INSTANCE);
284 
285         final Socket socket = socketFactory.createSocket(context);
286         final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
287         final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
288         final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(0, socket, target, remoteAddress, null,
289                 context);
290         sslSocket.close();
291     }
292 
293     @Test
294     public void testTLSOnly() throws Exception {
295         // @formatter:off
296         this.server = ServerBootstrap.bootstrap()
297                 .setServerInfo(LocalServerTestBase.ORIGIN)
298                 .setSslContext(SSLTestContexts.createServerSSLContext())
299                 .setSslSetupHandler(new SSLServerSetupHandler() {
300 
301                     @Override
302                     public void initialize(final SSLServerSocket socket) throws SSLException {
303                         socket.setEnabledProtocols(new String[] {"TLSv1"});
304                     }
305 
306                 })
307                 .create();
308         // @formatter:on
309         this.server.start();
310 
311         final HttpContext context = new BasicHttpContext();
312         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
313                 SSLTestContexts.createClientSSLContext());
314         final Socket socket = socketFactory.createSocket(context);
315         final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
316         final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
317         final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(0, socket, target, remoteAddress, null,
318                 context);
319         final SSLSession sslsession = sslSocket.getSession();
320         Assert.assertNotNull(sslsession);
321     }
322 
323     @Test(expected = IOException.class)
324     public void testSSLDisabledByDefault() throws Exception {
325         // @formatter:off
326         this.server = ServerBootstrap.bootstrap()
327                 .setServerInfo(LocalServerTestBase.ORIGIN)
328                 .setSslContext(SSLTestContexts.createServerSSLContext())
329                 .setSslSetupHandler(new SSLServerSetupHandler() {
330 
331                     @Override
332                     public void initialize(final SSLServerSocket socket) throws SSLException {
333                         socket.setEnabledProtocols(new String[] {"SSLv3"});
334                     }
335 
336                 })
337                 .create();
338         // @formatter:on
339         this.server.start();
340 
341         final HttpContext context = new BasicHttpContext();
342         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
343                 SSLTestContexts.createClientSSLContext());
344         final Socket socket = socketFactory.createSocket(context);
345         final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
346         final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
347         socketFactory.connectSocket(0, socket, target, remoteAddress, null, context);
348     }
349 
350     @Test
351     public void testSSLTimeout() throws Exception {
352         // @formatter:off
353         this.server = ServerBootstrap.bootstrap()
354                 .setServerInfo(LocalServerTestBase.ORIGIN)
355                 .setSslContext(SSLTestContexts.createServerSSLContext())
356                 .create();
357         // @formatter:on
358         this.server.start();
359 
360         final HttpContext context = new BasicHttpContext();
361         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
362                 SSLTestContexts.createClientSSLContext());
363         final Socket socket = socketFactory.createSocket(context);
364         final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
365         final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
366         final Socket sslSocket = socketFactory.connectSocket(0, socket, target, remoteAddress, null, context);
367         final InputStream inputStream = sslSocket.getInputStream();
368         try {
369             sslSocket.setSoTimeout(1);
370             inputStream.read();
371             Assert.fail("SocketTimeoutException expected");
372         } catch (final SocketTimeoutException ex){
373             Assert.assertThat(sslSocket.isClosed(), CoreMatchers.equalTo(false));
374             Assert.assertThat(socket.isClosed(), CoreMatchers.equalTo(false));
375         } finally {
376             inputStream.close();
377         }
378     }
379 
380     @Test
381     public void testStrongCipherSuites() {
382         final String[] strongCipherSuites = {
383                 "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
384                 "TLS_RSA_WITH_AES_256_CBC_SHA256",
385                 "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
386                 "TLS_RSA_WITH_AES_128_CBC_SHA",
387                 "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
388                 "TLS_RSA_WITH_AES_256_GCM_SHA384"
389         };
390         for (final String cipherSuite : strongCipherSuites) {
391             Assert.assertFalse(SSLConnectionSocketFactory.isWeakCipherSuite(cipherSuite));
392         }
393     }
394 
395     @Test
396     public void testWeakCiphersDisabledByDefault() {
397         final String[] weakCiphersSuites = {
398                 "SSL_RSA_WITH_RC4_128_SHA",
399                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
400                 "TLS_DH_anon_WITH_AES_128_CBC_SHA",
401                 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
402                 "SSL_RSA_WITH_NULL_SHA",
403                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
404                 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
405                 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
406                 "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
407                 "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
408                 "TLS_RSA_WITH_NULL_SHA256",
409                 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
410                 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
411 //                "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
412                 "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"
413         };
414         for (final String cipherSuite : weakCiphersSuites) {
415             Assert.assertTrue(SSLConnectionSocketFactory.isWeakCipherSuite(cipherSuite));
416             try {
417                 testWeakCipherDisabledByDefault(cipherSuite);
418                 Assert.fail("IOException expected");
419             } catch (final Exception e) {
420                 Assert.assertTrue(e instanceof IOException || e instanceof IllegalArgumentException);
421             }
422         }
423     }
424 
425     private void testWeakCipherDisabledByDefault(final String cipherSuite) throws Exception {
426         // @formatter:off
427         this.server = ServerBootstrap.bootstrap()
428                 .setServerInfo(LocalServerTestBase.ORIGIN)
429                 .setSslContext(SSLTestContexts.createServerSSLContext())
430                 .setSslSetupHandler(new SSLServerSetupHandler() {
431 
432                     @Override
433                     public void initialize(final SSLServerSocket socket) {
434                         socket.setEnabledCipherSuites(new String[] {cipherSuite});
435                     }
436 
437                 })
438                 .create();
439         // @formatter:on
440         this.server.start();
441 
442         final HttpContext context = new BasicHttpContext();
443         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
444                 SSLTestContexts.createClientSSLContext());
445         final Socket socket = socketFactory.createSocket(context);
446         try {
447             final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
448             final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
449             socketFactory.connectSocket(0, socket, target, remoteAddress, null, context);
450         } finally {
451             socket.close();
452         }
453     }
454 }