Java 8: How to perform timeout for SSLSocket.startHandshake()

Viewed 1160

We have below thread to perform SSLHandshake but in some edge cases I noticed ((SSLSocket) clientSocket).startHandshake(); is blocked forever and its not going to the next block of while loop code where SSL_HANDSHAKE_TIMEOUT is 1500 milli seconds and it works fine, Am wondering if adding clientSocket.setSoTimeout(90000); would fix this issue or should it handled in a different way?

MainServerHandshakeThread

public class MainServerHandshakeThread implements com.ssltunnel.utilities.threading.Shutdown, Runnable {
    private final Socket clientSocket;
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(MainServerHandshakeThread.class.getName());
    private boolean done;

    public MainServerHandshakeThread(Socket clientSocket) {
        this.clientSocket = clientSocket;        
    }

    private void handshake() throws CertificateExpiredException, InterruptedException, IOException {

        long start = System.currentTimeMillis();

        ((SSLSocket) clientSocket).setNeedClientAuth(true);
        MainServerHandshakeHandler handshake = new MainServerHandshakeHandler();
        ((SSLSocket) clientSocket).addHandshakeCompletedListener(handshake);
        ((SSLSocket) clientSocket).startHandshake();


        while (!handshake.isDone() && !done) {
            Thread.sleep(10);
            long duration = System.currentTimeMillis() - start;
            if (duration>SSL_HANDSHAKE_TIMEOUT) {
                done = true;
                LOG.warn("Handshake timeout");
            }
        }
        long stop = System.currentTimeMillis();        
        serialNumber = handshake.getSerialNumber();
        LOG.info("MainServer Handshake Handshake done in ms: " + ((stop - start))+" For serialNumber "+serialNumber );        

    }

    @Override
    public void run() {
        try {
            handshake();
        } catch (CertificateExpiredException ex) {
            LOG.error("Client Certificate Expired", ex.getMessage());
            SocketUtils.closeQuietly(clientSocket);
        }
        catch (InterruptedException ex) {
            LOG.error("Interrupted waiting for handshake", ex);
            SocketUtils.closeQuietly(clientSocket);
        } 
        catch (IOException ex) {
            LOG.error("IO Error waiting for handshake", ex);
            SocketUtils.closeQuietly(clientSocket);
        }
        finally {
            LOG.debug("Handshake thread is done");
            done = true;
        }

    }

    @Override
    public void shutdown() {
        if (clientSocket!=null) {
            SocketUtils.closeQuietly(clientSocket);
        }
    }
}
2 Answers

To summarize the comments (mostly from @user207421): yes, setting a socket timeout through socket.setSoTimeout(timeout) will be enough to trigger a SocketTimeoutException (a subclass of IOException) if the handshake process doesn't complete after "some time" (but not necessarily the specified timeout1).

That is simply explained because setSoTimeout() works at the socket level, below the SSL handshake: the handshake protocol performed by startHandshake() involves several reads/writes from the socket Input/OutputStream, which will trigger the timeout itself. In other words: it is not a "handshake timeout" per se, but the "read timeout" on all read operations performed by the handshake itself.

Also, note that you don't need to call startHandshake() yourself: the JVM will do that automatically when you first try to read or write from your SSLSocket (which is something you usually do early anyway after you get such a socket from an SSLServerSocket anyway).


1: The timeout specified by setSoTimeout(timeout) is for a single read(). So the handshake process can timeout (in the worst case) after the number of read() it performs, times the timeout value you specified.

Most implementations of SSLSocket support setHandshakeTimeout() but it is not part of the class definition. I suggest you to look for example at Android code. This is a good example of how to deal with it: ssl socket factory wrapper and ssl socket factory

Check how they try to set a handshake timeout if exist:

    private void setHandshakeTimeout(SSLSocket sslSocket, int timeout) {

    try {

        // Most implementations of SSLSocket support setHandshakeTimeout(), but it is not

        // actually part of the class definition. We will attempt to set it using reflection.

        // If the particular implementation of SSLSocket we are using does not support this

        // function, then we will just have to use the default handshake timeout.

        sslSocket.getClass().getMethod("setHandshakeTimeout", int.class).invoke(sslSocket,

                timeout);

    } catch (Exception e) {

        LogUtils.w(LogUtils.TAG, e, "unable to set handshake timeout");

    }

}

And look at how they get a connection:

    SSLSocket sslsock = (SSLSocket)

        ((sock != null) ? sock : createSocket());



    if ((localAddress != null) || (localPort > 0)) {



        // we need to bind explicitly

        if (localPort < 0)

            localPort = 0; // indicates "any"



        InetSocketAddress isa =

            new InetSocketAddress(localAddress, localPort);

        sslsock.bind(isa);

    }



    int connTimeout = HttpConnectionParams.getConnectionTimeout(params);

    int soTimeout = HttpConnectionParams.getSoTimeout(params);



    InetSocketAddress remoteAddress;

    if (nameResolver != null) {

        remoteAddress = new InetSocketAddress(nameResolver.resolve(host), port);

    } else {

        remoteAddress = new InetSocketAddress(host, port);

    }



    sslsock.connect(remoteAddress, connTimeout);



    sslsock.setSoTimeout(soTimeout);

    try {

        hostnameVerifier.verify(host, sslsock);

        // verifyHostName() didn't blowup - good!

    } catch (IOException iox) {

        // close the socket before re-throwing the exception

        try { sslsock.close(); } catch (Exception x) { /*ignore*/ }

        throw iox;

    }



    return sslsock;

Hope it helps.

Related