JDK-8328046 : Need to keep leading zeros in TlsPremasterSecret of TLS1.3 DHKeyAgreement
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.net.ssl
  • Affected Version: 8,23
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2024-03-13
  • Updated: 2025-06-30
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.
Other
tbdUnresolved
Related Reports
Cloners :  
Duplicate :  
Description
A DESCRIPTION OF THE PROBLEM :
When performing Diffie-Hellman key agreement for SSL/TLS, the TLS 1.3 specification (RFC 8446) says that  "The negotiated key (Z) is converted to a byte string by encoding in big-endian form and left-padded with zeros up to the size of the prime. [...] Note that this construction differs from previous versions of TLS which removed leading zeros." 

However, since JDK-8014618, com.sun.crypto.provider.DHKeyAgreement.java strips leading zero bytes. This causes approximately 1 out 256 TLS1.3  handshakes using ffdhe named groups to fail (when the leading byte happens, by chance, to be zero).


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Start a simple JSSE socket server with -Djavax.net.debug=all.

2. Connect to the server with e.g. OpenSSL command line tool, ensuring that ffdhe group gets selected (e.g.  " openssl s_client -groups ffdhe2048 -connect 192.168.81.1:9999 " ) repeatedly.  Other SSL clients can be used -- this is not an OpenSSL bug (see below).

3. Repeat the connection. After a couple of hundred successful connections, the connection will fail with "decryption failed or bad record mac".

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expected result is that every connection succeed.
ACTUAL -
Roughly one out of 256 connections fail.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
Java server:

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;

public class TestServer {
    public static void main(String args[]) throws Exception {
SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket ss = (SSLServerSocket) ssf.createServerSocket(9999);
System.out.println( " Listening on port 9999 " );
while (true) {
    SSLSocket s = (SSLSocket) ss.accept();
    System.out.println( " Connected with  " +s.getSession().getCipherSuite());
    s.close();
}
    }
}

Run as as follows:

keytool -storepass  " password "  -keypass  " password "  -genkey -keyalg RSA -keystore test_keystore.jks -dname CN=test
javac TestServer.java
java -Djavax.net.debug=all -Djavax.net.ssl.keyStore=./test_keystore.jks -Djavax.net.ssl.keyStorePassword=password TestServer

OpenSSL client:

set -e
while true; do
  openssl s_client -groups ffdhe2048 -connect 127.0.0.1:9999 -quiet -no_ign_eof < /dev/null
done



---------- END SOURCE ----------

Comments
this will only work with extractable keys; it won't work with PKCS11 in FIPS mode, for example (getEncoded returns null)
16-04-2024

Suggested fix: ```java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/DHKeyAgreement.java b/src/java.base/share/classes/com/sun/crypto/provider/DHKeyAgreement.java index 01f978fff61..34efee16653 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/DHKeyAgreement.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/DHKeyAgreement.java @@ -436,9 +436,9 @@ protected SecretKey engineGenerateSecret(String algorithm) } return skey; } else if (algorithm.equals("TlsPremasterSecret")) { - // remove leading zero bytes per RFC 5246 Section 8.1.2 - return new SecretKeySpec( - KeyUtil.trimZeroes(secret), "TlsPremasterSecret"); + // return entire secret + return new SecretKeySpec(secret, "TlsPremasterSecret"); + } else { throw new NoSuchAlgorithmException("Unsupported secret key " + "algorithm: "+ algorithm); diff --git a/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java b/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java index b76da75c763..064cfd100b0 100644 --- a/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java +++ b/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java @@ -24,6 +24,8 @@ */ package sun.security.ssl; +import sun.security.util.KeyUtil; + import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -75,6 +77,10 @@ private SecretKey t12DeriveKey(String algorithm, ka.doPhase(peerPublicKey, true); SecretKey preMasterSecret = ka.generateSecret("TlsPremasterSecret"); + // remove leading zero bytes per RFC 5246 Section 8.1.2 + preMasterSecret = new SecretKeySpec( + KeyUtil.trimZeroes(preMasterSecret.getEncoded()), + "TlsPremasterSecret"); SSLMasterKeyDerivation mskd = SSLMasterKeyDerivation.valueOf( context.negotiatedProtocol); ```
16-04-2024