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