131d07983b8251cf30364c7a5f6eb5ddd15edc5b
[netconf.git] / transport / transport-tls / src / main / java / org / opendaylight / netconf / transport / tls / TLSTransportStack.java
1 /*
2  * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others.  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 java.util.Objects.requireNonNull;
11 import static org.opendaylight.netconf.transport.tls.ConfigUtils.setAsymmetricKey;
12 import static org.opendaylight.netconf.transport.tls.ConfigUtils.setEndEntityCertificateWithKey;
13 import static org.opendaylight.netconf.transport.tls.ConfigUtils.setX509Certificates;
14 import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.buildKeyManagerFactory;
15 import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.buildTrustManagerFactory;
16 import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.newKeyStore;
17
18 import com.google.common.collect.ImmutableList;
19 import com.google.common.collect.ImmutableMap;
20 import io.netty.handler.ssl.SslContext;
21 import io.netty.handler.ssl.SslContextBuilder;
22 import java.security.KeyStore;
23 import java.util.List;
24 import javax.net.ssl.KeyManagerFactory;
25 import javax.net.ssl.SSLException;
26 import javax.net.ssl.TrustManagerFactory;
27 import org.eclipse.jdt.annotation.NonNull;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.opendaylight.netconf.transport.api.AbstractOverlayTransportStack;
30 import org.opendaylight.netconf.transport.api.TransportChannel;
31 import org.opendaylight.netconf.transport.api.TransportChannelListener;
32 import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
33 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.CipherSuiteAlgBase;
34 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsAes128CcmSha256;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsAes128GcmSha256;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsAes256GcmSha384;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsChacha20Poly1305Sha256;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes128Ccm;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes128GcmSha256;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes256Ccm;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithAes256GcmSha384;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDhePskWithChacha20Poly1305Sha256;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes128Ccm;
44 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes128GcmSha256;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes256Ccm;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithAes256GcmSha384;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsDheRsaWithChacha20Poly1305Sha256;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheEcdsaWithAes128GcmSha256;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheEcdsaWithAes256GcmSha384;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheEcdsaWithChacha20Poly1305Sha256;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithAes128CcmSha256;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithAes128GcmSha256;
53 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithAes256GcmSha384;
54 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdhePskWithChacha20Poly1305Sha256;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheRsaWithAes128GcmSha256;
56 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheRsaWithAes256GcmSha384;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.tls.cipher.suite.algs.rev220616.TlsEcdheRsaWithChacha20Poly1305Sha256;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228.InlineOrKeystoreAsymmetricKeyGrouping;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228.InlineOrKeystoreEndEntityCertWithKeyGrouping;
60 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.common.rev231228.HelloParamsGrouping;
61 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.common.rev231228.TlsVersionBase;
62 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228.InlineOrTruststoreCertsGrouping;
63 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228.InlineOrTruststorePublicKeysGrouping;
64
65 /**
66  * Base class for TLS TransportStacks.
67  */
68 public abstract sealed class TLSTransportStack extends AbstractOverlayTransportStack<TLSTransportChannel>
69         permits TLSClient, TLSServer {
70
71     private static final ImmutableMap<CipherSuiteAlgBase, String> CIPHER_SUITES =
72             ImmutableMap.<CipherSuiteAlgBase, String>builder()
73                     .put(TlsAes128CcmSha256.VALUE, "TLS_AES_128_CCM_SHA256")
74                     .put(TlsAes128GcmSha256.VALUE, "TLS_AES_128_GCM_SHA256")
75                     .put(TlsAes256GcmSha384.VALUE, "TLS_AES_256_GCM_SHA384")
76                     .put(TlsChacha20Poly1305Sha256.VALUE, "TLS_CHACHA20_POLY1305_SHA256")
77                     .put(TlsDhePskWithAes128Ccm.VALUE, "TLS_DHE_PSK_WITH_AES_128_CCM")
78                     .put(TlsDhePskWithAes128GcmSha256.VALUE, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256")
79                     .put(TlsDhePskWithAes256Ccm.VALUE, "TLS_DHE_PSK_WITH_AES_256_CCM")
80                     .put(TlsDhePskWithAes256GcmSha384.VALUE, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384")
81                     .put(TlsDhePskWithChacha20Poly1305Sha256.VALUE, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256")
82                     .put(TlsDheRsaWithAes128Ccm.VALUE, "TLS_DHE_RSA_WITH_AES_128_CCM")
83                     .put(TlsDheRsaWithAes128GcmSha256.VALUE, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256")
84                     .put(TlsDheRsaWithAes256Ccm.VALUE, "TLS_DHE_RSA_WITH_AES_256_CCM")
85                     .put(TlsDheRsaWithAes256GcmSha384.VALUE, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384")
86                     .put(TlsDheRsaWithChacha20Poly1305Sha256.VALUE, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
87                     .put(TlsEcdheEcdsaWithAes128GcmSha256.VALUE, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
88                     .put(TlsEcdheEcdsaWithAes256GcmSha384.VALUE, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
89                     .put(TlsEcdheEcdsaWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
90                     .put(TlsEcdhePskWithAes128CcmSha256.VALUE, "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256")
91                     .put(TlsEcdhePskWithAes128GcmSha256.VALUE, "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256")
92                     .put(TlsEcdhePskWithAes256GcmSha384.VALUE, "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384")
93                     .put(TlsEcdhePskWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256")
94                     .put(TlsEcdheRsaWithAes128GcmSha256.VALUE, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
95                     .put(TlsEcdheRsaWithAes256GcmSha384.VALUE, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
96                     .put(TlsEcdheRsaWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
97                     .build();
98
99     private final SslHandlerFactory factory;
100
101     TLSTransportStack(final TransportChannelListener listener, final SslContext sslContext) {
102         this(listener, channel -> sslContext.newHandler(channel.alloc()));
103     }
104
105     TLSTransportStack(final TransportChannelListener listener, final SslHandlerFactory factory) {
106         super(listener);
107         this.factory = requireNonNull(factory);
108     }
109
110     @Override
111     protected final void onUnderlayChannelEstablished(final TransportChannel underlayChannel) {
112         final var channel = underlayChannel.channel();
113         final var sslHandler = factory.createSslHandler(channel);
114
115         channel.pipeline().addLast(sslHandler);
116         sslHandler.handshakeFuture().addListener(future -> {
117             final var cause = future.cause();
118             if (cause != null) {
119                 notifyTransportChannelFailed(cause);
120                 channel.close();
121             } else {
122                 addTransportChannel(new TLSTransportChannel(underlayChannel));
123             }
124         });
125     }
126
127     static KeyManagerFactory newKeyManager(
128             final @NonNull InlineOrKeystoreEndEntityCertWithKeyGrouping endEntityCert
129     ) throws UnsupportedConfigurationException {
130         final var keyStore = newKeyStore();
131         setEndEntityCertificateWithKey(keyStore, endEntityCert);
132         return buildKeyManagerFactory(keyStore);
133     }
134
135     static KeyManagerFactory newKeyManager(final @NonNull InlineOrKeystoreAsymmetricKeyGrouping rawPrivateKey)
136             throws UnsupportedConfigurationException {
137         final var keyStore = newKeyStore();
138         setAsymmetricKey(keyStore, rawPrivateKey);
139         return buildKeyManagerFactory(keyStore);
140     }
141
142     // FIXME: should be TrustManagerBuilder
143     protected static @Nullable TrustManagerFactory newTrustManager(
144             final @Nullable InlineOrTruststoreCertsGrouping caCerts,
145             final @Nullable InlineOrTruststoreCertsGrouping eeCerts,
146             final @Nullable InlineOrTruststorePublicKeysGrouping publicKeys) throws UnsupportedConfigurationException {
147
148         if (publicKeys != null) {
149             // FIXME: implement this and advertize server-auth-raw-public-key from IetfTlsClientFeatureProvider
150             throw new UnsupportedConfigurationException("Public key authentication not implemented");
151         }
152         if (caCerts != null || eeCerts != null) {
153             // X.509 certificates
154             final KeyStore keyStore = newKeyStore();
155             setX509Certificates(keyStore, caCerts, eeCerts);
156             return buildTrustManagerFactory(keyStore);
157         }
158         return null;
159     }
160
161     static SslContext buildSslContext(final SslContextBuilder builder, final HelloParamsGrouping helloParams)
162             throws UnsupportedConfigurationException {
163         if (helloParams != null) {
164             final var tlsVersions = helloParams.getTlsVersions();
165             if (tlsVersions != null) {
166                 final var versions = tlsVersions.getTlsVersion();
167                 if (versions != null && !versions.isEmpty()) {
168                     builder.protocols(createTlsStrings(versions));
169                 }
170             }
171             final var cipherSuites = helloParams.getCipherSuites();
172             if (cipherSuites != null) {
173                 final var ciphers = cipherSuites.getCipherSuite();
174                 if (ciphers != null && !ciphers.isEmpty()) {
175                     builder.ciphers(createCipherStrings(ciphers));
176                 }
177             }
178         }
179         try {
180             return builder.build();
181         } catch (SSLException e) {
182             throw new UnsupportedConfigurationException("Cannot instantiate TLS context", e);
183         }
184     }
185
186     private static String[] createTlsStrings(final List<TlsVersionBase> versions)
187             throws UnsupportedConfigurationException {
188         // FIXME: cache these
189         final var ret = new String[versions.size()];
190         int idx = 0;
191         for (var version : versions) {
192             final var str = IetfTlsCommonFeatureProvider.algorithmNameOf(version);
193             if (str == null) {
194                 throw new UnsupportedConfigurationException("Unhandled TLS version " + version);
195             }
196             ret[idx++] = str;
197         }
198         return ret;
199     }
200
201     private static ImmutableList<String> createCipherStrings(final List<CipherSuiteAlgBase> ciphers)
202             throws UnsupportedConfigurationException {
203         // FIXME: cache these
204         final var builder = ImmutableList.<String>builderWithExpectedSize(ciphers.size());
205         for (var cipher : ciphers) {
206             final var str = CIPHER_SUITES.get(cipher);
207             if (str == null) {
208                 throw new UnsupportedConfigurationException("Unhandled cipher suite " + cipher);
209             }
210             builder.add(str);
211         }
212         return builder.build();
213     }
214 }