2 * Copyright (c) 2023 PANTHEON.tech s.r.o. 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.ssh;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.Lists;
15 import com.google.common.collect.Maps;
16 import io.netty.channel.ChannelHandlerContext;
17 import java.io.IOException;
18 import java.util.List;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.netconf.shaded.sshd.client.ClientBuilder;
22 import org.opendaylight.netconf.shaded.sshd.common.BaseBuilder;
23 import org.opendaylight.netconf.shaded.sshd.common.NamedFactory;
24 import org.opendaylight.netconf.shaded.sshd.common.cipher.BuiltinCiphers;
25 import org.opendaylight.netconf.shaded.sshd.common.cipher.Cipher;
26 import org.opendaylight.netconf.shaded.sshd.common.io.IoOutputStream;
27 import org.opendaylight.netconf.shaded.sshd.common.kex.BuiltinDHFactories;
28 import org.opendaylight.netconf.shaded.sshd.common.kex.KeyExchangeFactory;
29 import org.opendaylight.netconf.shaded.sshd.common.mac.BuiltinMacs;
30 import org.opendaylight.netconf.shaded.sshd.common.mac.Mac;
31 import org.opendaylight.netconf.shaded.sshd.common.signature.BuiltinSignatures;
32 import org.opendaylight.netconf.shaded.sshd.common.signature.Signature;
33 import org.opendaylight.netconf.shaded.sshd.server.ServerBuilder;
34 import org.opendaylight.netconf.transport.api.TransportChannel;
35 import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh._public.key.algs.rev220616.EcdsaSha2Nistp256;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh._public.key.algs.rev220616.EcdsaSha2Nistp384;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh._public.key.algs.rev220616.EcdsaSha2Nistp521;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh._public.key.algs.rev220616.PublicKeyAlgBase;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh._public.key.algs.rev220616.RsaSha2256;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh._public.key.algs.rev220616.RsaSha2512;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh._public.key.algs.rev220616.SshDss;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh._public.key.algs.rev220616.SshEd25519;
44 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh._public.key.algs.rev220616.SshRsa;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.AeadAes128Gcm;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.AeadAes256Gcm;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.Aes128Cbc;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.Aes128Ctr;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.Aes192Cbc;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.Aes192Ctr;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.Aes256Cbc;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.Aes256Ctr;
53 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.Arcfour128;
54 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.Arcfour256;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.BlowfishCbc;
56 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.EncryptionAlgBase;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.None;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.encryption.algs.rev220616.TripleDesCbc;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.Curve25519Sha256;
60 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.Curve448Sha512;
61 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.DiffieHellmanGroup14Sha1;
62 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.DiffieHellmanGroup14Sha256;
63 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.DiffieHellmanGroup15Sha512;
64 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.DiffieHellmanGroup16Sha512;
65 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.DiffieHellmanGroup17Sha512;
66 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.DiffieHellmanGroup18Sha512;
67 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.DiffieHellmanGroup1Sha1;
68 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.DiffieHellmanGroupExchangeSha1;
69 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.DiffieHellmanGroupExchangeSha256;
70 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.EcdhSha2Nistp256;
71 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.EcdhSha2Nistp384;
72 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.EcdhSha2Nistp521;
73 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.key.exchange.algs.rev220616.KeyExchangeAlgBase;
74 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.mac.algs.rev220616.HmacMd5;
75 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.mac.algs.rev220616.HmacMd596;
76 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.mac.algs.rev220616.HmacSha1;
77 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.mac.algs.rev220616.HmacSha196;
78 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.mac.algs.rev220616.HmacSha2256;
79 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.mac.algs.rev220616.HmacSha2512;
80 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.ssh.mac.algs.rev220616.MacAlgBase;
81 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.common.rev231228.transport.params.grouping.Encryption;
82 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.common.rev231228.transport.params.grouping.HostKey;
83 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.common.rev231228.transport.params.grouping.KeyExchange;
85 final class TransportUtils {
86 private static final Map<EncryptionAlgBase, NamedFactory<Cipher>> CIPHERS =
87 ImmutableMap.<EncryptionAlgBase, NamedFactory<Cipher>>builder()
88 .put(AeadAes128Gcm.VALUE, BuiltinCiphers.aes128gcm)
89 .put(AeadAes256Gcm.VALUE, BuiltinCiphers.aes256cbc)
90 .put(Aes128Cbc.VALUE, BuiltinCiphers.aes128cbc)
91 .put(Aes128Ctr.VALUE, BuiltinCiphers.aes128ctr)
92 .put(Aes192Cbc.VALUE, BuiltinCiphers.aes192cbc)
93 .put(Aes192Ctr.VALUE, BuiltinCiphers.aes192ctr)
94 .put(Aes256Cbc.VALUE, BuiltinCiphers.aes256cbc)
95 .put(Aes256Ctr.VALUE, BuiltinCiphers.aes256ctr)
96 .put(Arcfour128.VALUE, BuiltinCiphers.arcfour128)
97 .put(Arcfour256.VALUE, BuiltinCiphers.arcfour256)
98 .put(BlowfishCbc.VALUE, BuiltinCiphers.blowfishcbc)
99 .put(TripleDesCbc.VALUE, BuiltinCiphers.tripledescbc)
100 .put(None.VALUE, BuiltinCiphers.none)
102 private static final List<NamedFactory<Cipher>> DEFAULT_CIPHERS =
103 ImmutableList.<NamedFactory<Cipher>>builder().addAll(BaseBuilder.DEFAULT_CIPHERS_PREFERENCE).build();
105 private static final Map<KeyExchangeAlgBase, KeyExchangeFactory> CLIENT_KEXS;
106 private static final Map<KeyExchangeAlgBase, KeyExchangeFactory> SERVER_KEXS;
107 private static final List<KeyExchangeFactory> DEFAULT_CLIENT_KEXS;
108 private static final List<KeyExchangeFactory> DEFAULT_SERVER_KEXS;
111 final var factories = Maps.filterValues(ImmutableMap.<KeyExchangeAlgBase, BuiltinDHFactories>builder()
112 .put(Curve25519Sha256.VALUE, BuiltinDHFactories.curve25519)
113 .put(Curve448Sha512.VALUE, BuiltinDHFactories.curve448)
114 .put(DiffieHellmanGroup1Sha1.VALUE, BuiltinDHFactories.dhg1)
115 .put(DiffieHellmanGroup14Sha1.VALUE, BuiltinDHFactories.dhg14)
116 .put(DiffieHellmanGroup14Sha256.VALUE, BuiltinDHFactories.dhg14_256)
117 .put(DiffieHellmanGroup15Sha512.VALUE, BuiltinDHFactories.dhg15_512)
118 .put(DiffieHellmanGroup16Sha512.VALUE, BuiltinDHFactories.dhg16_512)
119 .put(DiffieHellmanGroup17Sha512.VALUE, BuiltinDHFactories.dhg17_512)
120 .put(DiffieHellmanGroup18Sha512.VALUE, BuiltinDHFactories.dhg18_512)
121 .put(DiffieHellmanGroupExchangeSha1.VALUE, BuiltinDHFactories.dhgex)
122 .put(DiffieHellmanGroupExchangeSha256.VALUE, BuiltinDHFactories.dhgex256)
124 .put(EcdhSha21284010045311.VALUE, null)
125 .put(EcdhSha213132016.VALUE, null)
126 .put(EcdhSha21313201.VALUE, null)
127 .put(EcdhSha213132026.VALUE, null)
128 .put(EcdhSha213132027.VALUE, null)
129 .put(EcdhSha213132033.VALUE, null)
130 .put(EcdhSha213132036.VALUE, null)
131 .put(EcdhSha213132037.VALUE, null)
132 .put(EcdhSha213132038.VALUE, null)
134 .put(EcdhSha2Nistp256.VALUE, BuiltinDHFactories.ecdhp256)
135 .put(EcdhSha2Nistp384.VALUE, BuiltinDHFactories.ecdhp384)
136 .put(EcdhSha2Nistp521.VALUE, BuiltinDHFactories.ecdhp521)
138 .put(EcmqvSha2.VALUE, null)
139 .put(ExtInfoC.VALUE, null)
140 .put(ExtInfoS.VALUE, null)
142 TODO: provide solution for remaining (commented out) KEX algorithms missing in BuiltinDHFactories
144 .build(), BuiltinDHFactories::isSupported);
146 CLIENT_KEXS = ImmutableMap.copyOf(Maps.transformValues(factories, ClientBuilder.DH2KEX::apply));
147 SERVER_KEXS = ImmutableMap.copyOf(Maps.transformValues(factories, ServerBuilder.DH2KEX::apply));
148 DEFAULT_CLIENT_KEXS =
149 ImmutableList.copyOf(Lists.transform(BaseBuilder.DEFAULT_KEX_PREFERENCE, ClientBuilder.DH2KEX::apply));
150 DEFAULT_SERVER_KEXS =
151 ImmutableList.copyOf(Lists.transform(BaseBuilder.DEFAULT_KEX_PREFERENCE, ServerBuilder.DH2KEX::apply));
154 private static final Map<MacAlgBase, NamedFactory<Mac>> MACS =
155 ImmutableMap.<MacAlgBase, NamedFactory<Mac>>builder()
156 .put(HmacMd5.VALUE, BuiltinMacs.hmacmd5)
157 .put(HmacMd596.VALUE, BuiltinMacs.hmacmd596)
158 .put(HmacSha1.VALUE, BuiltinMacs.hmacsha1)
159 .put(HmacSha196.VALUE, BuiltinMacs.hmacsha196)
160 .put(HmacSha2256.VALUE, BuiltinMacs.hmacsha256)
161 .put(HmacSha2512.VALUE, BuiltinMacs.hmacsha512)
166 openssh ETM extensions
167 TODO provide solution for remaining (commented out) macs missing in BuiltinMacs
170 private static final List<NamedFactory<Mac>> DEFAULT_MACS =
171 ImmutableList.<NamedFactory<Mac>>builder().addAll(BaseBuilder.DEFAULT_MAC_PREFERENCE).build();
173 static final Map<PublicKeyAlgBase, NamedFactory<Signature>> SIGNATURES =
174 ImmutableMap.<PublicKeyAlgBase, NamedFactory<Signature>>builder()
175 .put(EcdsaSha2Nistp256.VALUE, BuiltinSignatures.nistp256)
176 .put(EcdsaSha2Nistp384.VALUE, BuiltinSignatures.nistp384)
177 .put(EcdsaSha2Nistp521.VALUE, BuiltinSignatures.nistp521)
178 .put(RsaSha2512.VALUE, BuiltinSignatures.rsaSHA512)
179 // .put(PgpSignDss.VALUE, null)
180 // .put(PgpSignRsa.VALUE, null)
181 .put(RsaSha2256.VALUE, BuiltinSignatures.rsaSHA256)
182 // .put(SpkiSignRsa.VALUE, null)
183 // .put(SpkiSignDss.VALUE, null)
184 .put(SshDss.VALUE, BuiltinSignatures.dsa)
185 // .put(SshEd448.VALUE, null)
186 .put(SshEd25519.VALUE, BuiltinSignatures.ed25519)
187 .put(SshRsa.VALUE, BuiltinSignatures.rsa)
189 .put(X509v3EcdsaSha2Nistp256.VALUE, null)
190 .put(X509v3EcdsaSha2Nistp384.VALUE, null)
191 .put(X509v3EcdsaSha2Nistp521.VALUE, null)
192 .put(X509v3Rsa2048Sha256.VALUE, null)
193 .put(X509v3SshDss.VALUE, null)
194 .put(X509v3SshRsa.VALUE, null)
195 .put(Null.VALUE, null)
196 TODO provide solution for remaining (commented out) signatures missing in BuiltinSignatures
200 static final List<NamedFactory<Signature>> DEFAULT_SIGNATURES =
201 ImmutableList.<NamedFactory<Signature>>builder().addAll(BaseBuilder.DEFAULT_SIGNATURE_PREFERENCE).build();
203 private TransportUtils() {
207 public static List<NamedFactory<Cipher>> getCipherFactories(final @Nullable Encryption encryption)
208 throws UnsupportedConfigurationException {
209 if (encryption != null) {
210 final var encAlg = encryption.getEncryptionAlg();
211 if (encAlg != null && !encAlg.isEmpty()) {
212 return mapValues(CIPHERS, encAlg, "Unsupported Encryption algorithm %s");
215 return DEFAULT_CIPHERS;
218 public static List<NamedFactory<Signature>> getSignatureFactories(@Nullable final HostKey hostKey)
219 throws UnsupportedConfigurationException {
220 if (hostKey != null) {
221 final var hostKeyAlg = hostKey.getHostKeyAlg();
222 if (hostKeyAlg != null && hostKeyAlg.isEmpty()) {
223 return mapValues(SIGNATURES, hostKeyAlg, "Unsupported Host Key algorithm %s");
226 return DEFAULT_SIGNATURES;
229 public static List<KeyExchangeFactory> getClientKexFactories(final KeyExchange keyExchange)
230 throws UnsupportedConfigurationException {
231 return getKexFactories(keyExchange, CLIENT_KEXS, DEFAULT_CLIENT_KEXS);
234 public static List<KeyExchangeFactory> getServerKexFactories(final KeyExchange keyExchange)
235 throws UnsupportedConfigurationException {
236 return getKexFactories(keyExchange, SERVER_KEXS, DEFAULT_SERVER_KEXS);
239 private static List<KeyExchangeFactory> getKexFactories(final KeyExchange keyExchange,
240 final Map<KeyExchangeAlgBase, KeyExchangeFactory> map,
241 final List<KeyExchangeFactory> defaultResult) throws UnsupportedConfigurationException {
242 if (keyExchange != null) {
243 final var kexAlg = keyExchange.getKeyExchangeAlg();
244 if (kexAlg != null && !kexAlg.isEmpty()) {
245 return mapValues(map, kexAlg, "Unsupported Key Exchange algorithm %s");
248 return defaultResult;
251 public static List<NamedFactory<Mac>> getMacFactories(
252 final org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.common.rev231228
253 .transport.params.grouping.Mac mac) throws UnsupportedConfigurationException {
255 final var macAlg = mac.getMacAlg();
256 if (macAlg != null && !macAlg.isEmpty()) {
257 return mapValues(MACS, macAlg, "Unsupported MAC algorithm %s");
263 private static <K, V> List<V> mapValues(final Map<K, V> map, final List<K> values, final String errorTemplate)
264 throws UnsupportedConfigurationException {
265 final var builder = ImmutableList.<V>builderWithExpectedSize(values.size());
266 for (K value : values) {
267 final V mapped = map.get(value);
268 if (mapped == null) {
269 throw new UnsupportedOperationException(String.format(errorTemplate, value));
273 return builder.build();
276 static <T> T checkCast(final Class<T> clazz, final Object obj) throws IOException {
278 return clazz.cast(requireNonNull(obj));
279 } catch (ClassCastException e) {
280 throw new IOException(e);
285 interface ChannelInactive {
287 void onChannelInactive() throws Exception;
290 static ChannelHandlerContext attachUnderlay(final IoOutputStream out, final TransportChannel underlay,
291 final ChannelInactive inactive) {
292 // Note that there may be multiple handlers already present on the channel, hence we are attaching last, but
293 // from the logical perspective we are the head handlers.
294 final var pipeline = underlay.channel().pipeline();
296 // outbound packet handler, i.e. moving bytes from the channel into SSHD's pipeline
297 pipeline.addLast(new OutboundChannelHandler(out));
299 // invoke requested action on channel termination
300 underlay.channel().closeFuture().addListener(future -> inactive.onChannelInactive());
302 // last handler context is used as entry point to direct inbound packets (captured by SSH adapter)
303 // back to same channel pipeline
304 return pipeline.lastContext();