External service integration support for TLS transport
[netconf.git] / transport / transport-tls / src / test / java / org / opendaylight / netconf / transport / tls / TlsClientServerTest.java
index 87b752b87a995fa5c24280bc85a4ff68f42fb7cd..d04efaf6c22b1a4acdfa3d5fb2d278986719343c 100644 (file)
@@ -14,25 +14,35 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+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 static org.opendaylight.netconf.transport.tls.KeyUtils.EC_ALGORITHM;
 import static org.opendaylight.netconf.transport.tls.KeyUtils.RSA_ALGORITHM;
 import static org.opendaylight.netconf.transport.tls.TestUtils.buildEndEntityCertWithKeyGrouping;
-import static org.opendaylight.netconf.transport.tls.TestUtils.buildLocalOrTruststore;
+import static org.opendaylight.netconf.transport.tls.TestUtils.buildInlineOrTruststore;
 import static org.opendaylight.netconf.transport.tls.TestUtils.generateX509CertData;
 import static org.opendaylight.netconf.transport.tls.TestUtils.isRSA;
 
+import com.google.common.util.concurrent.ListenableFuture;
 import io.netty.channel.Channel;
 import io.netty.channel.EventLoopGroup;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContextBuilder;
 import io.netty.handler.ssl.SslHandler;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.ServerSocket;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
@@ -43,20 +53,20 @@ import org.mockito.junit.jupiter.MockitoExtension;
 import org.opendaylight.netconf.transport.api.TransportChannel;
 import org.opendaylight.netconf.transport.api.TransportChannelListener;
 import org.opendaylight.netconf.transport.tcp.NettyTransportSupport;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev221212.EcPrivateKeyFormat;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev221212.RsaPrivateKeyFormat;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev221212.SubjectPublicKeyInfoFormat;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.EcPrivateKeyFormat;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.RsaPrivateKeyFormat;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.SubjectPublicKeyInfoFormat;
 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;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.client.rev221212.TcpClientGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.server.rev221212.TcpServerGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev221212.TlsClientGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev221212.tls.client.grouping.ClientIdentityBuilder;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev221212.tls.client.grouping.ServerAuthenticationBuilder;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev221212.TlsServerGrouping;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev221212.tls.server.grouping.ClientAuthenticationBuilder;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev221212.tls.server.grouping.ServerIdentityBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.client.rev230417.TcpClientGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.server.rev230417.TcpServerGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417.TlsClientGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417.tls.client.grouping.ClientIdentityBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417.tls.client.grouping.ServerAuthenticationBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417.TlsServerGrouping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417.tls.server.grouping.ClientAuthenticationBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417.tls.server.grouping.ServerIdentityBuilder;
 import org.opendaylight.yangtools.yang.common.Uint16;
 
 @ExtendWith(MockitoExtension.class)
