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.InetAddress;
32 import java.net.InetSocketAddress;
33 import java.net.SocketAddress;
34 import java.util.concurrent.ExecutionException;
35 import java.util.concurrent.Future;
36 import java.util.stream.Stream;
37
38 import javax.net.ssl.SSLContext;
39 import javax.net.ssl.SSLHandshakeException;
40 import javax.net.ssl.SSLSession;
41
42 import org.apache.hc.core5.concurrent.BasicFuture;
43 import org.apache.hc.core5.concurrent.FutureCallback;
44 import org.apache.hc.core5.concurrent.FutureContribution;
45 import org.apache.hc.core5.http.ContentType;
46 import org.apache.hc.core5.http.HttpHost;
47 import org.apache.hc.core5.http.HttpResponse;
48 import org.apache.hc.core5.http.Message;
49 import org.apache.hc.core5.http.Method;
50 import org.apache.hc.core5.http.ProtocolVersion;
51 import org.apache.hc.core5.http.URIScheme;
52 import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap;
53 import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
54 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
55 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
56 import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
57 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
58 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
59 import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy;
60 import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy;
61 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
62 import org.apache.hc.core5.http.nio.ssl.TlsSupport;
63 import org.apache.hc.core5.http.nio.ssl.TlsUpgradeCapable;
64 import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
65 import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
66 import org.apache.hc.core5.http.protocol.UriPatternMatcher;
67 import org.apache.hc.core5.http.ssl.TLS;
68 import org.apache.hc.core5.io.CloseMode;
69 import org.apache.hc.core5.net.NamedEndpoint;
70 import org.apache.hc.core5.reactor.IOReactorConfig;
71 import org.apache.hc.core5.reactor.ListenerEndpoint;
72 import org.apache.hc.core5.reactor.ProtocolIOSession;
73 import org.apache.hc.core5.reactor.ssl.SSLBufferMode;
74 import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer;
75 import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier;
76 import org.apache.hc.core5.reactor.ssl.TlsDetails;
77 import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer;
78 import org.apache.hc.core5.ssl.SSLContexts;
79 import org.apache.hc.core5.testing.SSLTestContexts;
80 import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
81 import org.apache.hc.core5.util.Args;
82 import org.apache.hc.core5.util.ReflectionUtils;
83 import org.apache.hc.core5.util.Timeout;
84 import org.hamcrest.CoreMatchers;
85 import org.hamcrest.MatcherAssert;
86 import org.junit.jupiter.api.Assertions;
87 import org.junit.jupiter.api.Test;
88 import org.junit.jupiter.api.extension.AfterEachCallback;
89 import org.junit.jupiter.api.extension.ExtensionContext;
90 import org.junit.jupiter.api.extension.RegisterExtension;
91 import org.junit.jupiter.params.ParameterizedTest;
92 import org.junit.jupiter.params.provider.Arguments;
93 import org.junit.jupiter.params.provider.ArgumentsProvider;
94 import org.junit.jupiter.params.provider.ArgumentsSource;
95 import org.junit.jupiter.params.provider.ValueSource;
96
97 public class TLSIntegrationTest {
98
99 private static final Timeout TIMEOUT = Timeout.ofSeconds(30);
100
101 private HttpAsyncServer server;
102
103 @RegisterExtension
104 public final AfterEachCallback serverCleanup = new AfterEachCallback() {
105
106 @Override
107 public void afterEach(final ExtensionContext context) throws Exception {
108 if (server != null) {
109 try {
110 server.close(CloseMode.IMMEDIATE);
111 } catch (final Exception ignore) {
112 }
113 }
114 }
115
116 };
117
118 private HttpAsyncRequester client;
119
120 @RegisterExtension
121 public final AfterEachCallback clientCleanup = new AfterEachCallback() {
122
123 @Override
124 public void afterEach(final ExtensionContext context) throws Exception {
125 if (client != null) {
126 try {
127 client.close(CloseMode.GRACEFUL);
128 } catch (final Exception ignore) {
129 }
130 }
131 }
132
133 };
134
135 HttpAsyncServer createServer(final TlsStrategy tlsStrategy) {
136 return AsyncServerBootstrap.bootstrap()
137 .setLookupRegistry(new UriPatternMatcher<>())
138 .setIOReactorConfig(
139 IOReactorConfig.custom()
140 .setSoTimeout(TIMEOUT)
141 .setIoThreadCount(1)
142 .build())
143 .setTlsStrategy(tlsStrategy)
144 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
145 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
146 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
147 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
148 .register("*", () -> new EchoHandler(2048))
149 .create();
150 }
151
152 HttpAsyncRequester createClient(final TlsStrategy tlsStrategy) {
153 return AsyncRequesterBootstrap.bootstrap()
154 .setIOReactorConfig(IOReactorConfig.custom()
155 .setSoTimeout(TIMEOUT)
156 .build())
157 .setTlsStrategy(tlsStrategy)
158 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
159 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
160 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
161 .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
162 .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
163 .create();
164 }
165
166 Future<TlsDetails> executeTlsHandshake() throws Exception {
167 final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
168 final ListenerEndpoint listener = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
169 final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
170
171 final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
172
173 final BasicFuture<TlsDetails> tlsFuture = new BasicFuture<>(null);
174 client.connect(
175 new HttpHost(URIScheme.HTTP.id, "localhost", address.getPort()),
176 TIMEOUT, null,
177 new FutureContribution<AsyncClientEndpoint>(tlsFuture) {
178
179 @Override
180 public void completed(final AsyncClientEndpoint clientEndpoint) {
181 try {
182 ((TlsUpgradeCapable) clientEndpoint). tlsUpgrade(
183 target,
184 new FutureContribution<ProtocolIOSession>(tlsFuture) {
185
186 @Override
187 public void completed(final ProtocolIOSession protocolIOSession) {
188 tlsFuture.completed(protocolIOSession.getTlsDetails());
189 }
190
191 });
192 } catch (final Exception ex) {
193 tlsFuture.failed(ex);
194 }
195 }
196
197 });
198 return tlsFuture;
199 }
200
201 @ParameterizedTest(name = "TLS protocol {0}")
202 @ArgumentsSource(SupportedTLSProtocolProvider.class)
203 public void testTLSSuccess(final TLS tlsProtocol) throws Exception {
204 final TlsStrategy serverTlsStrategy = new TestTlsStrategy(
205 SSLTestContexts.createServerSSLContext(),
206 (endpoint, sslEngine) -> sslEngine.setEnabledProtocols(new String[]{tlsProtocol.getId()}),
207 null);
208 server = createServer(serverTlsStrategy);
209 server.start();
210
211 final TlsStrategy clientTlsStrategy = new TestTlsStrategy(SSLTestContexts.createClientSSLContext(),
212 (endpoint, sslEngine) -> sslEngine.setEnabledProtocols(new String[]{tlsProtocol.getId()}),
213 null);
214 client = createClient(clientTlsStrategy);
215 client.start();
216
217 final Future<TlsDetails> tlsSessionFuture = executeTlsHandshake();
218
219 final TlsDetails tlsDetails = tlsSessionFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
220 Assertions.assertNotNull(tlsDetails);
221 final SSLSession tlsSession = tlsDetails.getSSLSession();
222 final ProtocolVersion tlsVersion = TLS.parse(tlsSession.getProtocol());
223 MatcherAssert.assertThat(tlsVersion.greaterEquals(tlsProtocol.version), CoreMatchers.equalTo(true));
224 MatcherAssert.assertThat(tlsSession.getPeerPrincipal().getName(),
225 CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation"));
226 }
227
228 @Test
229 public void testTLSTrustFailure() throws Exception {
230 final TlsStrategy serverTlsStrategy = new BasicServerTlsStrategy(SSLTestContexts.createServerSSLContext());
231 server = createServer(serverTlsStrategy);
232 server.start();
233
234 final TlsStrategy clientTlsStrategy = new BasicClientTlsStrategy(SSLContexts.createDefault());
235 client = createClient(clientTlsStrategy);
236 client.start();
237
238 final Future<TlsDetails> tlsSessionFuture = executeTlsHandshake();
239
240 final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () ->
241 tlsSessionFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()));
242 final Throwable cause = exception.getCause();
243 Assertions.assertInstanceOf(SSLHandshakeException.class, cause);
244 }
245
246 @Test
247 public void testTLSClientAuthFailure() throws Exception {
248 final TlsStrategy serverTlsStrategy = new BasicServerTlsStrategy(
249 SSLTestContexts.createServerSSLContext(),
250 (endpoint, sslEngine) -> sslEngine.setNeedClientAuth(true),
251 null);
252 server = createServer(serverTlsStrategy);
253 server.start();
254
255 final TlsStrategy clientTlsStrategy = new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext());
256 client = createClient(clientTlsStrategy);
257 client.start();
258
259 final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
260 final ListenerEndpoint listener = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
261 final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
262
263 final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
264
265 final Future<Message<HttpResponse, String>> resultFuture = client.execute(
266 new BasicRequestProducer(Method.POST, target, "/stuff",
267 new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
268 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
269
270 final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () ->
271 resultFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()));
272 final Throwable cause = exception.getCause();
273 Assertions.assertInstanceOf(IOException.class, cause);
274 }
275
276 @Test
277 public void testSSLDisabledByDefault() throws Exception {
278 final TlsStrategy serverTlsStrategy = new TestTlsStrategy(
279 SSLTestContexts.createServerSSLContext(),
280 (endpoint, sslEngine) -> sslEngine.setEnabledProtocols(new String[]{"SSLv3"}),
281 null);
282 server = createServer(serverTlsStrategy);
283 server.start();
284
285 final TlsStrategy clientTlsStrategy = new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext());
286 client = createClient(clientTlsStrategy);
287 client.start();
288
289 final Future<TlsDetails> tlsSessionFuture = executeTlsHandshake();
290
291 Assertions.assertThrows(ExecutionException.class, () ->
292 tlsSessionFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()));
293 }
294
295 @ParameterizedTest(name = "cipher {0}")
296 @ValueSource(strings = {
297 "SSL_RSA_WITH_RC4_128_SHA",
298 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
299 "TLS_DH_anon_WITH_AES_128_CBC_SHA",
300 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
301 "SSL_RSA_WITH_NULL_SHA",
302 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
303 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
304 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
305 "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
306 "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
307 "TLS_RSA_WITH_NULL_SHA256",
308 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
309 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
310 "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
311 "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"
312 })
313 public void testWeakCipherDisabledByDefault(final String cipher) throws Exception {
314 final TlsStrategy serverTlsStrategy = new TestTlsStrategy(
315 SSLTestContexts.createServerSSLContext(),
316 (endpoint, sslEngine) -> sslEngine.setEnabledCipherSuites(new String[]{cipher}),
317 null);
318 server = createServer(serverTlsStrategy);
319 server.start();
320
321 final TlsStrategy clientTlsStrategy = new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext());
322 client = createClient(clientTlsStrategy);
323 client.start();
324
325 final Future<TlsDetails> tlsSessionFuture = executeTlsHandshake();
326
327 Assertions.assertThrows(ExecutionException.class, () ->
328 tlsSessionFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()));
329 }
330
331 @Test
332 public void testTLSVersionMismatch() throws Exception {
333 final TlsStrategy serverTlsStrategy = new TestTlsStrategy(
334 SSLTestContexts.createServerSSLContext(),
335 (endpoint, sslEngine) -> {
336 sslEngine.setEnabledProtocols(new String[]{TLS.V_1_0.getId()});
337 sslEngine.setEnabledCipherSuites(new String[]{
338 "TLS_RSA_WITH_AES_256_CBC_SHA",
339 "TLS_RSA_WITH_AES_128_CBC_SHA",
340 "TLS_RSA_WITH_3DES_EDE_CBC_SHA"});
341 },
342 null);
343 server = createServer(serverTlsStrategy);
344 server.start();
345
346 final TlsStrategy clientTlsStrategy = new BasicClientTlsStrategy(
347 SSLTestContexts.createClientSSLContext(),
348 (endpoint, sslEngine) -> sslEngine.setEnabledProtocols(new String[]{TLS.V_1_2.getId()}),
349 null);
350 client = createClient(clientTlsStrategy);
351 client.start();
352
353 final Future<TlsDetails> tlsSessionFuture = executeTlsHandshake();
354
355 final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () ->
356 tlsSessionFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()));
357 final Throwable cause = exception.getCause();
358 Assertions.assertInstanceOf(IOException.class, cause);
359 }
360
361 @Test
362 public void testHostNameVerification() throws Exception {
363 server = createServer(new BasicClientTlsStrategy(SSLTestContexts.createServerSSLContext()));
364 server.start();
365
366 client = createClient(new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext()));
367 client.start();
368
369 final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
370 final ListenerEndpoint listener = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
371 final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
372
373 final HttpHost target1 = new HttpHost(URIScheme.HTTPS.id, InetAddress.getLocalHost(), "localhost", address.getPort());
374 final Future<Message<HttpResponse, String>> resultFuture1 = client.execute(
375 target1,
376 new BasicRequestProducer(Method.POST, target1, "/stuff",
377 new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
378 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
379 final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
380 Assertions.assertNotNull(message1);
381 Assertions.assertEquals(200, message1.getHead().getCode());
382
383 final HttpHost target2 = new HttpHost(URIScheme.HTTPS.id, InetAddress.getLocalHost(), "some-other-host", address.getPort());
384 final Future<Message<HttpResponse, String>> resultFuture2 = client.execute(
385 target2,
386 new BasicRequestProducer(Method.POST, target2, "/stuff",
387 new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
388 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT, null);
389 final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () ->
390 resultFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit()));
391 final Throwable cause = exception.getCause();
392 Assertions.assertInstanceOf(SSLHandshakeException.class, cause);
393 }
394
395 static class SupportedTLSProtocolProvider implements ArgumentsProvider {
396
397 int javaVere = ReflectionUtils.determineJRELevel();
398
399 @Override
400 public Stream<? extends Arguments> provideArguments(final ExtensionContext context) {
401 if (javaVere >= 11) {
402 return Stream.of(Arguments.of(TLS.V_1_2), Arguments.of(TLS.V_1_3));
403 } else {
404 return Stream.of(Arguments.of(TLS.V_1_2));
405 }
406 }
407 }
408
409 static class TestTlsStrategy implements TlsStrategy {
410
411 private final SSLContext sslContext;
412 private final SSLSessionInitializer initializer;
413 private final SSLSessionVerifier verifier;
414
415 public TestTlsStrategy(
416 final SSLContext sslContext,
417 final SSLSessionInitializer initializer,
418 final SSLSessionVerifier verifier) {
419 this.sslContext = Args.notNull(sslContext, "SSL context");
420 this.initializer = initializer;
421 this.verifier = verifier;
422 }
423
424 @Override
425 public void upgrade(
426 final TransportSecurityLayer tlsSession,
427 final NamedEndpoint endpoint,
428 final Object attachment,
429 final Timeout handshakeTimeout,
430 final FutureCallback<TransportSecurityLayer> callback) {
431 tlsSession.startTls(sslContext, endpoint, SSLBufferMode.STATIC,
432 TlsSupport.enforceStrongSecurity(initializer), verifier, handshakeTimeout, callback);
433 }
434
435
436
437
438 @Deprecated
439 @Override
440 public boolean upgrade(
441 final TransportSecurityLayer tlsSession,
442 final HttpHost host,
443 final SocketAddress localAddress,
444 final SocketAddress remoteAddress,
445 final Object attachment,
446 final Timeout handshakeTimeout) {
447 tlsSession.startTls(sslContext, host, SSLBufferMode.STATIC,
448 TlsSupport.enforceStrongSecurity(initializer), verifier, handshakeTimeout, null);
449 return true;
450 }
451
452 }
453
454 }