1 package com.atlassian.sal.core.net;
2
3 import java.io.IOException;
4 import java.net.URL;
5 import java.net.UnknownHostException;
6 import java.security.KeyManagementException;
7 import java.security.NoSuchAlgorithmException;
8 import java.security.Security;
9 import java.security.cert.X509Certificate;
10 import java.util.Arrays;
11 import java.util.Set;
12
13 import javax.net.ssl.SSLContext;
14 import javax.net.ssl.TrustManager;
15 import javax.net.ssl.X509TrustManager;
16 import javax.servlet.ServletException;
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse;
19
20 import com.atlassian.sal.api.net.Request;
21 import com.atlassian.sal.api.net.ResponseException;
22 import com.atlassian.sal.api.net.ResponseHandler;
23 import com.atlassian.sal.api.net.ResponseProtocolException;
24
25 import com.google.common.collect.Sets;
26
27 import org.apache.commons.io.IOUtils;
28 import org.eclipse.jetty.server.Connector;
29 import org.eclipse.jetty.server.Handler;
30 import org.eclipse.jetty.server.Server;
31 import org.eclipse.jetty.server.handler.AbstractHandler;
32 import org.eclipse.jetty.server.ssl.SslSocketConnector;
33 import org.eclipse.jetty.util.ssl.SslContextFactory;
34 import org.junit.After;
35 import org.junit.Before;
36 import org.junit.Test;
37
38 import static junit.framework.Assert.assertEquals;
39 import static org.apache.commons.io.IOUtils.write;
40 import static org.junit.Assert.fail;
41
42 public class TestHttpClientRequestSSL
43 {
44 private HttpTestServer server;
45 private HttpClientRequestFactory requestFactory;
46
47 private SSLContext originalSSLContext;
48
49 @Before
50 public void setUp() throws NoSuchAlgorithmException, KeyManagementException
51 {
52 originalSSLContext = SSLContext.getDefault();
53 SSLContext.setDefault(getGullibleSSLContext());
54 requestFactory = new HttpClientRequestFactory();
55 }
56
57 @After
58 public void stopServer() throws Exception
59 {
60 SSLContext.setDefault(originalSSLContext);
61 System.clearProperty(SystemPropertiesConnectionConfig.HTTP_MAX_REDIRECTS_PROPERTY_NAME);
62 if (server != null)
63 {
64 server.stop();
65 }
66 }
67
68 @Test
69 public void testHttpClientAccessHttpUrl() throws Exception
70 {
71 server = new HttpTestServer();
72 server.start();
73
74 testGetUrl("http://localhost:" + server.getHttpPort());
75 }
76
77 @Test
78 public void testHttpClientAccessHttpsWithSSL() throws Exception
79 {
80 server = new SslTestServer();
81 server.start();
82
83 testGetUrl("https://localhost:" + server.getHttpPort());
84 }
85
86 @Test
87 public void testHttpClientAccessHttpsWithSSLv3Only() throws Exception
88 {
89 if (isSSLv3Disabled())
90 {
91 return;
92 }
93
94 server = new SslTestServer("SSLv3");
95 server.start();
96 try
97 {
98 System.setProperty("https.protocols", "SSLv3");
99 testGetUrl("https://localhost:" + server.getHttpPort());
100 }
101 finally
102 {
103 System.clearProperty("https.protocols");
104 }
105 }
106
107 @Test
108 public void testHttpClientAccessHttpsWithTLSv1Only() throws Exception
109 {
110 server = new SslTestServer("TLSv1");
111 server.start();
112
113 testGetUrl("https://localhost:" + server.getHttpPort());
114 }
115
116 @Test (expected = ResponseProtocolException.class)
117 public void testHttpClientAccessFailsWithSSlv3OnTLSv1() throws Exception
118 {
119 server = new SslTestServer("TLSv1");
120 server.start();
121
122 try
123 {
124 System.setProperty("https.protocols", "SSLv3");
125 HttpClientRequest request = requestFactory.createRequest(Request.MethodType.GET, ("http://localhost:" + server.getHttpPort()));
126 request.execute(checkResponseHandler);
127 }
128 finally
129 {
130 System.clearProperty("https.protocols");
131 }
132 }
133
134 @Test (expected = ResponseProtocolException.class)
135 public void testHttpClientAccessFailsWithTLSv1OnSSLv3() throws Exception
136 {
137 server = new SslTestServer("SSLv3");
138 server.start();
139
140 try
141 {
142 System.setProperty("https.protocols", "TLSv1");
143 HttpClientRequest request = requestFactory.createRequest(Request.MethodType.GET, ("http://localhost:" + server.getHttpPort()));
144 request.execute(checkResponseHandler);
145 }
146 finally
147 {
148 System.clearProperty("https.protocols");
149 }
150 }
151
152 @Test
153 public void testReusingRequestWithAndWithoutSSL() throws Exception
154 {
155 server = new HttpTestServer();
156 server.start();
157
158 HttpClientRequest request = requestFactory.createRequest(Request.MethodType.GET, ("http://localhost:" + server.getHttpPort()));
159 request.execute(checkResponseHandler);
160
161 server.stop();
162 server = new SslTestServer("TLSv1");
163 server.start();
164
165 request.setUrl("https://localhost:" + server.getHttpPort());
166 request.execute(checkResponseHandler);
167 }
168
169 @Test (expected = ResponseException.class)
170 public void testRedirectToSSLv3() throws Exception
171 {
172 HttpTestServer targetServer = new SslTestServer("SSLv3");
173 server = new HttpRedirectTestServer(targetServer, "localhost");
174 server.start();
175
176 try
177 {
178 targetServer.start();
179 testGetUrl("http://localhost:" + server.getHttpPort());
180 }
181 finally
182 {
183 targetServer.stop();
184 }
185 }
186
187 @Test
188 public void testRedirectFromSSLv3() throws Exception
189 {
190 if (isSSLv3Disabled())
191 {
192 return;
193 }
194
195 HttpTestServer targetServer = new HttpTestServer();
196 server = new SslTestServer("SSLv3").handler(new RedirectMockHandler(targetServer, "localhost"));
197 server.start();
198
199 try
200 {
201 System.setProperty("https.protocols", "SSLv3");
202 targetServer.start();
203 testGetUrl("https://localhost:" + server.getHttpPort());
204 }
205 finally
206 {
207 System.clearProperty("https.protocols");
208 targetServer.stop();
209 }
210 }
211
212 @Test (expected = ResponseException.class)
213 public void testRedirectFromTLSv1ToSSLv3FailsWithDefaultProtocolSettings() throws Exception
214 {
215 HttpTestServer targetServer = new SslTestServer("SSLv3");
216
217 server = new SslTestServer("TLSv1").handler(new RedirectMockHandler(targetServer, "localhost"));
218 server.start();
219 try
220 {
221 targetServer.start();
222
223 testGetUrl("https://localhost:" + server.getHttpPort());
224 }
225 finally
226 {
227 targetServer.stop();
228 }
229 }
230
231 private ResponseHandler<HttpClientResponse> checkResponseHandler = new ResponseHandler<HttpClientResponse>()
232 {
233 @Override
234 public void handle(HttpClientResponse response) throws ResponseException
235 {
236 assertEquals(200, response.getStatusCode());
237 try
238 {
239 assertEquals(HttpTestServer.RESPONSE, IOUtils.toString(response.getResponseBodyAsStream(), "UTF-8"));
240 } catch (IOException e)
241 {
242 fail("Failed to read response body: " + e.getMessage());
243 }
244 }
245 };
246
247 private void testGetUrl(String url) throws Exception
248 {
249 requestFactory.createRequest(Request.MethodType.GET, url).execute(checkResponseHandler);
250 }
251
252 private SSLContext getGullibleSSLContext() throws NoSuchAlgorithmException, KeyManagementException
253 {
254 SSLContext sc = SSLContext.getInstance("TLS");
255 sc.init(null, new TrustManager[] { getGullibleTrustManager() }, null);
256 return sc;
257 }
258
259 private TrustManager getGullibleTrustManager()
260 {
261 return
262 new X509TrustManager()
263 {
264 public X509Certificate[] getAcceptedIssuers()
265 {
266 return null;
267 }
268
269 public void checkClientTrusted(X509Certificate[] certs, String authType)
270 {
271 }
272
273 public void checkServerTrusted(X509Certificate[] certs, String authType)
274 {
275 }
276 };
277 }
278
279
280
281
282
283 private boolean isSSLv3Disabled()
284 {
285 final String disabledAlgorithms = Security.getProperty("jdk.tls.disabledAlgorithms");
286 return disabledAlgorithms != null && disabledAlgorithms.contains("SSLv3");
287 }
288
289 private class HttpTestServer
290 {
291 public static final int HTTP_PORT = 0;
292 public static final String RESPONSE = "Response body";
293
294 private Server server;
295 private String responseBody;
296 private Handler handler;
297
298 public void start() throws Exception
299 {
300 configureServer();
301 startServer();
302 }
303
304 private void startServer() throws Exception
305 {
306 server.start();
307 }
308
309 protected void configureServer()
310 {
311 server = new Server(HTTP_PORT);
312 server.setHandler(getHandler());
313 }
314
315 public int getHttpPort()
316 {
317 return server.getConnectors()[0].getLocalPort();
318 }
319
320 public Handler getHandler()
321 {
322 if (handler != null)
323 {
324 return handler;
325 }
326
327 return new AbstractHandler()
328 {
329 @Override
330 public void handle(final String s, final org.eclipse.jetty.server.Request request, final HttpServletRequest httpServletRequest, final HttpServletResponse response)
331 throws IOException, ServletException
332 {
333 setResponseBody(RESPONSE);
334 response.setStatus(200);
335 response.setContentType("text/plain;charset=utf-8");
336 write(getResponseBody(), response.getOutputStream());
337 request.setHandled(true);
338 }
339 };
340 }
341
342 public void stop() throws Exception
343 {
344 server.stop();
345 }
346
347 public void setResponseBody(String responseBody)
348 {
349 this.responseBody = responseBody;
350 }
351
352 public String getResponseBody()
353 {
354 return responseBody;
355 }
356
357 protected Server getServer()
358 {
359 return server;
360 }
361
362 public HttpTestServer handler(Handler handler)
363 {
364 this.handler = handler;
365 return this;
366 }
367 }
368
369 private class RedirectingHttpServer extends HttpTestServer
370 {
371 private final int maxRedirects;
372
373 public RedirectingHttpServer(int maxRedirects)
374 {
375 this.maxRedirects = maxRedirects;
376 }
377 public Handler getHandler()
378 {
379 return new RedirectMockHandler(this, "localhost", maxRedirects);
380 }
381 }
382
383 public class SslTestServer extends HttpTestServer
384 {
385 private static final int HTTPS_PORT = 0;
386
387 private final Set<String> excludedProtocols = Sets.newHashSet("TLSv1", "SSLv3", "SSLv2Hello");
388 private final String includeProtocols;
389
390 private Connector secureConnector;
391
392 public SslTestServer()
393 {
394 includeProtocols = "TLSv1,SSLv3,SSLv2Hello";
395 }
396
397 public SslTestServer(String includeProtocols)
398 {
399 this.includeProtocols = includeProtocols;
400 }
401
402 @Override
403 protected void configureServer()
404 {
405 super.configureServer();
406 secureConnector = createSecureConnector();
407 getServer().addConnector(secureConnector);
408 }
409
410 private Connector createSecureConnector()
411 {
412
413 SslContextFactory sslContextFactory = new SslContextFactory();
414 URL keystoreUrl = getClass().getClassLoader().getResource("keystore");
415 sslContextFactory.setKeyStorePath(keystoreUrl.getFile());
416 sslContextFactory.setKeyStorePassword("changeit");
417 String[] includes = includeProtocols.split(",");
418
419 excludedProtocols.removeAll(Arrays.asList(includes));
420 sslContextFactory.setExcludeProtocols(excludedProtocols.toArray(new String[excludedProtocols.size()]));
421 sslContextFactory.setIncludeProtocols(includes);
422
423 SslSocketConnector connector = new SslSocketConnector(sslContextFactory);
424 connector.setPort(HTTPS_PORT);
425
426 return connector;
427 }
428
429 @Override
430 public int getHttpPort()
431 {
432 return secureConnector.getLocalPort();
433 }
434
435 public Handler getHandler()
436 {
437 final Handler handler = super.getHandler();
438 return new AbstractHandler()
439 {
440 @Override
441 public void handle(final String s, final org.eclipse.jetty.server.Request request, final HttpServletRequest httpServletRequest, final HttpServletResponse response)
442 throws IOException, ServletException
443 {
444 assertEquals("localhost:" + getHttpPort(), httpServletRequest.getHeader("Host"));
445 handler.handle(s, request, httpServletRequest, response);
446 }
447 };
448 }
449 }
450
451 private class HttpRedirectTestServer extends HttpTestServer
452 {
453 private HttpRedirectTestServer(HttpTestServer targetServer, String targetHostname) throws UnknownHostException
454 {
455 handler(new RedirectMockHandler(targetServer, targetHostname));
456 }
457 }
458
459 private static class RedirectMockHandler extends AbstractHandler
460 {
461 private final String targetHostname;
462 private final HttpTestServer targetServer;
463 private final int maxRedirects;
464 private int redirectsCount = 0;
465
466 private RedirectMockHandler(HttpTestServer targetServer, String targetHostname) throws UnknownHostException
467 {
468 this(targetServer, targetHostname, 1);
469 }
470
471 private RedirectMockHandler(HttpTestServer targetServer, String targetHostname, int maxRedirects)
472 {
473 this.targetServer = targetServer;
474 this.targetHostname = targetHostname;
475 this.maxRedirects = maxRedirects;
476 }
477
478 @Override
479 public void handle(final String target, final org.eclipse.jetty.server.Request baseRequest, final HttpServletRequest request, final HttpServletResponse response)
480 throws IOException, ServletException
481 {
482 if(redirectsCount >= maxRedirects)
483 {
484 response.setStatus(200);
485 write(HttpTestServer.RESPONSE, response.getOutputStream());
486 }
487 else
488 {
489 redirectsCount++;
490 response.setStatus(301);
491 String scheme = (targetServer instanceof SslTestServer) ? "https://" : "http://";
492 final String location = scheme + targetHostname + ":" + targetServer.getHttpPort() + "?" + redirectsCount;
493 response.setHeader("Location", location);
494 write("Redirect", response.getOutputStream());
495 }
496
497 response.setContentType("text/plain;charset=utf-8");
498 baseRequest.setHandled(true);
499 }
500 }
501 }