javasocketssslsslsocketfactory

Why does an SSL Server Socket connection block in Java whereas a non SSL Server Socket does not?


Consider the following code for a non-SSL Socket server and client all on the one thread:

import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerClient {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(0); // open a random free port.

        Socket c = new Socket(ss.getInetAddress(), ss.getLocalPort());

        Socket s = ss.accept();

        final byte[] bytes = "Hello World!".getBytes();
        final OutputStream out = c.getOutputStream();
        System.out.println("writing to stream");
        out.write(bytes.length);
        out.write(bytes);

        System.out.println("reading from stream");

        final DataInputStream in = new DataInputStream(s.getInputStream());
        int len = in.read();
        final byte[] b = new byte[len];
        in.readFully(b);
        System.out.println(new String(b));

        c.close();
        ss.close();
    }
}

This produces the following output:

writing to stream
reading from stream
Hello World!

This process opened a server socket - connected with a client socket. Passed data down the socket and then closed down. There was no issue passing the data.

Consider a version to a prove a point with SSL Sockets:

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Logger;

public class SSLServerClient {

    private static Logger log = Logger.getLogger("InfoLogging");

    public static void main(String[] args) throws IOException {

        System.setProperty("javax.net.ssl.keyStore", "/path/KeyStore.jks");
        System.setProperty("javax.net.ssl.keyStorePassword", "password");

        SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

        SSLServerSocket serverListeningSSLSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(4380);
        log.info("Server started");

        SSLSocketFactory sslSocketFactory=(SSLSocketFactory) SSLSocketFactory.getDefault();
        SSLSocket clientSocket =  (SSLSocket) sslSocketFactory.createSocket(serverListeningSSLSocket.getInetAddress(),
                serverListeningSSLSocket.getLocalPort());

        SSLSocket serverCommsSSLSocket = (SSLSocket) serverListeningSSLSocket.accept();
        log.info("new client");

        final byte[] bytes = "Hello World!".getBytes();
        final OutputStream out = clientSocket.getOutputStream();
        System.out.println("writing to stream");
        out.write(bytes.length);
        out.write(bytes);

        System.out.println("reading from stream");

        final DataInputStream in = new DataInputStream(serverCommsSSLSocket.getInputStream());
        int len = in.read();
        final byte[] b = new byte[len];
        in.readFully(b);
        System.out.println(new String(b));

        clientSocket.close();
        serverCommsSSLSocket.close();
        serverListeningSSLSocket.close();
    }
}

This gives the following output:

Nov 21, 2018 10:23:51 PM com.gamble.ssl.SSLServerClient main INFO: Server started
Nov 21, 2018 10:23:52 PM com.gamble.ssl.SSLServerClient main INFO: new client
writing to stream

ie it blocks on the server socket starting.

My question is: Why does an SSL Server Socket connection block in Java whereas a non SSL Server Socket does not?


Solution

  • The simple answer to the question is:

    Because an SSL Handshake is asynchronous and the non SSL socket doesn't have to do that step.

    Ok - there were two issues with the original SSL Code:

    1. There was no TrustStore:

      System.setProperty( "javax.net.ssl.trustStore", "/path/KeyStore.jks");
      
    2. Because the Handshake is synchronous - you have to start the client on a different thread. ie:

      (new Thread() {
        public void run() {
          // do stuff
        }
       }).start();
      

    So the code looks like:

        import javax.net.ssl.SSLServerSocket;
        import javax.net.ssl.SSLServerSocketFactory;
        import javax.net.ssl.SSLSocket;
        import javax.net.ssl.SSLSocketFactory;
        import java.io.DataInputStream;
        import java.io.IOException;
        import java.io.OutputStream;
        import java.util.logging.Logger;
    
        public class SSLServerClient {
    
            public static void main(String[] args) throws IOException {
    
                System.setProperty("javax.net.ssl.keyStore", "/path/KeyStore.jks");
                System.setProperty( "javax.net.ssl.trustStore", "/path/KeyStore.jks");
    
                System.setProperty("javax.net.ssl.keyStorePassword", "password");
    
                SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
    
                SSLServerSocket serverListeningSSLSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(4380);
                System.out.println("--server started");
    
                SSLSocketFactory sslSocketFactory=(SSLSocketFactory) SSLSocketFactory.getDefault();
                SSLSocket clientSocket =  (SSLSocket) sslSocketFactory.createSocket(serverListeningSSLSocket.getInetAddress(),
                        serverListeningSSLSocket.getLocalPort());
    
                SSLSocket serverCommsSSLSocket = (SSLSocket) serverListeningSSLSocket.accept();
                System.out.println("--new client");
    
                final byte[] bytes = "--Hello World!".getBytes();
                final OutputStream out = clientSocket.getOutputStream();
                System.out.println("--Gotten output stream");
                final DataInputStream in = new DataInputStream(serverCommsSSLSocket.getInputStream());
    
                (new Thread() {
                    public void run() {
                        System.out.println("--reading from stream");
    
                        int len = 0;
                        try {
                            len = in.read();
                            final byte[] b = new byte[len];
                            in.readFully(b);
                            System.out.println(new String(b));
    
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
    
    
                System.out.println("--writing to stream");
                out.write(bytes.length);
                System.out.println("--writing to stream - length");
                out.write(bytes);
    
                clientSocket.close();
                serverCommsSSLSocket.close();
                serverListeningSSLSocket.close();
            }
        }
    

    And the output looks like

        --server started
        --new client
        --Gotten output stream
        --writing to stream
        --reading from stream
        --writing to stream - length
        --Hello World!
    
        Process finished with exit code 0
    

    Done

    (Note I added the leading -- to the output to aid when reading SSL Debug output.