Update draft-ietf-client-server models
[netconf.git] / transport / transport-tls / src / test / java / org / opendaylight / netconf / transport / tls / TlsClientServerTest.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech s.r.o. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.transport.tls;
9
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
12 import static org.junit.jupiter.api.Assertions.assertNotNull;
13 import static org.junit.jupiter.api.Assertions.assertTrue;
14 import static org.mockito.Mockito.timeout;
15 import static org.mockito.Mockito.verify;
16 import static org.mockito.Mockito.when;
17 import static org.opendaylight.netconf.transport.tls.KeyUtils.EC_ALGORITHM;
18 import static org.opendaylight.netconf.transport.tls.KeyUtils.RSA_ALGORITHM;
19 import static org.opendaylight.netconf.transport.tls.TestUtils.buildEndEntityCertWithKeyGrouping;
20 import static org.opendaylight.netconf.transport.tls.TestUtils.buildInlineOrTruststore;
21 import static org.opendaylight.netconf.transport.tls.TestUtils.generateX509CertData;
22 import static org.opendaylight.netconf.transport.tls.TestUtils.isRSA;
23
24 import io.netty.channel.Channel;
25 import io.netty.channel.EventLoopGroup;
26 import io.netty.handler.ssl.SslHandler;
27 import java.io.IOException;
28 import java.net.InetAddress;
29 import java.net.ServerSocket;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.concurrent.TimeUnit;
33 import org.junit.jupiter.api.AfterAll;
34 import org.junit.jupiter.api.BeforeAll;
35 import org.junit.jupiter.api.BeforeEach;
36 import org.junit.jupiter.api.extension.ExtendWith;
37 import org.junit.jupiter.params.ParameterizedTest;
38 import org.junit.jupiter.params.provider.ValueSource;
39 import org.mockito.ArgumentCaptor;
40 import org.mockito.Captor;
41 import org.mockito.Mock;
42 import org.mockito.junit.jupiter.MockitoExtension;
43 import org.opendaylight.netconf.transport.api.TransportChannel;
44 import org.opendaylight.netconf.transport.api.TransportChannelListener;
45 import org.opendaylight.netconf.transport.tcp.NettyTransportSupport;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.EcPrivateKeyFormat;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.RsaPrivateKeyFormat;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.SubjectPublicKeyInfoFormat;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.client.rev230417.TcpClientGrouping;
53 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.server.rev230417.TcpServerGrouping;
54 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417.TlsClientGrouping;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417.tls.client.grouping.ClientIdentityBuilder;
56 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417.tls.client.grouping.ServerAuthenticationBuilder;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417.TlsServerGrouping;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417.tls.server.grouping.ClientAuthenticationBuilder;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417.tls.server.grouping.ServerIdentityBuilder;
60 import org.opendaylight.yangtools.yang.common.Uint16;
61
62 @ExtendWith(MockitoExtension.class)
63 class TlsClientServerTest {
64
65     @Mock
66     private TcpClientGrouping tcpClientConfig;
67     @Mock
68     private TlsClientGrouping tlsClientConfig;
69     @Mock
70     private TransportChannelListener clientListener;
71     @Mock
72     private TcpServerGrouping tcpServerConfig;
73     @Mock
74     private TlsServerGrouping tlsServerConfig;
75     @Mock
76     private TransportChannelListener serverListener;
77
78     @Captor
79     ArgumentCaptor<TransportChannel> clientTransportChannelCaptor;
80     @Captor
81     ArgumentCaptor<TransportChannel> serverTransportChannelCaptor;
82
83     private static EventLoopGroup group;
84     private ServerSocket socket;
85
86     @BeforeAll
87     static void beforeAll() {
88         group = NettyTransportSupport.newEventLoopGroup("IntegrationTest");
89     }
90
91     @AfterAll
92     static void afterAll() {
93         group.shutdownGracefully();
94         group = null;
95     }
96
97     @BeforeEach
98     void beforeEach() throws IOException {
99
100         // create temp socket to get available port for test
101         socket = new ServerSocket(0);
102         final var localAddress = IetfInetUtil.ipAddressFor(InetAddress.getLoopbackAddress());
103         final var localPort = new PortNumber(Uint16.valueOf(socket.getLocalPort()));
104         socket.close();
105
106         when(tcpServerConfig.getLocalAddress()).thenReturn(localAddress);
107         when(tcpServerConfig.requireLocalAddress()).thenCallRealMethod();
108         when(tcpServerConfig.getLocalPort()).thenReturn(localPort);
109         when(tcpServerConfig.requireLocalPort()).thenCallRealMethod();
110
111         when(tcpClientConfig.getRemoteAddress()).thenReturn(new Host(localAddress));
112         when(tcpClientConfig.requireRemoteAddress()).thenCallRealMethod();
113         when(tcpClientConfig.getRemotePort()).thenReturn(localPort);
114         when(tcpClientConfig.requireRemotePort()).thenCallRealMethod();
115     }
116
117     @ParameterizedTest(name = "TLS using X.509 certificates: {0}")
118     @ValueSource(strings = {RSA_ALGORITHM, EC_ALGORITHM})
119     void itWithCertificateConfig(final String algorithm) throws Exception {
120
121         final var data = generateX509CertData(algorithm);
122
123         // common config parts
124         var inlineOrKeystore = buildEndEntityCertWithKeyGrouping(
125                 SubjectPublicKeyInfoFormat.VALUE, data.publicKey(),
126                 isRSA(algorithm) ? RsaPrivateKeyFormat.VALUE : EcPrivateKeyFormat.VALUE,
127                 data.privateKey(), data.certBytes()).getInlineOrKeystore();
128         var inlineOrTrustStore = buildInlineOrTruststore(Map.of("cert", data.certBytes()));
129
130         // client config
131         final var clientIdentity = new ClientIdentityBuilder()
132             .setAuthType(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417
133                 .tls.client.grouping.client.identity.auth.type.CertificateBuilder()
134                 .setCertificate(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417
135                     .tls.client.grouping.client.identity.auth.type.certificate.CertificateBuilder()
136                     .setInlineOrKeystore(inlineOrKeystore)
137                     .build())
138                 .build())
139             .build();
140         final var serverAuth = new ServerAuthenticationBuilder()
141             .setCaCerts(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev230417
142                 .tls.client.grouping.server.authentication.CaCertsBuilder()
143                 .setInlineOrTruststore(inlineOrTrustStore)
144                 .build())
145             .build();
146         when(tlsClientConfig.getClientIdentity()).thenReturn(clientIdentity);
147         when(tlsClientConfig.getServerAuthentication()).thenReturn(serverAuth);
148
149         // server config
150         final var serverIdentity = new ServerIdentityBuilder()
151             .setAuthType(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417
152                 .tls.server.grouping.server.identity.auth.type.CertificateBuilder()
153                 .setCertificate(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417
154                     .tls.server.grouping.server.identity.auth.type.certificate.CertificateBuilder()
155                     .setInlineOrKeystore(inlineOrKeystore)
156                     .build())
157                 .build())
158             .build();
159         final var clientAuth = new ClientAuthenticationBuilder()
160             .setCaCerts(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev230417
161                 .tls.server.grouping.client.authentication.CaCertsBuilder()
162                 .setInlineOrTruststore(inlineOrTrustStore)
163                 .build())
164             .build();
165         when(tlsServerConfig.getServerIdentity()).thenReturn(serverIdentity);
166         when(tlsServerConfig.getClientAuthentication()).thenReturn(clientAuth);
167
168         integrationTest();
169     }
170
171     private void integrationTest() throws Exception {
172         // start server
173         final var server = TLSServer.listen(serverListener, NettyTransportSupport.newServerBootstrap().group(group),
174                 tcpServerConfig, tlsServerConfig).get(2, TimeUnit.SECONDS);
175         try {
176             // connect with client
177             final var client = TLSClient.connect(clientListener, NettyTransportSupport.newBootstrap().group(group),
178                     tcpClientConfig, tlsClientConfig).get(2, TimeUnit.SECONDS);
179             try {
180                 verify(serverListener, timeout(500))
181                         .onTransportChannelEstablished(serverTransportChannelCaptor.capture());
182                 verify(clientListener, timeout(500))
183                         .onTransportChannelEstablished(clientTransportChannelCaptor.capture());
184                 // validate channels are in expected state
185                 var serverChannel = assertChannel(serverTransportChannelCaptor.getAllValues());
186                 var clientChannel = assertChannel(clientTransportChannelCaptor.getAllValues());
187                 // validate channels are connecting same sockets
188                 assertEquals(serverChannel.remoteAddress(), clientChannel.localAddress());
189                 assertEquals(serverChannel.localAddress(), clientChannel.remoteAddress());
190
191             } finally {
192                 client.shutdown().get(2, TimeUnit.SECONDS);
193             }
194         } finally {
195             server.shutdown().get(2, TimeUnit.SECONDS);
196         }
197     }
198
199     private static Channel assertChannel(final List<TransportChannel> transportChannels) {
200         assertNotNull(transportChannels);
201         assertEquals(1, transportChannels.size());
202         final var channel = assertInstanceOf(TLSTransportChannel.class, transportChannels.get(0)).channel();
203         assertNotNull(channel);
204         assertTrue(channel.isOpen()); // connection is open
205         assertNotNull(channel.pipeline().get(SslHandler.class)); //  has an SSL handler within a pipeline
206         return channel;
207     }
208 }