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.hc.client5.testing.sync;
29  
30  import java.io.IOException;
31  import java.net.InetSocketAddress;
32  import java.net.Socket;
33  import java.security.KeyManagementException;
34  import java.security.KeyStoreException;
35  import java.security.NoSuchAlgorithmException;
36  import java.security.cert.CertificateException;
37  import java.security.cert.X509Certificate;
38  
39  import javax.net.ssl.HostnameVerifier;
40  import javax.net.ssl.SSLContext;
41  import javax.net.ssl.SSLException;
42  import javax.net.ssl.SSLParameters;
43  import javax.net.ssl.SSLSession;
44  import javax.net.ssl.SSLSocket;
45  
46  import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
47  import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
48  import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
49  import org.apache.hc.client5.http.ssl.TrustAllStrategy;
50  import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy;
51  import org.apache.hc.client5.testing.SSLTestContexts;
52  import org.apache.hc.core5.function.Callback;
53  import org.apache.hc.core5.http.HttpHost;
54  import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
55  import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
56  import org.apache.hc.core5.http.protocol.BasicHttpContext;
57  import org.apache.hc.core5.http.protocol.HttpContext;
58  import org.apache.hc.core5.io.CloseMode;
59  import org.apache.hc.core5.ssl.SSLContexts;
60  import org.apache.hc.core5.ssl.TrustStrategy;
61  import org.apache.hc.core5.util.TimeValue;
62  import org.junit.After;
63  import org.junit.Assert;
64  import org.junit.Test;
65  
66  /**
67   * Unit tests for {@link SSLConnectionSocketFactory}.
68   */
69  public class TestSSLSocketFactory {
70  
71      private HttpServer server;
72  
73      @After
74      public void shutDown() throws Exception {
75          if (this.server != null) {
76              this.server.close(CloseMode.GRACEFUL);
77          }
78      }
79  
80      static class TestX509HostnameVerifier implements HostnameVerifier {
81  
82          private boolean fired;
83  
84          @Override
85          public boolean verify(final String host, final SSLSession session) {
86              this.fired = true;
87              return true;
88          }
89  
90          public boolean isFired() {
91              return this.fired;
92          }
93  
94      }
95  
96      @Test
97      public void testBasicSSL() throws Exception {
98          // @formatter:off
99          this.server = ServerBootstrap.bootstrap()
100                 .setSslContext(SSLTestContexts.createServerSSLContext())
101                 .create();
102         // @formatter:on
103         this.server.start();
104 
105         final HttpContext context = new BasicHttpContext();
106         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
107         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
108                 SSLTestContexts.createClientSSLContext(), hostVerifier);
109         try (final Socket socket = socketFactory.createSocket(context)) {
110             final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
111             final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
112             try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(
113                     TimeValue.ZERO_MILLISECONDS,
114                     socket,
115                     target,
116                     remoteAddress,
117                     null,
118                     context)) {
119                 final SSLSession sslsession = sslSocket.getSession();
120 
121                 Assert.assertNotNull(sslsession);
122                 Assert.assertTrue(hostVerifier.isFired());
123             }
124         }
125     }
126 
127     @Test
128     public void testBasicDefaultHostnameVerifier() throws Exception {
129         // @formatter:off
130         this.server = ServerBootstrap.bootstrap()
131                 .setSslContext(SSLTestContexts.createServerSSLContext())
132                 .create();
133         // @formatter:on
134         this.server.start();
135 
136         final HttpContext context = new BasicHttpContext();
137         final SSLConnectionSocketFactory socketFactory = SSLConnectionSocketFactoryBuilder.create()
138                 .setSslContext(SSLTestContexts.createClientSSLContext())
139                 .build();
140         try (final Socket socket = socketFactory.createSocket(context)) {
141             final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
142             final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
143             try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(
144                     TimeValue.ZERO_MILLISECONDS,
145                     socket,
146                     target,
147                     remoteAddress,
148                     null,
149                     context)) {
150                 final SSLSession sslsession = sslSocket.getSession();
151 
152                 Assert.assertNotNull(sslsession);
153             }
154         }
155     }
156 
157     @Test
158     public void testClientAuthSSL() throws Exception {
159         // @formatter:off
160         this.server = ServerBootstrap.bootstrap()
161                 .setSslContext(SSLTestContexts.createServerSSLContext())
162                 .create();
163         // @formatter:on
164         this.server.start();
165 
166         final HttpContext context = new BasicHttpContext();
167         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
168         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
169                 SSLTestContexts.createClientSSLContext(), hostVerifier);
170         try (final Socket socket = socketFactory.createSocket(context)) {
171             final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
172             final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
173             try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(
174                     TimeValue.ZERO_MILLISECONDS,
175                     socket,
176                     target,
177                     remoteAddress,
178                     null,
179                     context)) {
180                 final SSLSession sslsession = sslSocket.getSession();
181 
182                 Assert.assertNotNull(sslsession);
183                 Assert.assertTrue(hostVerifier.isFired());
184             }
185         }
186     }
187 
188     @Test(expected = IOException.class)
189     public void testClientAuthSSLFailure() throws Exception {
190         // @formatter:off
191         this.server = ServerBootstrap.bootstrap()
192                 .setSslContext(SSLTestContexts.createServerSSLContext())
193                 .setSslSetupHandler(new Callback<SSLParameters>() {
194 
195                     @Override
196                     public void execute(final SSLParameters sslParameters) {
197                         sslParameters.setNeedClientAuth(true);
198                     }
199 
200                 })
201                 .create();
202         // @formatter:on
203         this.server.start();
204 
205         final HttpContext context = new BasicHttpContext();
206         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
207         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
208                 SSLTestContexts.createClientSSLContext(), hostVerifier);
209         try (final Socket socket = socketFactory.createSocket(context)) {
210             final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
211             final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
212             try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(
213                     TimeValue.ZERO_MILLISECONDS,
214                     socket, target,
215                     remoteAddress,
216                     null,
217                     context)) {
218                 final SSLSession sslsession = sslSocket.getSession();
219 
220                 Assert.assertNotNull(sslsession);
221                 Assert.assertTrue(hostVerifier.isFired());
222                 sslSocket.getInputStream().read();
223             }
224         }
225     }
226 
227     @Test(expected = SSLException.class)
228     public void testSSLTrustVerification() throws Exception {
229         // @formatter:off
230         this.server = ServerBootstrap.bootstrap()
231                 .setSslContext(SSLTestContexts.createServerSSLContext())
232                 .create();
233         // @formatter:on
234         this.server.start();
235 
236         final HttpContext context = new BasicHttpContext();
237         // Use default SSL context
238         final SSLContext defaultsslcontext = SSLContexts.createDefault();
239 
240         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(defaultsslcontext,
241                 NoopHostnameVerifier.INSTANCE);
242 
243         try (final Socket socket = socketFactory.createSocket(context)) {
244             final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
245             final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
246             try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(TimeValue.ZERO_MILLISECONDS, socket, target, remoteAddress,
247                     null, context)) {
248                 // empty for now
249             }
250         }
251     }
252 
253     @Test
254     public void testSSLTrustVerificationOverrideWithCustsom() throws Exception {
255         final TrustStrategy trustStrategy = new TrustStrategy() {
256 
257             @Override
258             public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
259                 return chain.length == 1;
260             }
261 
262         };
263         testSSLTrustVerificationOverride(trustStrategy);
264     }
265 
266     @Test
267     public void testSSLTrustVerificationOverrideWithTrustSelfSignedStrategy() throws Exception {
268         testSSLTrustVerificationOverride(TrustSelfSignedStrategy.INSTANCE);
269     }
270 
271     @Test
272     public void testSSLTrustVerificationOverrideWithTrustAllStrategy() throws Exception {
273         testSSLTrustVerificationOverride(TrustAllStrategy.INSTANCE);
274     }
275 
276     private void testSSLTrustVerificationOverride(final TrustStrategy trustStrategy)
277             throws Exception, IOException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
278         // @formatter:off
279         this.server = ServerBootstrap.bootstrap()
280                 .setSslContext(SSLTestContexts.createServerSSLContext())
281                 .create();
282         // @formatter:on
283         this.server.start();
284 
285         final HttpContext context = new BasicHttpContext();
286 
287         // @formatter:off
288         final SSLContext sslcontext = SSLContexts.custom()
289             .loadTrustMaterial(null, trustStrategy)
290             .build();
291         // @formatter:on
292         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslcontext,
293                 NoopHostnameVerifier.INSTANCE);
294 
295         try (final Socket socket = socketFactory.createSocket(context)) {
296             final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
297             final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
298             try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(TimeValue.ZERO_MILLISECONDS, socket, target, remoteAddress,
299                     null, context)) {
300                 // empty for now
301             }
302         }
303     }
304 
305     @Test(expected = IOException.class)
306     public void testSSLDisabledByDefault() throws Exception {
307         // @formatter:off
308         this.server = ServerBootstrap.bootstrap()
309                 .setSslContext(SSLTestContexts.createServerSSLContext())
310                 .setSslSetupHandler(new Callback<SSLParameters>() {
311 
312                     @Override
313                     public void execute(final SSLParameters sslParameters) {
314                         sslParameters.setProtocols(new String[] {"SSLv3"});
315                     }
316 
317                 })
318                 .create();
319         // @formatter:on
320         this.server.start();
321 
322         final HttpContext context = new BasicHttpContext();
323         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
324                 SSLTestContexts.createClientSSLContext());
325         try (final Socket socket = socketFactory.createSocket(context)) {
326             final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
327             final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
328             socketFactory.connectSocket(TimeValue.ZERO_MILLISECONDS, socket, target, remoteAddress, null, context);
329         }
330     }
331 
332     @Test
333     public void testWeakCiphersDisabledByDefault() {
334         final String[] weakCiphersSuites = {
335                 "SSL_RSA_WITH_RC4_128_SHA",
336                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
337                 "TLS_DH_anon_WITH_AES_128_CBC_SHA",
338                 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
339                 "SSL_RSA_WITH_NULL_SHA",
340                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
341                 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
342                 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
343                 "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
344                 "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
345                 "TLS_RSA_WITH_NULL_SHA256",
346                 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
347                 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
348                 "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
349                 "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"
350         };
351         for (final String cipherSuite : weakCiphersSuites) {
352             try {
353                 testWeakCipherDisabledByDefault(cipherSuite);
354                 Assert.fail("IOException expected");
355             } catch (final Exception e) {
356                 Assert.assertTrue(e instanceof IOException || e instanceof IllegalArgumentException);
357             }
358         }
359     }
360 
361     private void testWeakCipherDisabledByDefault(final String cipherSuite) throws Exception {
362         // @formatter:off
363         this.server = ServerBootstrap.bootstrap()
364                 .setSslContext(SSLTestContexts.createServerSSLContext())
365                 .setSslSetupHandler(new Callback<SSLParameters>() {
366 
367                     @Override
368                     public void execute(final SSLParameters sslParameters) {
369                         sslParameters.setProtocols(new String[] {cipherSuite});
370                     }
371 
372                 })
373                 .create();
374         // @formatter:on
375         this.server.start();
376 
377         final HttpContext context = new BasicHttpContext();
378         final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
379                 SSLTestContexts.createClientSSLContext());
380         try (final Socket socket = socketFactory.createSocket(context)) {
381             final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
382             final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
383             socketFactory.connectSocket(TimeValue.ZERO_MILLISECONDS, socket, target, remoteAddress, null, context);
384         }
385     }
386 }