Skip to content
6 changes: 6 additions & 0 deletions android/src/main/java/org/conscrypt/Platform.java
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
25 changes: 18 additions & 7 deletions common/src/main/java/org/conscrypt/NativeSsl.java
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,25 @@ void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOExcept
ssl, this, parameters.enabledCipherSuites, parameters.enabledProtocols);
}

String namedGroupsProperty = System.getProperty("jdk.tls.namedGroups");
if (namedGroupsProperty == null || namedGroupsProperty.isEmpty()) {
// If the property is not set or empty, use the default named groups. See:
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html
setDefaultNamedGroups(ssl, this);
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) {
NativeCrypto.SSL_set1_groups(ssl, this, toBoringSslGroups(paramsNamedGroups));
} else {
int[] groups = parseNamedGroupsProperty(namedGroupsProperty);
NativeCrypto.SSL_set1_groups(ssl, this, groups);
// Use default named group.
String namedGroupsProperty = System.getProperty("jdk.tls.namedGroups");
if (namedGroupsProperty == null || namedGroupsProperty.isEmpty()) {
// If the property is not set or empty, use the default named groups. See:
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html
setDefaultNamedGroups(ssl, this);
} else {
int[] groups = parseNamedGroupsProperty(namedGroupsProperty);
NativeCrypto.SSL_set1_groups(ssl, this, groups);
}
}

if (parameters.applicationProtocols.length > 0) {
Expand Down
17 changes: 17 additions & 0 deletions common/src/main/java/org/conscrypt/SSLParametersImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*/
Expand Down
200 changes: 195 additions & 5 deletions common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -833,20 +833,32 @@ public void setSSLParameters_invalidCipherSuite_throwsIllegalArgumentException()
}
}

boolean sslParametersSupportsNamedGroups() throws SecurityException {
try {
Method unused = SSLParameters.class.getMethod("getNamedGroups");
return true;
} catch (NoSuchMethodException e) {
return false;
}
}

@Test
public void setAndGetSSLParameters_withSetNamedGroups_isIgnored() throws Exception {
public void setAndGetSSLParameters_withSetNamedGroups_worksIfSupported() 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()));

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));
if (sslParametersSupportsNamedGroups()) {
assertArrayEquals(new String[] {"foo", "bar"}, getNamedGroupsOrNull(sslParameters));
} else {
assertArrayEquals(null, getNamedGroupsOrNull(sslParameters));
}
}
}

Expand Down Expand Up @@ -984,6 +996,184 @@ public void handshake_namedGroupsProperty_usesFirstKnownEntry() throws Exception
context.close();
}

@Test
public void handshake_p256IsSupportedByDefault() 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<Void> s = runAsync(() -> {
server.startHandshake();
return null;
});
Future<Void> c = runAsync(() -> {
SSLParameters parameters = client.getSSLParameters();
setNamedGroups(parameters, new String[] {"P-256"});
client.setSSLParameters(parameters);
client.startHandshake();
return null;
});
s.get();
c.get();
// By default, BoringSSL uses X25519, P-256, and P-384.
if (sslParametersSupportsNamedGroups()) {
// If the client requests P-256, it will be chosen.
assertEquals("P-256", getCurveName(client));
assertEquals("P-256", getCurveName(server));
} else {
// Otherwise, X25519 gets priority.
assertEquals("X25519", getCurveName(client));
assertEquals("X25519", getCurveName(server));
}
client.close();
server.close();
context.close();
}

@Test
public void handshake_p384IsSupportedByDefault() 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<Void> s = runAsync(() -> {
SSLParameters parameters = server.getSSLParameters();
// secp384r1 is an alias for P-384.
setNamedGroups(parameters, new String[] {"secp384r1"});
server.setSSLParameters(parameters);
server.startHandshake();
return null;
});
Future<Void> c = runAsync(() -> {
client.startHandshake();
return null;
});
s.get();
c.get();
// By default, BoringSSL uses X25519, P-256, and P-384.
if (sslParametersSupportsNamedGroups()) {
// If the client requests P-384, it will be chosen.
assertEquals("P-384", getCurveName(client));
assertEquals("P-384", getCurveName(server));
} else {
// Otherwise, X25519 gets priority.
assertEquals("X25519", getCurveName(client));
assertEquals("X25519", getCurveName(server));
}
client.close();
server.close();
context.close();
}

@Test
public void handshake_setsNamedGroups_usesFirstServerNamedGroupThatClientSupports()
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<Void> s = runAsync(() -> {
SSLParameters parameters = server.getSSLParameters();
setNamedGroups(parameters, new String[] {"P-384", "X25519"});
server.setSSLParameters(parameters);
server.startHandshake();
return null;
});
Future<Void> c = runAsync(() -> {
SSLParameters parameters = client.getSSLParameters();
setNamedGroups(parameters, new String[] {"P-521", "X25519", "P-384"});
client.setSSLParameters(parameters);
client.startHandshake();
return null;
});
s.get();
c.get();
if (sslParametersSupportsNamedGroups()) {
// P-384 is the first named group in the server's list that both support.
assertEquals("P-384", getCurveName(client));
assertEquals("P-384", getCurveName(server));
} else {
// The defaults are used, and X25519 gets priority.
assertEquals("X25519", getCurveName(client));
assertEquals("X25519", getCurveName(server));
}
client.close();
server.close();
context.close();
}

