Rework SslHandlerFactory 62/110062/16
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 28 Jan 2024 17:40:33 +0000 (18:40 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 4 Feb 2024 15:42:29 +0000 (16:42 +0100)
Tracking down all the possible implementations and bridging of
SslHandlerFactor is not nice.

Turn SslHandlerFactory into an abstract class, with an explicit
fixed specialization.

This also allows us to put the configuration parsing bits to where
users can find them -- and create dynamic SslContexts as appropriate.

Furthermore this ends up differentiating the deprecated
netconf.client.SslHandlerFactory -- which becomes SslContextFactory and
we end up ditching the manual SSLEngine creation.

JIRA: NETCONF-1237
Change-Id: I2cd4721336f9867921123521d7b18d91a8743835
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
25 files changed:
apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/CallHomeMountTlsAuthProvider.java
apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/server/tls/CallHomeTlsAuthProvider.java
apps/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/server/tls/CallHomeTlsServerTest.java
apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/MountPointEndToEndTest.java
apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImpl.java
apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImplTest.java
apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/NetconfNodeHandlerTest.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/SslContextFactoryProvider.java [moved from plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/SslHandlerFactoryProvider.java with 73% similarity]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslContextFactory.java [new file with mode: 0644]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslContextFactoryProvider.java [moved from plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslHandlerFactoryProvider.java with 83% similarity]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/FilteredSslContext.java [new file with mode: 0644]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/FilteredSslContextFactory.java [new file with mode: 0644]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SslHandlerFactoryImpl.java [deleted file]
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslContextFactoryProviderTest.java [moved from plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslHandlerFactoryProviderTest.java with 97% similarity]
protocol/netconf-client/src/main/java/org/opendaylight/netconf/client/NetconfClientFactoryImpl.java
protocol/netconf-client/src/main/java/org/opendaylight/netconf/client/SslContextFactory.java [new file with mode: 0644]
protocol/netconf-client/src/main/java/org/opendaylight/netconf/client/SslHandlerFactory.java [deleted file]
protocol/netconf-client/src/test/java/org/opendaylight/netconf/client/NetconfClientFactoryImplTest.java
transport/transport-tls/src/main/java/org/opendaylight/netconf/transport/tls/FixedSslHandlerFactory.java [new file with mode: 0644]
transport/transport-tls/src/main/java/org/opendaylight/netconf/transport/tls/KeyStoreUtils.java
transport/transport-tls/src/main/java/org/opendaylight/netconf/transport/tls/SslHandlerFactory.java
transport/transport-tls/src/main/java/org/opendaylight/netconf/transport/tls/TLSClient.java
transport/transport-tls/src/main/java/org/opendaylight/netconf/transport/tls/TLSServer.java
transport/transport-tls/src/main/java/org/opendaylight/netconf/transport/tls/TLSTransportStack.java
transport/transport-tls/src/test/java/org/opendaylight/netconf/transport/tls/TlsClientServerTest.java

index 4d96123f5ffc99ffe09da15f7d225dfe81862d5f..0eb6c9d8548dd75fb259f09739dbe3930f557aad 100644 (file)
@@ -8,8 +8,8 @@
 package org.opendaylight.netconf.callhome.mount.tls;
 
 import com.google.common.collect.ImmutableMultimap;
-import io.netty.channel.Channel;
-import io.netty.handler.ssl.SslHandler;
+import io.netty.handler.ssl.SslContext;
+import java.net.SocketAddress;
 import java.security.PublicKey;
 import java.util.List;
 import java.util.Map;
@@ -25,8 +25,8 @@ import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
 import org.opendaylight.mdsal.binding.api.DataTreeModification;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.netconf.callhome.server.tls.CallHomeTlsAuthProvider;
-import org.opendaylight.netconf.client.SslHandlerFactory;
-import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
+import org.opendaylight.netconf.client.SslContextFactory;
+import org.opendaylight.netconf.client.mdsal.api.SslContextFactoryProvider;
 import org.opendaylight.netconf.keystore.legacy.NetconfKeystore;
 import org.opendaylight.netconf.keystore.legacy.NetconfKeystoreService;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.NetconfCallhomeServer;
@@ -45,22 +45,22 @@ import org.slf4j.LoggerFactory;
 
 @Component(service = CallHomeTlsAuthProvider.class, immediate = true)
 @Singleton
-public final class CallHomeMountTlsAuthProvider implements CallHomeTlsAuthProvider, AutoCloseable {
+public final class CallHomeMountTlsAuthProvider extends CallHomeTlsAuthProvider implements AutoCloseable {
     private static final Logger LOG = LoggerFactory.getLogger(CallHomeMountTlsAuthProvider.class);
 
     private final ConcurrentMap<String, String> deviceToPrivateKey = new ConcurrentHashMap<>();
     private final ConcurrentMap<String, String> deviceToCertificate = new ConcurrentHashMap<>();
     private final Registration allowedDevicesReg;
     private final Registration certificatesReg;
-    private final SslHandlerFactory sslHandlerFactory;
+    private final SslContextFactory sslContextFactory;
 
     private volatile ImmutableMultimap<PublicKey, String> publicKeyToName = ImmutableMultimap.of();
 
     @Inject
     @Activate
-    public CallHomeMountTlsAuthProvider(final @Reference SslHandlerFactoryProvider sslHandlerFactoryProvider,
-            final @Reference DataBroker dataBroker, final @Reference NetconfKeystoreService keystoreService) {
-        sslHandlerFactory = sslHandlerFactoryProvider.getSslHandlerFactory(null);
+    public CallHomeMountTlsAuthProvider(@Reference final SslContextFactoryProvider sslContextFactoryProvider,
+            @Reference final DataBroker dataBroker, @Reference final NetconfKeystoreService keystoreService) {
+        sslContextFactory = sslContextFactoryProvider.getSslContextFactory(null);
         allowedDevicesReg = dataBroker.registerTreeChangeListener(
             DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION,
                 InstanceIdentifier.create(NetconfCallhomeServer.class).child(AllowedDevices.class).child(Device.class)),
@@ -108,8 +108,8 @@ public final class CallHomeMountTlsAuthProvider implements CallHomeTlsAuthProvid
     }
 
     @Override
-    public SslHandler createSslHandler(final Channel channel) {
-        return sslHandlerFactory.createSslHandler(Set.copyOf(deviceToPrivateKey.values()));
+    protected SslContext getSslContext(final SocketAddress remoteAddress) {
+        return sslContextFactory.createSslContext(Set.copyOf(deviceToPrivateKey.values()));
     }
 
     private final class AllowedDevicesMonitor implements DataTreeChangeListener<Device> {
index ebc3ed71a96d2d5e3ea5537cfebf615422cd0470..a161b18349596012ed97e646a3dfeea9c4c16b57 100644 (file)
@@ -15,7 +15,7 @@ import org.opendaylight.netconf.transport.tls.SslHandlerFactory;
 /**
  * Authentication provider.
  */
-public interface CallHomeTlsAuthProvider extends SslHandlerFactory {
+public abstract class CallHomeTlsAuthProvider extends SslHandlerFactory {
 
-    @Nullable String idFor(@NonNull PublicKey publicKey);
+    public abstract @Nullable String idFor(@NonNull PublicKey publicKey);
 }
index 4bce3578d068332a2636ea07ee8437b26e6763c2..54a7303e3c50987e823febbbde1f56a7c38d99c9 100644 (file)
@@ -18,13 +18,14 @@ import static org.mockito.Mockito.verify;
 import com.google.common.util.concurrent.SettableFuture;
 import io.netty.channel.Channel;
 import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
 import io.netty.handler.ssl.SslContextBuilder;
-import io.netty.handler.ssl.SslHandler;
 import io.netty.util.concurrent.Promise;
 import java.math.BigInteger;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.ServerSocket;
+import java.net.SocketAddress;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.KeyStore;
@@ -47,7 +48,6 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
 import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -68,6 +68,7 @@ import org.opendaylight.netconf.server.osgi.AggregatedNetconfOperationServiceFac
 import org.opendaylight.netconf.transport.api.TransportChannel;
 import org.opendaylight.netconf.transport.api.TransportChannelListener;
 import org.opendaylight.netconf.transport.tcp.BootstrapFactory;
+import org.opendaylight.netconf.transport.tls.FixedSslHandlerFactory;
 import org.opendaylight.netconf.transport.tls.TLSServer;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
@@ -122,15 +123,14 @@ public class CallHomeTlsServerTest {
         // Auth provider
         final var authProvider = new CallHomeTlsAuthProvider() {
             @Override
-            public @Nullable String idFor(@NonNull
-            final PublicKey publicKey) {
+            public String idFor(final PublicKey publicKey) {
                 // identify client 3 only
                 return clientCert3.keyPair.getPublic().equals(publicKey) ? "client-id" : null;
             }
 
             @Override
-            public SslHandler createSslHandler(final Channel channel) {
-                return serverCtx.newHandler(channel.alloc());
+            protected SslContext getSslContext(final SocketAddress remoteAddress) {
+                return serverCtx;
             }
         };
 
@@ -182,21 +182,21 @@ public class CallHomeTlsServerTest {
             // client 1 rejected on handshake, ensure exception
             client1 = TLSServer.connect(
                 netconfTransportListener, bootstrapFactory.newBootstrap(), tcpConnectParams,
-                channel -> clientCtx1.newHandler(channel.alloc())).get(TIMEOUT, TimeUnit.MILLISECONDS);
+                new FixedSslHandlerFactory(clientCtx1)).get(TIMEOUT, TimeUnit.MILLISECONDS);
             verify(statusRecorder, timeout(TIMEOUT).times(1))
                 .onTransportChannelFailure(any(SSLHandshakeException.class));
 
             // client 2 rejected because it's not identified by public key accepted on handshake stage
             client2 = TLSServer.connect(
                 netconfTransportListener, bootstrapFactory.newBootstrap(), tcpConnectParams,
-                channel -> clientCtx2.newHandler(channel.alloc())).get(TIMEOUT, TimeUnit.MILLISECONDS);
+                new FixedSslHandlerFactory(clientCtx2)).get(TIMEOUT, TimeUnit.MILLISECONDS);
             verify(statusRecorder, timeout(TIMEOUT).times(1))
                 .reportUnknown(any(InetSocketAddress.class), eq(clientCert2.keyPair.getPublic()));
 
             // client 3 accepted
             client3 = TLSServer.connect(
                 netconfTransportListener, bootstrapFactory.newBootstrap(), tcpConnectParams,
-                channel -> clientCtx3.newHandler(channel.alloc())).get(TIMEOUT, TimeUnit.MILLISECONDS);
+                new FixedSslHandlerFactory(clientCtx3)).get(TIMEOUT, TimeUnit.MILLISECONDS);
             // verify netconf session established
             verify(clientSessionListener, timeout(TIMEOUT).times(1)).onSessionUp(any(NetconfClientSession.class));
             verify(serverSessionListener, timeout(TIMEOUT).times(1)).onSessionUp(any(NetconfServerSession.class));
index 48eba6c85fb87a0b4b7ba9779016b38bffd5735e..fcadab47e1d5aa6a0e5ceb7a1df009a633152b3e 100644 (file)
@@ -93,7 +93,7 @@ import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Rpcs;
 import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
-import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
+import org.opendaylight.netconf.client.mdsal.api.SslContextFactoryProvider;
 import org.opendaylight.netconf.client.mdsal.impl.DefaultSchemaResourceManager;
 import org.opendaylight.netconf.common.NetconfTimer;
 import org.opendaylight.netconf.topology.singleton.impl.utils.ClusteringRpcException;
@@ -193,7 +193,7 @@ public class MountPointEndToEndTest extends AbstractBaseSchemasTest {
     @Mock
     private CredentialProvider credentialProvider;
     @Mock
-    private SslHandlerFactoryProvider sslHandlerFactoryProvider;
+    private SslContextFactoryProvider sslHandlerFactoryProvider;
     @Mock
     private DOMMountPointListener masterMountPointListener;
     private final DOMMountPointService masterMountPointService = new DOMMountPointServiceImpl();
index d9562698c37fa3050aaa6cbb97043ab8ab630bdf..71fe16f6812150c40a12430d6c14b413413ae2a2 100644 (file)
@@ -18,9 +18,10 @@ import org.opendaylight.aaa.encrypt.AAAEncryptionService;
 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
 import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
 import org.opendaylight.netconf.client.mdsal.api.CredentialProvider;
-import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
+import org.opendaylight.netconf.client.mdsal.api.SslContextFactoryProvider;
 import org.opendaylight.netconf.shaded.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
 import org.opendaylight.netconf.shaded.sshd.common.keyprovider.KeyIdentityProvider;
+import org.opendaylight.netconf.transport.tls.FixedSslHandlerFactory;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.password.grouping.password.type.CleartextPasswordBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev231228.netconf.client.initiate.stack.grouping.transport.ssh.ssh.SshClientParametersBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev231228.netconf.client.initiate.stack.grouping.transport.ssh.ssh.TcpClientParametersBuilder;
@@ -44,7 +45,7 @@ import org.osgi.service.component.annotations.Reference;
 @Component
 @Singleton
 public final class NetconfClientConfigurationBuilderFactoryImpl implements NetconfClientConfigurationBuilderFactory {
-    private final SslHandlerFactoryProvider sslHandlerFactoryProvider;
+    private final SslContextFactoryProvider sslContextFactoryProvider;
     private final AAAEncryptionService encryptionService;
     private final CredentialProvider credentialProvider;
 
@@ -53,10 +54,10 @@ public final class NetconfClientConfigurationBuilderFactoryImpl implements Netco
     public NetconfClientConfigurationBuilderFactoryImpl(
             @Reference final AAAEncryptionService encryptionService,
             @Reference final CredentialProvider credentialProvider,
-            @Reference final SslHandlerFactoryProvider sslHandlerFactoryProvider) {
+            @Reference final SslContextFactoryProvider sslHandlerContextProvider) {
         this.encryptionService = requireNonNull(encryptionService);
         this.credentialProvider = requireNonNull(credentialProvider);
-        this.sslHandlerFactoryProvider = requireNonNull(sslHandlerFactoryProvider);
+        sslContextFactoryProvider = requireNonNull(sslHandlerContextProvider);
     }
 
     @Override
@@ -70,9 +71,10 @@ public final class NetconfClientConfigurationBuilderFactoryImpl implements Netco
             builder.withProtocol(NetconfClientProtocol.SSH);
             setSshParametersFromCredentials(builder, node.getCredentials());
         } else if (protocol.getName() == Name.TLS) {
-            final var handlerFactory = sslHandlerFactoryProvider.getSslHandlerFactory(protocol.getSpecification());
+            final var handlerFactory = sslContextFactoryProvider.getSslContextFactory(protocol.getSpecification());
+            final var sslContext = handlerFactory.createSslContext();
             builder.withProtocol(NetconfClientProtocol.TLS)
-                .withSslHandlerFactory(channel -> handlerFactory.createSslHandler());
+                .withSslHandlerFactory(new FixedSslHandlerFactory(sslContext));
         } else {
             throw new IllegalArgumentException("Unsupported protocol type: " + protocol.getName());
         }
index 8365b4594deec76882d81048c0e9fb43c2f9c8e7..0447b63bd5c372a78bb5ae68b6b1fe2503886a7b 100644 (file)
@@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 
+import io.netty.handler.ssl.SslContext;
 import java.util.NoSuchElementException;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -22,11 +23,11 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
 import org.opendaylight.netconf.client.NetconfClientSessionListener;
-import org.opendaylight.netconf.client.SslHandlerFactory;
+import org.opendaylight.netconf.client.SslContextFactory;
 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
 import org.opendaylight.netconf.client.mdsal.api.CredentialProvider;
-import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
+import org.opendaylight.netconf.client.mdsal.api.SslContextFactoryProvider;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
@@ -55,9 +56,11 @@ class NetconfClientConfigurationBuilderFactoryImplTest {
     @Mock
     private CredentialProvider credentialProvider;
     @Mock
-    private SslHandlerFactoryProvider sslHandlerFactoryProvider;
+    private SslContextFactoryProvider sslContextFactoryProvider;
     @Mock
-    private SslHandlerFactory sslHandlerFactory;
+    private SslContextFactory sslContextFactory;
+    @Mock
+    private SslContext sslContext;
 
     private NetconfNodeBuilder nodeBuilder;
     private NetconfClientConfigurationBuilderFactoryImpl factory;
@@ -80,7 +83,7 @@ class NetconfClientConfigurationBuilderFactoryImplTest {
             .setBackoffMultiplier(Decimal64.valueOf("1.5"))
             .setConnectionTimeoutMillis(Uint32.valueOf(20000));
         factory = new NetconfClientConfigurationBuilderFactoryImpl(encryptionService, credentialProvider,
-            sslHandlerFactoryProvider);
+            sslContextFactoryProvider);
     }
 
     private void assertConfig(final NetconfClientConfiguration config) {
@@ -117,7 +120,8 @@ class NetconfClientConfigurationBuilderFactoryImplTest {
 
     @Test
     void testTls() {
-        doReturn(sslHandlerFactory).when(sslHandlerFactoryProvider).getSslHandlerFactory(any());
+        doReturn(sslContextFactory).when(sslContextFactoryProvider).getSslContextFactory(any());
+        doReturn(sslContext).when(sslContextFactory).createSslContext();
         final var config = createConfig(
             nodeBuilder.setTcpOnly(false).setProtocol(new ProtocolBuilder().setName(Name.TLS).build()).build());
         assertConfig(config);
index 8c05becd2d64a1f70830a425af6dbcc1231a00cf..1442988cce9214309c822e7c1796de75021a80f9 100644 (file)
@@ -52,7 +52,7 @@ import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Rpcs;
 import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
-import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
+import org.opendaylight.netconf.client.mdsal.api.SslContextFactoryProvider;
 import org.opendaylight.netconf.client.mdsal.impl.DefaultBaseNetconfSchemas;
 import org.opendaylight.netconf.common.NetconfTimer;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
@@ -90,7 +90,7 @@ public class NetconfNodeHandlerTest {
 
     // DefaultNetconfClientConfigurationBuilderFactory setup
     @Mock
-    private SslHandlerFactoryProvider sslHandlerFactoryProvider;
+    private SslContextFactoryProvider sslContextFactoryProvider;
     @Mock
     private AAAEncryptionService encryptionService;
     @Mock
@@ -136,7 +136,7 @@ public class NetconfNodeHandlerTest {
         // Instantiate the handler
         handler = new NetconfNodeHandler(clientFactory, timer, BASE_SCHEMAS, schemaManager, schemaAssembler,
             new NetconfClientConfigurationBuilderFactoryImpl(encryptionService, credentialProvider,
-                sslHandlerFactoryProvider),
+                sslContextFactoryProvider),
             deviceActionFactory, delegate, DEVICE_ID, NODE_ID, new NetconfNodeBuilder()
                 .setHost(new Host(new IpAddress(new Ipv4Address("127.0.0.1"))))
                 .setPort(new PortNumber(Uint16.valueOf(9999)))
@@ -9,21 +9,21 @@ package org.opendaylight.netconf.client.mdsal.api;
 
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.netconf.client.SslHandlerFactory;
+import org.opendaylight.netconf.client.SslContextFactory;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.parameters.protocol.Specification;
 
 /**
- * A provider for {@link SslHandlerFactory} implementations. This allows the factory to be tailored with a
+ * A provider for {@link SslContextFactory} implementations. This allows the factory to be tailored with a
  * {@link Specification}.
  */
-public interface SslHandlerFactoryProvider {
+public interface SslContextFactoryProvider {
     /**
-     * Return a {@link SslHandlerFactory}, optionally conforming to a particular specification.
+     * Return a {@link SslContextFactory}, optionally conforming to a particular specification.
      *
      * @param specification A {@link Specification}, may be {@code null}
-     * @return A {@link SslHandlerFactory}
+     * @return A {@link SslContextFactory}
      * @throws IllegalArgumentException if {@code specification} is not {@code null} and it is not supported by this
      *         provider.
      */
-    @NonNull SslHandlerFactory getSslHandlerFactory(@Nullable Specification specification);
+    @NonNull SslContextFactory getSslContextFactory(@Nullable Specification specification);
 }
diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslContextFactory.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSslContextFactory.java
new file mode 100644 (file)
index 0000000..a0379ec
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.client.mdsal.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.JdkSslContext;
+import io.netty.handler.ssl.SslContext;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.Set;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.netconf.client.SslContextFactory;
+
+class DefaultSslContextFactory implements SslContextFactory {
+    private final DefaultSslContextFactoryProvider keyStoreProvider;
+
+    DefaultSslContextFactory(final DefaultSslContextFactoryProvider keyStoreProvider) {
+        this.keyStoreProvider = requireNonNull(keyStoreProvider);
+    }
+
+    @Override
+    public final SslContext createSslContext(final Set<String> allowedKeys) {
+        final SSLContext sslContext;
+        try {
+            final var keyStore = keyStoreProvider.getJavaKeyStore(allowedKeys);
+
+            final var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            kmf.init(keyStore, "".toCharArray());
+
+            final var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            tmf.init(keyStore);
+
+            sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+        } catch (IOException | GeneralSecurityException e) {
+            throw new IllegalStateException("Failed to initialize SSL context", e);
+        }
+
+        return wrapSslContext(new JdkSslContext(sslContext, true, ClientAuth.NONE));
+    }
+
+    @NonNull SslContext wrapSslContext(final @NonNull SslContext sslContext) {
+        return sslContext;
+    }
+}
@@ -19,8 +19,8 @@ import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.netconf.client.SslHandlerFactory;
-import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
+import org.opendaylight.netconf.client.SslContextFactory;
+import org.opendaylight.netconf.client.mdsal.api.SslContextFactoryProvider;
 import org.opendaylight.netconf.keystore.legacy.NetconfKeystore;
 import org.opendaylight.netconf.keystore.legacy.NetconfKeystoreService;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.parameters.protocol.Specification;
@@ -32,19 +32,19 @@ import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Reference;
 
 @Singleton
-@Component(service = SslHandlerFactoryProvider.class)
-public final class DefaultSslHandlerFactoryProvider implements SslHandlerFactoryProvider, AutoCloseable {
+@Component(service = SslContextFactoryProvider.class)
+public final class DefaultSslContextFactoryProvider implements SslContextFactoryProvider, AutoCloseable {
     private static final X509Certificate[] EMPTY_CERTS = { };
     private static final char[] EMPTY_CHARS = { };
 
-    private final @NonNull SslHandlerFactory nospecFactory = new SslHandlerFactoryImpl(this, Set.of());
+    private final @NonNull DefaultSslContextFactory nospecFactory = new DefaultSslContextFactory(this);
     private final Registration reg;
 
     private volatile @NonNull NetconfKeystore keystore = NetconfKeystore.EMPTY;
 
     @Inject
     @Activate
-    public DefaultSslHandlerFactoryProvider(@Reference final NetconfKeystoreService keystoreService) {
+    public DefaultSslContextFactoryProvider(@Reference final NetconfKeystoreService keystoreService) {
         reg = keystoreService.registerKeystoreConsumer(this::onKeystoreUpdated);
     }
 
@@ -60,16 +60,16 @@ public final class DefaultSslHandlerFactoryProvider implements SslHandlerFactory
     }
 
     @Override
-    public SslHandlerFactory getSslHandlerFactory(final Specification specification) {
+    public SslContextFactory getSslContextFactory(final Specification specification) {
         if (specification == null) {
             return nospecFactory;
-        }
-        if (specification instanceof TlsCase tlsSpecification) {
+        } else if (specification instanceof TlsCase tlsSpecification) {
             final var excludedVersions = tlsSpecification.nonnullTls().getExcludedVersions();
             return excludedVersions == null || excludedVersions.isEmpty() ? nospecFactory
-                : new SslHandlerFactoryImpl(this, excludedVersions);
+                : new FilteredSslContextFactory(this, excludedVersions);
+        } else {
+            throw new IllegalArgumentException("Cannot get TLS specification from: " + specification);
         }
-        throw new IllegalArgumentException("Cannot get TLS specification from: " + specification);
     }
 
     /**
diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/FilteredSslContext.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/FilteredSslContext.java
new file mode 100644 (file)
index 0000000..623399b
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.client.mdsal.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import io.netty.handler.ssl.DelegatingSslContext;
+import io.netty.handler.ssl.SslContext;
+import java.util.Arrays;
+import java.util.Set;
+import javax.net.ssl.SSLEngine;
+
+final class FilteredSslContext extends DelegatingSslContext {
+    private final Set<String> excludedVersions;
+
+    FilteredSslContext(final SslContext ctx, final Set<String> excludedVersions) {
+        super(ctx);
+        this.excludedVersions = requireNonNull(excludedVersions);
+    }
+
+    @Override
+    protected void initEngine(final SSLEngine engine) {
+        engine.setEnabledProtocols(Arrays.stream(engine.getSupportedProtocols())
+            .filter(protocol -> !excludedVersions.contains(protocol))
+            .toArray(String[]::new));
+        engine.setEnabledCipherSuites(engine.getSupportedCipherSuites());
+        engine.setEnableSessionCreation(true);
+    }
+}
diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/FilteredSslContextFactory.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/FilteredSslContextFactory.java
new file mode 100644 (file)
index 0000000..481e753
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.client.mdsal.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import io.netty.handler.ssl.SslContext;
+import java.util.Set;
+
+final class FilteredSslContextFactory extends DefaultSslContextFactory {
+    private final Set<String> excludedVersions;
+
+    FilteredSslContextFactory(final DefaultSslContextFactoryProvider keyStoreProvider,
+            final Set<String> excludedVersions) {
+        super(keyStoreProvider);
+        this.excludedVersions = requireNonNull(excludedVersions);
+    }
+
+    @Override
+    SslContext wrapSslContext(final SslContext sslContext) {
+        return new FilteredSslContext(sslContext, excludedVersions);
+    }
+}
diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SslHandlerFactoryImpl.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SslHandlerFactoryImpl.java
deleted file mode 100644 (file)
index 5d57d00..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (c) 2019 Pantheon Technologies, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.netconf.client.mdsal.impl;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.collect.Sets;
-import io.netty.handler.ssl.SslHandler;
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.util.Set;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.TrustManagerFactory;
-import org.opendaylight.netconf.client.SslHandlerFactory;
-
-final class SslHandlerFactoryImpl implements SslHandlerFactory {
-    private final DefaultSslHandlerFactoryProvider keyStoreProvider;
-    private final Set<String> excludedVersions;
-
-    SslHandlerFactoryImpl(final DefaultSslHandlerFactoryProvider keyStoreProvider, final Set<String> excludedVersions) {
-        this.keyStoreProvider = requireNonNull(keyStoreProvider);
-        this.excludedVersions = requireNonNull(excludedVersions);
-    }
-
-    @Override
-    public SslHandler createSslHandler(final Set<String> allowedKeys) {
-        try {
-            final KeyStore keyStore = keyStoreProvider.getJavaKeyStore(allowedKeys);
-
-            final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
-            kmf.init(keyStore, "".toCharArray());
-
-            final TrustManagerFactory tmf =
-                    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-            tmf.init(keyStore);
-
-            final SSLContext sslCtx = SSLContext.getInstance("TLS");
-            sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
-            final SSLEngine engine = sslCtx.createSSLEngine();
-            engine.setUseClientMode(true);
-
-            final String[] engineProtocols = engine.getSupportedProtocols();
-            final String[] enabledProtocols;
-            if (!excludedVersions.isEmpty()) {
-                final var protocols = Sets.newHashSet(engineProtocols);
-                protocols.removeAll(excludedVersions);
-                enabledProtocols = protocols.toArray(new String[0]);
-            } else {
-                enabledProtocols = engineProtocols;
-            }
-
-            engine.setEnabledProtocols(enabledProtocols);
-            engine.setEnabledCipherSuites(engine.getSupportedCipherSuites());
-            engine.setEnableSessionCreation(true);
-            return new SslHandler(engine);
-        } catch (GeneralSecurityException | IOException exc) {
-            throw new IllegalStateException(exc);
-        }
-    }
-}
\ No newline at end of file
@@ -46,7 +46,7 @@ import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
 @ExtendWith(MockitoExtension.class)
-class DefaultSslHandlerFactoryProviderTest {
+class DefaultSslContextFactoryProviderTest {
     private static final String XML_ELEMENT_PRIVATE_KEY = "private-key";
     private static final String XML_ELEMENT_NAME = "name";
     private static final String XML_ELEMENT_DATA = "data";
@@ -95,8 +95,8 @@ class DefaultSslHandlerFactoryProviderTest {
         keystore.close();
     }
 
-    private DefaultSslHandlerFactoryProvider newProvider() {
-        return new DefaultSslHandlerFactoryProvider(keystore);
+    private DefaultSslContextFactoryProvider newProvider() {
+        return new DefaultSslContextFactoryProvider(keystore);
     }
 
     @Test
@@ -209,6 +209,6 @@ class DefaultSslHandlerFactoryProviderTest {
 
     private static Document readKeystoreXML() throws Exception {
         return XmlUtil.readXmlToDocument(
-            DefaultSslHandlerFactoryProviderTest.class.getResourceAsStream("/netconf-keystore.xml"));
+            DefaultSslContextFactoryProviderTest.class.getResourceAsStream("/netconf-keystore.xml"));
     }
 }
index ab6e7e6356697890705f48faba6b157fc95e959c..0ad6ae59fe189e9269ca93fc318f2880e8af9b37 100644 (file)
@@ -26,6 +26,7 @@ import org.opendaylight.netconf.transport.api.TransportChannelListener;
 import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
 import org.opendaylight.netconf.transport.ssh.SSHTransportStackFactory;
 import org.opendaylight.netconf.transport.tcp.TCPClient;
+import org.opendaylight.netconf.transport.tls.FixedSslHandlerFactory;
 import org.opendaylight.netconf.transport.tls.TLSClient;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
 import org.osgi.service.component.annotations.Activate;
@@ -73,7 +74,7 @@ public final class NetconfClientFactoryImpl implements NetconfClientFactory {
         } else if (TLS.equals(protocol)) {
             if (configuration.getTlsParameters() != null) {
                 TLSClient.connect(new ClientTransportChannelListener(future, channelInitializer), bootstrap,
-                    configuration.getTcpParameters(), configuration.getTlsParameters());
+                    configuration.getTcpParameters(), new FixedSslHandlerFactory(configuration.getTlsParameters()));
             } else {
                 TLSClient.connect(new ClientTransportChannelListener(future, channelInitializer), bootstrap,
                     configuration.getTcpParameters(), configuration.getSslHandlerFactory());
diff --git a/protocol/netconf-client/src/main/java/org/opendaylight/netconf/client/SslContextFactory.java b/protocol/netconf-client/src/main/java/org/opendaylight/netconf/client/SslContextFactory.java
new file mode 100644 (file)
index 0000000..986e17c
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018 ZTE Corporation. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.client;
+
+import com.google.common.annotations.Beta;
+import io.netty.handler.ssl.SslContext;
+import java.util.Set;
+
+/**
+ * Basic interface for instantiating a {@link SslContext}. Used to establish TSL connection.
+ */
+@Beta
+public interface SslContextFactory {
+    /**
+     * This factory is used by the TLS client to create SslHandler that will be added into the channel pipeline when
+     * the channel is active.
+     */
+    default SslContext createSslContext() {
+        return createSslContext(Set.of());
+    }
+
+    SslContext createSslContext(Set<String> allowedKeys);
+}
diff --git a/protocol/netconf-client/src/main/java/org/opendaylight/netconf/client/SslHandlerFactory.java b/protocol/netconf-client/src/main/java/org/opendaylight/netconf/client/SslHandlerFactory.java
deleted file mode 100644 (file)
index e713156..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2018 ZTE Corporation. and others.  All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.netconf.client;
-
-import io.netty.handler.ssl.SslHandler;
-import java.util.Set;
-
-/**
- * Basic interface for {@link SslHandler} builder. Used to establish TSL connection.
- *
- * @deprecated due to design change. SslHandler will be created dynamically based on TLS layer configuration
- *     {@link org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev231228.TlsClientGrouping}
- *     by {@link NetconfClientFactory}.
- */
-@Deprecated
-public interface SslHandlerFactory {
-    /**
-     * This factory is used by the TLS client to create SslHandler that will be added into the channel pipeline when
-     * the channel is active.
-     */
-    default SslHandler createSslHandler() {
-        return createSslHandler(Set.of());
-    }
-
-    SslHandler createSslHandler(Set<String> allowedKeys);
-}
index 142009b7ba50aa88efcdf10190b0629195118e66..c85bc1839b1234f475b7224a65dd4a79c200dae0 100644 (file)
@@ -56,6 +56,7 @@ import org.opendaylight.netconf.transport.ssh.ClientFactoryManagerConfigurator;
 import org.opendaylight.netconf.transport.ssh.SSHTransportStackFactory;
 import org.opendaylight.netconf.transport.ssh.ServerFactoryManagerConfigurator;
 import org.opendaylight.netconf.transport.tcp.TCPServer;
+import org.opendaylight.netconf.transport.tls.FixedSslHandlerFactory;
 import org.opendaylight.netconf.transport.tls.TLSServer;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.crypt.hash.rev140806.CryptHash;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.RsaPrivateKeyFormat;
@@ -168,12 +169,12 @@ class NetconfClientFactoryImplTest {
         final var clientContext = SslContextBuilder.forClient().keyManager(keyMgr).trustManager(trustMgr).build();
 
         final var server = TLSServer.listen(serverTransportListener, SERVER_FACTORY.newServerBootstrap(),
-            tcpServerParams, channel -> serverContext.newHandler(channel.alloc())).get(1, TimeUnit.SECONDS);
+            tcpServerParams, new FixedSslHandlerFactory(serverContext)).get(1, TimeUnit.SECONDS);
         try {
             final var clientConfig = NetconfClientConfigurationBuilder.create()
                 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS)
                 .withTcpParameters(tcpClientParams)
-                .withSslHandlerFactory(channel -> clientContext.newHandler(channel.alloc()))
+                .withSslHandlerFactory(new FixedSslHandlerFactory(clientContext))
                 .withSessionListener(sessionListener).build();
             assertNotNull(factory.createClient(clientConfig));
             verify(serverTransportListener, timeout(1000L))
diff --git a/transport/transport-tls/src/main/java/org/opendaylight/netconf/transport/tls/FixedSslHandlerFactory.java b/transport/transport-tls/src/main/java/org/opendaylight/netconf/transport/tls/FixedSslHandlerFactory.java
new file mode 100644 (file)
index 0000000..ab6f16b
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.transport.tls;
+
+import static java.util.Objects.requireNonNull;
+
+import io.netty.handler.ssl.SslContext;
+import java.net.SocketAddress;
+import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev231228.TlsClientGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev231228.TlsServerGrouping;
+
+public final class FixedSslHandlerFactory extends SslHandlerFactory {
+    private final SslContext sslContext;
+
+    public FixedSslHandlerFactory(final SslContext sslContext) {
+        this.sslContext = requireNonNull(sslContext);
+    }
+
+    public FixedSslHandlerFactory(final TlsClientGrouping clientParams) throws UnsupportedConfigurationException {
+        this(createSslContext(clientParams));
+    }
+
+    public FixedSslHandlerFactory(final TlsServerGrouping serverParams) throws UnsupportedConfigurationException {
+        this(createSslContext(serverParams));
+    }
+
+    @Override
+    protected SslContext getSslContext(final SocketAddress remoteAddress) {
+        return sslContext;
+    }
+}
index 59f5c303fc93c6acd7955afebfdf1246688411e4..bebfad935b1548a210b4a28e4cb393558ee45907 100644 (file)
@@ -34,7 +34,7 @@ final class KeyStoreUtils {
      * @return key store instance
      * @throws UnsupportedConfigurationException if key store cannot be instantiated
      */
-    static KeyStore newKeyStore() throws UnsupportedConfigurationException {
+    static @NonNull KeyStore newKeyStore() throws UnsupportedConfigurationException {
         final KeyStore keyStore;
         try {
             keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
index f216aca1f1b4f650ed9a866996a24393c62991bb..a079b8b4691c4b0e472a5cdae89ed4d3837d3846 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 PANTHEON.tech s.r.o. and others. All rights reserved.
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others.  All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  */
 package org.opendaylight.netconf.transport.tls;
 
-import com.google.common.annotations.Beta;
+import static org.opendaylight.netconf.transport.tls.ConfigUtils.setAsymmetricKey;
+import static org.opendaylight.netconf.transport.tls.ConfigUtils.setEndEntityCertificateWithKey;
+import static org.opendaylight.netconf.transport.tls.ConfigUtils.setX509Certificates;
+import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.buildKeyManagerFactory;
+import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.buildTrustManagerFactory;
+import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.newKeyStore;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import io.netty.channel.Channel;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
 import io.netty.handler.ssl.SslHandler;
+import java.net.SocketAddress;
+import java.security.KeyStore;
+import java.util.List;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.CipherSuiteAlgBase;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsAes128CcmSha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsAes128GcmSha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsAes256GcmSha384;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsChacha20Poly1305Sha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes128Ccm;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes128GcmSha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes256Ccm;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes256GcmSha384;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithChacha20Poly1305Sha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes128Ccm;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes128GcmSha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes256Ccm;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes256GcmSha384;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithChacha20Poly1305Sha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheEcdsaWithAes128GcmSha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheEcdsaWithAes256GcmSha384;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheEcdsaWithChacha20Poly1305Sha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithAes128CcmSha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithAes128GcmSha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithAes256GcmSha384;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithChacha20Poly1305Sha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheRsaWithAes128GcmSha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheRsaWithAes256GcmSha384;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheRsaWithChacha20Poly1305Sha256;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228.InlineOrKeystoreAsymmetricKeyGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228.InlineOrKeystoreEndEntityCertWithKeyGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev231228.TlsClientGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev231228.tls.client.grouping.client.identity.auth.type.Certificate;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev231228.tls.client.grouping.client.identity.auth.type.RawPublicKey;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.common.rev231228.HelloParamsGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.common.rev231228.TlsVersionBase;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev231228.TlsServerGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev231228.tls.server.grouping.server.identity.auth.type.RawPrivateKey;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228.InlineOrTruststoreCertsGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228.InlineOrTruststorePublicKeysGrouping;
 
 /**
  * Extension interface for external service integration with TLS transport. Used to build {@link TLSClient} and
  * {@link TLSServer} instances.
  */
-@Beta
-@FunctionalInterface
-public interface SslHandlerFactory {
+public abstract class SslHandlerFactory {
+    private static final ImmutableMap<CipherSuiteAlgBase, String> CIPHER_SUITES =
+        ImmutableMap.<CipherSuiteAlgBase, String>builder()
+            .put(TlsAes128CcmSha256.VALUE, "TLS_AES_128_CCM_SHA256")
+            .put(TlsAes128GcmSha256.VALUE, "TLS_AES_128_GCM_SHA256")
+            .put(TlsAes256GcmSha384.VALUE, "TLS_AES_256_GCM_SHA384")
+            .put(TlsChacha20Poly1305Sha256.VALUE, "TLS_CHACHA20_POLY1305_SHA256")
+            .put(TlsDhePskWithAes128Ccm.VALUE, "TLS_DHE_PSK_WITH_AES_128_CCM")
+            .put(TlsDhePskWithAes128GcmSha256.VALUE, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256")
+            .put(TlsDhePskWithAes256Ccm.VALUE, "TLS_DHE_PSK_WITH_AES_256_CCM")
+            .put(TlsDhePskWithAes256GcmSha384.VALUE, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384")
+            .put(TlsDhePskWithChacha20Poly1305Sha256.VALUE, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256")
+            .put(TlsDheRsaWithAes128Ccm.VALUE, "TLS_DHE_RSA_WITH_AES_128_CCM")
+            .put(TlsDheRsaWithAes128GcmSha256.VALUE, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256")
+            .put(TlsDheRsaWithAes256Ccm.VALUE, "TLS_DHE_RSA_WITH_AES_256_CCM")
+            .put(TlsDheRsaWithAes256GcmSha384.VALUE, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384")
+            .put(TlsDheRsaWithChacha20Poly1305Sha256.VALUE, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
+            .put(TlsEcdheEcdsaWithAes128GcmSha256.VALUE, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
+            .put(TlsEcdheEcdsaWithAes256GcmSha384.VALUE, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
+            .put(TlsEcdheEcdsaWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
+            .put(TlsEcdhePskWithAes128CcmSha256.VALUE, "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256")
+            .put(TlsEcdhePskWithAes128GcmSha256.VALUE, "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256")
+            .put(TlsEcdhePskWithAes256GcmSha384.VALUE, "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384")
+            .put(TlsEcdhePskWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256")
+            .put(TlsEcdheRsaWithAes128GcmSha256.VALUE, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
+            .put(TlsEcdheRsaWithAes256GcmSha384.VALUE, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
+            .put(TlsEcdheRsaWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
+            .build();
+
     /**
      * Builds {@link SslHandler} instance for given {@link Channel}.
      *
      * @param channel channel
-     * @return A SslHandler
+     * @return A {@link SslHandler}, or {@code null} if the connection should be rejected
      */
-    SslHandler createSslHandler(Channel channel);
+    public final @Nullable SslHandler createSslHandler(final @NonNull Channel channel) {
+        final var sslContext = getSslContext(channel.remoteAddress());
+        return sslContext == null ? null : sslContext.newHandler(channel.alloc());
+    }
+
+    protected abstract @Nullable SslContext getSslContext(SocketAddress remoteAddress);
+
+    protected static final @NonNull SslContext createSslContext(final @NonNull TlsClientGrouping clientParams)
+            throws UnsupportedConfigurationException {
+        final var builder = SslContextBuilder.forClient();
+
+        final var clientIdentity = clientParams.getClientIdentity();
+        if (clientIdentity != null) {
+            final var authType = clientIdentity.getAuthType();
+            if (authType instanceof Certificate cert) {
+                // if-feature "client-ident-x509-cert"
+                final var certificate = cert.getCertificate();
+                if (certificate == null) {
+                    throw new UnsupportedConfigurationException("Missing certificate in " + cert);
+                }
+                builder.keyManager(newKeyManager(certificate));
+            } else if (authType instanceof RawPublicKey rawKey) {
+                // if-feature "client-ident-raw-public-key"
+                final var rawPrivateKey = rawKey.getRawPrivateKey();
+                if (rawPrivateKey == null) {
+                    throw new UnsupportedConfigurationException("Missing key in " + rawKey);
+                }
+                builder.keyManager(newKeyManager(rawPrivateKey));
+            } else if (authType != null) {
+                throw new UnsupportedConfigurationException("Unsupported client authentication type " + authType);
+            }
+        }
+
+        final var serverAuth = clientParams.getServerAuthentication();
+        if (serverAuth != null) {
+            // CA && EE X509 certificates : if-feature "server-ident-x509-cert"
+            // Raw public key : if-feature "server-ident-raw-public-key"
+            final var trustManager = newTrustManager(serverAuth.getCaCerts(), serverAuth.getEeCerts(),
+                    serverAuth.getRawPublicKeys());
+            if (trustManager == null) {
+                throw new UnsupportedOperationException("No server authentication methods in " + serverAuth);
+            }
+            builder.trustManager(trustManager);
+        }
+
+        return buildSslContext(builder, clientParams.getHelloParams());
+    }
+
+    protected static final @NonNull SslContext createSslContext(final @NonNull TlsServerGrouping serverParams)
+            throws UnsupportedConfigurationException {
+        final var serverIdentity = serverParams.getServerIdentity();
+        if (serverIdentity == null) {
+            throw new UnsupportedConfigurationException("Missing server identity");
+        }
+        final SslContextBuilder builder;
+        final var authType = serverIdentity.getAuthType();
+        if (authType
+                instanceof org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev231228
+                           .tls.server.grouping.server.identity.auth.type.Certificate cert) {
+            // if-feature "server-ident-x509-cert"
+            final var certificate = cert.getCertificate();
+            if (certificate == null) {
+                throw new UnsupportedConfigurationException("Missing certificate in " + cert);
+            }
+            builder = SslContextBuilder.forServer(newKeyManager(certificate));
+        } else if (authType instanceof RawPrivateKey rawKey) {
+            // if-feature "server-ident-raw-public-key"
+            final var rawPrivateKey = rawKey.getRawPrivateKey();
+            if (rawPrivateKey == null) {
+                throw new UnsupportedConfigurationException("Missing key in " + rawKey);
+            }
+            builder = SslContextBuilder.forServer(newKeyManager(rawPrivateKey));
+        } else if (authType != null) {
+            throw new UnsupportedConfigurationException("Unsupported server authentication type " + authType);
+        } else {
+            throw new UnsupportedConfigurationException("Missing server authentication type");
+        }
+
+        final var clientAuth = serverParams.getClientAuthentication();
+        if (clientAuth != null) {
+            // CA && EE Certs : if-feature "client-ident-x509-cert"
+            // Raw public keys : if-feature "client-ident-raw-public-key"
+            final var trustManager = newTrustManager(clientAuth.getCaCerts(), clientAuth.getEeCerts(),
+                    clientAuth.getRawPublicKeys());
+            if (trustManager == null) {
+                throw new UnsupportedOperationException("No client authentication methods in " + clientAuth);
+            }
+            builder.clientAuth(ClientAuth.REQUIRE).trustManager(trustManager);
+        } else {
+            builder.clientAuth(ClientAuth.NONE);
+        }
+
+        return buildSslContext(builder, serverParams.getHelloParams());
+    }
+
+    // FIXME: should be TrustManagerBuilder
+    private static @Nullable TrustManagerFactory newTrustManager(
+            final @Nullable InlineOrTruststoreCertsGrouping caCerts,
+            final @Nullable InlineOrTruststoreCertsGrouping eeCerts,
+            final @Nullable InlineOrTruststorePublicKeysGrouping publicKeys) throws UnsupportedConfigurationException {
+
+        if (publicKeys != null) {
+            // FIXME: implement this and advertize server-auth-raw-public-key from IetfTlsClientFeatureProvider
+            throw new UnsupportedConfigurationException("Public key authentication not implemented");
+        }
+        if (caCerts != null || eeCerts != null) {
+            // X.509 certificates
+            final KeyStore keyStore = newKeyStore();
+            setX509Certificates(keyStore, caCerts, eeCerts);
+            return buildTrustManagerFactory(keyStore);
+        }
+        return null;
+    }
+
+    private static KeyManagerFactory newKeyManager(
+            final @NonNull InlineOrKeystoreEndEntityCertWithKeyGrouping endEntityCert)
+            throws UnsupportedConfigurationException {
+        final var keyStore = newKeyStore();
+        setEndEntityCertificateWithKey(keyStore, endEntityCert);
+        return buildKeyManagerFactory(keyStore);
+    }
+
+    private static KeyManagerFactory newKeyManager(final @NonNull InlineOrKeystoreAsymmetricKeyGrouping rawPrivateKey)
+            throws UnsupportedConfigurationException {
+        final var keyStore = newKeyStore();
+        setAsymmetricKey(keyStore, rawPrivateKey);
+        return buildKeyManagerFactory(keyStore);
+    }
+
+    private static @NonNull SslContext buildSslContext(final SslContextBuilder builder,
+            final HelloParamsGrouping helloParams) throws UnsupportedConfigurationException {
+        if (helloParams != null) {
+            final var tlsVersions = helloParams.getTlsVersions();
+            if (tlsVersions != null) {
+                final var versions = tlsVersions.getTlsVersion();
+                if (versions != null && !versions.isEmpty()) {
+                    builder.protocols(createTlsStrings(versions));
+                }
+            }
+            final var cipherSuites = helloParams.getCipherSuites();
+            if (cipherSuites != null) {
+                final var ciphers = cipherSuites.getCipherSuite();
+                if (ciphers != null && !ciphers.isEmpty()) {
+                    builder.ciphers(createCipherStrings(ciphers));
+                }
+            }
+        }
+        try {
+            return builder.build();
+        } catch (SSLException e) {
+            throw new UnsupportedConfigurationException("Cannot instantiate TLS context", e);
+        }
+    }
+
+    private static String[] createTlsStrings(final List<TlsVersionBase> versions)
+            throws UnsupportedConfigurationException {
+        // FIXME: cache these
+        final var ret = new String[versions.size()];
+        int idx = 0;
+        for (var version : versions) {
+            final var str = IetfTlsCommonFeatureProvider.algorithmNameOf(version);
+            if (str == null) {
+                throw new UnsupportedConfigurationException("Unhandled TLS version " + version);
+            }
+            ret[idx++] = str;
+        }
+        return ret;
+    }
+
+    private static ImmutableList<String> createCipherStrings(final List<CipherSuiteAlgBase> ciphers)
+            throws UnsupportedConfigurationException {
+        // FIXME: cache these
+        final var builder = ImmutableList.<String>builderWithExpectedSize(ciphers.size());
+        for (var cipher : ciphers) {
+            final var str = CIPHER_SUITES.get(cipher);
+            if (str == null) {
+                throw new UnsupportedConfigurationException("Unhandled cipher suite " + cipher);
+            }
+            builder.add(str);
+        }
+        return builder.build();
+    }
 }
index 92bb0868b9b52fbae6caec932dad746ec5e4df0c..78fa08ac19312a75e427e7be287428c7569a3ae8 100644 (file)
@@ -11,7 +11,6 @@ import com.google.common.util.concurrent.ListenableFuture;
 import io.netty.bootstrap.Bootstrap;
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslContextBuilder;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.netconf.transport.api.TransportChannelListener;
 import org.opendaylight.netconf.transport.api.TransportStack;
@@ -20,9 +19,6 @@ import org.opendaylight.netconf.transport.tcp.TCPClient;
 import org.opendaylight.netconf.transport.tcp.TCPServer;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.client.rev231228.TcpClientGrouping;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.server.rev231228.TcpServerGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev231228.TlsClientGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev231228.tls.client.grouping.client.identity.auth.type.Certificate;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev231228.tls.client.grouping.client.identity.auth.type.RawPublicKey;
 
 /**
  * A {@link TransportStack} acting as a TLS client.
@@ -36,13 +32,6 @@ public final class TLSClient extends TLSTransportStack {
         super(listener, factory);
     }
 
-    public static @NonNull ListenableFuture<TLSClient> connect(final TransportChannelListener listener,
-            final Bootstrap bootstrap, final TcpClientGrouping connectParams, final TlsClientGrouping clientParams)
-                throws UnsupportedConfigurationException {
-        final var client = newClient(listener, clientParams);
-        return transformUnderlay(client, TCPClient.connect(client.asListener(), bootstrap, connectParams));
-    }
-
     public static @NonNull ListenableFuture<TLSClient> connect(final TransportChannelListener listener,
             final Bootstrap bootstrap, final TcpClientGrouping connectParams, final SslHandlerFactory factory)
                 throws UnsupportedConfigurationException {
@@ -50,58 +39,10 @@ public final class TLSClient extends TLSTransportStack {
         return transformUnderlay(client, TCPClient.connect(client.asListener(), bootstrap, connectParams));
     }
 
-    public static @NonNull ListenableFuture<TLSClient> listen(final TransportChannelListener listener,
-            final ServerBootstrap bootstrap, final TcpServerGrouping listenParams, final TlsClientGrouping clientParams)
-            throws UnsupportedConfigurationException {
-        final var client = newClient(listener, clientParams);
-        return transformUnderlay(client, TCPServer.listen(client.asListener(), bootstrap, listenParams));
-    }
-
     public static @NonNull ListenableFuture<TLSClient> listen(final TransportChannelListener listener,
             final ServerBootstrap bootstrap, final TcpServerGrouping listenParams, final SslHandlerFactory factory)
             throws UnsupportedConfigurationException {
         final var client = new TLSClient(listener, factory);
         return transformUnderlay(client, TCPServer.listen(client.asListener(), bootstrap, listenParams));
     }
-
-    private static TLSClient newClient(final TransportChannelListener listener, final TlsClientGrouping clientParams)
-            throws UnsupportedConfigurationException {
-        final var builder = SslContextBuilder.forClient();
-
-        final var clientIdentity = clientParams.getClientIdentity();
-        if (clientIdentity != null) {
-            final var authType = clientIdentity.getAuthType();
-            if (authType instanceof Certificate cert) {
-                // if-feature "client-ident-x509-cert"
-                final var certificate = cert.getCertificate();
-                if (certificate == null) {
-                    throw new UnsupportedConfigurationException("Missing certificate in " + cert);
-                }
-                builder.keyManager(newKeyManager(certificate));
-            } else if (authType instanceof RawPublicKey rawKey) {
-                // if-feature "client-ident-raw-public-key"
-                final var rawPrivateKey = rawKey.getRawPrivateKey();
-                if (rawPrivateKey == null) {
-                    throw new UnsupportedConfigurationException("Missing key in " + rawKey);
-                }
-                builder.keyManager(newKeyManager(rawPrivateKey));
-            } else if (authType != null) {
-                throw new UnsupportedConfigurationException("Unsupported client authentication type " + authType);
-            }
-        }
-
-        final var serverAuth = clientParams.getServerAuthentication();
-        if (serverAuth != null) {
-            // CA && EE X509 certificates : if-feature "server-ident-x509-cert"
-            // Raw public key : if-feature "server-ident-raw-public-key"
-            final var trustManager = newTrustManager(serverAuth.getCaCerts(), serverAuth.getEeCerts(),
-                    serverAuth.getRawPublicKeys());
-            if (trustManager == null) {
-                throw new UnsupportedOperationException("No server authentication methods in " + serverAuth);
-            }
-            builder.trustManager(trustManager);
-        }
-
-        return new TLSClient(listener, buildSslContext(builder, clientParams.getHelloParams()));
-    }
 }
index b436289dca38a669df6ac5cdc4dae411ad0d7ba6..bcaf0902e86c8c066cc1d4502b652e1700373176 100644 (file)
@@ -10,9 +10,7 @@ package org.opendaylight.netconf.transport.tls;
 import com.google.common.util.concurrent.ListenableFuture;
 import io.netty.bootstrap.Bootstrap;
 import io.netty.bootstrap.ServerBootstrap;
-import io.netty.handler.ssl.ClientAuth;
 import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslContextBuilder;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.netconf.transport.api.TransportChannelListener;
 import org.opendaylight.netconf.transport.api.TransportStack;
@@ -21,9 +19,6 @@ import org.opendaylight.netconf.transport.tcp.TCPClient;
 import org.opendaylight.netconf.transport.tcp.TCPServer;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.client.rev231228.TcpClientGrouping;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.server.rev231228.TcpServerGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev231228.TlsServerGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev231228.tls.server.grouping.server.identity.auth.type.Certificate;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev231228.tls.server.grouping.server.identity.auth.type.RawPrivateKey;
 
 /**
  * A {@link TransportStack} acting as a TLS server.
@@ -37,13 +32,6 @@ public final class TLSServer extends TLSTransportStack {
         super(listener, factory);
     }
 
-    public static @NonNull ListenableFuture<TLSServer> connect(final TransportChannelListener listener,
-            final Bootstrap bootstrap, final TcpClientGrouping connectParams, final TlsServerGrouping serverParams)
-                throws UnsupportedConfigurationException {
-        final var server = newServer(listener, serverParams);
-        return transformUnderlay(server, TCPClient.connect(server.asListener(), bootstrap, connectParams));
-    }
-
     public static @NonNull ListenableFuture<TLSServer> connect(final TransportChannelListener listener,
             final Bootstrap bootstrap, final TcpClientGrouping connectParams, final SslHandlerFactory factory)
             throws UnsupportedConfigurationException {
@@ -51,61 +39,10 @@ public final class TLSServer extends TLSTransportStack {
         return transformUnderlay(server, TCPClient.connect(server.asListener(), bootstrap, connectParams));
     }
 
-    public static @NonNull ListenableFuture<TLSServer> listen(final TransportChannelListener listener,
-            final ServerBootstrap bootstrap, final TcpServerGrouping listenParams, final TlsServerGrouping serverParams)
-                throws UnsupportedConfigurationException {
-        final var server = newServer(listener, serverParams);
-        return transformUnderlay(server, TCPServer.listen(server.asListener(), bootstrap, listenParams));
-    }
-
     public static @NonNull ListenableFuture<TLSServer> listen(final TransportChannelListener listener,
             final ServerBootstrap bootstrap, final TcpServerGrouping listenParams, final SslHandlerFactory factory)
                 throws UnsupportedConfigurationException {
         final var server = new TLSServer(listener, factory);
         return transformUnderlay(server, TCPServer.listen(server.asListener(), bootstrap, listenParams));
     }
-
-    private static TLSServer newServer(final TransportChannelListener listener, final TlsServerGrouping serverParams)
-            throws UnsupportedConfigurationException {
-        final var serverIdentity = serverParams.getServerIdentity();
-        if (serverIdentity == null) {
-            throw new UnsupportedConfigurationException("Missing server identity");
-        }
-        final SslContextBuilder builder;
-        final var authType = serverIdentity.getAuthType();
-        if (authType instanceof Certificate cert) {
-            // if-feature "server-ident-x509-cert"
-            final var certificate = cert.getCertificate();
-            if (certificate == null) {
-                throw new UnsupportedConfigurationException("Missing certificate in " + cert);
-            }
-            builder = SslContextBuilder.forServer(newKeyManager(certificate));
-        } else if (authType instanceof RawPrivateKey rawKey) {
-            // if-feature "server-ident-raw-public-key"
-            final var rawPrivateKey = rawKey.getRawPrivateKey();
-            if (rawPrivateKey == null) {
-                throw new UnsupportedConfigurationException("Missing key in " + rawKey);
-            }
-            builder = SslContextBuilder.forServer(newKeyManager(rawPrivateKey));
-        } else if (authType != null) {
-            throw new UnsupportedConfigurationException("Unsupported server authentication type " + authType);
-        } else {
-            throw new UnsupportedConfigurationException("Missing server authentication type");
-        }
-
-        final var clientAuth = serverParams.getClientAuthentication();
-        if (clientAuth != null) {
-            // CA && EE Certs : if-feature "client-ident-x509-cert"
-            // Raw public keys : if-feature "client-ident-raw-public-key"
-            final var trustManager = newTrustManager(clientAuth.getCaCerts(), clientAuth.getEeCerts(),
-                    clientAuth.getRawPublicKeys());
-            if (trustManager == null) {
-                throw new UnsupportedOperationException("No client authentication methods in " + clientAuth);
-            }
-            builder.clientAuth(ClientAuth.REQUIRE).trustManager(trustManager);
-        } else {
-            builder.clientAuth(ClientAuth.NONE);
-        }
-        return new TLSServer(listener, buildSslContext(builder, serverParams.getHelloParams()));
-    }
 }
index 131d07983b8251cf30364c7a5f6eb5ddd15edc5b..f6c09c3becb3d6a0a67e7da5daf9acb46dbb1029 100644 (file)
@@ -8,98 +8,21 @@
 package org.opendaylight.netconf.transport.tls;
 
 import static java.util.Objects.requireNonNull;
-import static org.opendaylight.netconf.transport.tls.ConfigUtils.setAsymmetricKey;
-import static org.opendaylight.netconf.transport.tls.ConfigUtils.setEndEntityCertificateWithKey;
-import static org.opendaylight.netconf.transport.tls.ConfigUtils.setX509Certificates;
-import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.buildKeyManagerFactory;
-import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.buildTrustManagerFactory;
-import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.newKeyStore;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslContextBuilder;
-import java.security.KeyStore;
-import java.util.List;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.TrustManagerFactory;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.netconf.transport.api.AbstractOverlayTransportStack;
 import org.opendaylight.netconf.transport.api.TransportChannel;
 import org.opendaylight.netconf.transport.api.TransportChannelListener;
-import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.CipherSuiteAlgBase;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsAes128CcmSha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsAes128GcmSha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsAes256GcmSha384;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsChacha20Poly1305Sha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes128Ccm;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes128GcmSha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes256Ccm;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes256GcmSha384;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithChacha20Poly1305Sha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes128Ccm;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes128GcmSha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes256Ccm;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes256GcmSha384;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithChacha20Poly1305Sha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheEcdsaWithAes128GcmSha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheEcdsaWithAes256GcmSha384;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheEcdsaWithChacha20Poly1305Sha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithAes128CcmSha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithAes128GcmSha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithAes256GcmSha384;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithChacha20Poly1305Sha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheRsaWithAes128GcmSha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheRsaWithAes256GcmSha384;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheRsaWithChacha20Poly1305Sha256;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228.InlineOrKeystoreAsymmetricKeyGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228.InlineOrKeystoreEndEntityCertWithKeyGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.common.rev231228.HelloParamsGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.common.rev231228.TlsVersionBase;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228.InlineOrTruststoreCertsGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228.InlineOrTruststorePublicKeysGrouping;
 
 /**
  * Base class for TLS TransportStacks.
  */
 public abstract sealed class TLSTransportStack extends AbstractOverlayTransportStack<TLSTransportChannel>
         permits TLSClient, TLSServer {
-
-    private static final ImmutableMap<CipherSuiteAlgBase, String> CIPHER_SUITES =
-            ImmutableMap.<CipherSuiteAlgBase, String>builder()
-                    .put(TlsAes128CcmSha256.VALUE, "TLS_AES_128_CCM_SHA256")
-                    .put(TlsAes128GcmSha256.VALUE, "TLS_AES_128_GCM_SHA256")
-                    .put(TlsAes256GcmSha384.VALUE, "TLS_AES_256_GCM_SHA384")
-                    .put(TlsChacha20Poly1305Sha256.VALUE, "TLS_CHACHA20_POLY1305_SHA256")
-                    .put(TlsDhePskWithAes128Ccm.VALUE, "TLS_DHE_PSK_WITH_AES_128_CCM")
-                    .put(TlsDhePskWithAes128GcmSha256.VALUE, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256")
-                    .put(TlsDhePskWithAes256Ccm.VALUE, "TLS_DHE_PSK_WITH_AES_256_CCM")
-                    .put(TlsDhePskWithAes256GcmSha384.VALUE, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384")
-                    .put(TlsDhePskWithChacha20Poly1305Sha256.VALUE, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256")
-                    .put(TlsDheRsaWithAes128Ccm.VALUE, "TLS_DHE_RSA_WITH_AES_128_CCM")
-                    .put(TlsDheRsaWithAes128GcmSha256.VALUE, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256")
-                    .put(TlsDheRsaWithAes256Ccm.VALUE, "TLS_DHE_RSA_WITH_AES_256_CCM")
-                    .put(TlsDheRsaWithAes256GcmSha384.VALUE, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384")
-                    .put(TlsDheRsaWithChacha20Poly1305Sha256.VALUE, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
-                    .put(TlsEcdheEcdsaWithAes128GcmSha256.VALUE, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
-                    .put(TlsEcdheEcdsaWithAes256GcmSha384.VALUE, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
-                    .put(TlsEcdheEcdsaWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
-                    .put(TlsEcdhePskWithAes128CcmSha256.VALUE, "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256")
-                    .put(TlsEcdhePskWithAes128GcmSha256.VALUE, "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256")
-                    .put(TlsEcdhePskWithAes256GcmSha384.VALUE, "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384")
-                    .put(TlsEcdhePskWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256")
-                    .put(TlsEcdheRsaWithAes128GcmSha256.VALUE, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
-                    .put(TlsEcdheRsaWithAes256GcmSha384.VALUE, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
-                    .put(TlsEcdheRsaWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
-                    .build();
-
     private final SslHandlerFactory factory;
 
     TLSTransportStack(final TransportChannelListener listener, final SslContext sslContext) {
-        this(listener, channel -> sslContext.newHandler(channel.alloc()));
+        this(listener, new FixedSslHandlerFactory(sslContext));
     }
 
     TLSTransportStack(final TransportChannelListener listener, final SslHandlerFactory factory) {
@@ -123,92 +46,4 @@ public abstract sealed class TLSTransportStack extends AbstractOverlayTransportS
             }
         });
     }
-
-    static KeyManagerFactory newKeyManager(
-            final @NonNull InlineOrKeystoreEndEntityCertWithKeyGrouping endEntityCert
-    ) throws UnsupportedConfigurationException {
-        final var keyStore = newKeyStore();
-        setEndEntityCertificateWithKey(keyStore, endEntityCert);
-        return buildKeyManagerFactory(keyStore);
-    }
-
-    static KeyManagerFactory newKeyManager(final @NonNull InlineOrKeystoreAsymmetricKeyGrouping rawPrivateKey)
-            throws UnsupportedConfigurationException {
-        final var keyStore = newKeyStore();
-        setAsymmetricKey(keyStore, rawPrivateKey);
-        return buildKeyManagerFactory(keyStore);
-    }
-
-    // FIXME: should be TrustManagerBuilder
-    protected static @Nullable TrustManagerFactory newTrustManager(
-            final @Nullable InlineOrTruststoreCertsGrouping caCerts,
-            final @Nullable InlineOrTruststoreCertsGrouping eeCerts,
-            final @Nullable InlineOrTruststorePublicKeysGrouping publicKeys) throws UnsupportedConfigurationException {
-
-        if (publicKeys != null) {
-            // FIXME: implement this and advertize server-auth-raw-public-key from IetfTlsClientFeatureProvider
-            throw new UnsupportedConfigurationException("Public key authentication not implemented");
-        }
-        if (caCerts != null || eeCerts != null) {
-            // X.509 certificates
-            final KeyStore keyStore = newKeyStore();
-            setX509Certificates(keyStore, caCerts, eeCerts);
-            return buildTrustManagerFactory(keyStore);
-        }
-        return null;
-    }
-
-    static SslContext buildSslContext(final SslContextBuilder builder, final HelloParamsGrouping helloParams)
-            throws UnsupportedConfigurationException {
-        if (helloParams != null) {
-            final var tlsVersions = helloParams.getTlsVersions();
-            if (tlsVersions != null) {
-                final var versions = tlsVersions.getTlsVersion();
-                if (versions != null && !versions.isEmpty()) {
-                    builder.protocols(createTlsStrings(versions));
-                }
-            }
-            final var cipherSuites = helloParams.getCipherSuites();
-            if (cipherSuites != null) {
-                final var ciphers = cipherSuites.getCipherSuite();
-                if (ciphers != null && !ciphers.isEmpty()) {
-                    builder.ciphers(createCipherStrings(ciphers));
-                }
-            }
-        }
-        try {
-            return builder.build();
-        } catch (SSLException e) {
-            throw new UnsupportedConfigurationException("Cannot instantiate TLS context", e);
-        }
-    }
-
-    private static String[] createTlsStrings(final List<TlsVersionBase> versions)
-            throws UnsupportedConfigurationException {
-        // FIXME: cache these
-        final var ret = new String[versions.size()];
-        int idx = 0;
-        for (var version : versions) {
-            final var str = IetfTlsCommonFeatureProvider.algorithmNameOf(version);
-            if (str == null) {
-                throw new UnsupportedConfigurationException("Unhandled TLS version " + version);
-            }
-            ret[idx++] = str;
-        }
-        return ret;
-    }
-
-    private static ImmutableList<String> createCipherStrings(final List<CipherSuiteAlgBase> ciphers)
-            throws UnsupportedConfigurationException {
-        // FIXME: cache these
-        final var builder = ImmutableList.<String>builderWithExpectedSize(ciphers.size());
-        for (var cipher : ciphers) {
-            final var str = CIPHER_SUITES.get(cipher);
-            if (str == null) {
-                throw new UnsupportedConfigurationException("Unhandled cipher suite " + cipher);
-            }
-            builder.add(str);
-        }
-        return builder.build();
-    }
 }
index 45f75378d29afcbf83925b0f90d424cad357269d..a38a6475eb8835e06125a971764e892dee9c3d3e 100644 (file)
@@ -182,9 +182,9 @@ class TlsClientServerTest {
 
         integrationTest(
             () -> TLSServer.listen(serverListener, NettyTransportSupport.newServerBootstrap().group(group),
-                tcpServerConfig, tlsServerConfig),
+                tcpServerConfig, new FixedSslHandlerFactory(tlsServerConfig)),
             () -> TLSClient.connect(clientListener, NettyTransportSupport.newBootstrap().group(group),
-                tcpClientConfig, tlsClientConfig)
+                tcpClientConfig, new FixedSslHandlerFactory(tlsClientConfig))
         );
     }
 
@@ -201,9 +201,9 @@ class TlsClientServerTest {
 
         integrationTest(
             () -> TLSServer.listen(serverListener, NettyTransportSupport.newServerBootstrap().group(group),
-                tcpServerConfig, channel -> serverContext.newHandler(channel.alloc())),
+                tcpServerConfig, new FixedSslHandlerFactory(serverContext)),
             () -> TLSClient.connect(clientListener, NettyTransportSupport.newBootstrap().group(group),
-                tcpClientConfig, channel -> clientContext.newHandler(channel.alloc()))
+                tcpClientConfig, new FixedSslHandlerFactory(clientContext))
         );
     }
 
@@ -255,14 +255,14 @@ class TlsClientServerTest {
 
         // start call-home client
         final var client = TLSClient.listen(clientListener, NettyTransportSupport.newServerBootstrap().group(group),
-            tcpServerConfig, channel -> clientContext.newHandler(channel.alloc())).get(2, TimeUnit.SECONDS);
+            tcpServerConfig, new FixedSslHandlerFactory(clientContext)).get(2, TimeUnit.SECONDS);
         try {
             // connect with call-home servers
             final var server1 = TLSServer.connect(serverListener, NettyTransportSupport.newBootstrap().group(group),
-                tcpClientConfig, channel -> serverContext.newHandler(channel.alloc())).get(2, TimeUnit.SECONDS);
+                tcpClientConfig, new FixedSslHandlerFactory(serverContext)).get(2, TimeUnit.SECONDS);
             final var server2 = TLSServer.connect(otherServerListener,
                 NettyTransportSupport.newBootstrap().group(group),
-                tcpClientConfig, channel -> serverContext.newHandler(channel.alloc())).get(2, TimeUnit.SECONDS);
+                tcpClientConfig, new FixedSslHandlerFactory(serverContext)).get(2, TimeUnit.SECONDS);
             try {
                 verify(serverListener, timeout(500))
                     .onTransportChannelEstablished(serverTransportChannelCaptor.capture());
@@ -272,9 +272,9 @@ class TlsClientServerTest {
                     .onTransportChannelEstablished(clientTransportChannelCaptor.capture());
                 // extract channels sorted by server address
                 var serverChannels = assertChannels(serverTransportChannelCaptor.getAllValues(), 2,
-                    Comparator.comparing((Channel channel) -> channel.localAddress().toString()));
+                    Comparator.comparing((final Channel channel) -> channel.localAddress().toString()));
                 var clientChannels = assertChannels(clientTransportChannelCaptor.getAllValues(), 2,
-                    Comparator.comparing((Channel channel) -> channel.remoteAddress().toString()));
+                    Comparator.comparing((final Channel channel) -> channel.remoteAddress().toString()));
                 for (int i = 0; i < 2; i++) {
                     // validate channels are connecting same sockets
                     assertEquals(serverChannels.get(i).remoteAddress(), clientChannels.get(i).localAddress());