F-4858: fail-fast validate DH public key in publicKeyDecodeX509 JNI import

pull/245/head
Chris Conlon 2026-07-23 13:49:58 -06:00
parent c5d0402877
commit 00d55d66cc
5 changed files with 158 additions and 3 deletions

View File

@ -359,6 +359,14 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RsaOaepEnabl
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_DhEnabled
(JNIEnv *, jclass);
/*
* Class: com_wolfssl_wolfcrypt_FeatureDetect
* Method: DhExtraEnabled
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_DhExtraEnabled
(JNIEnv *, jclass);
/*
* Class: com_wolfssl_wolfcrypt_FeatureDetect
* Method: EccEnabled

View File

@ -1298,9 +1298,38 @@ Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPublicKeyDecode(
/* Decode X.509 public key */
ret = wc_DhKeyDecode(x509, &idx, key, x509Sz);
LogStr("wc_DhKeyDecode(x509=%p, key=%p) = %d\n", x509, key, ret);
#ifdef WOLFSSL_DH_EXTRA
if (ret == 0) {
/* Fail fast on an out of range public key. wc_DhAgree also validates
* at use. Export the decoded Y to validate. */
byte* pubVal = NULL;
word32 pubValSz = (DH_MAX_SIZE + 7) / 8;
pubVal = (byte*)XMALLOC(pubValSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (pubVal == NULL) {
ret = MEMORY_E;
}
else {
ret = wc_DhExportKeyPair(key, NULL, NULL, pubVal, &pubValSz);
if (ret == 0) {
ret = wc_DhCheckPubKey(key, pubVal, pubValSz);
}
#if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && \
!defined(WOLFSSL_NO_FORCE_ZERO)
wc_ForceZero(pubVal, (DH_MAX_SIZE + 7) / 8);
#else
XMEMSET(pubVal, 0, (DH_MAX_SIZE + 7) / 8);
#endif
XFREE(pubVal, NULL, DYNAMIC_TYPE_TMP_BUFFER);
}
LogStr("DH public key validation = %d\n", ret);
}
#endif
if (ret == 0) {
/* Return the same DER data (validated) */
/* Return the DER as passed in */
result = (*env)->NewByteArray(env, x509Sz);
if (result) {
(*env)->SetByteArrayRegion(env, result, 0, x509Sz,
@ -1312,8 +1341,6 @@ Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPublicKeyDecode(
}
}
LogStr("wc_DhKeyDecode(x509=%p, key=%p) = %d\n", x509, key, ret);
releaseByteArray(env, x509_object, x509, JNI_ABORT);
if (ret != 0) {

View File

@ -558,6 +558,18 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_DhEnabled
#endif
}
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_DhExtraEnabled
(JNIEnv* env, jclass jcl)
{
(void)env;
(void)jcl;
#if !defined(NO_DH) && defined(WOLFSSL_DH_EXTRA)
return JNI_TRUE;
#else
return JNI_FALSE;
#endif
}
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_EccEnabled
(JNIEnv* env, jclass jcl)
{

View File

@ -347,6 +347,13 @@ public class FeatureDetect {
*/
public static native boolean DhEnabled();
/**
* Tests if WOLFSSL_DH_EXTRA is compiled into the native wolfSSL library.
*
* @return true if enabled, otherwise false if not compiled in.
*/
public static native boolean DhExtraEnabled();
/**
* Tests if ECC is compiled into the native wolfSSL library.
*

View File

@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicIntegerArray;
import com.wolfssl.wolfcrypt.Dh;
import com.wolfssl.wolfcrypt.Rng;
import com.wolfssl.wolfcrypt.FeatureDetect;
import com.wolfssl.wolfcrypt.WolfCryptError;
import com.wolfssl.wolfcrypt.WolfCryptException;
import com.wolfssl.wolfcrypt.Fips;
@ -49,6 +50,45 @@ public class DhTest {
private static Rng rng = new Rng();
private final Object rngLock = new Rng();
/* 2048-bit DH prime p, generator g is 2. */
private static final String DH_P_2048_HEX =
"8E7EC04F98E157D62585624B5283FFD8D33B5EC35F0812FE79227319974A3517" +
"6F858116030A97494618413BB82E87BBCFF26A24DF32CAEEF40BE3FBBC18C709" +
"A167423BEE8C84539B0FADEBD305039AA800C3AA4A4E3C6B775FAD8E11E30FF9" +
"B1E1CA138C9DC9947751A5EE935AC54BB4B045D0CE6BA1AB4D40701D62F4990E" +
"A1CB9783CC3769171C8FFDC1AE7A10EC1C82AF9240B67CC667A00F70F2FE9B3B" +
"4BDABE25EB42FBEC5533E53A4BA5B60B32B7CBF326D42F620757A11C554B91DF" +
"C67B54403807E1A228B858B8D2614BF9D0E08B840F5CA95BA0ADDD641F2481B9" +
"0A7C47583D2F14E9C3A5C80440E74B7AA41BBD71025B9F754F424BF382BB5817";
/* X.509 SubjectPublicKeyInfo for the group above with public value
* Y = g = 2, a valid in range public key. */
private static final String DH_PUB_X509_VALID_HEX =
"308201213082011706092A864886F70D0103013082010802820101008E7EC04F" +
"98E157D62585624B5283FFD8D33B5EC35F0812FE79227319974A35176F858116" +
"030A97494618413BB82E87BBCFF26A24DF32CAEEF40BE3FBBC18C709A167423B" +
"EE8C84539B0FADEBD305039AA800C3AA4A4E3C6B775FAD8E11E30FF9B1E1CA13" +
"8C9DC9947751A5EE935AC54BB4B045D0CE6BA1AB4D40701D62F4990EA1CB9783" +
"CC3769171C8FFDC1AE7A10EC1C82AF9240B67CC667A00F70F2FE9B3B4BDABE25" +
"EB42FBEC5533E53A4BA5B60B32B7CBF326D42F620757A11C554B91DFC67B5440" +
"3807E1A228B858B8D2614BF9D0E08B840F5CA95BA0ADDD641F2481B90A7C4758" +
"3D2F14E9C3A5C80440E74B7AA41BBD71025B9F754F424BF382BB581702010203" +
"0400020102";
/* Same as above but with public value Y = 1, an out of range small
* subgroup value that must be rejected. */
private static final String DH_PUB_X509_SMALLSUBGROUP_HEX =
"308201213082011706092A864886F70D0103013082010802820101008E7EC04F" +
"98E157D62585624B5283FFD8D33B5EC35F0812FE79227319974A35176F858116" +
"030A97494618413BB82E87BBCFF26A24DF32CAEEF40BE3FBBC18C709A167423B" +
"EE8C84539B0FADEBD305039AA800C3AA4A4E3C6B775FAD8E11E30FF9B1E1CA13" +
"8C9DC9947751A5EE935AC54BB4B045D0CE6BA1AB4D40701D62F4990EA1CB9783" +
"CC3769171C8FFDC1AE7A10EC1C82AF9240B67CC667A00F70F2FE9B3B4BDABE25" +
"EB42FBEC5533E53A4BA5B60B32B7CBF326D42F620757A11C554B91DFC67B5440" +
"3807E1A228B858B8D2614BF9D0E08B840F5CA95BA0ADDD641F2481B90A7C4758" +
"3D2F14E9C3A5C80440E74B7AA41BBD71025B9F754F424BF382BB581702010203" +
"0400020101";
@Rule(order = Integer.MIN_VALUE)
public TestRule testWatcher = TimedTestWatcher.create();
@ -119,6 +159,67 @@ public class DhTest {
assertArrayEquals(sharedSecretA, sharedSecretB);
}
/**
* checkPublicKey() must reject an out of range public key value. This
* is the same validation wc_DhAgree applies to a peer public key, so a
* small subgroup value cannot reach the shared secret computation.
*/
@Test
public void checkPublicKeyRejectsOutOfRangeValue() {
Dh dh = new Dh();
dh.setParams(Util.h2b(DH_P_2048_HEX), Util.h2b("02"));
try {
/* Y = 1 is below the valid range 2 <= Y <= p-2 */
dh.checkPublicKey(new byte[] { 0x01 });
fail("checkPublicKey should reject Y = 1");
} catch (WolfCryptException e) {
/* expected */
} finally {
dh.releaseNativeStruct();
}
}
/**
* publicKeyDecodeX509() must accept a valid in range public key.
*/
@Test
public void publicKeyDecodeX509AcceptsValidKey() {
Assume.assumeTrue("WOLFSSL_DH_EXTRA required",
FeatureDetect.DhExtraEnabled());
Dh dh = new Dh();
try {
byte[] der = dh.publicKeyDecodeX509(
Util.h2b(DH_PUB_X509_VALID_HEX));
assertNotNull(der);
} finally {
dh.releaseNativeStruct();
}
}
/**
* publicKeyDecodeX509() must reject a public key whose value is an out
* of range small subgroup element, failing fast at import instead of
* deferring to the shared secret computation.
*/
@Test
public void publicKeyDecodeX509RejectsSmallSubgroupValue() {
Assume.assumeTrue("WOLFSSL_DH_EXTRA required",
FeatureDetect.DhExtraEnabled());
Dh dh = new Dh();
try {
dh.publicKeyDecodeX509(
Util.h2b(DH_PUB_X509_SMALLSUBGROUP_HEX));
fail("publicKeyDecodeX509 should reject Y = 1");
} catch (WolfCryptException e) {
/* expected */
} finally {
dh.releaseNativeStruct();
}
}
@Test
public void bufferOverflowRegressionTest() {
/*