Refresh IETF client/server models
[netconf.git] / transport / transport-ssh / src / main / java / org / opendaylight / netconf / transport / ssh / TransportUtils.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.ssh;
9
10 import static java.util.Objects.requireNonNull;
11
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;
19 import java.util.Map;
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;
84
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)
101                     .build();
102     private static final List<NamedFactory<Cipher>> DEFAULT_CIPHERS =
103             ImmutableList.<NamedFactory<Cipher>>builder().addAll(BaseBuilder.DEFAULT_CIPHERS_PREFERENCE).build();
104
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;
109
110     static {
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)
123                 /*
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)
133                  */
134                 .put(EcdhSha2Nistp256.VALUE, BuiltinDHFactories.ecdhp256)
135                 .put(EcdhSha2Nistp384.VALUE, BuiltinDHFactories.ecdhp384)
136                 .put(EcdhSha2Nistp521.VALUE, BuiltinDHFactories.ecdhp521)
137                 /*
138                 .put(EcmqvSha2.VALUE, null)
139                 .put(ExtInfoC.VALUE, null)
140                 .put(ExtInfoS.VALUE, null)
141                  Gss*
142                  TODO: provide solution for remaining (commented out) KEX algorithms missing in BuiltinDHFactories
143                 */
144                 .build(), BuiltinDHFactories::isSupported);
145
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));
152     }
153
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)
162                     /*
163                      AeadAes128Gcm.VALUE
164                      AeadAes256Gcm.VALUE
165                      None.VALUE
166                      openssh ETM extensions
167                      TODO provide solution for remaining (commented out) macs missing in BuiltinMacs
168                       */
169                     .build();
170     private static final List<NamedFactory<Mac>> DEFAULT_MACS =
171             ImmutableList.<NamedFactory<Mac>>builder().addAll(BaseBuilder.DEFAULT_MAC_PREFERENCE).build();
172
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)
188                     /*
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
197                     */
198                     .build();
199
200     static final List<NamedFactory<Signature>> DEFAULT_SIGNATURES =
201             ImmutableList.<NamedFactory<Signature>>builder().addAll(BaseBuilder.DEFAULT_SIGNATURE_PREFERENCE).build();
202
203     private TransportUtils() {
204         // utility class
205     }
206
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");
213             }
214         }
215         return DEFAULT_CIPHERS;
216     }
217
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");
224             }
225         }
226         return DEFAULT_SIGNATURES;
227     }
228
229     public static List<KeyExchangeFactory> getClientKexFactories(final KeyExchange keyExchange)
230             throws UnsupportedConfigurationException {
231         return getKexFactories(keyExchange, CLIENT_KEXS, DEFAULT_CLIENT_KEXS);
232     }
233
234     public static List<KeyExchangeFactory> getServerKexFactories(final KeyExchange keyExchange)
235             throws UnsupportedConfigurationException {
236         return getKexFactories(keyExchange, SERVER_KEXS, DEFAULT_SERVER_KEXS);
237     }
238
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");
246             }
247         }
248         return defaultResult;
249     }
250
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 {
254         if (mac != null) {
255             final var macAlg = mac.getMacAlg();
256             if (macAlg != null && !macAlg.isEmpty()) {
257                 return mapValues(MACS, macAlg, "Unsupported MAC algorithm %s");
258             }
259         }
260         return DEFAULT_MACS;
261     }
262
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));
270             }
271             builder.add(mapped);
272         }
273         return builder.build();
274     }
275
276     static <T> T checkCast(final Class<T> clazz, final Object obj) throws IOException {
277         try {
278             return clazz.cast(requireNonNull(obj));
279         } catch (ClassCastException e) {
280             throw new IOException(e);
281         }
282     }
283
284     @FunctionalInterface
285     interface ChannelInactive {
286
287         void onChannelInactive() throws Exception;
288     }
289
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();
295
296         // outbound packet handler, i.e. moving bytes from the channel into SSHD's pipeline
297         pipeline.addLast(new OutboundChannelHandler(out));
298
299         // invoke requested action on channel termination
300         underlay.channel().closeFuture().addListener(future -> inactive.onChannelInactive());
301
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();
305     }
306 }