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.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         Oracle disabled SSLv3 in Java 7_75 and 8_31
231         See https://blogs.oracle.com/security/entry/january_2015_critical_patch_update
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 }