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.core5.testing.nio;
29  
30  import java.io.IOException;
31  import java.net.InetSocketAddress;
32  import java.util.concurrent.ExecutionException;
33  import java.util.concurrent.Future;
34  import java.util.concurrent.atomic.AtomicReference;
35  
36  import javax.net.ssl.SSLEngine;
37  import javax.net.ssl.SSLException;
38  import javax.net.ssl.SSLHandshakeException;
39  import javax.net.ssl.SSLSession;
40  
41  import org.apache.hc.core5.function.Supplier;
42  import org.apache.hc.core5.http.ContentType;
43  import org.apache.hc.core5.http.HttpHost;
44  import org.apache.hc.core5.http.HttpResponse;
45  import org.apache.hc.core5.http.HttpStatus;
46  import org.apache.hc.core5.http.Message;
47  import org.apache.hc.core5.http.Method;
48  import org.apache.hc.core5.http.ProtocolVersion;
49  import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
50  import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
51  import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
52  import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
53  import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
54  import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
55  import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy;
56  import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy;
57  import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
58  import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
59  import org.apache.hc.core5.http.protocol.UriPatternMatcher;
60  import org.apache.hc.core5.http.ssl.TLS;
61  import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
62  import org.apache.hc.core5.io.CloseMode;
63  import org.apache.hc.core5.net.NamedEndpoint;
64  import org.apache.hc.core5.reactor.IOReactorConfig;
65  import org.apache.hc.core5.reactor.ListenerEndpoint;
66  import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer;
67  import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier;
68  import org.apache.hc.core5.reactor.ssl.TlsDetails;
69  import org.apache.hc.core5.ssl.SSLContexts;
70  import org.apache.hc.core5.testing.SSLTestContexts;
71  import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
72  import org.apache.hc.core5.util.ReflectionUtils;
73  import org.apache.hc.core5.util.Timeout;
74  import org.hamcrest.CoreMatchers;
75  import org.junit.Assert;
76  import org.junit.Assume;
77  import org.junit.BeforeClass;
78  import org.junit.Rule;
79  import org.junit.Test;
80  import org.junit.rules.ExternalResource;
81  
82  public class H2TLSIntegrationTest {
83  
84      private static final Timeout TIMEOUT = Timeout.ofSeconds(30);
85  
86      private HttpAsyncServer server;
87  
88      @Rule
89      public ExternalResource serverResource = new ExternalResource() {
90  
91          @Override
92          protected void after() {
93              if (server != null) {
94                  try {
95                      server.close(CloseMode.IMMEDIATE);
96                  } catch (final Exception ignore) {
97                  }
98              }
99          }
100 
101     };
102 
103     private HttpAsyncRequester requester;
104 
105     @Rule
106     public ExternalResource clientResource = new ExternalResource() {
107 
108         @Override
109         protected void after() {
110             if (requester != null) {
111                 try {
112                     requester.close(CloseMode.GRACEFUL);
113                 } catch (final Exception ignore) {
114                 }
115             }
116         }
117 
118     };
119 
120     private static int javaVersion;
121 
122     @BeforeClass
123     public static void determineJavaVersion() {
124         javaVersion = ReflectionUtils.determineJRELevel();
125     }
126 
127     @Test
128     public void testTLSSuccess() throws Exception {
129         server = AsyncServerBootstrap.bootstrap()
130                 .setLookupRegistry(new UriPatternMatcher<Supplier<AsyncServerExchangeHandler>>())
131                 .setIOReactorConfig(
132                         IOReactorConfig.custom()
133                                 .setSoTimeout(TIMEOUT)
134                                 .build())
135                 .setTlsStrategy(new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext(), SecureAllPortsStrategy.INSTANCE))
136                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
137                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
138                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
139                 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
140                 .register("*", new Supplier<AsyncServerExchangeHandler>() {
141 
142                     @Override
143                     public AsyncServerExchangeHandler get() {
144                         return new EchoHandler(2048);
145                     }
146 
147                 })
148                 .create();
149         server.start();
150 
151         final AtomicReference<SSLSession> sslSessionRef = new AtomicReference<>(null);
152 
153         requester = H2RequesterBootstrap.bootstrap()
154                 .setIOReactorConfig(IOReactorConfig.custom()
155                         .setSoTimeout(TIMEOUT)
156                         .build())
157                 .setTlsStrategy(new BasicClientTlsStrategy(
158                         SSLTestContexts.createClientSSLContext(),
159                         new SSLSessionVerifier() {
160 
161                             @Override
162                             public TlsDetails verify(
163                                     final NamedEndpoint endpoint, final SSLEngine sslEngine) throws SSLException {
164                                 sslSessionRef.set(sslEngine.getSession());
165                                 return null;
166                             }
167 
168                         }))
169                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
170                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
171                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
172                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
173                 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
174                 .create();
175 
176         server.start();
177         final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
178         final ListenerEndpoint listener = future.get();
179         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
180         requester.start();
181 
182         final HttpHost target = new HttpHost("https", "localhost", address.getPort());
183         final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
184                 new BasicRequestProducer(Method.POST, target, "/stuff",
185                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
186                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
187         final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
188         Assert.assertThat(message1, CoreMatchers.notNullValue());
189         final HttpResponse response1 = message1.getHead();
190         Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
191         final String body1 = message1.getBody();
192         Assert.assertThat(body1, CoreMatchers.equalTo("some stuff"));
193 
194         final SSLSession sslSession = sslSessionRef.getAndSet(null);
195         final ProtocolVersion tlsVersion = TLS.parse(sslSession.getProtocol());
196         Assert.assertThat(tlsVersion.greaterEquals(TLS.V_1_2.version), CoreMatchers.equalTo(true));
197         Assert.assertThat(sslSession.getPeerPrincipal().getName(),
198                 CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation"));
199     }
200 
201     @Test
202     public void testTLSTrustFailure() throws Exception {
203         server = AsyncServerBootstrap.bootstrap()
204                 .setLookupRegistry(new UriPatternMatcher<Supplier<AsyncServerExchangeHandler>>())
205                 .setIOReactorConfig(
206                         IOReactorConfig.custom()
207                                 .setSoTimeout(TIMEOUT)
208                                 .build())
209                 .setTlsStrategy(new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext(), SecureAllPortsStrategy.INSTANCE))
210                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
211                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
212                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
213                 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
214                 .register("*", new Supplier<AsyncServerExchangeHandler>() {
215 
216                     @Override
217                     public AsyncServerExchangeHandler get() {
218                         return new EchoHandler(2048);
219                     }
220 
221                 })
222                 .create();
223         server.start();
224 
225         requester = H2RequesterBootstrap.bootstrap()
226                 .setIOReactorConfig(IOReactorConfig.custom()
227                         .setSoTimeout(TIMEOUT)
228                         .build())
229                 .setTlsStrategy(new BasicClientTlsStrategy(SSLContexts.createDefault()))
230                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
231                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
232                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
233                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
234                 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
235                 .create();
236 
237         server.start();
238         final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
239         final ListenerEndpoint listener = future.get();
240         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
241         requester.start();
242 
243         final HttpHost target = new HttpHost("https", "localhost", address.getPort());
244         final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
245                 new BasicRequestProducer(Method.POST, target, "/stuff",
246                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
247                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
248         try {
249             resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
250             Assert.fail("ExecutionException expected");
251         } catch (final ExecutionException ex) {
252             final Throwable cause = ex.getCause();
253             Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(SSLHandshakeException.class));
254         }
255     }
256 
257     @Test
258     public void testTLSClientAuthFailure() throws Exception {
259         server = AsyncServerBootstrap.bootstrap()
260                 .setLookupRegistry(new UriPatternMatcher<Supplier<AsyncServerExchangeHandler>>())
261                 .setIOReactorConfig(
262                         IOReactorConfig.custom()
263                                 .setSoTimeout(TIMEOUT)
264                                 .build())
265                 .setTlsStrategy(new BasicServerTlsStrategy(
266                         SSLTestContexts.createServerSSLContext(),
267                         SecureAllPortsStrategy.INSTANCE,
268                         new SSLSessionInitializer() {
269 
270                             @Override
271                             public void initialize(final NamedEndpoint endpoint, final SSLEngine sslEngine) {
272                                 sslEngine.setNeedClientAuth(true);
273                             }
274                         },
275                         null))
276                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
277                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
278                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
279                 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
280                 .register("*", new Supplier<AsyncServerExchangeHandler>() {
281 
282                     @Override
283                     public AsyncServerExchangeHandler get() {
284                         return new EchoHandler(2048);
285                     }
286 
287                 })
288                 .create();
289         server.start();
290 
291         requester = H2RequesterBootstrap.bootstrap()
292                 .setIOReactorConfig(IOReactorConfig.custom()
293                         .setSoTimeout(TIMEOUT)
294                         .build())
295                 .setTlsStrategy(new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
296                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
297                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
298                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
299                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
300                 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
301                 .create();
302 
303         server.start();
304         final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
305         final ListenerEndpoint listener = future.get();
306         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
307         requester.start();
308 
309         final HttpHost target = new HttpHost("https", "localhost", address.getPort());
310         final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
311                 new BasicRequestProducer(Method.POST, target, "/stuff",
312                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
313                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
314         try {
315             resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
316             Assert.fail("ExecutionException expected");
317         } catch (final ExecutionException ex) {
318             final Throwable cause = ex.getCause();
319             Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
320         }
321     }
322 
323     @Test
324     public void testSSLDisabledByDefault() throws Exception {
325         server = AsyncServerBootstrap.bootstrap()
326                 .setLookupRegistry(new UriPatternMatcher<Supplier<AsyncServerExchangeHandler>>())
327                 .setIOReactorConfig(
328                         IOReactorConfig.custom()
329                                 .setSoTimeout(TIMEOUT)
330                                 .build())
331                 .setTlsStrategy(new BasicServerTlsStrategy(
332                         SSLTestContexts.createServerSSLContext(),
333                         SecureAllPortsStrategy.INSTANCE,
334                         new SSLSessionInitializer() {
335 
336                             @Override
337                             public void initialize(final NamedEndpoint endpoint, final SSLEngine sslEngine) {
338                                 sslEngine.setEnabledProtocols(new String[]{"SSLv3"});
339                             }
340                         },
341                         null))
342                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
343                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
344                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
345                 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
346                 .register("*", new Supplier<AsyncServerExchangeHandler>() {
347 
348                     @Override
349                     public AsyncServerExchangeHandler get() {
350                         return new EchoHandler(2048);
351                     }
352 
353                 })
354                 .create();
355         server.start();
356 
357         requester = H2RequesterBootstrap.bootstrap()
358                 .setIOReactorConfig(IOReactorConfig.custom()
359                         .setSoTimeout(TIMEOUT)
360                         .build())
361                 .setTlsStrategy(new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
362                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
363                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
364                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
365                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
366                 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
367                 .create();
368 
369         server.start();
370         final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
371         final ListenerEndpoint listener = future.get();
372         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
373         requester.start();
374 
375         final HttpHost target = new HttpHost("https", "localhost", address.getPort());
376         final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
377                 new BasicRequestProducer(Method.POST, target, "/stuff",
378                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
379                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
380         try {
381             resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
382             Assert.fail("ExecutionException expected");
383         } catch (final ExecutionException ex) {
384             final Throwable cause = ex.getCause();
385             Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
386         }
387     }
388 
389     @Test
390     public void testWeakCiphersDisabledByDefault() throws Exception {
391         Assume.assumeTrue("Java version must be 1.8 or lesser",  javaVersion <= 8);
392         requester = H2RequesterBootstrap.bootstrap()
393                 .setIOReactorConfig(IOReactorConfig.custom()
394                         .setSoTimeout(TIMEOUT)
395                         .build())
396                 .setTlsStrategy(new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
397                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
398                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
399                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
400                 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
401                 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
402                 .create();
403         requester.start();
404 
405         final String[] weakCiphersSuites = {
406                 "SSL_RSA_WITH_RC4_128_SHA",
407                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
408                 "TLS_DH_anon_WITH_AES_128_CBC_SHA",
409                 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
410                 "SSL_RSA_WITH_NULL_SHA",
411                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
412                 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
413                 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
414                 "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
415                 "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
416                 "TLS_RSA_WITH_NULL_SHA256",
417                 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
418                 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
419                 "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
420                 "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"
421         };
422 
423         for (final String cipherSuite : weakCiphersSuites) {
424             server = AsyncServerBootstrap.bootstrap()
425                     .setLookupRegistry(new UriPatternMatcher<Supplier<AsyncServerExchangeHandler>>())
426                     .setIOReactorConfig(
427                             IOReactorConfig.custom()
428                                     .setSoTimeout(TIMEOUT)
429                                     .build())
430                     .setTlsStrategy(new BasicServerTlsStrategy(
431                             SSLTestContexts.createServerSSLContext(),
432                             SecureAllPortsStrategy.INSTANCE,
433                             new SSLSessionInitializer() {
434 
435                                 @Override
436                                 public void initialize(final NamedEndpoint endpoint, final SSLEngine sslEngine) {
437                                     sslEngine.setEnabledCipherSuites(new String[]{cipherSuite});
438                                 }
439                             },
440                             null))
441                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
442                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
443                     .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
444                     .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
445                     .register("*", new Supplier<AsyncServerExchangeHandler>() {
446 
447                         @Override
448                         public AsyncServerExchangeHandler get() {
449                             return new EchoHandler(2048);
450                         }
451 
452                     })
453                     .create();
454             try {
455                 server.start();
456                 final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
457                 final ListenerEndpoint listener = future.get();
458                 final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
459 
460                 final HttpHost target = new HttpHost("https", "localhost", address.getPort());
461                 final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
462                         new BasicRequestProducer(Method.POST, target, "/stuff",
463                                 new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
464                         new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
465                 try {
466                     resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
467                     Assert.fail("ExecutionException expected");
468                 } catch (final ExecutionException ex) {
469                     final Throwable cause = ex.getCause();
470                     Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
471                 }
472             } finally {
473                 server.close(CloseMode.IMMEDIATE);
474             }
475         }
476     }
477 
478 }