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.URIScheme;
50 import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
51 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
52 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
53 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
54 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
55 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
56 import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy;
57 import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy;
58 import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
59 import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
60 import org.apache.hc.core5.http.protocol.UriPatternMatcher;
61 import org.apache.hc.core5.http.ssl.TLS;
62 import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
63 import org.apache.hc.core5.io.CloseMode;
64 import org.apache.hc.core5.net.NamedEndpoint;
65 import org.apache.hc.core5.reactor.IOReactorConfig;
66 import org.apache.hc.core5.reactor.ListenerEndpoint;
67 import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer;
68 import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier;
69 import org.apache.hc.core5.reactor.ssl.TlsDetails;
70 import org.apache.hc.core5.ssl.SSLContexts;
71 import org.apache.hc.core5.testing.SSLTestContexts;
72 import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
73 import org.apache.hc.core5.util.Timeout;
74 import org.hamcrest.CoreMatchers;
75 import org.hamcrest.MatcherAssert;
76 import org.junit.Assert;
77 import org.junit.Rule;
78 import org.junit.Test;
79 import org.junit.rules.ExternalResource;
80
81 public class H2TLSIntegrationTest {
82
83 private static final Timeout TIMEOUT = Timeout.ofSeconds(30);
84
85 private HttpAsyncServer server;
86
87 @Rule
88 public ExternalResource serverResource = new ExternalResource() {
89
90 @Override
91 protected void after() {
92 if (server != null) {
93 try {
94 server.close(CloseMode.IMMEDIATE);
95 } catch (final Exception ignore) {
96 }
97 }
98 }
99
100 };
101
102 private HttpAsyncRequester requester;
103
104 @Rule
105 public ExternalResource clientResource = new ExternalResource() {
106
107 @Override
108 protected void after() {
109 if (requester != null) {
110 try {
111 requester.close(CloseMode.GRACEFUL);
112 } catch (final Exception ignore) {
113 }
114 }
115 }
116
117 };
118
119 @Test
120 public void testTLSSuccess() throws Exception {
121 server = AsyncServerBootstrap.bootstrap()
122 .setLookupRegistry(new UriPatternMatcher<Supplier<AsyncServerExchangeHandler>>())
123 .setIOReactorConfig(
124 IOReactorConfig.custom()
125 .setSoTimeout(TIMEOUT)
126 .build())
127 .setTlsStrategy(new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
128 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
129 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
130 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
131 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
132 .register("*", new Supplier<AsyncServerExchangeHandler>() {
133
134 @Override
135 public AsyncServerExchangeHandler get() {
136 return new EchoHandler(2048);
137 }
138
139 })
140 .create();
141 server.start();
142
143 final AtomicReference<SSLSession> sslSessionRef = new AtomicReference<>(null);
144
145 requester = H2RequesterBootstrap.bootstrap()
146 .setIOReactorConfig(IOReactorConfig.custom()
147 .setSoTimeout(TIMEOUT)
148 .build())
149 .setTlsStrategy(new BasicClientTlsStrategy(
150 SSLTestContexts.createClientSSLContext(),
151 new SSLSessionVerifier() {
152
153 @Override
154 public TlsDetails verify(
155 final NamedEndpoint endpoint, final SSLEngine sslEngine) throws SSLException {
156 sslSessionRef.set(sslEngine.getSession());
157 return null;
158 }
159
160 }))
161 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
162 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
163 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
164 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
165 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
166 .create();
167
168 server.start();
169 final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
170 final ListenerEndpoint listener = future.get();
171 final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
172 requester.start();
173
174 final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
175 final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
176 new BasicRequestProducer(Method.POST, target, "/stuff",
177 new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
178 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
179 final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
180 MatcherAssert.assertThat(message1, CoreMatchers.notNullValue());
181 final HttpResponse response1 = message1.getHead();
182 MatcherAssert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
183 final String body1 = message1.getBody();
184 MatcherAssert.assertThat(body1, CoreMatchers.equalTo("some stuff"));
185
186 final SSLSession sslSession = sslSessionRef.getAndSet(null);
187 final ProtocolVersion tlsVersion = TLS.parse(sslSession.getProtocol());
188 MatcherAssert.assertThat(tlsVersion.greaterEquals(TLS.V_1_2.version), CoreMatchers.equalTo(true));
189 MatcherAssert.assertThat(sslSession.getPeerPrincipal().getName(),
190 CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation"));
191 }
192
193 @Test
194 public void testTLSTrustFailure() throws Exception {
195 server = AsyncServerBootstrap.bootstrap()
196 .setLookupRegistry(new UriPatternMatcher<Supplier<AsyncServerExchangeHandler>>())
197 .setIOReactorConfig(
198 IOReactorConfig.custom()
199 .setSoTimeout(TIMEOUT)
200 .build())
201 .setTlsStrategy(new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
202 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
203 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
204 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
205 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
206 .register("*", new Supplier<AsyncServerExchangeHandler>() {
207
208 @Override
209 public AsyncServerExchangeHandler get() {
210 return new EchoHandler(2048);
211 }
212
213 })
214 .create();
215 server.start();
216
217 requester = H2RequesterBootstrap.bootstrap()
218 .setIOReactorConfig(IOReactorConfig.custom()
219 .setSoTimeout(TIMEOUT)
220 .build())
221 .setTlsStrategy(new BasicClientTlsStrategy(SSLContexts.createDefault()))
222 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
223 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
224 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
225 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
226 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
227 .create();
228
229 server.start();
230 final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
231 final ListenerEndpoint listener = future.get();
232 final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
233 requester.start();
234
235 final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
236 final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
237 new BasicRequestProducer(Method.POST, target, "/stuff",
238 new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
239 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
240 try {
241 resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
242 Assert.fail("ExecutionException expected");
243 } catch (final ExecutionException ex) {
244 final Throwable cause = ex.getCause();
245 MatcherAssert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(SSLHandshakeException.class));
246 }
247 }
248
249 @Test
250 public void testTLSClientAuthFailure() throws Exception {
251 server = AsyncServerBootstrap.bootstrap()
252 .setLookupRegistry(new UriPatternMatcher<Supplier<AsyncServerExchangeHandler>>())
253 .setIOReactorConfig(
254 IOReactorConfig.custom()
255 .setSoTimeout(TIMEOUT)
256 .build())
257 .setTlsStrategy(new BasicServerTlsStrategy(
258 SSLTestContexts.createServerSSLContext(),
259 new SSLSessionInitializer() {
260
261 @Override
262 public void initialize(final NamedEndpoint endpoint, final SSLEngine sslEngine) {
263 sslEngine.setNeedClientAuth(true);
264 }
265 },
266 null))
267 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
268 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
269 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
270 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
271 .register("*", new Supplier<AsyncServerExchangeHandler>() {
272
273 @Override
274 public AsyncServerExchangeHandler get() {
275 return new EchoHandler(2048);
276 }
277
278 })
279 .create();
280 server.start();
281
282 requester = H2RequesterBootstrap.bootstrap()
283 .setIOReactorConfig(IOReactorConfig.custom()
284 .setSoTimeout(TIMEOUT)
285 .build())
286 .setTlsStrategy(new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
287 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
288 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
289 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
290 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
291 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
292 .create();
293
294 server.start();
295 final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
296 final ListenerEndpoint listener = future.get();
297 final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
298 requester.start();
299
300 final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
301 final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
302 new BasicRequestProducer(Method.POST, target, "/stuff",
303 new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
304 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
305 try {
306 resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
307 Assert.fail("ExecutionException expected");
308 } catch (final ExecutionException ex) {
309 final Throwable cause = ex.getCause();
310 MatcherAssert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
311 }
312 }
313
314 @Test
315 public void testSSLDisabledByDefault() throws Exception {
316 server = AsyncServerBootstrap.bootstrap()
317 .setLookupRegistry(new UriPatternMatcher<Supplier<AsyncServerExchangeHandler>>())
318 .setIOReactorConfig(
319 IOReactorConfig.custom()
320 .setSoTimeout(TIMEOUT)
321 .build())
322 .setTlsStrategy(new BasicServerTlsStrategy(
323 SSLTestContexts.createServerSSLContext(),
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), URIScheme.HTTPS);
361 final ListenerEndpoint listener = future.get();
362 final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
363 requester.start();
364
365 final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "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 MatcherAssert.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 new SSLSessionInitializer() {
422
423 @Override
424 public void initialize(final NamedEndpoint endpoint, final SSLEngine sslEngine) {
425 sslEngine.setEnabledCipherSuites(new String[]{cipherSuite});
426 }
427 },
428 null))
429 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
430 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
431 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
432 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
433 .register("*", new Supplier<AsyncServerExchangeHandler>() {
434
435 @Override
436 public AsyncServerExchangeHandler get() {
437 return new EchoHandler(2048);
438 }
439
440 })
441 .create();
442 try {
443 server.start();
444 final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
445 final ListenerEndpoint listener = future.get();
446 final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
447
448 final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
449 final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
450 new BasicRequestProducer(Method.POST, target, "/stuff",
451 new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
452 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
453 try {
454 resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
455 Assert.fail("ExecutionException expected");
456 } catch (final ExecutionException ex) {
457 final Throwable cause = ex.getCause();
458 MatcherAssert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
459 }
460 } finally {
461 server.close(CloseMode.IMMEDIATE);
462 }
463 }
464 }
465
466 }