@@ -99,7 +109,7 @@ class TlsClientServerTest {
 
         // create temp socket to get available port for test
         socket = new ServerSocket(0);
-        final var localAddress = IetfInetUtil.INSTANCE.ipAddressFor(InetAddress.getLoopbackAddress());
+        final var localAddress = IetfInetUtil.ipAddressFor(InetAddress.getLoopbackAddress());
         final var localPort = new PortNumber(Uint16.valueOf(socket.getLocalPort()));
         socket.close();
 
@@ -121,55 +131,92 @@ class TlsClientServerTest {
         final var data = generateX509CertData(algorithm);
 
         // common config parts
-        var localOrKeystore = buildEndEntityCertWithKeyGrouping(
+        var inlineOrKeystore = buildEndEntityCertWithKeyGrouping(
                 SubjectPublicKeyInfoFormat.VALUE, data.publicKey(),
                 isRSA(algorithm) ? RsaPrivateKeyFormat.VALUE : EcPrivateKeyFormat.VALUE,
-                data.privateKey(), data.certBytes()).getLocalOrKeystore();
-        var localOrTrustStore = buildLocalOrTruststore(Map.of("cert", data.certBytes()));
+                data.privateKey(), data.certBytes()).getInlineOrKeystore();
+        var inlineOrTrustStore = buildInlineOrTruststore(Map.of("cert", data.certBytes()));
 
         // client config
         final var clientIdentity = new ClientIdentityBuilder()
-                .setAuthType(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev221212
-                        .tls.client.grouping.client.identity.auth.type.CertificateBuilder()
-                        .setCertificate(
-                                new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev221212
-                                        .tls.client.grouping.client.identity.auth.type.certificate.CertificateBuilder()
-                                        .setLocalOrKeystore(localOrKeystore)
-                                        .build()).build()).build();
-        final var serverAuth = new ServerAuthenticationBuilder().setCaCerts(
-                new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev221212
-                        .tls.client.grouping.server.authentication.CaCertsBuilder()
-                        .setLocalOrTruststore(localOrTrustStore).build()).build();
+            .setAuthType(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417
+                .tls.client.grouping.client.identity.auth.type.CertificateBuilder()
+                .setCertificate(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417
+                    .tls.client.grouping.client.identity.auth.type.certificate.CertificateBuilder()
+                    .setInlineOrKeystore(inlineOrKeystore)
+                    .build())
+                .build())
+            .build();
+        final var serverAuth = new ServerAuthenticationBuilder()
+            .setCaCerts(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417
+                .tls.client.grouping.server.authentication.CaCertsBuilder()
+                .setInlineOrTruststore(inlineOrTrustStore)
+                .build())
+            .build();
         when(tlsClientConfig.getClientIdentity()).thenReturn(clientIdentity);
         when(tlsClientConfig.getServerAuthentication()).thenReturn(serverAuth);
 
         // server config
         final var serverIdentity = new ServerIdentityBuilder()
-                .setAuthType(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev221212
-                        .tls.server.grouping.server.identity.auth.type.CertificateBuilder()
-                        .setCertificate(
-                                new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev221212
-                                        .tls.server.grouping.server.identity.auth.type.certificate.CertificateBuilder()
-                                        .setLocalOrKeystore(localOrKeystore)
-                                        .build()).build()).build();
-        final var clientAuth = new ClientAuthenticationBuilder().setCaCerts(
-                new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev221212
-                        .tls.server.grouping.client.authentication.CaCertsBuilder()
-                        .setLocalOrTruststore(localOrTrustStore).build()).build();
+            .setAuthType(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417
+                .tls.server.grouping.server.identity.auth.type.CertificateBuilder()
+                .setCertificate(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417
+                    .tls.server.grouping.server.identity.auth.type.certificate.CertificateBuilder()
+                    .setInlineOrKeystore(inlineOrKeystore)
+                    .build())
+                .build())
+            .build();
+        final var clientAuth = new ClientAuthenticationBuilder()
+            .setCaCerts(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417
+                .tls.server.grouping.client.authentication.CaCertsBuilder()
+                .setInlineOrTruststore(inlineOrTrustStore)
+                .build())
+            .build();
         when(tlsServerConfig.getServerIdentity()).thenReturn(serverIdentity);
         when(tlsServerConfig.getClientAuthentication()).thenReturn(clientAuth);
 
-        integrationTest();
+        integrationTest(
+            TLSServer.listen(serverListener, NettyTransportSupport.newServerBootstrap().group(group),
+                tcpServerConfig, tlsServerConfig),
+            TLSClient.connect(clientListener, NettyTransportSupport.newBootstrap().group(group),
+                tcpClientConfig, tlsClientConfig)
+        );
     }
 
-    private void integrationTest() throws Exception {
+    @Test
+    @DisplayName("External SslHandlerFactory integration")
+    void sslHandlerFactory() throws Exception {
+
+        final var serverKs = buildKeystoreWithGeneratedCert(RSA_ALGORITHM);
+        final var clientKs = buildKeystoreWithGeneratedCert(EC_ALGORITHM);
+        final var serverContext = SslContextBuilder.forServer(buildKeyManagerFactory(serverKs))
+            .clientAuth(ClientAuth.REQUIRE).trustManager(buildTrustManagerFactory(clientKs)).build();
+        final var clientContext = SslContextBuilder.forClient().keyManager(buildKeyManagerFactory(clientKs))
+            .trustManager(buildTrustManagerFactory(serverKs)).build();
+
+        integrationTest(
+            TLSServer.listen(serverListener, NettyTransportSupport.newServerBootstrap().group(group),
+                tcpServerConfig, channel -> serverContext.newHandler(channel.alloc())),
+            TLSClient.connect(clientListener, NettyTransportSupport.newBootstrap().group(group),
+                tcpClientConfig, channel -> clientContext.newHandler(channel.alloc()))
+        );
+    }
+
+    private static KeyStore buildKeystoreWithGeneratedCert(final String algorithm) throws Exception {
+        final var data = generateX509CertData(algorithm);
+        final var ret = newKeyStore();
+        ret.setCertificateEntry("certificate", data.certificate());
+        ret.setKeyEntry("key", data.keyPair().getPrivate(), new char[0], new Certificate[]{data.certificate()});
+        return ret;
+    }
+
+    private void integrationTest(final ListenableFuture<TLSServer> serverFuture,
+            final ListenableFuture<TLSClient> clientFuture) throws Exception {
         // start server
-        final var server = TLSServer.listen(serverListener, NettyTransportSupport.newServerBootstrap().group(group),
-                tcpServerConfig, tlsServerConfig).get(2, TimeUnit.SECONDS);
+        final var server = serverFuture.get(2, TimeUnit.SECONDS);
         try {
             // connect with client
-            final var client = TLSClient.connect(clientListener, NettyTransportSupport.newBootstrap().group(group),
-                    tcpClientConfig, tlsClientConfig).get(2, TimeUnit.SECONDS);
+            final var client = clientFuture.get(2, TimeUnit.SECONDS);
             try {
                 verify(serverListener, timeout(500))
                         .onTransportChannelEstablished(serverTransportChannelCaptor.capture());
@@ -190,7 +237,7 @@ class TlsClientServerTest {
         }
     }
 
-    private static Channel assertChannel(List<TransportChannel> transportChannels) {
+    private static Channel assertChannel(final List<TransportChannel> transportChannels) {
         assertNotNull(transportChannels);
         assertEquals(1, transportChannels.size());
         final var channel = assertInstanceOf(TLSTransportChannel.class, transportChannels.get(0)).channel();