Last active
August 19, 2022 06:10
-
-
Save artem-smotrakov/bd14e4bde4d7238f7e5ab12c697a86a3 to your computer and use it in GitHub Desktop.
An example of TLS 1.3 client and server on Java. See details in https://blog.gypsyengineer.com/en/security/an-example-of-tls-13-client-and-server-on-java.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
Hi @dejpec
In the logs, I see that TLS 1.3 was negotiated:
ClientHello.java:838|Negotiated protocol version: TLSv1.3
ServerHello.java:987|Negotiated protocol version: TLSv1.3
Also, the handshake messages follow TLS 1.3 spec. For example, see EncryptedExtensions
and NewSessionTicket
messages that are available only in TLS 1.3.
Please note that ClientHello.client_version
and ServerHello.server_version
still contain "TLSv1.2". This is expected behavior. You can also notice messages that mention TLSv1.2:
SSLSocketInputRecord.java:213|READ: TLSv1.2 handshake, length = 122
I think it happens because TLSPlaintext.legacy_record_version
has to be"TLSv1.2" in TLS 1.3. That's also expected behavior.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks artem-smotrakov for looking into. Here is the ouput file