-
-
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.