Exploring SSL/TLS - Part 1


Bhaskar S 10/15/2017


Introduction

Secure Sockets Layer (SSL) and Transport Layer Security (TLS) are industry standard protocols used to secure and encrypt the communication link between a client and a server.

SSL and TLS are used interchangeably to refer to the private and secure exchange of data between two parties, but in fact SSL is the predecessor of TLS.

SSL (or TLS) can operate in two modes - Server (referred to as one-way SSL authentication) and Mutual (referred to as two-way SSL authentication).

The most commonly used mode is the Server authentication mode, where the server certificate is used to identify and verify the server to the client.

In the Mutual authentication mode, the client certificate is verified by the server in addition to the server certificate being verified by the client.

Server Authentication

The following are the basic steps involved in the one-way SSL/TLS handshake:

The following diagram illustrates the above steps in a pictorial form:

One-Way SSL
One-Way SSL/TLS

Hands-on SSL/TLS using Java

All the code is implemented using Oracle Java 8 SE (64-bit) and tested on a Ubuntu 16.04 Linux (64-bit) variant.

We will now show how one can implement a basic SSL/TLS client and server using the Java Secure Sockets Extensions (JSSE).

The following is the simple SSL enabled echo server:

SecureEchoServer.java
/*
 *
 *  Name:        SecureEchoServer
 *  Description: Echo server that uses the secure sockets
 *  
 */

package com.polarsparc.pki;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;

public class SecureEchoServer {
    private static final int _SSL_PORT = 8443;
    
    public static void main(String[] args) {
        try {
            SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
            
            SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(_SSL_PORT);
            
            System.out.printf("Echo (server) started on %d\n", _SSL_PORT);
            
            for (;;) {
                try (SSLSocket client = (SSLSocket) server.accept()) {
                    try (BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream()))) {
                        String line = null;
                        while ((line = input.readLine()) != null) {
                            System.out.printf("-> Echo (server): %s\n", line);
                            System.out.flush();
                        }
                    }
                    catch (Exception inputEx) {
                        inputEx.printStackTrace();
                    }
                }
                catch (Exception sockEx) {
                    sockEx.printStackTrace();
                }
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Let us explain and understand some of the classes/methods used in the SecureEchoServer code shown above.

The class javax.net.ssl.SSLServerSocketFactory extends the base server socket factory class javax.net.ServerSocketFactory and is used for initializing and creating a secure server socket endpoint. To get a default instance of the factory class, invoke the getDefault() static method on the factory class.

The class javax.net.ssl.SSLServerSocket inherits from the base server socket class java.net.ServerSocket for accepting connections from clients. This class maintains the state for the supported protocols and cipher suites.

The method createServerSocket() on the factory class instance creates and returns a secure server socket bound to the specified port.

The method accept() on the server socket class instance waits and blocks until a client connection request is received. Upon accepting a connection, it returns an instance of the class javax.net.ssl.SSLSocket which represents the socket endpoint for the client.

The following is the simple SSL enabled echo client:

SecureEchoClient.java
/*
 *
 *  Name:        SecureEchoClient
 *  Description: Echo client that uses the secure sockets to communicate with the secure echo server
 *  
 */

package com.polarsparc.pki;

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

public class SecureEchoClient {
    private static final int _SSL_PORT = 8443;
    private static final String _SSL_HOST = "localhost";
    
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.printf("Usage: java com.polarsparc.pki.SecureEchoClient <message>\n");
            System.exit(1);
        }
        
        try {
            SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
            
            SSLSocket socket = (SSLSocket) factory.createSocket(_SSL_HOST, _SSL_PORT);
            
            BufferedWriter output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            
            output.write(args[0]+"\n");
            output.flush();
            
            socket.close();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Let us explain and understand some of the classes/methods used in the SecureEchoClient code shown above.

The class javax.net.ssl.SSLSocketFactory extends the base socket factory class javax.net.SocketFactory and is used for creating a secure socket endpoint. To get a default instance of the factory class, invoke the getDefault() static method on the factory class. The default instance is pre-configured with support for server authentication.

The class javax.net.ssl.SSLSocket inherits from the base socket class java.net.Socket for creating connections with a secure server. This class maintains the state for the supported protocols and cipher suites.

The method createSocket() on the factory class instance creates and returns a secure socket endpoint that is connected to the specified server at the specified port.

Open a new Terminal window, and execute the following command to start the SSL/TLS echo server:

java -cp build/classes com.polarsparc.pki.SecureEchoServer

The following should be the typical output:

Output.1

Echo (server) started on 8443

Open another Terminal window, and execute the following command to start the SSL/TLS echo client:

java -cp build/classes com.polarsparc.pki.SecureEchoClient "Hello SSL/TLS World"

The following should be the typical output:

Output.2

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
  at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
  at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
  at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2033)
  at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1135)
  at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
  at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:757)
  at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:123)
  at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
  at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
  at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:295)
  at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141)
  at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229)
  at java.io.BufferedWriter.flush(BufferedWriter.java:254)
  at com.polarsparc.pki.SecureEchoClient.main(SecureEchoClient.java:35)

