-
-
Save artem-smotrakov/bd14e4bde4d7238f7e5ab12c697a86a3 to your computer and use it in GitHub Desktop.
| 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); | |
| } | |
| } | |
| } |
@vkolomeyko did you get it fixed? I also got same error.
@vkolomeyko Sorry for the late reply, for some reason I missed your message.
I forgot to mention that the program needs a keystore and a truststore. Otherwise, the server can't find a certificate to authenticate itself, and as a result a SSLHandshakeException is thrown.
@phaugat7 To fix the problem, you need to set javax.net.ssl.keyStore, javax.net.ssl.keyStorePassword, javax.net.ssl.trustStore and javax.net.ssl.trustStorePassword system properties. 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
Hi @artem-smotrakov,
Thank you for the example. I am trying to send over 2^14 bytes (Updated the String message & byte[] data) but the program limits it. Is there a specific reason? Or can the code be modified to handle messages over 2^14 bytes?
Thanks.
Hi @vcheruk2
According to the spec, 2^14 is the max length of a TLSPlaintext record:
https://tools.ietf.org/html/rfc8446#section-5.1
If you need to send data that exceeds this limit, you need to split the data. Have you tried that? Do you get any exception?
I think that the default TLS implementation in Java (JSSE) should be able to split the data that exceeds the limit. If it doesn't, then it looks like a bug or a potential enhancement in JSSE.
I tried your example and it worked well. But then I turned on -Djavax.net.debug=all.
Then I see that the TLS handshake for 1.3 fails and the client tries again with TLS 1.2 ...
So the example works but it does not use TLS 1.3. I tried with open jdk 11.0.12.7 and open jdk-14.0.1.7.
Any idea whats going wrong ? When I debug I see that java does not have a cipher and protocol combination which allows to run tls 1.3.
Hi @dejpec
Then I see that the TLS handshake for 1.3 fails and the client tries again with TLS 1.2
That sounds strange. The example code enables only TLS 1.3 via setEnabledProtocols() call. I assume you didn't update the protocols variable. Furthermore, the only enabled cipher suite TLS_AES_128_GCM_SHA256 is available only in TLS 1.3.
Could you please post somewhere the output with -Djavax.net.debug=all?
Thanks artem-smotrakov for looking into. Here is the ouput file
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.
Hello @artem-smotrakov - thanks for preparing this example!
There are not that many samples on the Web for Java 11/TLS 1.3.
I am trying to run this example without any modification on Oracle JDK 11.0.2 and getting this:
I will dig a bit further to understand the root cause, but wanted to just flag the issue I am experiencing.