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