From 4ca596cfad9db904e18020621c3a49b7923553a8 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Mon, 11 Nov 2024 11:31:23 +0100 Subject: [PATCH] Allow to configure groups via BoringSSLContextOption (#755) Motivation: At the moment its only possible to specify the used named groups per JVM via system property. We can do better when using our native provider. Modifications: Introduce BoringSSLContextOption.GROUPS that can be used to configure the named groups per context Result: More flexible way of configure ssl. --- .../codec/quic/BoringSSLContextOption.java | 37 +++++++++++++++++++ .../codec/quic/QuicSslContextBuilder.java | 36 ++++++++++++++++-- .../codec/quic/QuicheQuicSslContext.java | 26 +++++++++++-- 3 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/BoringSSLContextOption.java diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/BoringSSLContextOption.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/BoringSSLContextOption.java new file mode 100644 index 00000000..1276428c --- /dev/null +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/BoringSSLContextOption.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.incubator.codec.quic; + +import io.netty.handler.ssl.SslContextOption; + +/** + * {@link SslContextOption}s that are specific to BoringSSL. + * + * @param the type of the value. + */ +public final class BoringSSLContextOption extends SslContextOption { + private BoringSSLContextOption(String name) { + super(name); + } + + /** + * Set the groups that should be used. This will override curves set with {@code -Djdk.tls.namedGroups}. + *

+ * See + * SSL_CTX_set1_groups_list. + */ + public static final BoringSSLContextOption GROUPS = new BoringSSLContextOption<>("GROUPS"); +} diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicSslContextBuilder.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicSslContextBuilder.java index ff407ee2..7adba7f4 100644 --- a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicSslContextBuilder.java +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicSslContextBuilder.java @@ -17,6 +17,7 @@ package io.netty.incubator.codec.quic; import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.SslContextOption; import io.netty.handler.ssl.util.KeyManagerFactoryWrapper; import io.netty.handler.ssl.util.TrustManagerFactoryWrapper; import io.netty.util.Mapping; @@ -34,6 +35,10 @@ import java.security.Principal; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static io.netty.util.internal.ObjectUtil.checkNotNull; @@ -155,7 +160,10 @@ public static QuicSslContext buildForServerWithSni(Mapping, Object> options = new HashMap<>(); private TrustManagerFactory trustManagerFactory; private String keyPassword; private KeyManagerFactory keyManagerFactory; @@ -176,6 +184,18 @@ private QuicSslContextBuilder sni(Mapping QuicSslContextBuilder option(SslContextOption option, T value) { + if (value == null) { + options.remove(option); + } else { + options.put(option, value); + } + return this; + } + /** * Enable / disable the usage of early data. */ @@ -373,13 +393,23 @@ public QuicSslContextBuilder clientAuth(ClientAuth clientAuth) { public QuicSslContext build() { if (forServer) { return new QuicheQuicSslContext(true, sessionTimeout, sessionCacheSize, clientAuth, trustManagerFactory, - keyManagerFactory, keyPassword, mapping, earlyData, keylog, applicationProtocols); + keyManagerFactory, keyPassword, mapping, earlyData, keylog, + applicationProtocols, toArray(options.entrySet(), EMPTY_ENTRIES)); } else { return new QuicheQuicSslContext(false, sessionTimeout, sessionCacheSize, clientAuth, trustManagerFactory, keyManagerFactory, keyPassword, mapping, earlyData, keylog, - applicationProtocols); + applicationProtocols, toArray(options.entrySet(), EMPTY_ENTRIES)); } } - + private static T[] toArray(Iterable iterable, T[] prototype) { + if (iterable == null) { + return null; + } + final List list = new ArrayList<>(); + for (T element : iterable) { + list.add(element); + } + return list.toArray(prototype); + } } diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicSslContext.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicSslContext.java index 3282f189..c13e72d1 100644 --- a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicSslContext.java +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicSslContext.java @@ -18,6 +18,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.handler.ssl.ApplicationProtocolNegotiator; import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.SslContextOption; import io.netty.handler.ssl.SslHandler; import io.netty.util.AbstractReferenceCounted; import io.netty.util.Mapping; @@ -53,6 +54,7 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.Executor; @@ -153,7 +155,7 @@ final class QuicheQuicSslContext extends QuicSslContext { @Nullable KeyManagerFactory keyManagerFactory, String password, @Nullable Mapping mapping, @Nullable Boolean earlyData, @Nullable BoringSSLKeylog keylog, - String... applicationProtocols) { + String[] applicationProtocols, Map.Entry, Object>... ctxOptions) { Quic.ensureAvailability(); this.server = server; this.clientAuth = server ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE; @@ -179,6 +181,24 @@ final class QuicheQuicSslContext extends QuicSslContext { } else { keyManager = chooseKeyManager(keyManagerFactory); } + String[] groups = NAMED_GROUPS; + if (ctxOptions != null) { + for (Map.Entry, Object> ctxOpt : ctxOptions) { + SslContextOption option = ctxOpt.getKey(); + + if (option == BoringSSLContextOption.GROUPS) { + String[] groupsArray = (String[]) ctxOpt.getValue(); + Set groupsSet = new LinkedHashSet(groupsArray.length); + for (String group : groupsArray) { + groupsSet.add(GroupsConverter.toBoringSSL(group)); + } + groups = groupsSet.toArray(EmptyArrays.EMPTY_STRINGS); + } else { + LOGGER.debug("Skipping unsupported " + SslContextOption.class.getSimpleName() + + ": " + ctxOpt.getKey()); + } + } + } final BoringSSLPrivateKeyMethod privateKeyMethod; if (keyManagerFactory instanceof BoringSSLKeylessManagerFactory) { privateKeyMethod = new BoringSSLAsyncPrivateKeyMethodAdapter(engineMap, @@ -199,8 +219,8 @@ final class QuicheQuicSslContext extends QuicSslContext { BoringSSL.subjectNames(trustManager.getAcceptedIssuers()))); boolean success = false; try { - if (NAMED_GROUPS.length > 0 && BoringSSL.SSLContext_set1_groups_list(nativeSslContext.ctx, NAMED_GROUPS) == 0) { - String msg = "failed to set curves / groups list: " + Arrays.toString(NAMED_GROUPS); + if (groups.length > 0 && BoringSSL.SSLContext_set1_groups_list(nativeSslContext.ctx, groups) == 0) { + String msg = "failed to set curves / groups list: " + Arrays.toString(groups); String lastError = BoringSSL.ERR_last_error(); if (lastError != null) { // We have some more details about why the operations failed, include these into the message.