F-9129: check the negotiated DH group size against the configured DH minimum in checkKeySize

pull/401/head
Chris Conlon 2026-08-14 15:54:10 -06:00
parent c5d622670c
commit ea207d9f94
6 changed files with 357 additions and 44 deletions

View File

@ -4492,6 +4492,22 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getKeySize
#endif
}
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getDhKeySize
(JNIEnv* jenv, jobject jcl, jlong ssl)
{
(void)jenv;
(void)jcl;
#ifndef NO_DH
/* Returns negotiated DH group size in bits, 0 if no DH suite was
* negotiated, or BAD_FUNC_ARG if ssl is NULL. */
return wolfSSL_GetDhKey_Sz((WOLFSSL*)(uintptr_t)ssl);
#else
(void)ssl;
return NOT_COMPILED_IN;
#endif
}
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getSide
(JNIEnv* jenv, jobject jcl, jlong ssl)
{

View File

@ -655,6 +655,14 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_WolfSSLSession_getServerWriteIV
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getKeySize
(JNIEnv *, jobject, jlong);
/*
* Class: com_wolfssl_WolfSSLSession
* Method: getDhKeySize
* Signature: (J)I
*/
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getDhKeySize
(JNIEnv *, jobject, jlong);
/*
* Class: com_wolfssl_WolfSSLSession
* Method: getSide

View File

@ -659,6 +659,7 @@ public class WolfSSLSession {
private native byte[] getServerWriteKey(long ssl);
private native byte[] getServerWriteIV(long ssl);
private native int getKeySize(long ssl);
private native int getDhKeySize(long ssl);
private native int getSide(long ssl);
private native int isTLSv1_1(long ssl);
private native int getBulkCipher(long ssl);
@ -4381,6 +4382,27 @@ public class WolfSSLSession {
}
}
/**
* Get DH group size (bits) negotiated during handshake.
*
* @return negotiated DH group size in bits, or 0 if the handshake
* did not use a Diffie-Hellman cipher suite. Returns negative
* error code on error (ex: NOT_COMPILED_IN, BAD_FUNC_ARG, etc).
* @throws IllegalStateException WolfSSLSession object has been freed
* @see #getKeySize()
*/
public int getDhKeySize() throws IllegalStateException {
confirmObjectIsActive();
synchronized (sslLock) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
WolfSSLDebug.INFO, this.sslPtr, () -> "entered getDhKeySize()");
return getDhKeySize(this.sslPtr);
}
}
/**
* Allows retrieval of the side of this wolfSSL connection.
*

View File

@ -2164,60 +2164,42 @@ public class WolfSSLEngineHelper {
return ret;
}
/**
* Verify the Diffie-Hellman group negotiated during the handshake is at
* least the minimum size configured in jdk.tls.disabledAlgorithms.
*/
private void checkKeySize(WolfSSLSession ssl, boolean clientMode)
throws SSLException, WolfSSLException {
int keySize = this.ssl.getKeySize();
/* Skip unless a DHE group was negotiated. getDhKeySize() returns 0
* for a non-DHE handshake and a negative code when DH is not compiled
* into native wolfSSL, neither of which needs checking. */
int dhKeySize = ssl.getDhKeySize();
if (dhKeySize <= 0) {
return;
}
/*
* Before we update the cached values, and return from the handshake,
* we check if we are running a legacy cipher suite, if so, we make sure
* that the actual key size is at least 1024 bits.
*/
String[] cipherSuites = getCiphers();
try {
int minDHKeySize =
WolfSSLUtil.getDisabledAlgorithmsKeySizeLimit("DH");
if (containsDHECiphers(cipherSuites)) {
/* Get the minimum DH key size from security settings. */
int minDHEKeySize;
try {
minDHEKeySize =
WolfSSLUtil.getDisabledAlgorithmsKeySizeLimit("DH");
/*
* If we're trying to use DHE with
* insufficient key size, throw early. */
if (isLegacyDHEnabled() && keySize < minDHEKeySize) {
if (clientMode) {
throw new SSLHandshakeException(
"DH ServerKeyExchange does not comply to " +
"algorithm constraints");
} else {
throw new SSLHandshakeException(
"Received fatal alert: insufficient_security");
}
/* A minimum of 0 means no limit is configured */
if (dhKeySize < minDHKeySize) {
if (clientMode) {
throw new SSLHandshakeException(
"DH ServerKeyExchange does not comply to " +
"algorithm constraints");
} else {
throw new SSLHandshakeException(
"Received fatal alert: insufficient_security");
}
} catch (WolfSSLException e) {
throw new WolfSSLException(
"Failed to check DH key size constraints: ", e);
}
} catch (WolfSSLException e) {
throw new WolfSSLException(
"Failed to check DH key size constraints: ", e);
}
}
private boolean containsDHECiphers(String[] cipherSuites) {
for (String suite : cipherSuites) {
if (suite.contains("_DHE_")) {
return true;
}
}
return false;
}
private boolean isLegacyDHEnabled() {
/* Check if legacy DH is enabled through system properties. */
String dhKeySize = System.getProperty("jdk.tls.ephemeralDHKeySize");
return "legacy".equals(dhKeySize);
}
/**
* Validates Server Name Indication (SNI) match between client request and
* server matchers.

View File

@ -421,6 +421,120 @@ public class WolfSSLSocketTest {
}
/**
* A valid TLS 1.2 DHE_RSA handshake over a 2048 bit FFDHE group must
* complete even with the legacy jdk.tls.ephemeralDHKeySize property set.
* The group is above the DH minimum, so checkKeySize must not reject it.
*/
@Test
public void testLegacyEphemeralDHKeySizeAllowsValidDheHandshake()
throws Exception {
Assume.assumeTrue("TLS 1.2 and RSA required in native wolfSSL",
WolfSSL.TLSv12Enabled() && WolfSSL.RsaEnabled());
String[] dheSuites = dheRsaIanaSuites();
Assume.assumeTrue("DHE_RSA cipher suites not available",
dheSuites.length > 0);
synchronized (WolfSSLPQCTestUtil.GROUP_PROP_LOCK) {
String prevWolf = WolfSSLPQCTestUtil.setCurvesProperty(null);
String prevJdk =
WolfSSLPQCTestUtil.setJdkNamedGroupsProperty("ffdhe2048");
String prevEphemeral =
System.getProperty("jdk.tls.ephemeralDHKeySize");
System.clearProperty("jdk.tls.ephemeralDHKeySize");
try {
/* Probe without legacy property first, skip if build cannot
* complete ffdhe2048 DHE handshake. */
try {
dheSocketHandshake(dheSuites);
} catch (Exception e) {
Assume.assumeNoException(
"TLS 1.2 ffdhe2048 DHE handshake not supported by " +
"this build", e);
}
/* Legacy property must not cause checkKeySize to reject same
* valid handshake. */
System.setProperty("jdk.tls.ephemeralDHKeySize", "legacy");
String suite = dheSocketHandshake(dheSuites);
assertTrue("negotiated cipher suite must be DHE_RSA, got: " +
suite, suite.startsWith("TLS_DHE_RSA_") ||
suite.startsWith("DHE-RSA-"));
}
finally {
if (prevEphemeral == null) {
System.clearProperty("jdk.tls.ephemeralDHKeySize");
} else {
System.setProperty("jdk.tls.ephemeralDHKeySize",
prevEphemeral);
}
WolfSSLPQCTestUtil.restoreCurvesProperty(prevWolf);
WolfSSLPQCTestUtil.restoreJdkNamedGroupsProperty(prevJdk);
}
}
}
/* Complete a TLS 1.2 DHE_RSA SSLSocket handshake with the given suites
* and return the negotiated cipher suite. Throws on handshake failure. */
private String dheSocketHandshake(String[] dheSuites) throws Exception {
SSLContext dheCtx = tf.createSSLContext("TLSv1.2", "wolfJSSE");
SSLServerSocket ss = null;
SSLSocket cs = null;
SSLSocket server = null;
ExecutorService es = Executors.newSingleThreadExecutor();
try {
ss = (SSLServerSocket)dheCtx.getServerSocketFactory()
.createServerSocket(0);
ss.setEnabledCipherSuites(dheSuites);
ss.setEnabledProtocols(new String[] { "TLSv1.2" });
cs = (SSLSocket)dheCtx.getSocketFactory().createSocket();
cs.setEnabledCipherSuites(dheSuites);
cs.setEnabledProtocols(new String[] { "TLSv1.2" });
cs.connect(new InetSocketAddress(ss.getLocalPort()));
final SSLSocket server0 = (SSLSocket)ss.accept();
server = server0;
Future<Void> f = es.submit(() -> {
server0.startHandshake();
return null;
});
cs.startHandshake();
f.get(10, TimeUnit.SECONDS);
return cs.getSession().getCipherSuite();
}
finally {
es.shutdownNow();
if (cs != null) {
cs.close();
}
if (server != null) {
server.close();
}
if (ss != null) {
ss.close();
}
}
}
/* IANA-named DHE_RSA cipher suites compiled into native wolfSSL. */
private static String[] dheRsaIanaSuites() {
ArrayList<String> matched = new ArrayList<>();
for (String suite : WolfSSL.getCiphersIana()) {
if (suite.startsWith("TLS_DHE_RSA_")) {
matched.add(suite);
}
}
return matched.toArray(new String[matched.size()]);
}
@Test
public void testGetSupportedProtocols()
throws NoSuchProviderException, NoSuchAlgorithmException {

View File

@ -705,6 +705,177 @@ public class WolfSSLSessionTest {
}
}
@Test
public void test_WolfSSLSession_getDhKeySizeBeforeHandshakeAndAfterFree()
throws WolfSSLJNIException, WolfSSLException {
WolfSSLSession ssl = new WolfSSLSession(ctx);
/* Before any handshake, no DH group is negotiated */
int dhSz = ssl.getDhKeySize();
assertTrue("getDhKeySize() before handshake should be <= 0, got " +
dhSz, dhSz <= 0);
ssl.freeSSL();
try {
ssl.getDhKeySize();
fail("getDhKeySize() after freeSSL() should throw " +
"IllegalStateException");
} catch (IllegalStateException e) {
/* expected */
}
}
@Test
public void test_WolfSSLSession_getDhKeySizeAfterDheHandshake()
throws Exception {
Assume.assumeTrue("TLS 1.2 not compiled into native wolfSSL",
WolfSSL.TLSv12Enabled());
Assume.assumeTrue("DHE-RSA-AES128-GCM-SHA256 not available",
suiteAvailable("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"));
Assume.assumeTrue("ffdhe2048 named group not supported",
ffdhe2048Supported());
final String dheSuite = "DHE-RSA-AES128-GCM-SHA256";
final int[] ffdhe = new int[] { WolfSSL.WOLFSSL_FFDHE_2048 };
final WolfSSLContext srvCtx = createAndSetupWolfSSLContext(
srvCert, srvKey, WolfSSL.SSL_FILETYPE_PEM, cliCert,
WolfSSL.TLSv1_2_ServerMethod());
WolfSSLContext cliCtx = createAndSetupWolfSSLContext(
cliCert, cliKey, WolfSSL.SSL_FILETYPE_PEM, caCert,
WolfSSL.TLSv1_2_ClientMethod());
final ServerSocket srvSocket = new ServerSocket(0);
final int port = srvSocket.getLocalPort();
Socket cliSock = null;
WolfSSLSession ssl = null;
ExecutorService es = Executors.newSingleThreadExecutor();
try {
final Future<Void> srvFuture = es.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
Socket server = null;
WolfSSLSession srvSes = null;
try {
server = srvSocket.accept();
srvSes = new WolfSSLSession(srvCtx);
assertEquals("server setCipherList",
WolfSSL.SSL_SUCCESS,
srvSes.setCipherList(dheSuite));
assertEquals("server useSupportedCurves",
WolfSSL.SSL_SUCCESS,
srvSes.useSupportedCurves(ffdhe));
assertEquals("server setFd", WolfSSL.SSL_SUCCESS,
srvSes.setFd(server));
int ret, err;
do {
ret = srvSes.accept();
err = srvSes.getError(ret);
} while (ret != WolfSSL.SSL_SUCCESS &&
(err == WolfSSL.SSL_ERROR_WANT_READ ||
err == WolfSSL.SSL_ERROR_WANT_WRITE));
} finally {
if (srvSes != null) {
srvSes.shutdownSSL();
srvSes.freeSSL();
}
if (server != null) {
server.close();
}
}
return null;
}
});
cliSock = new Socket("localhost", port);
ssl = new WolfSSLSession(cliCtx);
assertEquals("client setCipherList", WolfSSL.SSL_SUCCESS,
ssl.setCipherList(dheSuite));
assertEquals("client useSupportedCurves", WolfSSL.SSL_SUCCESS,
ssl.useSupportedCurves(ffdhe));
assertEquals("client setFd", WolfSSL.SSL_SUCCESS,
ssl.setFd(cliSock));
int ret, err;
do {
ret = ssl.connect();
err = ssl.getError(ret);
} while (ret != WolfSSL.SSL_SUCCESS &&
(err == WolfSSL.SSL_ERROR_WANT_READ ||
err == WolfSSL.SSL_ERROR_WANT_WRITE));
/* Allow for server-side exception to come through */
srvFuture.get(10, TimeUnit.SECONDS);
/* Skip if build cannot complete the ffdhe2048 DHE handshake */
Assume.assumeTrue("build cannot complete ffdhe2048 DHE handshake",
ret == WolfSSL.SSL_SUCCESS);
int dhSz = ssl.getDhKeySize();
assertTrue("getDhKeySize() after DHE handshake should be > 0, " +
"got " + dhSz, dhSz > 0);
assertEquals("ffdhe2048 handshake should report 2048 bits",
2048, dhSz);
ssl.shutdownSSL();
} finally {
es.shutdownNow();
if (ssl != null) {
ssl.freeSSL();
}
if (cliSock != null) {
cliSock.close();
}
srvSocket.close();
cliCtx.free();
srvCtx.free();
}
}
/* True if the given IANA cipher suite is compiled into native wolfSSL. */
private static boolean suiteAvailable(String ianaName) {
for (String suite : WolfSSL.getCiphersIana()) {
if (suite.equals(ianaName)) {
return true;
}
}
return false;
}
/* True if native wolfSSL supports the ffdhe2048 named group. */
private static boolean ffdhe2048Supported() {
WolfSSLContext c = null;
WolfSSLSession s = null;
try {
c = new WolfSSLContext(WolfSSL.SSLv23_ClientMethod());
s = new WolfSSLSession(c);
int[] ffdhe2048 = new int[] { WolfSSL.WOLFSSL_FFDHE_2048 };
return s.useSupportedCurves(ffdhe2048) == WolfSSL.SSL_SUCCESS;
} catch (Exception e) {
return false;
} finally {
if (s != null) {
try {
s.freeSSL();
} catch (Exception e) {
/* ignore */
}
}
if (c != null) {
try {
c.free();
} catch (Exception e) {
/* ignore */
}
}
}
}
@Test
public void test_WolfSSLSession_getPskIdentity()
throws WolfSSLJNIException, WolfSSLException {