diff --git a/android/src/main/java/org/conscrypt/Platform.java b/android/src/main/java/org/conscrypt/Platform.java index c4e447a49..1bdf9ccc5 100644 --- a/android/src/main/java/org/conscrypt/Platform.java +++ b/android/src/main/java/org/conscrypt/Platform.java @@ -254,6 +254,9 @@ private static void setSSLParametersOnImpl(SSLParameters params, SSLParametersIm Method m_getUseCipherSuitesOrder = params.getClass().getMethod("getUseCipherSuitesOrder"); impl.setUseCipherSuitesOrder((boolean) m_getUseCipherSuitesOrder.invoke(params)); + + Method getNamedGroupsMethod = params.getClass().getMethod("getNamedGroups"); + impl.setNamedGroups((String[]) getNamedGroupsMethod.invoke(params)); } public static void setSSLParameters( @@ -323,6 +326,9 @@ private static void getSSLParametersFromImpl(SSLParameters params, SSLParameters Method m_setUseCipherSuitesOrder = params.getClass().getMethod("setUseCipherSuitesOrder", boolean.class); m_setUseCipherSuitesOrder.invoke(params, impl.getUseCipherSuitesOrder()); + + Method setNamedGroupsMethod = params.getClass().getMethod("setNamedGroups", String[].class); + setNamedGroupsMethod.invoke(params, (Object[]) impl.getNamedGroups()); } public static void getSSLParameters( diff --git a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java index fdd434d7c..42f602366 100644 --- a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java +++ b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java @@ -432,7 +432,7 @@ public final void setEnabledProtocols(String[] protocols) { } @Override - final String getCurveNameForTesting() { + public String getCurveNameForTesting() { return engine.getCurveNameForTesting(); } diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java index 62f30bea1..af0a34323 100644 --- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java +++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java @@ -761,7 +761,7 @@ public final void setEnabledProtocols(String[] protocols) { } @Override - final String getCurveNameForTesting() { + public String getCurveNameForTesting() { return ssl.getCurveNameForTesting(); } diff --git a/common/src/main/java/org/conscrypt/NativeSsl.java b/common/src/main/java/org/conscrypt/NativeSsl.java index 762314d5a..748cade8c 100644 --- a/common/src/main/java/org/conscrypt/NativeSsl.java +++ b/common/src/main/java/org/conscrypt/NativeSsl.java @@ -278,6 +278,55 @@ byte[] getTlsChannelId() throws SSLException { return NativeCrypto.SSL_get_tls_channel_id(ssl, this); } + // Converts a Java "named group" to the corresponding BoringSSL group NID, or -1. + private static int toBoringSslGroupNid(String javaNamedGroup) { + if (javaNamedGroup.equals("X25519") || javaNamedGroup.equals("x25519")) { + return NativeConstants.NID_X25519; + } + if (javaNamedGroup.equals("P-256") || javaNamedGroup.equals("secp256r1")) { + return NativeConstants.NID_X9_62_prime256v1; + } + if (javaNamedGroup.equals("P-384") || javaNamedGroup.equals("secp384r1")) { + return NativeConstants.NID_secp384r1; + } + if (javaNamedGroup.equals("P-521") || javaNamedGroup.equals("secp521r1")) { + return NativeConstants.NID_secp521r1; + } + if (javaNamedGroup.equals("X25519MLKEM768")) { + return NativeConstants.NID_X25519MLKEM768; + } + if (javaNamedGroup.equals("X25519Kyber768Draft00")) { + return NativeConstants.NID_X25519Kyber768Draft00; + } + if (javaNamedGroup.equals("MLKEM1024")) { + return NativeConstants.NID_ML_KEM_1024; + } + return -1; // Unknown curve. + } + + // Default curves to use if namedGroups is null. + // Same curves as StandardNames.ELLIPTIC_CURVES_DEFAULT + private static final int[] DEFAULT_GROUPS = new int[] {NativeConstants.NID_X25519, + NativeConstants.NID_X9_62_prime256v1, NativeConstants.NID_secp384r1}; + + /** + * Converts a list of java named groups to an array of groups that can be passed to BoringSSL. + * + *
Unknown curves are ignored.
+ */
+ public static int[] toBoringSslGroups(String[] javaNamedGroups) {
+ int[] outputGroups = new int[javaNamedGroups.length];
+ int i = 0;
+ for (String javaNamedGroup : javaNamedGroups) {
+ int group = toBoringSslGroupNid(javaNamedGroup);
+ if (group > 0) {
+ outputGroups[i] = group;
+ }
+ i++;
+ }
+ return outputGroups;
+ }
+
void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOException {
boolean enableSessionCreation = parameters.getEnableSessionCreation();
if (!enableSessionCreation) {
@@ -320,6 +369,19 @@ void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOExcept
ssl, this, parameters.enabledCipherSuites, parameters.enabledProtocols);
}
+ String[] paramsNamedGroups = parameters.getNamedGroups();
+ // - If the named groups are null, we use the default groups.
+ // - If the named groups are not null, it overrides the default groups.
+ // - Unknown curves are ignored.
+ // See:
+ // https://docs.oracle.com/en/java/javase/25/docs/api/java.base/javax/net/ssl/SSLParameters.html#getNamedGroups()
+ if (paramsNamedGroups == null) {
+ // Use the default curves.
+ NativeCrypto.SSL_set1_groups(ssl, this, DEFAULT_GROUPS);
+ } else {
+ NativeCrypto.SSL_set1_groups(ssl, this, toBoringSslGroups(paramsNamedGroups));
+ }
+
if (parameters.applicationProtocols.length > 0) {
NativeCrypto.setApplicationProtocols(ssl, this, isClient(), parameters.applicationProtocols);
}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java b/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
index 5aabdc358..f0c2571e1 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
@@ -151,4 +151,6 @@ public final byte[] getAlpnSelectedProtocol() {
public final void setAlpnProtocols(byte[] protocols) {
setApplicationProtocols(SSLUtils.decodeProtocols(protocols == null ? EmptyArray.BYTE : protocols));
}
+
+ @Override public abstract String getCurveNameForTesting();
}
diff --git a/common/src/main/java/org/conscrypt/SSLParametersImpl.java b/common/src/main/java/org/conscrypt/SSLParametersImpl.java
index f2056f2bd..f78b8691b 100644
--- a/common/src/main/java/org/conscrypt/SSLParametersImpl.java
+++ b/common/src/main/java/org/conscrypt/SSLParametersImpl.java
@@ -82,6 +82,8 @@ final class SSLParametersImpl implements Cloneable {
// cannot be customized, so for simplicity this field never contains any TLS 1.3 suites.
String[] enabledCipherSuites;
+ String[] namedGroups;
+
// if the peer with this parameters tuned to work in client mode
private boolean client_mode = true;
// if the peer with this parameters tuned to require client authentication
@@ -363,6 +365,21 @@ void setEnabledProtocols(String[] protocols) {
enabledProtocols = NativeCrypto.checkEnabledProtocols(filteredProtocols).clone();
}
+ void setNamedGroups(String[] namedGroups) {
+ if (namedGroups == null) {
+ this.namedGroups = null;
+ return;
+ }
+ this.namedGroups = namedGroups.clone();
+ }
+
+ String[] getNamedGroups() {
+ if (namedGroups == null) {
+ return null;
+ }
+ return this.namedGroups.clone();
+ }
+
/*
* Sets the list of ALPN protocols.
*/
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java
index 083a10a9e..4ca4fcc7b 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java
@@ -27,6 +27,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
+import org.conscrypt.OpenSSLSocketImpl;
import org.conscrypt.TestUtils;
import org.conscrypt.java.security.StandardNames;
import org.conscrypt.java.security.TestKeyStore;
@@ -61,6 +62,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -93,6 +95,10 @@ public class SSLSocketTest {
private final ExecutorService executor =
Executors.newCachedThreadPool(t -> new Thread(threadGroup, t));
+ String getCurveName(SSLSocket socket) {
+ return ((OpenSSLSocketImpl) socket).getCurveNameForTesting();
+ }
+
/**
* Returns the named groups, or null if the method is not available (older versions of
* Java/Android).
@@ -812,19 +818,21 @@ public void setSSLParameters_invalidCipherSuite_throwsIllegalArgumentException()
}
@Test
- public void setAndGetSSLParameters_withSetNamedGroups_isIgnored() throws Exception {
+ public void setAndGetSSLParameters_withSetNamedGroups_works() throws Exception {
SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
try (SSLSocket ssl = (SSLSocket) sf.createSocket()) {
SSLParameters parameters = new SSLParameters(
new String[] {"TLS_AES_128_GCM_SHA256"}, new String[] {"TLSv1.3"});
+
+ assertArrayEquals(null, getNamedGroupsOrNull(ssl.getSSLParameters()));
+
+ // values passed to setNamedGroups are not validated, any strings work.
setNamedGroups(parameters, new String[] {"foo", "bar"});
ssl.setSSLParameters(parameters);
SSLParameters sslParameters = ssl.getSSLParameters();
- // getNamedGroups currently returns null because setNamedGroups is not supported.
- // This is allowed, see:
- // https://docs.oracle.com/en/java/javase/24/docs/api/java.base/javax/net/ssl/SSLParameters.html#getNamedGroups()
- assertArrayEquals(null, getNamedGroupsOrNull(sslParameters));
+
+ assertArrayEquals(new String[] {"foo", "bar"}, getNamedGroupsOrNull(sslParameters));
}
}
@@ -911,6 +919,176 @@ public void test_SSLSocket_ClientHello_cipherSuites() throws Exception {
getSSLSocketFactoriesToTest());
}
+ @Test
+ public void handshake_noNamedGroups_usesX25519() throws Exception {
+ TestSSLContext context = TestSSLContext.create();
+ final SSLSocket client = (SSLSocket) context.clientContext.getSocketFactory().createSocket(
+ context.host, context.port);
+ final SSLSocket server = (SSLSocket) context.serverSocket.accept();
+ Future