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