Java 11 supports TLS 1.3 protocol which was published in August 2018. During implementing the new TLS protocol, Java security-libs team significantly re-worked Java Secure Sockets Extension (JSSE). I used to work on security-libs in Java for 6 years, so I can tell that was not an easy task for sure. But nevertheless, Java security-libs team delivered TLS 1.3 implementation in Java 11. Great job!

But TLS 1.3 implementation in Java 11 doesn’t not support all the features of the new TLS protocol. Here is what JSSE supports (see more details in JEP 332):

  • Protocol version negotiation
  • Full handshake for both client and server sides
  • Session resumption
  • Key and IV update
  • Updated OCSP stapling
  • Backward compatibility mode
  • Required extensions and algorithms
  • Two new cipher suites: TLS_AES_128_GCM_SHA256 and TLS_AES_256_GCM_SHA384
  • RSASSA-PSS signature algorithms
  • Both SSLSocket and SSLEngine

And here is what are not supported:

Java 11 doesn’t introduce new public classes and methods for TLS 1.3. It just adds a couple of new constants for the new protocol name, cipher suites, ets. And this is actually great because it makes it very easy to switch to the new TLS version. You just need to configure JSSE to use new protocol and ciphers, and the rest of the code should not change. Depending on an application, migration to the new version may not even require any modification of the application code. For example, if an application is configured by JSSE system properties such as https.protocols and jdk.tls.client.protocols. (well, if third-parties you’l like to talk to with TLS 1.3 don’t support it, then the migration may not be that easy).

Here is an example of TLS 1.3 client and server in Java. As you may notice, it’s just a regular example of SSLSocket-based client and server except it uses new constants “TLSv1.3” and “TLS_AES_128_GCM_SHA256”.

package com.gypsyengineer.tlsbunny.jsse;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
/*
* Don't forget to set the following system properties when you run the class:
*
* javax.net.ssl.keyStore
* javax.net.ssl.keyStorePassword
* javax.net.ssl.trustStore
* javax.net.ssl.trustStorePassword
*
* More details can be found in JSSE docs.
*
* For example:
*
* java -cp classes \
* -Djavax.net.ssl.keyStore=keystore \
* -Djavax.net.ssl.keyStorePassword=passphrase \
* -Djavax.net.ssl.trustStore=keystore \
* -Djavax.net.ssl.trustStorePassword=passphrase \
* com.gypsyengineer.tlsbunny.jsse.TLSv13Test
*
* For testing purposes, you can download the keystore file from
*
* https://github.com/openjdk/jdk/tree/master/test/jdk/javax/net/ssl/etc
*/
public class TLSv13Test {
private static final int delay = 1000; // in millis
private static final String[] protocols = new String[] {"TLSv1.3"};
private static final String[] cipher_suites = new String[] {"TLS_AES_128_GCM_SHA256"};
private static final String message =
"Like most of life's problems, this one can be solved with bending!";
public static void main(String[] args) throws Exception {
try (EchoServer server = EchoServer.create()) {
new Thread(server).start();
Thread.sleep(delay);
try (SSLSocket socket = createSocket("localhost", server.port())) {
InputStream is = new BufferedInputStream(socket.getInputStream());
OutputStream os = new BufferedOutputStream(socket.getOutputStream());
os.write(message.getBytes());
os.flush();
byte[] data = new byte[2048];
int len = is.read(data);
if (len <= 0) {
throw new IOException("no data received");
}
System.out.printf("client received %d bytes: %s%n",
len, new String(data, 0, len));
}
}
}
public static SSLSocket createSocket(String host, int port) throws IOException {
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault()
.createSocket(host, port);
socket.setEnabledProtocols(protocols);
socket.setEnabledCipherSuites(cipher_suites);
return socket;
}
public static class EchoServer implements Runnable, AutoCloseable {
private static final int FREE_PORT = 0;
private final SSLServerSocket sslServerSocket;
private EchoServer(SSLServerSocket sslServerSocket) {
this.sslServerSocket = sslServerSocket;
}
public int port() {
return sslServerSocket.getLocalPort();
}
@Override
public void close() throws IOException {
if (sslServerSocket != null && !sslServerSocket.isClosed()) {
sslServerSocket.close();
}
}
@Override
public void run() {
System.out.printf("server started on port %d%n", port());
try (SSLSocket socket = (SSLSocket) sslServerSocket.accept()) {
System.out.println("accepted");
InputStream is = new BufferedInputStream(socket.getInputStream());
OutputStream os = new BufferedOutputStream(socket.getOutputStream());
byte[] data = new byte[2048];
int len = is.read(data);
if (len <= 0) {
throw new IOException("no data received");
}
System.out.printf("server received %d bytes: %s%n",
len, new String(data, 0, len));
os.write(data, 0, len);
os.flush();
} catch (Exception e) {
System.out.printf("exception: %s%n", e.getMessage());
}
System.out.println("server stopped");
}
public static EchoServer create() throws IOException {
return create(FREE_PORT);
}
public static EchoServer create(int port) throws IOException {
SSLServerSocket socket = (SSLServerSocket)
SSLServerSocketFactory.getDefault().createServerSocket(port);
socket.setEnabledProtocols(protocols);
socket.setEnabledCipherSuites(cipher_suites);
return new EchoServer(socket);
}
}
}
view raw TLSv13Test.java hosted with ❤ by GitHub