blob: 99bf66e3cf8d9e7a07b42303524500e2280ba9ff [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
// JettyTest.java --
//
// Junit test that shows the Jetty SSL bug.
//
package org.eclipse.jetty.server.ssl;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URL;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
*
*/
public class SSLEngineTest
{
// Useful constants
private static final String HELLO_WORLD="Hello world. The quick brown fox jumped over the lazy dog. How now brown cow. The rain in spain falls mainly on the plain.\n";
private static final String JETTY_VERSION= Server.getVersion();
private static final String PROTOCOL_VERSION="2.0";
/** The request. */
private static final String REQUEST0_HEADER="POST /r0 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Content-Length: ";
private static final String REQUEST1_HEADER="POST /r1 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Connection: close\n"+"Content-Length: ";
private static final String REQUEST_CONTENT=
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<requests xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+
" xsi:noNamespaceSchemaLocation=\"commander.xsd\" version=\""+PROTOCOL_VERSION+"\">\n"+
"</requests>";
private static final String REQUEST0=REQUEST0_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT;
private static final String REQUEST1=REQUEST1_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT;
/** The expected response. */
private static final String RESPONSE0="HTTP/1.1 200 OK\n"+"Content-Length: "+HELLO_WORLD.length()+"\n"+"Server: Jetty("+JETTY_VERSION+")\n"+'\n'+HELLO_WORLD;
private static final String RESPONSE1="HTTP/1.1 200 OK\n"+"Connection: close\n"+"Server: Jetty("+JETTY_VERSION+")\n"+'\n'+HELLO_WORLD;
private static final int BODY_SIZE=300;
private Server server;
private ServerConnector connector;
@Before
public void startServer() throws Exception
{
String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setKeyManagerPassword("keypwd");
server=new Server();
HttpConnectionFactory http = new HttpConnectionFactory();
http.setInputBufferSize(512);
http.getHttpConfiguration().setRequestHeaderSize(512);
connector=new ServerConnector(server, sslContextFactory, http);
connector.setPort(0);
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
server.addConnector(connector);
}
@After
public void stopServer() throws Exception
{
server.stop();
server.join();
}
@Test
public void testHelloWorld() throws Exception
{
server.setHandler(new HelloWorldHandler());
server.start();
SSLContext ctx=SSLContext.getInstance("TLS");
ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
int port=connector.getLocalPort();
Socket client=ctx.getSocketFactory().createSocket("localhost",port);
OutputStream os=client.getOutputStream();
String request =
"GET / HTTP/1.1\r\n"+
"Host: localhost:"+port+"\r\n"+
"Connection: close\r\n"+
"\r\n";
os.write(request.getBytes());
os.flush();
String response = IO.toString(client.getInputStream());
assertThat(response, Matchers.containsString("200 OK"));
assertThat(response,Matchers.containsString(HELLO_WORLD));
}
@Test
public void testBigResponse() throws Exception
{
server.setHandler(new HelloWorldHandler());
server.start();
SSLContext ctx=SSLContext.getInstance("TLS");
ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
int port=connector.getLocalPort();
Socket client=ctx.getSocketFactory().createSocket("localhost",port);
OutputStream os=client.getOutputStream();
String request =
"GET /?dump=102400 HTTP/1.1\r\n"+
"Host: localhost:"+port+"\r\n"+
"Connection: close\r\n"+
"\r\n";
os.write(request.getBytes());
os.flush();
String response = IO.toString(client.getInputStream());
assertThat(response.length(),greaterThan(102400));
}
@Test
public void testRequestJettyHttps() throws Exception
{
server.setHandler(new HelloWorldHandler());
server.start();
final int loops=10;
final int numConns=20;
Socket[] client=new Socket[numConns];
SSLContext ctx=SSLContext.getInstance("TLSv1.2");
ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
int port=connector.getLocalPort();
try
{
for (int l=0;l<loops;l++)
{
// System.err.print('.');
try
{
for (int i=0; i<numConns; ++i)
{
// System.err.println("write:"+i);
client[i]=ctx.getSocketFactory().createSocket("localhost",port);
OutputStream os=client[i].getOutputStream();
os.write(REQUEST0.getBytes());
os.write(REQUEST0.getBytes());
os.flush();
}
for (int i=0; i<numConns; ++i)
{
// System.err.println("flush:"+i);
OutputStream os=client[i].getOutputStream();
os.write(REQUEST1.getBytes());
os.flush();
}
for (int i=0; i<numConns; ++i)
{
// System.err.println("read:"+i);
// Read the response.
String responses=readResponse(client[i]);
// Check the responses
assertEquals(String.format("responses loop=%d connection=%d",l,i),RESPONSE0+RESPONSE0+RESPONSE1,responses);
}
}
finally
{
for (int i=0; i<numConns; ++i)
{
if (client[i]!=null)
{
try
{
assertEquals(-1,client[i].getInputStream().read());
}
catch(SocketException e)
{
}
}
}
}
}
}
finally
{
// System.err.println();
}
}
@Test
public void testURLConnectionChunkedPost() throws Exception
{
StreamHandler handler = new StreamHandler();
server.setHandler(handler);
server.start();
SSLContext context = SSLContext.getInstance("SSL");
context.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
URL url = new URL("https://localhost:"+connector.getLocalPort()+"/test");
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
if (conn instanceof HttpsURLConnection)
{
((HttpsURLConnection)conn).setHostnameVerifier(new HostnameVerifier()
{
@Override
public boolean verify(String urlHostName, SSLSession session)
{
return true;
}
});
}
conn.setConnectTimeout(10000);
conn.setReadTimeout(100000);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type","text/plain");
conn.setChunkedStreamingMode(128);
conn.connect();
byte[] b = new byte[BODY_SIZE];
for (int i = 0; i < BODY_SIZE; i++)
{
b[i] = 'x';
}
OutputStream os = conn.getOutputStream();
os.write(b);
os.flush();
int len = 0;
InputStream is = conn.getInputStream();
int bytes=0;
while ((len = is.read(b)) > -1)
bytes+=len;
is.close();
assertEquals(BODY_SIZE,handler.bytes);
assertEquals(BODY_SIZE,bytes);
}
/**
* Reads entire response from the client. Close the output.
*
* @param client Open client socket.
* @return The response string.
* @throws IOException in case of I/O errors
*/
private static String readResponse(Socket client) throws IOException
{
BufferedReader br=null;
StringBuilder sb=new StringBuilder(1000);
try
{
client.setSoTimeout(5000);
br=new BufferedReader(new InputStreamReader(client.getInputStream()));
String line;
while ((line=br.readLine())!=null)
{
sb.append(line);
sb.append('\n');
}
}
catch(SocketTimeoutException e)
{
System.err.println("Test timedout: "+e.toString());
e.printStackTrace(); // added to see if we can get more info from failures on CI
}
finally
{
if (br!=null)
{
br.close();
}
}
return sb.toString();
}
private static class HelloWorldHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// System.err.println("HANDLE "+request.getRequestURI());
String ssl_id = (String)request.getAttribute("javax.servlet.request.ssl_session_id");
assertNotNull(ssl_id);
if (request.getParameter("dump")!=null)
{
ServletOutputStream out=response.getOutputStream();
byte[] buf = new byte[Integer.valueOf(request.getParameter("dump"))];
// System.err.println("DUMP "+buf.length);
for (int i=0;i<buf.length;i++)
buf[i]=(byte)('0'+(i%10));
out.write(buf);
out.close();
}
else
{
PrintWriter out=response.getWriter();
out.print(HELLO_WORLD);
out.close();
}
}
}
private static class StreamHandler extends AbstractHandler
{
private int bytes=0;
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.setContentType("text/plain");
response.setBufferSize(128);
byte[] b = new byte[BODY_SIZE];
int len = 0;
InputStream is = request.getInputStream();
while ((len = is.read(b)) > -1)
{
bytes+=len;
}
OutputStream os = response.getOutputStream();
for (int i = 0; i < BODY_SIZE; i++)
{
b[i] = 'x';
}
os.write(b);
response.flushBuffer();
}
}
}