View Javadoc

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