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.classic;
29
30 import static org.hamcrest.MatcherAssert.assertThat;
31
32 import java.io.IOException;
33 import java.net.InetAddress;
34 import java.util.concurrent.atomic.AtomicReference;
35
36 import javax.net.ssl.SSLHandshakeException;
37 import javax.net.ssl.SSLSession;
38
39 import org.apache.hc.core5.http.ClassicHttpRequest;
40 import org.apache.hc.core5.http.ClassicHttpResponse;
41 import org.apache.hc.core5.http.ContentType;
42 import org.apache.hc.core5.http.HttpHost;
43 import org.apache.hc.core5.http.HttpStatus;
44 import org.apache.hc.core5.http.Method;
45 import org.apache.hc.core5.http.ProtocolVersion;
46 import org.apache.hc.core5.http.impl.bootstrap.HttpRequester;
47 import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
48 import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap;
49 import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
50 import org.apache.hc.core5.http.io.SocketConfig;
51 import org.apache.hc.core5.http.io.entity.EntityUtils;
52 import org.apache.hc.core5.http.io.entity.StringEntity;
53 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
54 import org.apache.hc.core5.http.protocol.BasicHttpContext;
55 import org.apache.hc.core5.http.protocol.HttpContext;
56 import org.apache.hc.core5.http.protocol.HttpCoreContext;
57 import org.apache.hc.core5.http.ssl.TLS;
58 import org.apache.hc.core5.io.CloseMode;
59 import org.apache.hc.core5.ssl.SSLContexts;
60 import org.apache.hc.core5.testing.SSLTestContexts;
61 import org.apache.hc.core5.util.Timeout;
62 import org.hamcrest.CoreMatchers;
63 import org.junit.jupiter.api.Assertions;
64 import org.junit.jupiter.api.Test;
65 import org.junit.jupiter.api.extension.AfterEachCallback;
66 import org.junit.jupiter.api.extension.ExtensionContext;
67 import org.junit.jupiter.api.extension.RegisterExtension;
68
69 public class ClassicTLSIntegrationTest {
70
71 private static final Timeout TIMEOUT = Timeout.ofMinutes(1);
72
73 private HttpServer server;
74
75 @RegisterExtension
76 public final AfterEachCallback serverCleanup = new AfterEachCallback() {
77
78 @Override
79 public void afterEach(final ExtensionContext context) throws Exception {
80 if (server != null) {
81 try {
82 server.close(CloseMode.IMMEDIATE);
83 } catch (final Exception ignore) {
84 }
85 }
86 }
87
88 };
89
90 private HttpRequester requester;
91
92 @RegisterExtension
93 public final AfterEachCallback clientCleanup = new AfterEachCallback() {
94
95 @Override
96 public void afterEach(final ExtensionContext context) throws Exception {
97 if (requester != null) {
98 try {
99 requester.close(CloseMode.GRACEFUL);
100 } catch (final Exception ignore) {
101 }
102 }
103 }
104
105 };
106
107 @Test
108 public void testTLSSuccess() throws Exception {
109 server = ServerBootstrap.bootstrap()
110 .setSocketConfig(SocketConfig.custom()
111 .setSoTimeout(TIMEOUT)
112 .build())
113 .setSslContext(SSLTestContexts.createServerSSLContext())
114 .setExceptionListener(LoggingExceptionListener.INSTANCE)
115 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
116 .register("*", new EchoHandler())
117 .create();
118 server.start();
119
120 final AtomicReference<SSLSession> sslSessionRef = new AtomicReference<>();
121
122 requester = RequesterBootstrap.bootstrap()
123 .setSslContext(SSLTestContexts.createClientSSLContext())
124 .setSslSessionVerifier((endpoint, sslSession) -> sslSessionRef.set(sslSession))
125 .setSocketConfig(SocketConfig.custom()
126 .setSoTimeout(TIMEOUT)
127 .build())
128 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
129 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
130 .create();
131
132 final HttpCoreContext context = HttpCoreContext.create() ;
133 final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
134 final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
135 request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
136 try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
137 assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
138 final String body1 = EntityUtils.toString(response1.getEntity());
139 assertThat(body1, CoreMatchers.equalTo("some stuff"));
140 }
141
142 final SSLSession sslSession = sslSessionRef.getAndSet(null);
143 final ProtocolVersion tlsVersion = TLS.parse(sslSession.getProtocol());
144 assertThat(tlsVersion.greaterEquals(TLS.V_1_2.getVersion()), CoreMatchers.equalTo(true));
145 assertThat(sslSession.getPeerPrincipal().getName(),
146 CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation"));
147 }
148
149 @Test
150 public void testTLSTrustFailure() throws Exception {
151 server = ServerBootstrap.bootstrap()
152 .setSocketConfig(SocketConfig.custom()
153 .setSoTimeout(TIMEOUT)
154 .build())
155 .setSslContext(SSLTestContexts.createServerSSLContext())
156 .setExceptionListener(LoggingExceptionListener.INSTANCE)
157 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
158 .register("*", new EchoHandler())
159 .create();
160 server.start();
161
162 requester = RequesterBootstrap.bootstrap()
163 .setSslContext(SSLContexts.createDefault())
164 .setSocketConfig(SocketConfig.custom()
165 .setSoTimeout(TIMEOUT)
166 .build())
167 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
168 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
169 .create();
170
171 final HttpCoreContext context = HttpCoreContext.create() ;
172 final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
173 final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
174 request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
175 Assertions.assertThrows(IOException.class, () -> {
176 try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
177 EntityUtils.consume(response1.getEntity());
178 }
179 });
180 }
181
182 @Test
183 public void testTLSClientAuthFailure() throws Exception {
184 server = ServerBootstrap.bootstrap()
185 .setSslContext(SSLTestContexts.createClientSSLContext())
186 .setSocketConfig(SocketConfig.custom()
187 .setSoTimeout(TIMEOUT)
188 .build())
189 .setSslContext(SSLTestContexts.createServerSSLContext())
190 .setSslSetupHandler(sslParameters -> sslParameters.setNeedClientAuth(true))
191 .setExceptionListener(LoggingExceptionListener.INSTANCE)
192 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
193 .register("*", new EchoHandler())
194 .create();
195 server.start();
196
197 requester = RequesterBootstrap.bootstrap()
198 .setSslContext(SSLTestContexts.createClientSSLContext())
199 .setSocketConfig(SocketConfig.custom()
200 .setSoTimeout(TIMEOUT)
201 .build())
202 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
203 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
204 .create();
205
206 final HttpCoreContext context = HttpCoreContext.create() ;
207 final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
208 final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
209 request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
210 Assertions.assertThrows(IOException.class, () -> {
211 try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
212 EntityUtils.consume(response1.getEntity());
213 }
214 });
215 }
216
217 @Test
218 public void testSSLDisabledByDefault() throws Exception {
219 server = ServerBootstrap.bootstrap()
220 .setSslContext(SSLTestContexts.createServerSSLContext())
221 .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[]{"SSLv3"}))
222 .create();
223 server.start();
224
225 requester = RequesterBootstrap.bootstrap()
226 .setSslContext(SSLTestContexts.createClientSSLContext())
227 .setSocketConfig(SocketConfig.custom()
228 .setSoTimeout(TIMEOUT)
229 .build())
230 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
231 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
232 .create();
233
234 final HttpCoreContext context = HttpCoreContext.create() ;
235 final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
236 final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
237 request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
238 Assertions.assertThrows(IOException.class, () -> {
239 try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
240 EntityUtils.consume(response1.getEntity());
241 }
242 });
243 }
244
245 @Test
246 public void testWeakCiphersDisabledByDefault() throws Exception {
247
248 requester = RequesterBootstrap.bootstrap()
249 .setSslContext(SSLTestContexts.createClientSSLContext())
250 .setSocketConfig(SocketConfig.custom()
251 .setSoTimeout(TIMEOUT)
252 .build())
253 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
254 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
255 .create();
256
257 final String[] weakCiphersSuites = {
258 "SSL_RSA_WITH_RC4_128_SHA",
259 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
260 "TLS_DH_anon_WITH_AES_128_CBC_SHA",
261 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
262 "SSL_RSA_WITH_NULL_SHA",
263 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
264 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
265 "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
266 "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
267 "TLS_RSA_WITH_NULL_SHA256",
268 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
269 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
270 "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
271 "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"
272 };
273
274 for (final String cipherSuite : weakCiphersSuites) {
275 server = ServerBootstrap.bootstrap()
276 .setSslContext(SSLTestContexts.createServerSSLContext())
277 .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[]{cipherSuite}))
278 .create();
279 Assertions.assertThrows(Exception.class, () -> {
280 try {
281 server.start();
282
283 final HttpCoreContext context = HttpCoreContext.create() ;
284 final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
285 final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
286 request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
287 try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
288 EntityUtils.consume(response1.getEntity());
289 }
290 } finally {
291 server.close(CloseMode.IMMEDIATE);
292 }
293 });
294 }
295 }
296
297 @Test
298 public void testHostNameVerification() throws Exception {
299 server = ServerBootstrap.bootstrap()
300 .setSslContext(SSLTestContexts.createServerSSLContext())
301 .create();
302 server.start();
303
304 requester = RequesterBootstrap.bootstrap()
305 .setSslContext(SSLTestContexts.createClientSSLContext())
306 .setSocketConfig(SocketConfig.custom()
307 .setSoTimeout(TIMEOUT)
308 .build())
309 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
310 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
311 .create();
312
313 final HttpContext context = new BasicHttpContext();
314 final HttpHost target1 = new HttpHost("https", InetAddress.getLocalHost(), "localhost", server.getLocalPort());
315 final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
316 request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
317 try (final ClassicHttpResponse response1 = requester.execute(target1, request1, TIMEOUT, context)) {
318 EntityUtils.consume(response1.getEntity());
319 }
320
321 Assertions.assertThrows(SSLHandshakeException.class, () -> {
322 final HttpHost target2 = new HttpHost("https", InetAddress.getLocalHost(), "some-other-host", server.getLocalPort());
323 final ClassicHttpRequest request2 = new BasicClassicHttpRequest(Method.POST, "/stuff");
324 request2.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
325 try (final ClassicHttpResponse response2 = requester.execute(target2, request2, TIMEOUT, context)) {
326 EntityUtils.consume(response2.getEntity());
327 }
328 });
329 }
330
331 }