package org.mortbay.jetty.integration.ssl;
// ========================================================================
// Copyright 2008 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
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.Socket;
import java.net.SocketException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CRLException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.codehaus.plexus.util.Os;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.server.ssl.SslSocketConnector;
import org.eclipse.jetty.toolchain.test.OS;
import org.jsslutils.sslcontext.PKIXSSLContextFactory;
import org.jsslutils.sslcontext.X509SSLContextFactory;
import junit.framework.TestCase;
/**
* Testing setSslContext using jSSLutils. (Some code was borrowed from
* SSLEngineTest.)
*
*
*
*/
public class SSLContextTest extends TestCase
{
// Useful constants
private static final String HELLO_WORLD = "Hello world\r\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 / HTTP/1.1\n" + "Host: localhost\n" + "Content-Type: text/xml\n" + "Content-Length: ";
private static final String REQUEST1_HEADER = "POST / 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=\"ISO-8859-1\"?>\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\n";
private static final String RESPONSE1 = "HTTP/1.1 200 OK\n" + "Connection: close\n" + "Server: Jetty(" + JETTY_VERSION + ")\n" + '\n' + "Hello world\n";
private static final String TEST_KEYSTORES_PATH = "certificates/";
/**
* Returns the store of CA certificates, to be used as a trust store. The
* default value is to load 'dummy.jks', part of this test suite.
*
* @return KeyStore containing the certificates to trust.
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws CertificateException
*/
public KeyStore getCaKeyStore() throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException
{
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(ClassLoader.getSystemResourceAsStream(TEST_KEYSTORES_PATH + "jks/dummy.jks"),"testtest".toCharArray());
return keyStore;
}
/**
* Returns the keystore containing the key and the certificate to be used by
* the server.
*
* @return KeyStore containing the server credentials.
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws CertificateException
*/
public KeyStore getServerCertKeyStore() throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException
{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(ClassLoader.getSystemResourceAsStream(TEST_KEYSTORES_PATH + "localhost.p12"),"testtest".toCharArray());
return keyStore;
}
/**
* Returns the keystore containing a test key and certificate that is to be
* trusted by the server. This is the "good" keystore in that its
* certificate has not been revoked by the demo CA. This should work
* whether-or-not CRLs are used.
*
* @return KeyStore containing the "good" client credentials.
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws CertificateException
*/
public KeyStore getGoodClientCertKeyStore() throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException
{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(ClassLoader.getSystemResourceAsStream(TEST_KEYSTORES_PATH + "testclient.p12"),"testtest".toCharArray());
return keyStore;
}
/**
* Returns the keystore containing a test key and certificate that is not to
* be trusted by the server when CRLs are enabled. This is the "bad"
* keystore in that its certificate has been revoked by the demo CA. This
* should pass work when CRLs checks are disabled, but fail when they are
* used.
*
* @return KeyStore containing the "bad" client credentials.
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws CertificateException
*/
public KeyStore getBadClientCertKeyStore() throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException
{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(ClassLoader.getSystemResourceAsStream(TEST_KEYSTORES_PATH + "testclient-r.p12"),"testtest".toCharArray());
return keyStore;
}
/**
* Returns a collection of CRLs to be used by the tests. This is loaded from
* 'newca.crl'.
*
* @return CRLs
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws CertificateException
* @throws CRLException
*/
public Collection<X509CRL> getLocalCRLs() throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException, CRLException
{
InputStream inStream = ClassLoader.getSystemResourceAsStream(TEST_KEYSTORES_PATH + "newca.crl");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509CRL crl = (X509CRL)cf.generateCRL(inStream);
inStream.close();
ArrayList<X509CRL> crls = new ArrayList<X509CRL>();
crls.add(crl);
return crls;
}
/**
* This runs the main test: it runs a client and a server.
*
* @param sslClientContext
* SSLContext to be used by the client.
* @return true if the server accepted the SSL certificate.
* @throws IOException
*/
public boolean runSSLContextTest(SSLContext sslClientContext, Connector connector) throws Exception
{
boolean result = false;
Server server = new Server();
server.setConnectors(new Connector[]
{ connector });
server.setHandler(new HelloWorldHandler());
server.start();
int testPort = connector.getLocalPort();
SSLSocket sslClientSocket = null;
try
{
SSLSocketFactory sslClientSocketFactory = sslClientContext.getSocketFactory();
sslClientSocket = (SSLSocket)sslClientSocketFactory.createSocket("localhost",testPort);
assertTrue("Client socket connected",sslClientSocket.isConnected());
sslClientSocket.setSoTimeout(500);
OutputStream os = sslClientSocket.getOutputStream();
os.write(REQUEST0.getBytes());
os.write(REQUEST0.getBytes());
os.flush();
os.write(REQUEST1.getBytes());
os.flush();
// Read the response.
String responses = readResponse(sslClientSocket);
// Check the response
assertEquals(RESPONSE0 + RESPONSE0 + RESPONSE1,responses);
result = true;
}
catch (SocketException e)
{
result = false;
}
catch (SSLHandshakeException e)
{
result = false;
printSslException("! Client: ",e,sslClientSocket);
}
finally
{
server.stop();
}
return result;
}
public void testSslSelectChannelConnector_PKIX_GoodClient() throws Exception
{
PKIXSSLContextFactory serverSSLContextFactory = new PKIXSSLContextFactory(getServerCertKeyStore(),"testtest",getCaKeyStore());
serverSSLContextFactory.addCrlCollection(getLocalCRLs());
SslSelectChannelConnector connector = new SslSelectChannelConnector();
connector.setNeedClientAuth(true);
connector.setPort(0);
connector.setSslContext(serverSSLContextFactory.buildSSLContext("SSLv3"));
PKIXSSLContextFactory clientSSLContextFactory;
clientSSLContextFactory = new PKIXSSLContextFactory(getGoodClientCertKeyStore(),"testtest",getCaKeyStore());
clientSSLContextFactory.addCrlCollection(getLocalCRLs());
assertTrue(runSSLContextTest(clientSSLContextFactory.buildSSLContext("SSLv3"),connector));
}
public void testSslSelectChannelConnector_PKIX_BadClient() throws Exception
{
PKIXSSLContextFactory serverSSLContextFactory = new PKIXSSLContextFactory(getServerCertKeyStore(),"testtest",getCaKeyStore());
serverSSLContextFactory.addCrlCollection(getLocalCRLs());
SslSelectChannelConnector connector = new SslSelectChannelConnector();
connector.setNeedClientAuth(true);
connector.setPort(0);
connector.setSslContext(serverSSLContextFactory.buildSSLContext("SSLv3"));
PKIXSSLContextFactory clientSSLContextFactory;
clientSSLContextFactory = new PKIXSSLContextFactory(getBadClientCertKeyStore(),"testtest",getCaKeyStore());
clientSSLContextFactory.addCrlCollection(getLocalCRLs());
assertTrue(!runSSLContextTest(clientSSLContextFactory.buildSSLContext("SSLv3"),connector));
}
public void testSslSelectChannelConnector_X509_GoodClient() throws Exception
{
X509SSLContextFactory serverSSLContextFactory = new X509SSLContextFactory(getServerCertKeyStore(),"testtest",getCaKeyStore());
SslSelectChannelConnector connector = new SslSelectChannelConnector();
connector.setNeedClientAuth(true);
connector.setPort(0);
connector.setSslContext(serverSSLContextFactory.buildSSLContext("SSLv3"));
X509SSLContextFactory clientSSLContextFactory;
clientSSLContextFactory = new X509SSLContextFactory(getGoodClientCertKeyStore(),"testtest",getCaKeyStore());
assertTrue(runSSLContextTest(clientSSLContextFactory.buildSSLContext("SSLv3"),connector));
}
public void testSslSelectChannelConnector_X509_BadClient() throws Exception
{
X509SSLContextFactory serverSSLContextFactory = new X509SSLContextFactory(getServerCertKeyStore(),"testtest",getCaKeyStore());
SslSelectChannelConnector connector = new SslSelectChannelConnector();
connector.setNeedClientAuth(true);
connector.setPort(0);
connector.setSslContext(serverSSLContextFactory.buildSSLContext("SSLv3"));
X509SSLContextFactory clientSSLContextFactory;
clientSSLContextFactory = new X509SSLContextFactory(getBadClientCertKeyStore(),"testtest",getCaKeyStore());
assertTrue(runSSLContextTest(clientSSLContextFactory.buildSSLContext("SSLv3"),connector));
}
public void testSslSocketConnector_PKIX_GoodClient() throws Exception
{
PKIXSSLContextFactory serverSSLContextFactory = new PKIXSSLContextFactory(getServerCertKeyStore(),"testtest",getCaKeyStore());
serverSSLContextFactory.addCrlCollection(getLocalCRLs());
SslSocketConnector connector = new SslSocketConnector();
connector.setNeedClientAuth(true);
connector.setPort(0);
connector.setSslContext(serverSSLContextFactory.buildSSLContext("SSLv3"));
PKIXSSLContextFactory clientSSLContextFactory;
clientSSLContextFactory = new PKIXSSLContextFactory(getGoodClientCertKeyStore(),"testtest",getCaKeyStore());
clientSSLContextFactory.addCrlCollection(getLocalCRLs());
assertTrue(runSSLContextTest(clientSSLContextFactory.buildSSLContext("SSLv3"),connector));
}
public void testSslSocketConnector_PKIX_BadClient() throws Exception
{
// skipped due to a bug in sun.security.x509.X509CRLEntryImpl.getExtensions in 1.7u4
if (!OS.IS_OSX )
{
PKIXSSLContextFactory serverSSLContextFactory = new PKIXSSLContextFactory(getServerCertKeyStore(),"testtest",getCaKeyStore());
serverSSLContextFactory.addCrlCollection(getLocalCRLs());
SslSocketConnector connector = new SslSocketConnector();
connector.setNeedClientAuth(true);
connector.setPort(0);
connector.setSslContext(serverSSLContextFactory.buildSSLContext("SSLv3"));
PKIXSSLContextFactory clientSSLContextFactory;
clientSSLContextFactory = new PKIXSSLContextFactory(getBadClientCertKeyStore(),"testtest",getCaKeyStore());
clientSSLContextFactory.addCrlCollection(getLocalCRLs());
assertTrue(!runSSLContextTest(clientSSLContextFactory.buildSSLContext("SSLv3"),connector));
}
}
public void testSslSocketConnector_X509_GoodClient() throws Exception
{
X509SSLContextFactory serverSSLContextFactory = new X509SSLContextFactory(getServerCertKeyStore(),"testtest",getCaKeyStore());
SslSocketConnector connector = new SslSocketConnector();
connector.setNeedClientAuth(true);
connector.setPort(0);
connector.setSslContext(serverSSLContextFactory.buildSSLContext("SSLv3"));
X509SSLContextFactory clientSSLContextFactory;
clientSSLContextFactory = new X509SSLContextFactory(getGoodClientCertKeyStore(),"testtest",getCaKeyStore());
assertTrue(runSSLContextTest(clientSSLContextFactory.buildSSLContext("SSLv3"),connector));
}
public void testSslSocketConnector_X509_BadClient() throws Exception
{
X509SSLContextFactory serverSSLContextFactory = new X509SSLContextFactory(getServerCertKeyStore(),"testtest",getCaKeyStore());
SslSocketConnector connector = new SslSocketConnector();
connector.setNeedClientAuth(true);
connector.setPort(0);
connector.setSslContext(serverSSLContextFactory.buildSSLContext("SSLv3"));
X509SSLContextFactory clientSSLContextFactory;
clientSSLContextFactory = new X509SSLContextFactory(getBadClientCertKeyStore(),"testtest",getCaKeyStore());
assertTrue(runSSLContextTest(clientSSLContextFactory.buildSSLContext("SSLv3"),connector));
}
/**
* Used for printing out more info when there's a problem.
*
* @param prefix
* @param sslException
* @param socket
* @return
*/
private Throwable printSslException(String prefix, SSLException sslException, SSLSocket socket)
{
Throwable cause = sslException;
while ((cause = cause.getCause()) != null)
{
if (cause instanceof CertPathValidatorException)
{
CertPathValidatorException certException = (CertPathValidatorException)cause;
CertPath certPath = certException.getCertPath();
List<? extends Certificate> certificates = certPath.getCertificates();
int index = certException.getIndex();
if (index >= 0)
{
Certificate pbCertificate = certificates.get(index);
if (pbCertificate instanceof X509Certificate)
{
System.out.println(prefix + "Problem caused by cert: " + ((X509Certificate)pbCertificate).getSubjectX500Principal().getName());
}
else
{
System.out.println(prefix + "Problem caused by cert: " + pbCertificate);
}
}
else
{
System.out.println(prefix + "Unknown index: " + cause);
}
break;
}
else
{
System.out.println(prefix + cause);
if (socket != null)
{
printSslSocketInfo(socket);
}
}
}
return cause;
}
/**
* Used for printing out more info when there's a problem.
*
* @param socket
*/
private void printSslSocketInfo(SSLSocket socket)
{
System.out.println("Socket: " + socket);
SSLSession session = socket.getSession();
if (session != null)
{
System.out.println("Session: " + session);
System.out.println(" Local certificates: " + session.getLocalCertificates());
System.out.println(" Local principal: " + session.getLocalPrincipal());
SSLSessionContext context = session.getSessionContext();
if (context != null)
{
System.out.println("Session context: " + context);
}
}
}
/**
* Read entire response from the client. Close the output.
*
* @param client
* Open client socket.
*
* @return The response string.
*
* @throws IOException
*/
private static String readResponse(Socket client) throws IOException
{
BufferedReader br = null;
try
{
br = new BufferedReader(new InputStreamReader(client.getInputStream()));
StringBuilder sb = new StringBuilder(1000);
String line;
while ((line = br.readLine()) != null)
{
sb.append(line);
sb.append('\n');
}
return sb.toString();
}
finally
{
if (br != null)
{
br.close();
}
}
}
private static class HelloWorldHandler extends AbstractHandler
{
// ~ Methods
// ------------------------------------------------------------
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
PrintWriter out = response.getWriter();
try
{
out.print(HELLO_WORLD);
}
finally
{
out.close();
}
}
}
}