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