Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ public final void setEnabledProtocols(String[] protocols) {
}

@Override
final String getCurveNameForTesting() {
public String getCurveNameForTesting() {
return engine.getCurveNameForTesting();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ public final void setEnabledProtocols(String[] protocols) {
}

@Override
final String getCurveNameForTesting() {
public String getCurveNameForTesting() {
return ssl.getCurveNameForTesting();
}

Expand Down
62 changes: 62 additions & 0 deletions common/src/main/java/org/conscrypt/NativeSsl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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) {
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
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
188 changes: 183 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 @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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));
}
}

Expand Down Expand Up @@ -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<Void> s = runAsync(() -> {
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, in this order.
// So X25519 gets priority.
assertEquals("X25519", getCurveName(client));
assertEquals("X25519", getCurveName(server));
client.close();
server.close();
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 the client
// requests P-256, it will be chosen.
assertEquals("P-256", getCurveName(client));
assertEquals("P-256", 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 the server only supports P-384,
// it will be chosen.
assertEquals("P-384", getCurveName(client));
assertEquals("P-384", 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();
assertEquals("P-384", getCurveName(client));
assertEquals("P-384", 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();
assertEquals("X25519MLKEM768", getCurveName(client));
assertEquals("X25519MLKEM768", 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;
});
ExecutionException serverException = assertThrows(ExecutionException.class, s::get);
assertTrue(serverException.getCause() instanceof SSLHandshakeException);
ExecutionException clientException = assertThrows(ExecutionException.class, c::get);
assertTrue(clientException.getCause() instanceof SSLHandshakeException);
client.close();
server.close();
context.close();
}

@Test
public void test_SSLSocket_ClientHello_supportedCurves() throws Exception {
ForEachRunner.runNamed(sslSocketFactory -> {
Expand Down
Loading
Loading