@Test
public void handshake_withX25519MLKEM768_works() 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<Void> s = runAsync(() -> {
SSLParameters parameters = server.getSSLParameters();
setNamedGroups(parameters, new String[] {"X25519MLKEM768"});
server.setSSLParameters(parameters);
server.startHandshake();
return null;
});
Future<Void> c = runAsync(() -> {
SSLParameters parameters = client.getSSLParameters();
setNamedGroups(parameters, new String[] {"X25519MLKEM768"});
client.setSSLParameters(parameters);
client.startHandshake();
return null;
});
s.get();
c.get();
if (sslParametersSupportsNamedGroups()) {
assertEquals("X25519MLKEM768", getCurveName(client));
assertEquals("X25519MLKEM768", getCurveName(server));
} else {
// The defaults are used, and X25519 gets priority.
assertEquals("X25519", getCurveName(client));
assertEquals("X25519", getCurveName(server));
}
client.close();
server.close();
context.close();
}

@Test
public void handshake_namedGroupsDontIntersect_throwsException() 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<Void> s = runAsync(() -> {
SSLParameters parameters = server.getSSLParameters();
setNamedGroups(parameters, new String[] {"X25519", "P-384"});
server.setSSLParameters(parameters);
server.startHandshake();
return null;
});
Future<Void> c = runAsync(() -> {
SSLParameters parameters = client.getSSLParameters();
setNamedGroups(parameters, new String[] {"P-256", "P-521"});
client.setSSLParameters(parameters);
client.startHandshake();
return null;
});
if (sslParametersSupportsNamedGroups()) {
ExecutionException serverException = assertThrows(ExecutionException.class, s::get);
assertTrue(serverException.getCause() instanceof SSLHandshakeException);
ExecutionException clientException = assertThrows(ExecutionException.class, c::get);
assertTrue(clientException.getCause() instanceof SSLHandshakeException);
} else {
s.get();
c.get();
// The defaults are used, and X25519 gets priority.
assertEquals("X25519", getCurveName(client));
assertEquals("X25519", getCurveName(server));
}
client.close();
server.close();
context.close();
}

@Test
public void handshake_namedGroupsProperty_failsIfAllValuesAreInvalid() throws Exception {
System.setProperty("jdk.tls.namedGroups", "invalid,invalid2");
Expand Down
32 changes: 30 additions & 2 deletions openjdk/src/main/java/org/conscrypt/Java9PlatformUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,56 @@ final class Java9PlatformUtil {
static void setSSLParameters(
SSLParameters src, SSLParametersImpl dest, AbstractConscryptSocket socket) {
Java8PlatformUtil.setSSLParameters(src, dest, socket);

try {
Method getNamedGroupsMethod = src.getClass().getMethod("getNamedGroups");
dest.setNamedGroups((String[]) getNamedGroupsMethod.invoke(src));
} catch (ReflectiveOperationException | SecurityException e) {
// Method is not available. Ignore.
}
dest.setApplicationProtocols(getApplicationProtocols(src));
}

static void getSSLParameters(
SSLParameters dest, SSLParametersImpl src, AbstractConscryptSocket socket) {
Java8PlatformUtil.getSSLParameters(dest, src, socket);

try {
String[] namedGroups = src.getNamedGroups();
Method setNamedGroupsMethod =
dest.getClass().getMethod("setNamedGroups", String[].class);
setNamedGroupsMethod.invoke(dest, (Object) namedGroups);
} catch (ReflectiveOperationException | SecurityException e) {
// Method is not available. Ignore.
}
setApplicationProtocols(dest, src.getApplicationProtocols());
}

static void setSSLParameters(
SSLParameters src, SSLParametersImpl dest, ConscryptEngine engine) {
Java8PlatformUtil.setSSLParameters(src, dest, engine);

try {
Method getNamedGroupsMethod = src.getClass().getMethod("getNamedGroups");
dest.setNamedGroups((String[]) getNamedGroupsMethod.invoke(src));
} catch (ReflectiveOperationException | SecurityException e) {
// Method is not available. Ignore.
}

dest.setApplicationProtocols(getApplicationProtocols(src));
}

static void getSSLParameters(
SSLParameters dest, SSLParametersImpl src, ConscryptEngine engine) {
Java8PlatformUtil.getSSLParameters(dest, src, engine);

try {
String[] namedGroups = src.getNamedGroups();
Method setNamedGroupsMethod =
dest.getClass().getMethod("setNamedGroups", String[].class);
setNamedGroupsMethod.invoke(dest, (Object) namedGroups);
} catch (ReflectiveOperationException | SecurityException e) {
// Method is not available. Ignore.
}

setApplicationProtocols(dest, src.getApplicationProtocols());
}

Expand Down
Loading
Loading