2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.netconf.transport.tls;
10 import static org.opendaylight.netconf.transport.tls.ConfigUtils.setAsymmetricKey;
11 import static org.opendaylight.netconf.transport.tls.ConfigUtils.setEndEntityCertificateWithKey;
12 import static org.opendaylight.netconf.transport.tls.ConfigUtils.setX509Certificates;
13 import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.buildKeyManagerFactory;
14 import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.buildTrustManagerFactory;
15 import static org.opendaylight.netconf.transport.tls.KeyStoreUtils.newKeyStore;
17 import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.ImmutableMap;
19 import io.netty.channel.Channel;
20 import io.netty.handler.ssl.ClientAuth;
21 import io.netty.handler.ssl.SslContext;
22 import io.netty.handler.ssl.SslContextBuilder;
23 import io.netty.handler.ssl.SslHandler;
24 import java.net.SocketAddress;
25 import java.security.KeyStore;
26 import java.util.List;
27 import javax.net.ssl.KeyManagerFactory;
28 import javax.net.ssl.SSLException;
29 import javax.net.ssl.TrustManagerFactory;
30 import org.eclipse.jdt.annotation.NonNull;
31 import org.eclipse.jdt.annotation.Nullable;
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.client.rev231228.TlsClientGrouping;
61 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev231228.tls.client.grouping.client.identity.auth.type.Certificate;
62 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.client.rev231228.tls.client.grouping.client.identity.auth.type.RawPublicKey;
63 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.common.rev231228.HelloParamsGrouping;
64 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.common.rev231228.TlsVersionBase;
65 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev231228.TlsServerGrouping;
66 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev231228.tls.server.grouping.server.identity.auth.type.RawPrivateKey;
67 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228.InlineOrTruststoreCertsGrouping;
68 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228.InlineOrTruststorePublicKeysGrouping;
71 * Extension interface for external service integration with TLS transport. Used to build {@link TLSClient} and
72 * {@link TLSServer} instances.
74 public abstract class SslHandlerFactory {
75 private static final ImmutableMap<CipherSuiteAlgBase, String> CIPHER_SUITES =
76 ImmutableMap.<CipherSuiteAlgBase, String>builder()
77 .put(TlsAes128CcmSha256.VALUE, "TLS_AES_128_CCM_SHA256")
78 .put(TlsAes128GcmSha256.VALUE, "TLS_AES_128_GCM_SHA256")
79 .put(TlsAes256GcmSha384.VALUE, "TLS_AES_256_GCM_SHA384")
80 .put(TlsChacha20Poly1305Sha256.VALUE, "TLS_CHACHA20_POLY1305_SHA256")
81 .put(TlsDhePskWithAes128Ccm.VALUE, "TLS_DHE_PSK_WITH_AES_128_CCM")
82 .put(TlsDhePskWithAes128GcmSha256.VALUE, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256")
83 .put(TlsDhePskWithAes256Ccm.VALUE, "TLS_DHE_PSK_WITH_AES_256_CCM")
84 .put(TlsDhePskWithAes256GcmSha384.VALUE, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384")
85 .put(TlsDhePskWithChacha20Poly1305Sha256.VALUE, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256")
86 .put(TlsDheRsaWithAes128Ccm.VALUE, "TLS_DHE_RSA_WITH_AES_128_CCM")
87 .put(TlsDheRsaWithAes128GcmSha256.VALUE, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256")
88 .put(TlsDheRsaWithAes256Ccm.VALUE, "TLS_DHE_RSA_WITH_AES_256_CCM")
89 .put(TlsDheRsaWithAes256GcmSha384.VALUE, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384")
90 .put(TlsDheRsaWithChacha20Poly1305Sha256.VALUE, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
91 .put(TlsEcdheEcdsaWithAes128GcmSha256.VALUE, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
92 .put(TlsEcdheEcdsaWithAes256GcmSha384.VALUE, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
93 .put(TlsEcdheEcdsaWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
94 .put(TlsEcdhePskWithAes128CcmSha256.VALUE, "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256")
95 .put(TlsEcdhePskWithAes128GcmSha256.VALUE, "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256")
96 .put(TlsEcdhePskWithAes256GcmSha384.VALUE, "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384")
97 .put(TlsEcdhePskWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256")
98 .put(TlsEcdheRsaWithAes128GcmSha256.VALUE, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
99 .put(TlsEcdheRsaWithAes256GcmSha384.VALUE, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
100 .put(TlsEcdheRsaWithChacha20Poly1305Sha256.VALUE, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
104 * Builds {@link SslHandler} instance for given {@link Channel}.
106 * @param channel channel
107 * @return A {@link SslHandler}, or {@code null} if the connection should be rejected
109 public final @Nullable SslHandler createSslHandler(final @NonNull Channel channel) {
110 final var sslContext = getSslContext(channel.remoteAddress());
111 return sslContext == null ? null : sslContext.newHandler(channel.alloc());
114 protected abstract @Nullable SslContext getSslContext(SocketAddress remoteAddress);
116 protected static final @NonNull SslContext createSslContext(final @NonNull TlsClientGrouping clientParams)
117 throws UnsupportedConfigurationException {
118 final var builder = SslContextBuilder.forClient();
120 final var clientIdentity = clientParams.getClientIdentity();
121 if (clientIdentity != null) {
122 final var authType = clientIdentity.getAuthType();
123 if (authType instanceof Certificate cert) {
124 // if-feature "client-ident-x509-cert"
125 final var certificate = cert.getCertificate();
126 if (certificate == null) {
127 throw new UnsupportedConfigurationException("Missing certificate in " + cert);
129 builder.keyManager(newKeyManager(certificate));
130 } else if (authType instanceof RawPublicKey rawKey) {
131 // if-feature "client-ident-raw-public-key"
132 final var rawPrivateKey = rawKey.getRawPrivateKey();
133 if (rawPrivateKey == null) {
134 throw new UnsupportedConfigurationException("Missing key in " + rawKey);
136 builder.keyManager(newKeyManager(rawPrivateKey));
137 } else if (authType != null) {
138 throw new UnsupportedConfigurationException("Unsupported client authentication type " + authType);
142 final var serverAuth = clientParams.getServerAuthentication();
143 if (serverAuth != null) {
144 // CA && EE X509 certificates : if-feature "server-ident-x509-cert"
145 // Raw public key : if-feature "server-ident-raw-public-key"
146 final var trustManager = newTrustManager(serverAuth.getCaCerts(), serverAuth.getEeCerts(),
147 serverAuth.getRawPublicKeys());
148 if (trustManager == null) {
149 throw new UnsupportedOperationException("No server authentication methods in " + serverAuth);
151 builder.trustManager(trustManager);
154 return buildSslContext(builder, clientParams.getHelloParams());
157 protected static final @NonNull SslContext createSslContext(final @NonNull TlsServerGrouping serverParams)
158 throws UnsupportedConfigurationException {
159 final var serverIdentity = serverParams.getServerIdentity();
160 if (serverIdentity == null) {
161 throw new UnsupportedConfigurationException("Missing server identity");
163 final SslContextBuilder builder;
164 final var authType = serverIdentity.getAuthType();
166 instanceof org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tls.server.rev231228
167 .tls.server.grouping.server.identity.auth.type.Certificate cert) {
168 // if-feature "server-ident-x509-cert"
169 final var certificate = cert.getCertificate();
170 if (certificate == null) {
171 throw new UnsupportedConfigurationException("Missing certificate in " + cert);
173 builder = SslContextBuilder.forServer(newKeyManager(certificate));
174 } else if (authType instanceof RawPrivateKey rawKey) {
175 // if-feature "server-ident-raw-public-key"
176 final var rawPrivateKey = rawKey.getRawPrivateKey();
177 if (rawPrivateKey == null) {
178 throw new UnsupportedConfigurationException("Missing key in " + rawKey);
180 builder = SslContextBuilder.forServer(newKeyManager(rawPrivateKey));
181 } else if (authType != null) {
182 throw new UnsupportedConfigurationException("Unsupported server authentication type " + authType);
184 throw new UnsupportedConfigurationException("Missing server authentication type");
187 final var clientAuth = serverParams.getClientAuthentication();
188 if (clientAuth != null) {
189 // CA && EE Certs : if-feature "client-ident-x509-cert"
190 // Raw public keys : if-feature "client-ident-raw-public-key"
191 final var trustManager = newTrustManager(clientAuth.getCaCerts(), clientAuth.getEeCerts(),
192 clientAuth.getRawPublicKeys());
193 if (trustManager == null) {
194 throw new UnsupportedOperationException("No client authentication methods in " + clientAuth);
196 builder.clientAuth(ClientAuth.REQUIRE).trustManager(trustManager);
198 builder.clientAuth(ClientAuth.NONE);
201 return buildSslContext(builder, serverParams.getHelloParams());
204 // FIXME: should be TrustManagerBuilder
205 private static @Nullable TrustManagerFactory newTrustManager(
206 final @Nullable InlineOrTruststoreCertsGrouping caCerts,
207 final @Nullable InlineOrTruststoreCertsGrouping eeCerts,
208 final @Nullable InlineOrTruststorePublicKeysGrouping publicKeys) throws UnsupportedConfigurationException {
210 if (publicKeys != null) {
211 // FIXME: implement this and advertize server-auth-raw-public-key from IetfTlsClientFeatureProvider
212 throw new UnsupportedConfigurationException("Public key authentication not implemented");
214 if (caCerts != null || eeCerts != null) {
215 // X.509 certificates
216 final KeyStore keyStore = newKeyStore();
217 setX509Certificates(keyStore, caCerts, eeCerts);
218 return buildTrustManagerFactory(keyStore);
223 private static KeyManagerFactory newKeyManager(
224 final @NonNull InlineOrKeystoreEndEntityCertWithKeyGrouping endEntityCert)
225 throws UnsupportedConfigurationException {
226 final var keyStore = newKeyStore();
227 setEndEntityCertificateWithKey(keyStore, endEntityCert);
228 return buildKeyManagerFactory(keyStore);
231 private static KeyManagerFactory newKeyManager(final @NonNull InlineOrKeystoreAsymmetricKeyGrouping rawPrivateKey)
232 throws UnsupportedConfigurationException {
233 final var keyStore = newKeyStore();
234 setAsymmetricKey(keyStore, rawPrivateKey);
235 return buildKeyManagerFactory(keyStore);
238 private static @NonNull SslContext buildSslContext(final SslContextBuilder builder,
239 final HelloParamsGrouping helloParams) throws UnsupportedConfigurationException {
240 if (helloParams != null) {
241 final var tlsVersions = helloParams.getTlsVersions();
242 if (tlsVersions != null) {
243 final var versions = tlsVersions.getTlsVersion();
244 if (versions != null && !versions.isEmpty()) {
245 builder.protocols(createTlsStrings(versions));
248 final var cipherSuites = helloParams.getCipherSuites();
249 if (cipherSuites != null) {
250 final var ciphers = cipherSuites.getCipherSuite();
251 if (ciphers != null && !ciphers.isEmpty()) {
252 builder.ciphers(createCipherStrings(ciphers));
257 return builder.build();
258 } catch (SSLException e) {
259 throw new UnsupportedConfigurationException("Cannot instantiate TLS context", e);
263 private static String[] createTlsStrings(final List<TlsVersionBase> versions)
264 throws UnsupportedConfigurationException {
265 // FIXME: cache these
266 final var ret = new String[versions.size()];
268 for (var version : versions) {
269 final var str = IetfTlsCommonFeatureProvider.algorithmNameOf(version);
271 throw new UnsupportedConfigurationException("Unhandled TLS version " + version);
278 private static ImmutableList<String> createCipherStrings(final List<CipherSuiteAlgBase> ciphers)
279 throws UnsupportedConfigurationException {
280 // FIXME: cache these
281 final var builder = ImmutableList.<String>builderWithExpectedSize(ciphers.size());
282 for (var cipher : ciphers) {
283 final var str = CIPHER_SUITES.get(cipher);
285 throw new UnsupportedConfigurationException("Unhandled cipher suite " + cipher);
289 return builder.build();