[jetty-user] Client-Server with SSL hangs if TrustManager throws exception

Siim Annuk <[email protected]> Thu, 22 Nov 2012 15:08:17 +0200
Newsgroups gmane.comp.java.jetty.support
Message-ID <[email protected]>
--------------010109070003000909070005
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Hello!

When using SSL (with custom Trust/KeyManagers) in Jetty in a 
client-server environment, the program hangs and times out if the 
TrustManager throws an exception during handshake.

For example, if the TrustManager.checkServerTrusted method simply throws 
an CertificateException, the program does not terminate with an 
exception and just waits until the client times out. The exception that 
is thrown is only logged and a "General SSLEngine problem" appears.

So, the question is, how should SSL error handling be done in such case?

I have attached a simple client-server program that triggers the behaviour.

Best regards,
Siim Annuk

--------------010109070003000909070005
Content-Type: text/x-java;
 name="CertificateExceptionTest.java"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="CertificateExceptionTest.java"

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Objects;

import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.client.Address;
import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient;
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.util.ssl.SslContextFactory;

public final class CertificateExceptionTest {

    private static final int PORT_NUMBER = 8443;

    private static HttpClient client;
    private static Server echoServer;

    private static X509Certificate clientCert;
    private static X509Certificate serverCert;
    private static X509Certificate caCert;

    private static PrivateKey clientKey;
    private static PrivateKey serverKey;

    public static void main(String[] args) throws Exception {
        System.setProperty("javax.net.debug", "ssl");
        try {
            loadKeystores();

            createServer();
            createClient();

            echoServer.start();
            client.start();

            sendMessage("Hello, world!");

            client.stop();
            echoServer.stop();
            echoServer.join();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    private static void loadKeystores() throws Exception {
        clientCert = (X509Certificate) ...
        serverCert = (X509Certificate) ...
        caCert = (X509Certificate) ...

        clientKey = (PrivateKey) ...
        serverKey = (PrivateKey) ...
    }

    private static void sendMessage(String message) throws Exception {
        final byte[] data = message.getBytes(StandardCharsets.UTF_8);

        ContentExchange exchange = new ContentExchange() {
            @Override
            protected void onResponseComplete() throws IOException {
                System.out.println(
                        "Received from server: " + getResponseContent());
            }
        };

        exchange.setMethod("POST");
        exchange.setAddress(new Address("127.0.0.1", PORT_NUMBER));
        exchange.setScheme("https");
        exchange.setRequestURI("/");
        exchange.setRequestContentSource(new ByteArrayInputStream(data));

        client.send(exchange);
        exchange.waitForDone();
    }

    private static void createClient() throws Exception {
        SslContextFactory cf = new SslContextFactory(false);
        cf.setNeedClientAuth(true);

        KeyManager[] keyManagers = new KeyManager[] {
                new MyClientKeyManager() };
        TrustManager[] trustManagers = new TrustManager[] {
                new MyClientTrustManager() };

        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(keyManagers, trustManagers, new SecureRandom());

        SSLEngine engine = ctx.createSSLEngine();
        engine.setNeedClientAuth(cf.getNeedClientAuth());
        engine.setEnabledCipherSuites(engine.getSupportedCipherSuites());
        engine.setEnabledProtocols(engine.getSupportedProtocols());

        cf.setSslContext(ctx);

        client = new HttpClient(cf);
        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
        client.setMaxConnectionsPerAddress(1);
    }

    private static class MyClientKeyManager extends X509ExtendedKeyManager {
        @Override
        public String chooseClientAlias(String[] arg0, Principal[] arg1,
                Socket arg2) {
            return null;
        }

        @Override
        public String chooseServerAlias(String arg0, Principal[] arg1,
                Socket arg2) {
            return null;
        }

        @Override
        public X509Certificate[] getCertificateChain(String arg0) {
            return new X509Certificate[] { clientCert };
        }

        @Override
        public String[] getClientAliases(String arg0, Principal[] arg1) {
            return null;
        }

        @Override
        public PrivateKey getPrivateKey(String arg0) {
            return clientKey;
        }

        @Override
        public String[] getServerAliases(String arg0, Principal[] arg1) {
            return null;
        }

        @Override
        public String chooseEngineClientAlias(String[] keyType,
                Principal[] issuers, SSLEngine engine) {
            return "MyClientKeyManager";
        }

        @Override
        public String chooseEngineServerAlias(String keyType,
                Principal[] issuers, SSLEngine engine) {
            return null;
        }
    }

    private static class MyClientTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] arg0, String arg1)
                throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] arg0, String arg1)
                throws CertificateException {
            // XXX: Causes to hang
            throw new CertificateException("Not trusted!");
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }

    private static void createServer() throws Exception {
        echoServer = new Server(8080);
        echoServer.setHandler(new AbstractHandler() {
            @Override
            public void handle(String target, Request baseRequest,
                    HttpServletRequest request, HttpServletResponse response)
                    throws IOException, ServletException {
                StringWriter sw = new StringWriter();
                IOUtils.copy(request.getInputStream(), sw);

                System.out.println("Received from client: " + sw.toString());

                response.getOutputStream().print(sw.toString());
                baseRequest.setHandled(true);
            }
        });

        SslContextFactory cf = new SslContextFactory(false);
        cf.setNeedClientAuth(true);

        KeyManager[] keyManagers = new KeyManager[] {
                new MyServerKeyManager() };
        TrustManager[] trustManagers = new TrustManager[] {
                new MyServerTrustManager() };

        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(keyManagers, trustManagers, new SecureRandom());

        SSLEngine engine = ctx.createSSLEngine();
        engine.setNeedClientAuth(cf.getNeedClientAuth());
        engine.setEnabledCipherSuites(engine.getSupportedCipherSuites());
        engine.setEnabledProtocols(engine.getSupportedProtocols());

        cf.setSslContext(ctx);

        SslSelectChannelConnector connector = new SslSelectChannelConnector(cf);

        connector.setName("ClientConnector");
        connector.setHost("localhost");
        connector.setPort(PORT_NUMBER);

        echoServer.addConnector(connector);
    }

    private static class MyServerKeyManager extends X509ExtendedKeyManager {
        @Override
        public String chooseClientAlias(String[] arg0, Principal[] arg1,
                Socket arg2) {
            return null;
        }

        @Override
        public String chooseServerAlias(String arg0, Principal[] arg1,
                Socket arg2) {
            return null;
        }

        @Override
        public X509Certificate[] getCertificateChain(String arg0) {
            return new X509Certificate[] { serverCert };
        }

        @Override
        public String[] getClientAliases(String arg0, Principal[] arg1) {
            return null;
        }

        @Override
        public PrivateKey getPrivateKey(String arg0) {
            return serverKey;
        }

        @Override
        public String[] getServerAliases(String arg0, Principal[] arg1) {
            return null;
        }

        @Override
        public String chooseEngineClientAlias(String[] keyType,
                Principal[] issuers, SSLEngine engine) {
            return null;
        }

        @Override
        public String chooseEngineServerAlias(String keyType,
                Principal[] issuers, SSLEngine engine) {
            return "MyServerKeyManager";
        }
    }

    private static class MyServerTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] arg0, String arg1)
                throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] arg0, String arg1)
                throws CertificateException {
            // XXX: Deliberately cause a handshake failure
            //throw new CertificateException("checkClientTrusted");
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[] { caCert };
        }
    }
}



--------------010109070003000909070005
Content-Type: text/plain; charset=us-ascii


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email

--------------010109070003000909070005--