What happened here ???

From the terminal window where the server was started, we see the following output:

Output.3

javax.net.ssl.SSLHandshakeException: no cipher suites in common
  at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
  at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1959)
  at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
  at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:292)
  at sun.security.ssl.ServerHandshaker.chooseCipherSuite(ServerHandshaker.java:1045)
  at sun.security.ssl.ServerHandshaker.clientHello(ServerHandshaker.java:741)
  at sun.security.ssl.ServerHandshaker.processMessage(ServerHandshaker.java:224)
  at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026)
  at sun.security.ssl.Handshaker.process_record(Handshaker.java:961)
  at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072)
  at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
  at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:938)
  at sun.security.ssl.AppInputStream.read(AppInputStream.java:105)
  at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
  at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
  at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
  at java.io.InputStreamReader.read(InputStreamReader.java:184)
  at java.io.BufferedReader.fill(BufferedReader.java:161)
  at java.io.BufferedReader.readLine(BufferedReader.java:324)
  at java.io.BufferedReader.readLine(BufferedReader.java:389)
  at com.polarsparc.pki.SecureEchoServer.main(SecureEchoServer.java:33)

For both the client and the server, we have not specified any keystore that holds the private keys and digital certificates. As a result, there are no cryptographically strong ciphers that could be negotiated between the client and the server.

Is there a way to fix this ???

Sure there is, but use caution - one could enable all the supported ciphers including the ones that are cryptographically weaker than the ones enabled by default. We need to do this on both the server and the client side.

The following is the simple SSL enabled echo server with the code that enables all the supported ciphers:

SecureEchoServer2.java
/*
 *
 *  Name:        SecureEchoServer2
 *  Description: Echo server that uses the secure sockets with all supported ciphers enabled
 *  
 */

package com.polarsparc.pki;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;

public class SecureEchoServer2 {
    private static final int _SSL_PORT = 8443;
    
    public static void main(String[] args) {
        try {
            SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
            
            SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(_SSL_PORT);
            server.setEnabledCipherSuites(server.getSupportedCipherSuites());
            
            System.out.printf("Echo (server-2) started on %d\n", _SSL_PORT);
            
            for (;;) {
                try (SSLSocket client = (SSLSocket) server.accept()) {
                    try (BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream()))) {
                        String line = null;
                        while ((line = input.readLine()) != null) {
                            System.out.printf("-> Echo (server-2): %s\n", line);
                            System.out.flush();
                        }
                    }
                    catch (Exception inputEx) {
                        inputEx.printStackTrace();
                    }
                }
                catch (Exception sockEx) {
                    sockEx.printStackTrace();
                }
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

The method getSupportedCipherSuites() on the server socket class instance returns the names of all the crytographic ciphers that could be enabled for this SSL/TLS connection.

The method setEnabledCipherSuites() on the server socket class instance enables all the specified crytographic ciphers to be used on this SSL/TLS connection.

Similarly, the following is the simple SSL enabled echo client with the code that enables all the supported ciphers:

SecureEchoClient.java
/*
 *
 *  Name:        SecureEchoClient2
 *  Description: Echo client that uses the secure sockets to communicate with the secure echo server with all supported
 *               ciphers enabled
 *  
 */

package com.polarsparc.pki;

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

public class SecureEchoClient2 {
    private static final int _SSL_PORT = 8443;
    private static final String _SSL_HOST = "localhost";
    
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.printf("Usage: java com.polarsparc.pki.SecureEchoClient2 <message>\n");
            System.exit(1);
        }
        
        try {
            SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
            
            SSLSocket socket = (SSLSocket) factory.createSocket(_SSL_HOST, _SSL_PORT);
            socket.setEnabledCipherSuites(socket.getSupportedCipherSuites());
            
            BufferedWriter output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            
            output.write(args[0]+"\n");
            output.flush();
            
            socket.close();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

From the terminal window where the server was started, re-execute the following command to start the SSL/TLS echo server:

java -cp build/classes com.polarsparc.pki.SecureEchoServer

The following should be the typical output:

Output.4

Echo (server-2) started on 8443

From the terminal window where the client was started, re-execute the following command to start the SSL/TLS echo client:

java -cp build/classes com.polarsparc.pki.SecureEchoClient "Hello SSL/TLS World"

From the terminal window where the server was started, we see the following output:

Output.5

-> Echo (server-2): Hello SSL/TLS World

!!! WARNING !!!

***DO NOT*** enable cryptographically insecure ciphers using:

socket.setEnabledCipherSuites(socket.getSupportedCipherSuites())