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