JDK-8277307 : Pre shared key sent under both session_ticket and pre_shared_key extensions
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.net.ssl
  • Affected Version: 17,18
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2021-10-26
  • Updated: 2024-02-20
  • Resolved: 2022-06-08
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 17 JDK 19
17.0.10Fixed 19 b26Fixed
Related Reports
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Tested on orcacle JDK 17.0.1 and Open JDK 17+35

A DESCRIPTION OF THE PROBLEM :
This occurs when communicating jvm to jvm on java 17 with TLSv1.3. The pre_shared_key is sent under the session_ticket extension as well.

REGRESSION : Last worked in version 11.0.13

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the example code, provide keystore and truststore as instructed in the code example

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
On the second request, the pre_shared_key extension populated in client hello, but not in session_ticket extension
ACTUAL -
Both extensions carry the same data.

---------- BEGIN SOURCE ----------
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;

/*
 * This code is based on https://blog.gypsyengineer.com/en/security/an-example-of-tls-13-client-and-server-on-java.html
 * to reproduce a TLS issue with pre_shared_key and session_ticket.
 * 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 \
 *             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 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);

            var port = server.port();

            try (SSLSocket socket = createSocket("localhost", port)) {
                talkToServer(socket);
            }
            Thread.sleep(delay);
            try (SSLSocket socket = createSocket("localhost", port)) {
                // This time on JDK17 (OpenJDK Runtime Environment Temurin-17+35 (build 17+35))
                // the Client Hello will contain the same data in pre_shared_key and session_ticket
                // as can be observed in Wireshark for example
                talkToServer(socket);
            }
        }
    }

    private static void talkToServer(SSLSocket socket) throws IOException {
        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.setEnableSessionCreation(true);
        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());

            while (!sslServerSocket.isClosed()) {
                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());
                    e.printStackTrace(System.err);
                }
            }
        }

        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);
            return new EchoServer(socket);
        }
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Set jdk.tls.client.enableSessionTicketExtension=false on the client

FREQUENCY : often



Comments
[17u] Reasoning accepted. Approved for 17.0.10 then.
28-11-2023

[jdk17u-fix-request] Hi, I'm relabeling for a second chance, after my previous clarifying comment and the testing description in https://github.com/openjdk/jdk17u-dev/pull/1946#issuecomment-1814958199.
23-11-2023

Hi [~goetz]: We are aware of users that hit a Tomcat bug [1] because of this issue ([~wetmore] had already pointed this out in an early comment). While this isn't a real fix for the Tomcat issue (fixed by [2]), it works as a mitigation for TLS clients when the server can't be updated. It also reduces network traffic and the issue wasn't present in Java 11, which considerably increases the client hello size when upgrading to 17. Waiting until April 2024 (17.0.11 GA date) seems a bit long to me, given we haven't reached the rampdown for 17.0.10 yet, but I'm ok with targeting 17.0.11. [1]: https://bz.apache.org/bugzilla/show_bug.cgi?id=67938 [2]: https://github.com/apache/tomcat/commit/7dd70b6c071c1d2651305047b83491967cac80d9
08-11-2023

[17u] As this is a functional change, we only want to push this to 17.0.11. Development of 17.0.11 starts Nov 29th, see [17u wiki page](https://wiki.openjdk.org/display/JDKUpdates/JDK+17u). Please label again after that. If you think this is needed in 17.0.10, please give a detailed reasoning.
08-11-2023

[jdk17u-fix-request] Approval Request from Francisco Ferrari Bihurriet 8277307 Clean backport requested for parity with 17.0.11-oracle.
07-11-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk17u-dev/pull/1946 Date: 2023-11-06 11:40:10 +0000
06-11-2023

> The concrete error this issue caused was that the client hello message grew too large > for one record > 16kB, which Spring Boot embedded Tomcat (9.0.54) didn't expect. [~pnarayanaswa]/[~pkoppula] What TLS stack is the Spring Boot embedded Tomcat using? We believe SunJSSE correctly fragments/reassembles handshake messages up to the limits in SSLConfiguration (jdk.tls.maxHandshakeMessageSize), so if Spring Boot embedded Tomcat was using SunJSSE or if our messages are incorrect, please open a new bug. Thanks.
09-06-2022

Changeset: 4662e06b Author: Daniel JeliƄski <djelinski@openjdk.org> Date: 2022-06-08 06:33:40 +0000 URL: https://git.openjdk.java.net/jdk/commit/4662e06bff2cef7425c194a9cdd7a6fe7469179e
08-06-2022

A pull request was submitted for review. URL: https://git.openjdk.java.net/jdk/pull/8922 Date: 2022-05-27 13:20:24 +0000
27-05-2022

This is not the expected behavior; when resuming a session using TLS1.3, we should send: "session_ticket (35)": { <empty> }, "pre_shared_key (41)": { "PreSharedKey": { ... } } instead we are sending the same bytes under PSK identity and session_ticket, inflating the client hello size for no practical reason. We should only send data under session_ticket when resuming a pre-TLS1.3 session, otherwise we should send an empty extension.
23-05-2022

It's expected behavior. Hence proposing to close this request/ issue.
14-12-2021

Additional Information from submitter: =========================== The concrete error this issue caused was that the client hello message grew too large for one record > 16kB, which Spring Boot embedded Tomcat (9.0.54) didn't expect. The pre_shared_key is about 8kB, probably because of mTLS being used.
19-11-2021

This is expected behavior, was introduced as part of JDK-8211018 and it's not backported to jdk11u yet, hence we see the difference in jdk11u.
15-11-2021

The observations on Windows 10: JDK 11: Failed, both server and client received 66 bytes. JDK 17: Failed. JDK 18ea+1: Failed. There was no regression observed.
02-11-2021

Requested the submitter to confirm the outputs of the reproducer.
28-10-2021