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.InetAddress;
34  import java.net.Socket;
35  import java.util.Objects;
36  
37  import javax.net.ssl.HostnameVerifier;
38  import javax.net.ssl.SSLContext;
39  import javax.net.ssl.SSLException;
40  import javax.net.ssl.SSLHandshakeException;
41  import javax.net.ssl.SSLPeerUnverifiedException;
42  import javax.net.ssl.SSLSession;
43  import javax.net.ssl.SSLSocket;
44  
45  import org.apache.hc.client5.http.protocol.HttpClientContext;
46  import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
47  import org.apache.hc.client5.http.ssl.HostnameVerificationPolicy;
48  import org.apache.hc.client5.http.ssl.HttpsSupport;
49  import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
50  import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
51  import org.apache.hc.client5.testing.SSLTestContexts;
52  import org.apache.hc.core5.http.HttpHost;
53  import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
54  import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
55  import org.apache.hc.core5.io.CloseMode;
56  import org.apache.hc.core5.ssl.SSLContexts;
57  import org.apache.hc.core5.ssl.TrustStrategy;
58  import org.hamcrest.CoreMatchers;
59  import org.hamcrest.MatcherAssert;
60  import org.hamcrest.Matchers;
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 DefaultClientTlsStrategy}.
67   */
68  class TestDefaultClientTlsStrategy {
69  
70      private HttpServer server;
71  
72      @AfterEach
73      void shutDown() {
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      void testBasicSSL() throws Exception {
97          // @formatter:off
98          this.server = ServerBootstrap.bootstrap()
99                  .setSslContext(SSLTestContexts.createServerSSLContext())
100                 .setRequestRouter((r, c) -> null)
101                 .create();
102         // @formatter:on
103         this.server.start();
104 
105         final HttpClientContext context = HttpClientContext.create();
106         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
107         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
108                 SSLTestContexts.createClientSSLContext(), hostVerifier);
109         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
110         try (final Socket socket = new Socket(target.getHostName(), target.getPort())) {
111             try (final SSLSocket sslSocket = tlsStrategy.upgrade(
112                     socket,
113                     target.getHostName(),
114                     target.getPort(),
115                     null,
116                     context)) {
117                 final SSLSession sslsession = sslSocket.getSession();
118 
119                 Assertions.assertNotNull(sslsession);
120                 Assertions.assertTrue(hostVerifier.isFired());
121             }
122         }
123     }
124 
125     @Test
126     void testBasicDefaultHostnameVerifier() throws Exception {
127         // @formatter:off
128         this.server = ServerBootstrap.bootstrap()
129                 .setSslContext(SSLTestContexts.createServerSSLContext())
130                 .setRequestRouter((r, c) -> null)
131                 .create();
132         // @formatter:on
133         this.server.start();
134 
135         final HttpClientContext context = HttpClientContext.create();
136         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(SSLTestContexts.createClientSSLContext());
137         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
138         try (final Socket socket = new Socket(target.getHostName(), target.getPort())) {
139             try (final SSLSocket sslSocket = tlsStrategy.upgrade(
140                     socket,
141                     target.getHostName(),
142                     target.getPort(),
143                     null,
144                     context)) {
145                 final SSLSession sslsession = sslSocket.getSession();
146 
147                 Assertions.assertNotNull(sslsession);
148             }
149         }
150     }
151 
152     @Test
153     void testClientAuthSSL() throws Exception {
154         // @formatter:off
155         this.server = ServerBootstrap.bootstrap()
156                 .setSslContext(SSLTestContexts.createServerSSLContext())
157                 .setRequestRouter((r, c) -> null)
158                 .create();
159         // @formatter:on
160         this.server.start();
161 
162         final HttpClientContext context = HttpClientContext.create();
163         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
164         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
165                 SSLTestContexts.createClientSSLContext(), hostVerifier);
166         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
167         try (final Socket socket = new Socket(target.getHostName(), target.getPort())) {
168             try (final SSLSocket sslSocket = tlsStrategy.upgrade(
169                     socket,
170                     target.getHostName(),
171                     target.getPort(),
172                     null,
173                     context)) {
174                 final SSLSession sslsession = sslSocket.getSession();
175 
176                 Assertions.assertNotNull(sslsession);
177                 Assertions.assertTrue(hostVerifier.isFired());
178             }
179         }
180     }
181 
182     @Test
183     void testClientAuthSSLFailure() throws Exception {
184         // @formatter:off
185         this.server = ServerBootstrap.bootstrap()
186                 .setSslContext(SSLTestContexts.createServerSSLContext())
187                 .setSslSetupHandler(sslParameters -> sslParameters.setNeedClientAuth(true))
188                 .setRequestRouter((r, c) -> null)
189                 .create();
190         // @formatter:on
191         this.server.start();
192 
193         final HttpClientContext context = HttpClientContext.create();
194         final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
195         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
196                 SSLTestContexts.createClientSSLContext(), hostVerifier);
197         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
198         try (final Socket socket = new Socket(target.getHostName(), target.getPort())) {
199             Assertions.assertThrows(IOException.class, () -> {
200                 try (final SSLSocket sslSocket = tlsStrategy.upgrade(
201                         socket,
202                         target.getHostName(),
203                         target.getPort(),
204                         null,
205                         context)) {
206                     final SSLSession sslsession = sslSocket.getSession();
207 
208                     Assertions.assertNotNull(sslsession);
209                     Assertions.assertTrue(hostVerifier.isFired());
210                     sslSocket.getInputStream().read();
211                 }
212             });
213         }
214     }
215 
216     @Test
217     void testSSLTrustVerification() throws Exception {
218         // @formatter:off
219         this.server = ServerBootstrap.bootstrap()
220                 .setSslContext(SSLTestContexts.createServerSSLContext())
221                 .setRequestRouter((r, c) -> null)
222                 .create();
223         // @formatter:on
224         this.server.start();
225 
226         final HttpClientContext context = HttpClientContext.create();
227         // Use default SSL context
228         final SSLContext defaultSslContext = SSLContexts.createDefault();
229 
230         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(defaultSslContext,
231                 NoopHostnameVerifier.INSTANCE);
232 
233         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
234         try (final Socket socket = new Socket(target.getHostName(), target.getPort())) {
235             Assertions.assertThrows(SSLException.class, () -> {
236                 try (final SSLSocket sslSocket = tlsStrategy.upgrade(
237                         socket, target.getHostName(), target.getPort(), null, context)) {
238                     // empty for now
239                 }
240             });
241         }
242     }
243 
244     @Test
245     void testSSLTrustVerificationOverrideWithCustom() throws Exception {
246         final TrustStrategy trustStrategy = (chain, authType) -> chain.length == 1;
247         testSSLTrustVerificationOverride(trustStrategy);
248     }
249 
250     private void testSSLTrustVerificationOverride(final TrustStrategy trustStrategy)
251             throws Exception {
252         // @formatter:off
253         this.server = ServerBootstrap.bootstrap()
254                 .setSslContext(SSLTestContexts.createServerSSLContext())
255                 .setRequestRouter((r, c) -> null)
256                 .create();
257         // @formatter:on
258         this.server.start();
259 
260         final HttpClientContext context = HttpClientContext.create();
261 
262         // @formatter:off
263         final SSLContext sslContext = SSLContexts.custom()
264             .loadTrustMaterial(null, trustStrategy)
265             .build();
266         // @formatter:on
267         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(sslContext,
268                 NoopHostnameVerifier.INSTANCE);
269 
270         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
271         try (final Socket socket = new Socket(target.getHostName(), target.getPort())) {
272             try (final SSLSocket sslSocket = tlsStrategy.upgrade(
273                     socket,
274                     target.getHostName(),
275                     target.getPort(),
276                     null,
277                     context)) {
278                 // empty for now
279             }
280         }
281     }
282 
283     @Test
284     void testSSLDisabledByDefault() throws Exception {
285         // @formatter:off
286         this.server = ServerBootstrap.bootstrap()
287                 .setSslContext(SSLTestContexts.createServerSSLContext())
288                 .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[] {"SSLv3"}))
289                 .setRequestRouter((r, c) -> null)
290                 .create();
291         // @formatter:on
292         this.server.start();
293 
294         final HttpClientContext context = HttpClientContext.create();
295         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
296                 SSLTestContexts.createClientSSLContext());
297         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
298         try (final Socket socket = new Socket(target.getHostName(), target.getPort())) {
299             Assertions.assertThrows(IOException.class, () ->
300                     tlsStrategy.upgrade(
301                             socket,
302                             target.getHostName(),
303                             target.getPort(),
304                             null,
305                             context));
306         }
307     }
308 
309     @Test
310     void testWeakCiphersDisabledByDefault() {
311         final String[] weakCiphersSuites = {
312                 "SSL_RSA_WITH_RC4_128_SHA",
313                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
314                 "TLS_DH_anon_WITH_AES_128_CBC_SHA",
315                 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
316                 "SSL_RSA_WITH_NULL_SHA",
317                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
318                 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
319                 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
320                 "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
321                 "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
322                 "TLS_RSA_WITH_NULL_SHA256",
323                 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
324                 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
325                 "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
326                 "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"
327         };
328         for (final String cipherSuite : weakCiphersSuites) {
329             final Exception exception = Assertions.assertThrows(Exception.class, () ->
330                     testWeakCipherDisabledByDefault(cipherSuite));
331             assertThat(exception, CoreMatchers.anyOf(
332                     CoreMatchers.instanceOf(IOException.class),
333                     CoreMatchers.instanceOf(IllegalArgumentException.class)));
334         }
335     }
336 
337     private void testWeakCipherDisabledByDefault(final String cipherSuite) throws Exception {
338         // @formatter:off
339         this.server = ServerBootstrap.bootstrap()
340                 .setSslContext(SSLTestContexts.createServerSSLContext())
341                 .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[] {cipherSuite}))
342                 .setRequestRouter((r, c) -> null)
343                 .create();
344         // @formatter:on
345         this.server.start();
346 
347         final HttpClientContext context = HttpClientContext.create();
348         final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
349                 SSLTestContexts.createClientSSLContext());
350         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
351         try (final Socket socket = new Socket(target.getHostName(), target.getPort())) {
352             tlsStrategy.upgrade(
353                     socket,
354                     target.getHostName(),
355                     target.getPort(),
356                     null,
357                     context);
358         }
359     }
360 
361     @Test
362     void testHostnameVerificationClient() throws Exception {
363         // @formatter:off
364         this.server = ServerBootstrap.bootstrap()
365                 .setSslContext(SSLTestContexts.createServerSSLContext())
366                 .setRequestRouter((r, c) -> null)
367                 .create();
368         // @formatter:on
369         this.server.start();
370 
371         final HttpHost target1 = new HttpHost("https", "localhost", server.getLocalPort());
372 
373         try (final Socket socket = new Socket(InetAddress.getLocalHost(), server.getLocalPort())) {
374             final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
375                     SSLTestContexts.createClientSSLContext(),
376                     HostnameVerificationPolicy.CLIENT,
377                     HttpsSupport.getDefaultHostnameVerifier());
378             final HttpClientContext context = HttpClientContext.create();
379             final SSLSocket upgradedSocket = tlsStrategy.upgrade(
380                     socket,
381                     target1.getHostName(),
382                     target1.getPort(),
383                     null,
384                     context);
385             final SSLSession session = upgradedSocket.getSession();
386             MatcherAssert.assertThat(Objects.toString(session.getPeerPrincipal()), Matchers.startsWith("CN=localhost"));
387         }
388 
389         final HttpHost target2 = new HttpHost("https", "some-other-host", server.getLocalPort());
390 
391         try (final Socket socket = new Socket(InetAddress.getLocalHost(), server.getLocalPort())) {
392             final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
393                     SSLTestContexts.createClientSSLContext(),
394                     HostnameVerificationPolicy.CLIENT,
395                     HttpsSupport.getDefaultHostnameVerifier());
396             final HttpClientContext context = HttpClientContext.create();
397             Assertions.assertThrows(SSLPeerUnverifiedException.class, () ->
398                     tlsStrategy.upgrade(
399                             socket,
400                             target2.getHostName(),
401                             target2.getPort(),
402                             null,
403                             context));
404         }
405 
406         try (final Socket socket = new Socket(InetAddress.getLocalHost(), server.getLocalPort())) {
407             final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
408                     SSLTestContexts.createClientSSLContext(),
409                     HostnameVerificationPolicy.CLIENT,
410                     NoopHostnameVerifier.INSTANCE);
411             final HttpClientContext context = HttpClientContext.create();
412             final SSLSocket upgradedSocket = tlsStrategy.upgrade(
413                     socket,
414                     target1.getHostName(),
415                     target1.getPort(),
416                     null,
417                     context);
418             final SSLSession session = upgradedSocket.getSession();
419             MatcherAssert.assertThat(Objects.toString(session.getPeerPrincipal()), Matchers.startsWith("CN=localhost"));
420         }
421     }
422 
423     @Test
424     void testHostnameVerificationBuiltIn() throws Exception {
425         // @formatter:off
426         this.server = ServerBootstrap.bootstrap()
427                 .setSslContext(SSLTestContexts.createServerSSLContext())
428                 .setRequestRouter((r, c) -> null)
429                 .create();
430         // @formatter:on
431         this.server.start();
432 
433         final HttpHost target1 = new HttpHost("https", "localhost", server.getLocalPort());
434 
435         try (final Socket socket = new Socket(InetAddress.getLocalHost(), server.getLocalPort())) {
436             final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
437                     SSLTestContexts.createClientSSLContext(),
438                     HostnameVerificationPolicy.BUILTIN,
439                     NoopHostnameVerifier.INSTANCE);
440             final HttpClientContext context = HttpClientContext.create();
441             final SSLSocket upgradedSocket = tlsStrategy.upgrade(
442                     socket,
443                     target1.getHostName(),
444                     target1.getPort(),
445                     null,
446                     context);
447             final SSLSession session = upgradedSocket.getSession();
448             MatcherAssert.assertThat(Objects.toString(session.getPeerPrincipal()), Matchers.startsWith("CN=localhost"));
449         }
450 
451         final HttpHost target2 = new HttpHost("https", "some-other-host", server.getLocalPort());
452 
453         try (final Socket socket = new Socket(InetAddress.getLocalHost(), server.getLocalPort())) {
454             final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
455                     SSLTestContexts.createClientSSLContext(),
456                     HostnameVerificationPolicy.BUILTIN,
457                     NoopHostnameVerifier.INSTANCE);
458             final HttpClientContext context = HttpClientContext.create();
459             Assertions.assertThrows(SSLHandshakeException.class, () ->
460                     tlsStrategy.upgrade(
461                             socket,
462                             target2.getHostName(),
463                             target2.getPort(),
464                             null,
465                             context));
466         }
467     }
468 
469 }