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.client5.testing.sync;
29
30 import static org.hamcrest.MatcherAssert.assertThat;
31
32 import java.io.IOException;
33 import java.net.InetSocketAddress;
34 import java.net.Socket;
35 import java.security.KeyManagementException;
36 import java.security.KeyStoreException;
37 import java.security.NoSuchAlgorithmException;
38 import java.util.concurrent.atomic.AtomicBoolean;
39
40 import javax.net.ssl.HostnameVerifier;
41 import javax.net.ssl.SSLContext;
42 import javax.net.ssl.SSLException;
43 import javax.net.ssl.SSLSession;
44 import javax.net.ssl.SSLSocket;
45
46 import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
47 import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
48 import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
49 import org.apache.hc.client5.testing.SSLTestContexts;
50 import org.apache.hc.core5.http.HttpHost;
51 import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
52 import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
53 import org.apache.hc.core5.http.protocol.BasicHttpContext;
54 import org.apache.hc.core5.http.protocol.HttpContext;
55 import org.apache.hc.core5.io.CloseMode;
56 import org.apache.hc.core5.ssl.SSLContexts;
57 import org.apache.hc.core5.ssl.TrustStrategy;
58 import org.apache.hc.core5.util.TimeValue;
59 import org.apache.hc.core5.util.Timeout;
60 import org.hamcrest.CoreMatchers;
61 import org.junit.jupiter.api.AfterEach;
62 import org.junit.jupiter.api.Assertions;
63 import org.junit.jupiter.api.Test;
64
65
66
67
68 public class TestSSLSocketFactory {
69
70 private HttpServer server;
71
72 @AfterEach
73 public void shutDown() throws Exception {
74 if (this.server != null) {
75 this.server.close(CloseMode.GRACEFUL);
76 }
77 }
78
79 static class TestX509HostnameVerifier implements HostnameVerifier {
80
81 private boolean fired;
82
83 @Override
84 public boolean verify(final String host, final SSLSession session) {
85 this.fired = true;
86 return true;
87 }
88
89 public boolean isFired() {
90 return this.fired;
91 }
92
93 }
94
95 @Test
96 public void testBasicSSL() throws Exception {
97
98 this.server = ServerBootstrap.bootstrap()
99 .setSslContext(SSLTestContexts.createServerSSLContext())
100 .create();
101
102 this.server.start();
103
104 final HttpContext context = new BasicHttpContext();
105 final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
106 final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
107 SSLTestContexts.createClientSSLContext(), hostVerifier);
108 try (final Socket socket = socketFactory.createSocket(context)) {
109 final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
110 final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
111 try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(
112 TimeValue.ZERO_MILLISECONDS,
113 socket,
114 target,
115 remoteAddress,
116 null,
117 context)) {
118 final SSLSession sslsession = sslSocket.getSession();
119
120 Assertions.assertNotNull(sslsession);
121 Assertions.assertTrue(hostVerifier.isFired());
122 }
123 }
124 }
125
126 @Test
127 public void testBasicSslConnectOverride() throws Exception {
128 this.server = ServerBootstrap.bootstrap()
129 .setSslContext(SSLTestContexts.createServerSSLContext())
130 .create();
131 this.server.start();
132
133 final HttpContext context = new BasicHttpContext();
134 final AtomicBoolean connectCalled = new AtomicBoolean();
135 final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
136 SSLTestContexts.createClientSSLContext()) {
137 @Override
138 protected void connectSocket(
139 final Socket sock,
140 final InetSocketAddress remoteAddress,
141 final Timeout connectTimeout,
142 final HttpContext context) throws IOException {
143 connectCalled.set(true);
144 super.connectSocket(sock, remoteAddress, connectTimeout, context);
145 }
146 };
147 try (final Socket socket = socketFactory.createSocket(context)) {
148 final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
149 final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
150 try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(
151 TimeValue.ZERO_MILLISECONDS,
152 socket,
153 target,
154 remoteAddress,
155 null,
156 context)) {
157 final SSLSession sslsession = sslSocket.getSession();
158 Assertions.assertNotNull(sslsession);
159 Assertions.assertTrue(connectCalled.get());
160 }
161 }
162 }
163
164 @Test
165 public void testBasicDefaultHostnameVerifier() throws Exception {
166
167 this.server = ServerBootstrap.bootstrap()
168 .setSslContext(SSLTestContexts.createServerSSLContext())
169 .create();
170
171 this.server.start();
172
173 final HttpContext context = new BasicHttpContext();
174 final SSLConnectionSocketFactory socketFactory = SSLConnectionSocketFactoryBuilder.create()
175 .setSslContext(SSLTestContexts.createClientSSLContext())
176 .build();
177 try (final Socket socket = socketFactory.createSocket(context)) {
178 final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
179 final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
180 try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(
181 TimeValue.ZERO_MILLISECONDS,
182 socket,
183 target,
184 remoteAddress,
185 null,
186 context)) {
187 final SSLSession sslsession = sslSocket.getSession();
188
189 Assertions.assertNotNull(sslsession);
190 }
191 }
192 }
193
194 @Test
195 public void testClientAuthSSL() throws Exception {
196
197 this.server = ServerBootstrap.bootstrap()
198 .setSslContext(SSLTestContexts.createServerSSLContext())
199 .create();
200
201 this.server.start();
202
203 final HttpContext context = new BasicHttpContext();
204 final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
205 final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
206 SSLTestContexts.createClientSSLContext(), hostVerifier);
207 try (final Socket socket = socketFactory.createSocket(context)) {
208 final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
209 final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
210 try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(
211 TimeValue.ZERO_MILLISECONDS,
212 socket,
213 target,
214 remoteAddress,
215 null,
216 context)) {
217 final SSLSession sslsession = sslSocket.getSession();
218
219 Assertions.assertNotNull(sslsession);
220 Assertions.assertTrue(hostVerifier.isFired());
221 }
222 }
223 }
224
225 @Test
226 public void testClientAuthSSLFailure() throws Exception {
227
228 this.server = ServerBootstrap.bootstrap()
229 .setSslContext(SSLTestContexts.createServerSSLContext())
230 .setSslSetupHandler(sslParameters -> sslParameters.setNeedClientAuth(true))
231 .create();
232
233 this.server.start();
234
235 final HttpContext context = new BasicHttpContext();
236 final TestX509HostnameVerifier hostVerifier = new TestX509HostnameVerifier();
237 final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
238 SSLTestContexts.createClientSSLContext(), hostVerifier);
239 try (final Socket socket = socketFactory.createSocket(context)) {
240 final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
241 final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
242 Assertions.assertThrows(IOException.class, () -> {
243 try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(
244 TimeValue.ZERO_MILLISECONDS,
245 socket, target,
246 remoteAddress,
247 null,
248 context)) {
249 final SSLSession sslsession = sslSocket.getSession();
250
251 Assertions.assertNotNull(sslsession);
252 Assertions.assertTrue(hostVerifier.isFired());
253 sslSocket.getInputStream().read();
254 }
255 });
256 }
257 }
258
259 @Test
260 public void testSSLTrustVerification() throws Exception {
261
262 this.server = ServerBootstrap.bootstrap()
263 .setSslContext(SSLTestContexts.createServerSSLContext())
264 .create();
265
266 this.server.start();
267
268 final HttpContext context = new BasicHttpContext();
269
270 final SSLContext defaultSslContext = SSLContexts.createDefault();
271
272 final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(defaultSslContext,
273 NoopHostnameVerifier.INSTANCE);
274
275 try (final Socket socket = socketFactory.createSocket(context)) {
276 final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
277 final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
278 Assertions.assertThrows(SSLException.class, () -> {
279 try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(
280 TimeValue.ZERO_MILLISECONDS, socket, target, remoteAddress, null, context)) {
281
282 }
283 });
284 }
285 }
286
287 @Test
288 public void testSSLTrustVerificationOverrideWithCustom() throws Exception {
289 final TrustStrategy trustStrategy = (chain, authType) -> chain.length == 1;
290 testSSLTrustVerificationOverride(trustStrategy);
291 }
292
293 private void testSSLTrustVerificationOverride(final TrustStrategy trustStrategy)
294 throws Exception, IOException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
295
296 this.server = ServerBootstrap.bootstrap()
297 .setSslContext(SSLTestContexts.createServerSSLContext())
298 .create();
299
300 this.server.start();
301
302 final HttpContext context = new BasicHttpContext();
303
304
305 final SSLContext sslContext = SSLContexts.custom()
306 .loadTrustMaterial(null, trustStrategy)
307 .build();
308
309 final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext,
310 NoopHostnameVerifier.INSTANCE);
311
312 try (final Socket socket = socketFactory.createSocket(context)) {
313 final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
314 final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
315 try (final SSLSocket sslSocket = (SSLSocket) socketFactory.connectSocket(TimeValue.ZERO_MILLISECONDS, socket, target, remoteAddress,
316 null, context)) {
317
318 }
319 }
320 }
321
322 @Test
323 public void testSSLDisabledByDefault() throws Exception {
324
325 this.server = ServerBootstrap.bootstrap()
326 .setSslContext(SSLTestContexts.createServerSSLContext())
327 .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[] {"SSLv3"}))
328 .create();
329
330 this.server.start();
331
332 final HttpContext context = new BasicHttpContext();
333 final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
334 SSLTestContexts.createClientSSLContext());
335 try (final Socket socket = socketFactory.createSocket(context)) {
336 final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
337 final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
338 Assertions.assertThrows(IOException.class, () ->
339 socketFactory.connectSocket(
340 TimeValue.ZERO_MILLISECONDS, socket, target, remoteAddress, null, context));
341 }
342 }
343
344 @Test
345 public void testWeakCiphersDisabledByDefault() {
346 final String[] weakCiphersSuites = {
347 "SSL_RSA_WITH_RC4_128_SHA",
348 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
349 "TLS_DH_anon_WITH_AES_128_CBC_SHA",
350 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
351 "SSL_RSA_WITH_NULL_SHA",
352 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
353 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
354 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
355 "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
356 "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
357 "TLS_RSA_WITH_NULL_SHA256",
358 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
359 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
360 "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
361 "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"
362 };
363 for (final String cipherSuite : weakCiphersSuites) {
364 final Exception exception = Assertions.assertThrows(Exception.class, () ->
365 testWeakCipherDisabledByDefault(cipherSuite));
366 assertThat(exception, CoreMatchers.anyOf(
367 CoreMatchers.instanceOf(IOException.class),
368 CoreMatchers.instanceOf(IllegalArgumentException.class)));
369 }
370 }
371
372 private void testWeakCipherDisabledByDefault(final String cipherSuite) throws Exception {
373
374 this.server = ServerBootstrap.bootstrap()
375 .setSslContext(SSLTestContexts.createServerSSLContext())
376 .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[] {cipherSuite}))
377 .create();
378
379 this.server.start();
380
381 final HttpContext context = new BasicHttpContext();
382 final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
383 SSLTestContexts.createClientSSLContext());
384 try (final Socket socket = socketFactory.createSocket(context)) {
385 final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
386 final HttpHost target = new HttpHost("https", "localhost", this.server.getLocalPort());
387 socketFactory.connectSocket(TimeValue.ZERO_MILLISECONDS, socket, target, remoteAddress, null, context);
388 }
389 }
390 }