Skip to content

Commit c8f08e7

Browse files
authored
Allow to configure used named groups via -Djdk.tls.namedGroup (#754)
Motivation: We should allow to configure named groups via the known system property jdk.tls.namedGroup Modifications: Add code that allows to configure named groups Result: Be able to configure named groups via system property
1 parent 053b131 commit c8f08e7

File tree

4 files changed

+200
-18
lines changed

4 files changed

+200
-18
lines changed

codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/BoringSSL.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.security.cert.X509Certificate;
2525

2626
final class BoringSSL {
27-
private BoringSSL() { }
2827

2928
static final int SSL_VERIFY_NONE = BoringSSLNativeStaticallyReferencedJniMethods.ssl_verify_none();
3029
static final int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = BoringSSLNativeStaticallyReferencedJniMethods
@@ -70,6 +69,8 @@ static long SSLContext_new(boolean server, String[] applicationProtocols,
7069
}
7170
}
7271

72+
static native long SSLContext_new();
73+
7374
private static native long SSLContext_new0(boolean server,
7475
byte @Nullable [] applicationProtocols, Object handshakeCompleteCallback,
7576
Object certificateCallback, Object verifyCallback,
@@ -84,6 +85,24 @@ private static native long SSLContext_new0(boolean server,
8485

8586
static native void SSLContext_setSessionTicketKeys(long context, boolean enableCallback);
8687

88+
static int SSLContext_set1_groups_list(long ctx, String... groups) {
89+
if (groups == null) {
90+
throw new NullPointerException("curves");
91+
}
92+
if (groups.length == 0) {
93+
throw new IllegalArgumentException();
94+
}
95+
StringBuilder sb = new StringBuilder();
96+
for (String group: groups) {
97+
sb.append(group);
98+
// Groups are separated by : as explained in the manpage.
99+
sb.append(':');
100+
}
101+
sb.setLength(sb.length() - 1);
102+
return SSLContext_set1_groups_list(ctx, sb.toString());
103+
}
104+
105+
private static native int SSLContext_set1_groups_list(long context, String groups);
87106
static native void SSLContext_free(long context);
88107
static long SSL_new(long context, boolean server, String hostname) {
89108
return SSL_new0(context, server, tlsExtHostName(hostname));
@@ -129,4 +148,6 @@ static byte[][] subjectNames(X509Certificate[] certificates) {
129148
}
130149
return subjectNames;
131150
}
151+
152+
private BoringSSL() { }
132153
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2024 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.incubator.codec.quic;
17+
18+
import java.util.Collections;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
/**
23+
* Convert java naming to BoringSSL naming if possible and if not return the original name.
24+
*/
25+
final class GroupsConverter {
26+
27+
private static final Map<String, String> mappings;
28+
29+
static {
30+
// See https://tools.ietf.org/search/rfc4492#appendix-A and https://www.java.com/en/configure_crypto.html
31+
Map<String, String> map = new HashMap<String, String>();
32+
map.put("secp224r1", "P-224");
33+
map.put("prime256v1", "P-256");
34+
map.put("secp256r1", "P-256");
35+
map.put("secp384r1", "P-384");
36+
map.put("secp521r1", "P-521");
37+
map.put("x25519", "X25519");
38+
mappings = Collections.unmodifiableMap(map);
39+
}
40+
41+
static String toBoringSSL(String key) {
42+
String mapping = mappings.get(key);
43+
if (mapping == null) {
44+
return key;
45+
}
46+
return mapping;
47+
}
48+
49+
private GroupsConverter() { }
50+
}

codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicSslContext.java

Lines changed: 114 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
import io.netty.util.AbstractReferenceCounted;
2323
import io.netty.util.Mapping;
2424
import io.netty.util.ReferenceCounted;
25+
import io.netty.util.internal.EmptyArrays;
26+
import io.netty.util.internal.SystemPropertyUtil;
27+
import io.netty.util.internal.logging.InternalLogger;
28+
import io.netty.util.internal.logging.InternalLoggerFactory;
2529
import org.jetbrains.annotations.Nullable;
2630

2731
import javax.crypto.NoSuchPaddingException;
@@ -46,15 +50,90 @@
4650
import java.util.Arrays;
4751
import java.util.Collections;
4852
import java.util.Enumeration;
53+
import java.util.Iterator;
54+
import java.util.LinkedHashSet;
4955
import java.util.List;
5056
import java.util.NoSuchElementException;
57+
import java.util.Set;
5158
import java.util.concurrent.Executor;
5259
import java.util.function.BiConsumer;
5360
import java.util.function.LongFunction;
5461

5562
import static io.netty.util.internal.ObjectUtil.checkNotNull;
5663

5764
final class QuicheQuicSslContext extends QuicSslContext {
65+
66+
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(QuicheQuicSslContext.class);
67+
68+
// Use default that is supported in java 11 and earlier and also in OpenSSL / BoringSSL.
69+
// See https://github.com/netty/netty-tcnative/issues/567
70+
// See https://www.java.com/en/configure_crypto.html for ordering
71+
private static final String[] DEFAULT_NAMED_GROUPS = { "x25519", "secp256r1", "secp384r1", "secp521r1" };
72+
private static final String[] NAMED_GROUPS;
73+
74+
static {
75+
String[] namedGroups = DEFAULT_NAMED_GROUPS;
76+
Set<String> defaultConvertedNamedGroups = new LinkedHashSet<>(namedGroups.length);
77+
for (int i = 0; i < namedGroups.length; i++) {
78+
defaultConvertedNamedGroups.add(GroupsConverter.toBoringSSL(namedGroups[i]));
79+
}
80+
81+
final long sslCtx = BoringSSL.SSLContext_new();
82+
try {
83+
// Let's filter out any group that is not supported from the default.
84+
Iterator<String> defaultGroupsIter = defaultConvertedNamedGroups.iterator();
85+
while (defaultGroupsIter.hasNext()) {
86+
if (BoringSSL.SSLContext_set1_groups_list(sslCtx, defaultGroupsIter.next()) == 0) {
87+
// Not supported, let's remove it. This could for example be the case if we use
88+
// fips and the configure group is not supported when using FIPS.
89+
// See https://github.com/netty/netty-tcnative/issues/883
90+
defaultGroupsIter.remove();
91+
}
92+
}
93+
94+
String groups = SystemPropertyUtil.get("jdk.tls.namedGroups", null);
95+
if (groups != null) {
96+
String[] nGroups = groups.split(",");
97+
Set<String> supportedNamedGroups = new LinkedHashSet<>(nGroups.length);
98+
Set<String> supportedConvertedNamedGroups = new LinkedHashSet<>(nGroups.length);
99+
100+
Set<String> unsupportedNamedGroups = new LinkedHashSet<>();
101+
for (String namedGroup : nGroups) {
102+
String converted = GroupsConverter.toBoringSSL(namedGroup);
103+
if (BoringSSL.SSLContext_set1_groups_list(sslCtx, converted) == 0) {
104+
supportedConvertedNamedGroups.add(converted);
105+
supportedNamedGroups.add(namedGroup);
106+
} else {
107+
unsupportedNamedGroups.add(namedGroup);
108+
}
109+
}
110+
111+
if (supportedNamedGroups.isEmpty()) {
112+
namedGroups = defaultConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
113+
LOGGER.info("All configured namedGroups are not supported: {}. Use default: {}.",
114+
Arrays.toString(unsupportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS)),
115+
Arrays.toString(DEFAULT_NAMED_GROUPS));
116+
} else {
117+
String[] groupArray = supportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
118+
if (unsupportedNamedGroups.isEmpty()) {
119+
LOGGER.info("Using configured namedGroups -D 'jdk.tls.namedGroup': {} ",
120+
Arrays.toString(groupArray));
121+
} else {
122+
LOGGER.info("Using supported configured namedGroups: {}. Unsupported namedGroups: {}. ",
123+
Arrays.toString(groupArray),
124+
Arrays.toString(unsupportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS)));
125+
}
126+
namedGroups = supportedConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
127+
}
128+
} else {
129+
namedGroups = defaultConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
130+
}
131+
} finally {
132+
BoringSSL.SSLContext_free(sslCtx);
133+
}
134+
NAMED_GROUPS = namedGroups;
135+
}
136+
58137
final ClientAuth clientAuth;
59138
private final boolean server;
60139
@SuppressWarnings("deprecation")
@@ -118,25 +197,43 @@ final class QuicheQuicSslContext extends QuicSslContext {
118197
server ? null : new BoringSSLSessionCallback(engineMap, sessionCache), privateKeyMethod,
119198
sessionTicketCallback, verifyMode,
120199
BoringSSL.subjectNames(trustManager.getAcceptedIssuers())));
121-
apn = new QuicheQuicApplicationProtocolNegotiator(applicationProtocols);
122-
if (this.sessionCache != null) {
123-
// Cache is handled via our own implementation.
124-
this.sessionCache.setSessionCacheSize((int) sessionCacheSize);
125-
this.sessionCache.setSessionTimeout((int) sessionTimeout);
126-
} else {
127-
// Cache is handled by BoringSSL internally
128-
BoringSSL.SSLContext_setSessionCacheSize(
129-
nativeSslContext.address(), sessionCacheSize);
130-
this.sessionCacheSize = sessionCacheSize;
200+
boolean success = false;
201+
try {
202+
if (NAMED_GROUPS.length > 0 && BoringSSL.SSLContext_set1_groups_list(nativeSslContext.ctx, NAMED_GROUPS) == 0) {
203+
String msg = "failed to set curves / groups list: " + Arrays.toString(NAMED_GROUPS);
204+
String lastError = BoringSSL.ERR_last_error();
205+
if (lastError != null) {
206+
// We have some more details about why the operations failed, include these into the message.
207+
msg += ". " + lastError;
208+
}
209+
throw new IllegalStateException(msg);
210+
}
131211

132-
BoringSSL.SSLContext_setSessionCacheTimeout(
133-
nativeSslContext.address(), sessionTimeout);
134-
this.sessionTimeout = sessionTimeout;
135-
}
136-
if (earlyData != null) {
137-
BoringSSL.SSLContext_set_early_data_enabled(nativeSslContext.address(), earlyData);
212+
apn = new QuicheQuicApplicationProtocolNegotiator(applicationProtocols);
213+
if (this.sessionCache != null) {
214+
// Cache is handled via our own implementation.
215+
this.sessionCache.setSessionCacheSize((int) sessionCacheSize);
216+
this.sessionCache.setSessionTimeout((int) sessionTimeout);
217+
} else {
218+
// Cache is handled by BoringSSL internally
219+
BoringSSL.SSLContext_setSessionCacheSize(
220+
nativeSslContext.address(), sessionCacheSize);
221+
this.sessionCacheSize = sessionCacheSize;
222+
223+
BoringSSL.SSLContext_setSessionCacheTimeout(
224+
nativeSslContext.address(), sessionTimeout);
225+
this.sessionTimeout = sessionTimeout;
226+
}
227+
if (earlyData != null) {
228+
BoringSSL.SSLContext_set_early_data_enabled(nativeSslContext.address(), earlyData);
229+
}
230+
sessionCtx = new QuicheQuicSslSessionContext(this);
231+
success = true;
232+
} finally {
233+
if (!success) {
234+
nativeSslContext.release();
235+
}
138236
}
139-
sessionCtx = new QuicheQuicSslSessionContext(this);
140237
}
141238

142239
private X509ExtendedKeyManager chooseKeyManager(KeyManagerFactory keyManagerFactory) {

codec-native-quic/src/main/c/netty_quic_boringssl.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,11 @@ int new_session_callback(SSL *ssl, SSL_SESSION *session) {
993993
return 0;
994994
}
995995

996+
static jlong netty_boringssl_SSLContext_new(JNIEnv* env, jclass clazz) {
997+
SSL_CTX *ctx = SSL_CTX_new(TLS_with_buffers_method());
998+
return (jlong) ctx;
999+
}
1000+
9961001
static jlong netty_boringssl_SSLContext_new0(JNIEnv* env, jclass clazz, jboolean server, jbyteArray alpn_protos, jobject handshakeCompleteCallback, jobject certificateCallback, jobject verifyCallback, jobject servernameCallback, jobject keylogCallback, jobject sessionCallback, jobject privateKeyMethod, jobject sessionTicketCallback, jint verifyMode, jobjectArray subjectNames) {
9971002
jobject handshakeCompleteCallbackRef = NULL;
9981003
jobject certificateCallbackRef = NULL;
@@ -1415,6 +1420,13 @@ void netty_boringssl_SSLContext_setSessionTicketKeys(JNIEnv* env, jclass clazz,
14151420
}
14161421
}
14171422

1423+
jint netty_boringssl_SSLContext_set1_groups_list(JNIEnv* env, jclass clazz, jlong ctx, jstring groups) {
1424+
const char *nativeString = (*env)->GetStringUTFChars(env, groups, 0);
1425+
int ret = SSL_CTX_set1_groups_list((SSL_CTX *) ctx, nativeString);
1426+
(*env)->ReleaseStringUTFChars(env, groups, nativeString);
1427+
return (jint) ret;
1428+
}
1429+
14181430
// JNI Registered Methods End
14191431

14201432
// JNI Method Registration Table Begin
@@ -1444,12 +1456,14 @@ static const JNINativeMethod statically_referenced_fixed_method_table[] = {
14441456

14451457
static const jint statically_referenced_fixed_method_table_size = sizeof(statically_referenced_fixed_method_table) / sizeof(statically_referenced_fixed_method_table[0]);
14461458
static const JNINativeMethod fixed_method_table[] = {
1459+
{ "SSLContext_new", "()J", (void *) netty_boringssl_SSLContext_new },
14471460
{ "SSLContext_new0", "(Z[BLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I[[B)J", (void *) netty_boringssl_SSLContext_new0 },
14481461
{ "SSLContext_free", "(J)V", (void *) netty_boringssl_SSLContext_free },
14491462
{ "SSLContext_setSessionCacheTimeout", "(JJ)J", (void *) netty_boringssl_SSLContext_setSessionCacheTimeout },
14501463
{ "SSLContext_setSessionCacheSize", "(JJ)J", (void *) netty_boringssl_SSLContext_setSessionCacheSize },
14511464
{ "SSLContext_set_early_data_enabled", "(JZ)V", (void *) netty_boringssl_SSLContext_set_early_data_enabled },
14521465
{ "SSLContext_setSessionTicketKeys", "(JZ)V", (void *) netty_boringssl_SSLContext_setSessionTicketKeys },
1466+
{ "SSLContext_set1_groups_list", "(JLjava/lang/String;)I", (void *) netty_boringssl_SSLContext_set1_groups_list },
14531467
{ "SSL_new0", "(JZLjava/lang/String;)J", (void *) netty_boringssl_SSL_new0 },
14541468
{ "SSL_free", "(J)V", (void *) netty_boringssl_SSL_free },
14551469
{ "SSL_getTask", "(J)Ljava/lang/Runnable;", (void *) netty_boringssl_SSL_getTask },

0 commit comments

Comments
 (0)