diff --git a/src/java/com/wolfssl/WolfSSL.java b/src/java/com/wolfssl/WolfSSL.java index c8217ae..c3a9dd9 100644 --- a/src/java/com/wolfssl/WolfSSL.java +++ b/src/java/com/wolfssl/WolfSSL.java @@ -2189,6 +2189,32 @@ public class WolfSSL { return PQC_GROUP_IDS.contains(Integer.valueOf(namedGroup)); } + /** + * Returns true if the given native named-group enum value identifies a + * classic elliptic curve group (SECT/SECP/Brainpool/X25519/X448/SM2), + * as opposed to an FFDHE or post-quantum group. + * + * The classification follows the IANA TLS Supported Groups registry + * partition (RFC 7919). Every value in the range 1-255 is treated as + * an elliptic curve group, including IANA-assigned curve IDs that do + * not yet have WOLFSSL_ECC_* constants on this class (ex: + * brainpoolP256r1tls13 through brainpoolP512r1tls13 at 31-33, GOST + * curves at 34-40). Values 256-511 are the FFDHE block and PQC + * standalone/hybrid groups sit at 512 and above, all returning false. + * + * @param namedGroup native named-group enum value, typically the + * result of {@link #getNamedGroupFromString(String)} + * or one of the WOLFSSL_ECC_* constants on this class. + * @return true if the group is in the elliptic curve range of the + * supported groups registry, false otherwise (including for + * {@link #WOLFSSL_NAMED_GROUP_INVALID}, FFDHE groups, and + * PQC standalone/hybrid groups). + */ + public static boolean isECCNamedGroup(int namedGroup) { + return (namedGroup > WOLFSSL_NAMED_GROUP_INVALID && + namedGroup < WOLFSSL_FFDHE_2048); + } + @SuppressWarnings({"deprecation", "removal"}) @Override protected void finalize() throws Throwable diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java b/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java index 5c82e23..3c6b123 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java @@ -291,6 +291,20 @@ public class WolfSSLEngineHelper { keyAlgos.add("ML-DSA-87"); } + /* On server side, only prefer an ECC alias when an ECDSA certificate + * can actually complete a key exchange for this session. When the + * TLS named groups have been explicitly restricted to a list with + * no ECC curves (ex: FFDHE groups only via jdk.tls.namedGroups), + * ECDHE/ECDH cipher suites cannot be negotiated. */ + if (!clientMode && keyAlgos.contains("EC") && + !eccCertUsableWithNamedGroups()) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "no ECC named groups configured for session, " + + "preferring non-ECC server cert/key types"); + keyAlgos.remove("EC"); + keyAlgos.add("EC"); + } + String[] keyTypes = new String[keyAlgos.size()]; keyTypes = keyAlgos.toArray(keyTypes); @@ -338,6 +352,58 @@ public class WolfSSLEngineHelper { return alias; } + /** + * Return true if an ECDSA certificate is usable for key exchange on + * this session, given the configured TLS named groups and the + * SSLContext protocol version. + * + * Reads the named-groups configuration with the same source precedence + * as setLocalSupportedGroups(): SSLParameters.setNamedGroups(), then + * jdk.tls.namedGroups System property, then the + * wolfjsse.enabledSupportedCurves Security property. + * + * Returns true when no source is configured (native default groups + * include ECC curves), when the configured list contains at least one + * classic ECC curve, or when the SSLContext is (D)TLS 1.3-only, since + * TLS 1.3 certificate authentication is independent of the negotiated + * key exchange group. + */ + private boolean eccCertUsableWithNamedGroups() { + + String[] groups = + WolfSSLParametersHelper.getNamedGroupsFromParams(this.params); + + if (groups == null) { + groups = WolfSSLUtil.getJdkTlsNamedGroups(); + } + + if (groups == null) { + groups = WolfSSLUtil.getSupportedCurves(); + } + + if (groups == null) { + return true; + } + + for (String name : groups) { + if (WolfSSL.isECCNamedGroup( + WolfSSL.getNamedGroupFromString(name))) { + return true; + } + } + + /* No ECC curves configured. ECDHE/ECDH cannot complete on + * (D)TLS <= 1.2, but TLS 1.3 does not tie cert type to the group. + * Use the context protocol version, not the enabled protocols + * list: cert selection can run before the app finishes configuring + * the session (ex: SSLEngine.getSession() loads the cert), while + * a (D)TLS 1.3-only context can never negotiate lower. */ + WolfSSL.TLS_VERSION ctxVersion = + this.authStore.getProtocolVersion(); + return (ctxVersion == WolfSSL.TLS_VERSION.TLSv1_3 || + ctxVersion == WolfSSL.TLS_VERSION.DTLSv1_3); + } + /** * Loads the private key and certificate chain for this * SSLSocket/SSLEngine to be used for performing authentication of diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLNamedGroupsTest.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLNamedGroupsTest.java index 85c6e98..e088bd0 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLNamedGroupsTest.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLNamedGroupsTest.java @@ -22,13 +22,17 @@ package com.wolfssl.provider.jsse.test; import com.wolfssl.WolfSSL; +import com.wolfssl.WolfSSLContext; import com.wolfssl.WolfSSLException; +import com.wolfssl.WolfSSLSession; import com.wolfssl.provider.jsse.WolfSSLParametersHelper; import com.wolfssl.provider.jsse.WolfSSLProvider; import com.wolfssl.test.TimedTestWatcher; import java.lang.reflect.Method; +import java.util.ArrayList; import java.security.NoSuchProviderException; import java.security.Security; +import java.security.cert.Certificate; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; @@ -41,6 +45,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; /** * Tests for TLS named-groups (supported_groups) configuration in wolfJSSE. @@ -348,6 +353,400 @@ public class WolfSSLNamedGroupsTest { } } + /** + * A server whose KeyStore holds both RSA and ECC private keys must + * complete a TLS 1.2 handshake when jdk.tls.namedGroups is restricted + * to FFDHE groups only. + * + * Server certificate selection prefers an ECC alias over RSA when the + * KeyStore holds both. With no ECC curves in supported_groups, ECDHE + * suites cannot be negotiated on TLS 1.2, so an ECDSA certificate + * cannot complete any key exchange and the handshake previously failed + * with no shared cipher suite. The RSA alias must be preferred instead + * so a DHE_RSA suite can complete over the RFC 7919 group. + */ + @Test + public void testJdkTlsNamedGroupsFfdheOnlyTls12PrefersRsaCert() + throws Exception { + + Assume.assumeTrue("TLS 1.2, RSA and ECC required in native wolfSSL", + WolfSSL.TLSv12Enabled() && WolfSSL.RsaEnabled() && + WolfSSL.EccEnabled()); + Assume.assumeTrue("DHE_RSA cipher suites not available", + dheRsaSuitesAvailable()); + Assume.assumeTrue("ffdhe2048 named group not supported", + ffdhe2048GroupSupported()); + + synchronized (WolfSSLPQCTestUtil.GROUP_PROP_LOCK) { + Assume.assumeTrue("TLS 1.2 FFDHE handshakes not supported by " + + "native wolfSSL build", tls12FfdheHandshakeSupported()); + + String prevWolf = + WolfSSLPQCTestUtil.setCurvesProperty(null); + String prevJdk = + WolfSSLPQCTestUtil.setJdkNamedGroupsProperty("ffdhe2048"); + + try { + SSLContext ctx = tf.createSSLContext("TLSv1.2", PROVIDER); + SSLEngine server = ctx.createSSLEngine(); + server.setUseClientMode(false); + server.setNeedClientAuth(false); + SSLEngine client = + ctx.createSSLEngine("wolfSSL named groups test", 11111); + client.setUseClientMode(true); + + int ret = tf.testConnection(server, client, + suitesMatching("TLS_DHE_RSA_"), + new String[] { "TLSv1.2" }, APP_DATA); + assertEquals("TLS 1.2 handshake with FFDHE-only " + + "jdk.tls.namedGroups and a KeyStore holding both RSA " + + "and ECC keys must succeed", 0, ret); + + String suite = client.getSession().getCipherSuite(); + assertTrue("negotiated cipher suite must be DHE_RSA, " + + "got: " + suite, suite.startsWith("TLS_DHE_RSA_") || + suite.startsWith("DHE-RSA-")); + } + finally { + WolfSSLPQCTestUtil.restoreCurvesProperty(prevWolf); + WolfSSLPQCTestUtil.restoreJdkNamedGroupsProperty(prevJdk); + } + } + } + + /** + * True when native wolfSSL was compiled with DH support, using the + * presence of DHE_RSA cipher suites as a proxy. FFDHE named groups + * need DH for both TLS 1.2 DHE_RSA suites and TLS 1.3 FFDHE key + * shares. + */ + private static boolean dheRsaSuitesAvailable() { + for (String suite : WolfSSL.getCiphersIana()) { + if (suite.startsWith("TLS_DHE_RSA_")) { + return true; + } + } + return false; + } + + /** + * Return compiled-in cipher suites whose IANA names start with any of + * the given prefixes. + */ + private static String[] suitesMatching(String... prefixes) { + + ArrayList matched = new ArrayList<>(); + + for (String suite : WolfSSL.getCiphersIana()) { + for (String prefix : prefixes) { + if (suite.startsWith(prefix)) { + matched.add(suite); + break; + } + } + } + + return matched.toArray(new String[matched.size()]); + } + + /* Cached result of tls12FfdheHandshakeSupported() */ + private static Boolean tls12FfdheCapable = null; + + /** + * True when this environment can complete a TLS 1.2 handshake with + * FFDHE-only named groups at all, probed with an RSA-only server + * KeyStore so that certificate selection plays no part. + * + * Caller must hold WolfSSLPQCTestUtil.GROUP_PROP_LOCK. + */ + private static boolean tls12FfdheHandshakeSupported() throws Exception { + + if (tls12FfdheCapable != null) { + return tls12FfdheCapable.booleanValue(); + } + + String prevJdk = + WolfSSLPQCTestUtil.setJdkNamedGroupsProperty("ffdhe2048"); + + try { + SSLContext srvCtx = tf.createSSLContext("TLSv1.2", PROVIDER, + tf.createTrustManager("SunX509", tf.caClientJKS, PROVIDER), + tf.createKeyManager("SunX509", tf.serverRSAJKS, PROVIDER)); + SSLContext cliCtx = tf.createSSLContext("TLSv1.2", PROVIDER, + tf.createTrustManager("SunX509", tf.caServerJKS, PROVIDER), + tf.createKeyManager("SunX509", tf.clientRSAJKS, PROVIDER)); + + SSLEngine server = srvCtx.createSSLEngine(); + server.setUseClientMode(false); + server.setNeedClientAuth(false); + SSLEngine client = + cliCtx.createSSLEngine("wolfSSL named groups test", 11111); + client.setUseClientMode(true); + + int ret = tf.testConnection(server, client, + suitesMatching("TLS_DHE_RSA_"), + new String[] { "TLSv1.2" }, APP_DATA); + tls12FfdheCapable = Boolean.valueOf(ret == 0); + } + finally { + WolfSSLPQCTestUtil.restoreJdkNamedGroupsProperty(prevJdk); + } + + return tls12FfdheCapable.booleanValue(); + } + + /** + * True when native wolfSSL supports the ffdhe2048 TLS named group + * (HAVE_FFDHE_2048), probed the same way wolfJSSE applies configured + * groups to a session. + */ + private static boolean ffdhe2048GroupSupported() { + + WolfSSLContext ctx = null; + WolfSSLSession ssl = null; + + try { + ctx = new WolfSSLContext(WolfSSL.SSLv23_ClientMethod()); + ssl = new WolfSSLSession(ctx); + + return (ssl.useSupportedCurves( + new int[] { WolfSSL.WOLFSSL_FFDHE_2048 }) == + WolfSSL.SSL_SUCCESS); + } catch (Exception e) { + return false; + } finally { + /* Best-effort cleanup, must not throw out of this probe */ + if (ssl != null) { + try { + ssl.freeSSL(); + } catch (Exception e) { + /* ignore */ + } + } + if (ctx != null) { + try { + ctx.free(); + } catch (Exception e) { + /* ignore */ + } + } + } + } + + /** + * Same server RSA-alias preference as + * testJdkTlsNamedGroupsFfdheOnlyTls12PrefersRsaCert, driven by the + * lowest-precedence configuration source, the + * wolfjsse.enabledSupportedCurves Security property. + */ + @Test + public void testWolfjsseCurvesPropertyFfdheOnlyTls12PrefersRsaCert() + throws Exception { + + Assume.assumeTrue("TLS 1.2, RSA and ECC required in native wolfSSL", + WolfSSL.TLSv12Enabled() && WolfSSL.RsaEnabled() && + WolfSSL.EccEnabled()); + Assume.assumeTrue("DHE_RSA cipher suites not available", + dheRsaSuitesAvailable()); + Assume.assumeTrue("ffdhe2048 named group not supported", + ffdhe2048GroupSupported()); + + synchronized (WolfSSLPQCTestUtil.GROUP_PROP_LOCK) { + Assume.assumeTrue("TLS 1.2 FFDHE handshakes not supported by " + + "native wolfSSL build", tls12FfdheHandshakeSupported()); + + String prevJdk = + WolfSSLPQCTestUtil.setJdkNamedGroupsProperty(null); + String prevWolf = + WolfSSLPQCTestUtil.setCurvesProperty("ffdhe2048"); + + try { + SSLContext ctx = tf.createSSLContext("TLSv1.2", PROVIDER); + SSLEngine server = ctx.createSSLEngine(); + server.setUseClientMode(false); + server.setNeedClientAuth(false); + SSLEngine client = + ctx.createSSLEngine("wolfSSL named groups test", 11111); + client.setUseClientMode(true); + + int ret = tf.testConnection(server, client, + suitesMatching("TLS_DHE_RSA_"), + new String[] { "TLSv1.2" }, APP_DATA); + assertEquals("TLS 1.2 handshake with FFDHE-only " + + "wolfjsse.enabledSupportedCurves and a KeyStore " + + "holding both RSA and ECC keys must succeed", 0, ret); + + String suite = client.getSession().getCipherSuite(); + assertTrue("negotiated cipher suite must be DHE_RSA, " + + "got: " + suite, suite.startsWith("TLS_DHE_RSA_") || + suite.startsWith("DHE-RSA-")); + } + finally { + WolfSSLPQCTestUtil.restoreCurvesProperty(prevWolf); + WolfSSLPQCTestUtil.restoreJdkNamedGroupsProperty(prevJdk); + } + } + } + + /** + * Same server RSA-alias preference as + * testJdkTlsNamedGroupsFfdheOnlyTls12PrefersRsaCert, driven by the + * highest-precedence configuration source, per-engine + * SSLParameters.setNamedGroups() (JDK 20+). + */ + @Test + public void testSetNamedGroupsFfdheOnlyTls12PrefersRsaCert() + throws Exception { + + Assume.assumeTrue("Host JDK lacks SSLParameters.setNamedGroups", + jdkHasNamedGroups); + Assume.assumeTrue("TLS 1.2, RSA and ECC required in native wolfSSL", + WolfSSL.TLSv12Enabled() && WolfSSL.RsaEnabled() && + WolfSSL.EccEnabled()); + Assume.assumeTrue("DHE_RSA cipher suites not available", + dheRsaSuitesAvailable()); + Assume.assumeTrue("ffdhe2048 named group not supported", + ffdhe2048GroupSupported()); + + synchronized (WolfSSLPQCTestUtil.GROUP_PROP_LOCK) { + Assume.assumeTrue("TLS 1.2 FFDHE handshakes not supported by " + + "native wolfSSL build", tls12FfdheHandshakeSupported()); + + String prevWolf = + WolfSSLPQCTestUtil.setCurvesProperty(null); + String prevJdk = + WolfSSLPQCTestUtil.setJdkNamedGroupsProperty(null); + + try { + SSLContext ctx = tf.createSSLContext("TLSv1.2", PROVIDER); + SSLEngine server = ctx.createSSLEngine(); + server.setUseClientMode(false); + server.setNeedClientAuth(false); + SSLEngine client = + ctx.createSSLEngine("wolfSSL named groups test", 11111); + client.setUseClientMode(true); + + setEngineNamedGroups(server, new String[] { "ffdhe2048" }); + setEngineNamedGroups(client, new String[] { "ffdhe2048" }); + + int ret = tf.testConnection(server, client, + suitesMatching("TLS_DHE_RSA_"), + new String[] { "TLSv1.2" }, APP_DATA); + assertEquals("TLS 1.2 handshake with FFDHE-only " + + "SSLParameters.setNamedGroups() and a KeyStore " + + "holding both RSA and ECC keys must succeed", 0, ret); + + String suite = client.getSession().getCipherSuite(); + assertTrue("negotiated cipher suite must be DHE_RSA, " + + "got: " + suite, suite.startsWith("TLS_DHE_RSA_") || + suite.startsWith("DHE-RSA-")); + } + finally { + WolfSSLPQCTestUtil.restoreCurvesProperty(prevWolf); + WolfSSLPQCTestUtil.restoreJdkNamedGroupsProperty(prevJdk); + } + } + } + + /** + * On a TLS 1.3-only session, FFDHE-only named groups must not change + * server certificate selection: TLS 1.3 negotiates the key exchange + * group independently of the certificate type, so the default ECC + * alias preference stays and the handshake completes with the ECDSA + * certificate over an ffdhe2048 key share. + */ + @Test + public void testJdkTlsNamedGroupsFfdheOnlyTls13KeepsEccCert() + throws Exception { + + Assume.assumeTrue("TLS 1.3, RSA and ECC required in native wolfSSL", + WolfSSL.TLSv13Enabled() && WolfSSL.RsaEnabled() && + WolfSSL.EccEnabled()); + Assume.assumeTrue("ffdhe2048 named group not supported", + ffdhe2048GroupSupported()); + + synchronized (WolfSSLPQCTestUtil.GROUP_PROP_LOCK) { + String prevWolf = + WolfSSLPQCTestUtil.setCurvesProperty(null); + String prevJdk = + WolfSSLPQCTestUtil.setJdkNamedGroupsProperty("ffdhe2048"); + + try { + SSLContext ctx = tf.createSSLContext("TLSv1.3", PROVIDER); + SSLEngine server = ctx.createSSLEngine(); + server.setUseClientMode(false); + server.setNeedClientAuth(false); + SSLEngine client = + ctx.createSSLEngine("wolfSSL named groups test", 11111); + client.setUseClientMode(true); + + int ret = tf.testConnection(server, client, null, + new String[] { "TLSv1.3" }, APP_DATA); + assertEquals("TLS 1.3 handshake with FFDHE-only " + + "jdk.tls.namedGroups must succeed", 0, ret); + + Certificate[] peerCerts = + client.getSession().getPeerCertificates(); + String keyAlgo = peerCerts[0].getPublicKey().getAlgorithm(); + assertEquals("TLS 1.3 server must keep default ECC cert " + + "preference with FFDHE-only named groups", "EC", + keyAlgo); + } + finally { + WolfSSLPQCTestUtil.restoreCurvesProperty(prevWolf); + WolfSSLPQCTestUtil.restoreJdkNamedGroupsProperty(prevJdk); + } + } + } + + /** + * When the configured named groups contain at least one ECC curve + * alongside FFDHE groups, server certificate selection must keep the + * default ECC preference and negotiate an ECDHE_ECDSA suite on + * TLS 1.2. + */ + @Test + public void testJdkTlsNamedGroupsMixedEccFfdheTls12KeepsEccCert() + throws Exception { + + Assume.assumeTrue("TLS 1.2, RSA and ECC required in native wolfSSL", + WolfSSL.TLSv12Enabled() && WolfSSL.RsaEnabled() && + WolfSSL.EccEnabled()); + + synchronized (WolfSSLPQCTestUtil.GROUP_PROP_LOCK) { + String prevWolf = + WolfSSLPQCTestUtil.setCurvesProperty(null); + String prevJdk = WolfSSLPQCTestUtil.setJdkNamedGroupsProperty( + "secp256r1,ffdhe2048"); + + try { + SSLContext ctx = tf.createSSLContext("TLSv1.2", PROVIDER); + SSLEngine server = ctx.createSSLEngine(); + server.setUseClientMode(false); + server.setNeedClientAuth(false); + SSLEngine client = + ctx.createSSLEngine("wolfSSL named groups test", 11111); + client.setUseClientMode(true); + + int ret = tf.testConnection(server, client, + suitesMatching("TLS_ECDHE_ECDSA_", "TLS_DHE_RSA_"), + new String[] { "TLSv1.2" }, APP_DATA); + assertEquals("TLS 1.2 handshake with mixed ECC+FFDHE " + + "jdk.tls.namedGroups must succeed", 0, ret); + + String suite = client.getSession().getCipherSuite(); + assertTrue("negotiated cipher suite must be ECDHE_ECDSA " + + "when an ECC group is configured, got: " + suite, + suite.startsWith("TLS_ECDHE_ECDSA_") || + suite.startsWith("ECDHE-ECDSA-")); + } + finally { + WolfSSLPQCTestUtil.restoreCurvesProperty(prevWolf); + WolfSSLPQCTestUtil.restoreJdkNamedGroupsProperty(prevJdk); + } + } + } + /** * When both static properties are set, jdk.tls.namedGroups overrides * wolfjsse.enabledSupportedCurves. A single source is applied per diff --git a/src/test/com/wolfssl/test/WolfSSLTest.java b/src/test/com/wolfssl/test/WolfSSLTest.java index 97e53e2..aa57213 100644 --- a/src/test/com/wolfssl/test/WolfSSLTest.java +++ b/src/test/com/wolfssl/test/WolfSSLTest.java @@ -517,6 +517,51 @@ public class WolfSSLTest { assertFalse(WolfSSL.isPQCNamedGroup(-1)); } + @Test + public void test_isECCNamedGroup() { + + /* Classic ECC curves return true, spanning the low and high ends + * of the curve ID range (SECT, SECP, Brainpool, X25519/X448, SM2). */ + assertTrue(WolfSSL.isECCNamedGroup(WolfSSL.WOLFSSL_ECC_SECT163K1)); + assertTrue(WolfSSL.isECCNamedGroup(WolfSSL.WOLFSSL_ECC_SECP256R1)); + assertTrue(WolfSSL.isECCNamedGroup(WolfSSL.WOLFSSL_ECC_SECP384R1)); + assertTrue(WolfSSL.isECCNamedGroup(WolfSSL.WOLFSSL_ECC_SECP521R1)); + assertTrue(WolfSSL.isECCNamedGroup( + WolfSSL.WOLFSSL_ECC_BRAINPOOLP512R1)); + assertTrue(WolfSSL.isECCNamedGroup(WolfSSL.WOLFSSL_ECC_X25519)); + assertTrue(WolfSSL.isECCNamedGroup(WolfSSL.WOLFSSL_ECC_X448)); + assertTrue(WolfSSL.isECCNamedGroup(WolfSSL.WOLFSSL_ECC_SM2P256V1)); + + /* The whole registry range below the FFDHE block classifies as + * ECC (RFC 7919 partition), including IANA-assigned curve IDs + * without WOLFSSL_ECC_* constants yet: 31 is + * brainpoolP256r1tls13 (RFC 8734), 34 is GC256A (RFC 9189), and + * 255 is the top of the elliptic curve range. */ + assertTrue(WolfSSL.isECCNamedGroup(31)); + assertTrue(WolfSSL.isECCNamedGroup(34)); + assertTrue(WolfSSL.isECCNamedGroup(255)); + + /* FFDHE groups and the reserved FFDHE block (256-511) return + * false. */ + assertFalse(WolfSSL.isECCNamedGroup(WolfSSL.WOLFSSL_FFDHE_2048)); + assertFalse(WolfSSL.isECCNamedGroup(WolfSSL.WOLFSSL_FFDHE_8192)); + assertFalse(WolfSSL.isECCNamedGroup(511)); + + /* PQC standalone and hybrid groups return false, including + * hybrids with an ECDHE component. */ + assertFalse(WolfSSL.isECCNamedGroup(WolfSSL.WOLFSSL_ML_KEM_768)); + assertFalse(WolfSSL.isECCNamedGroup( + WolfSSL.WOLFSSL_X25519MLKEM768)); + assertFalse(WolfSSL.isECCNamedGroup( + WolfSSL.WOLFSSL_SECP256R1MLKEM768)); + + /* Sentinel and out-of-range integers return false. */ + assertFalse(WolfSSL.isECCNamedGroup( + WolfSSL.WOLFSSL_NAMED_GROUP_INVALID)); + assertFalse(WolfSSL.isECCNamedGroup(0)); + assertFalse(WolfSSL.isECCNamedGroup(-1)); + } + @Test public void test_PQC_FeatureDetect_NativeReturns() {