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