1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 }