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