View Javadoc
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 org.apache.commons.io.IOUtils.write;
36  import static org.junit.Assert.assertEquals;
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         Oracle disabled SSLv3 in Java 7_75 and 8_31
233         See https://blogs.oracle.com/security/entry/january_2015_critical_patch_update
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 }