bssh_russh/
negotiation.rs

1// Copyright 2016 Pierre-Étienne Meunier
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15use std::borrow::Cow;
16
17use log::debug;
18use rand::RngCore;
19use ssh_encoding::{Decode, Encode};
20use ssh_key::{Algorithm, EcdsaCurve, HashAlg, PrivateKey};
21
22use crate::cipher::CIPHERS;
23use crate::helpers::NameList;
24use crate::kex::{
25    EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT, EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER, KexCause,
26};
27#[cfg(not(target_arch = "wasm32"))]
28use crate::server::Config;
29use crate::sshbuffer::PacketWriter;
30use crate::{AlgorithmKind, CryptoVec, Error, cipher, compression, kex, mac, msg};
31
32#[cfg(target_arch = "wasm32")]
33/// WASM-only stub
34pub struct Config {
35    keys: Vec<PrivateKey>,
36}
37
38#[derive(Debug, Clone)]
39pub struct Names {
40    pub kex: kex::Name,
41    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
42    pub key: Algorithm,
43    pub cipher: cipher::Name,
44    pub client_mac: mac::Name,
45    pub server_mac: mac::Name,
46    pub server_compression: compression::Compression,
47    pub client_compression: compression::Compression,
48    pub ignore_guessed: bool,
49    // Prevent accidentally contructing [Names] without a [KeyCause]
50    // as strict kext algo is not sent during a rekey and hence the state
51    // of [strict_kex] cannot be known without a [KexCause].
52    strict_kex: bool,
53}
54
55impl Names {
56    pub fn strict_kex(&self) -> bool {
57        self.strict_kex
58    }
59}
60
61/// Lists of preferred algorithms. This is normally hard-coded into implementations.
62#[derive(Debug, Clone)]
63pub struct Preferred {
64    /// Preferred key exchange algorithms.
65    pub kex: Cow<'static, [kex::Name]>,
66    /// Preferred host & public key algorithms.
67    pub key: Cow<'static, [Algorithm]>,
68    /// Preferred symmetric ciphers.
69    pub cipher: Cow<'static, [cipher::Name]>,
70    /// Preferred MAC algorithms.
71    pub mac: Cow<'static, [mac::Name]>,
72    /// Preferred compression algorithms.
73    pub compression: Cow<'static, [compression::Name]>,
74}
75
76pub(crate) fn is_key_compatible_with_algo(key: &PrivateKey, algo: &Algorithm) -> bool {
77    match algo {
78        // All RSA keys are compatible with all RSA based algos.
79        Algorithm::Rsa { .. } => key.algorithm().is_rsa(),
80        // Other keys have to match exactly
81        a => key.algorithm() == *a,
82    }
83}
84
85impl Preferred {
86    pub(crate) fn possible_host_key_algos_for_keys(
87        &self,
88        available_host_keys: &[PrivateKey],
89    ) -> Vec<Algorithm> {
90        self.key
91            .iter()
92            .filter(|n| {
93                available_host_keys
94                    .iter()
95                    .any(|k| is_key_compatible_with_algo(k, n))
96            })
97            .cloned()
98            .collect::<Vec<_>>()
99    }
100}
101
102const SAFE_KEX_ORDER: &[kex::Name] = &[
103    kex::MLKEM768X25519_SHA256,
104    kex::CURVE25519,
105    kex::CURVE25519_PRE_RFC_8731,
106    kex::DH_GEX_SHA256,
107    kex::DH_G18_SHA512,
108    kex::DH_G17_SHA512,
109    kex::DH_G16_SHA512,
110    kex::DH_G15_SHA512,
111    kex::DH_G14_SHA256,
112    kex::EXTENSION_SUPPORT_AS_CLIENT,
113    kex::EXTENSION_SUPPORT_AS_SERVER,
114    kex::EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT,
115    kex::EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER,
116];
117
118const KEX_EXTENSION_NAMES: &[kex::Name] = &[
119    kex::EXTENSION_SUPPORT_AS_CLIENT,
120    kex::EXTENSION_SUPPORT_AS_SERVER,
121    kex::EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT,
122    kex::EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER,
123];
124
125const CIPHER_ORDER: &[cipher::Name] = &[
126    cipher::CHACHA20_POLY1305,
127    cipher::AES_256_GCM,
128    cipher::AES_256_CTR,
129    cipher::AES_192_CTR,
130    cipher::AES_128_CTR,
131];
132
133const HMAC_ORDER: &[mac::Name] = &[
134    mac::HMAC_SHA512_ETM,
135    mac::HMAC_SHA256_ETM,
136    mac::HMAC_SHA512,
137    mac::HMAC_SHA256,
138    mac::HMAC_SHA1_ETM,
139    mac::HMAC_SHA1,
140];
141
142const COMPRESSION_ORDER: &[compression::Name] = &[
143    compression::NONE,
144    #[cfg(feature = "flate2")]
145    compression::ZLIB,
146    #[cfg(feature = "flate2")]
147    compression::ZLIB_LEGACY,
148];
149
150impl Preferred {
151    pub const DEFAULT: Preferred = Preferred {
152        kex: Cow::Borrowed(SAFE_KEX_ORDER),
153        key: Cow::Borrowed(&[
154            Algorithm::Ed25519,
155            Algorithm::Ecdsa {
156                curve: EcdsaCurve::NistP256,
157            },
158            Algorithm::Ecdsa {
159                curve: EcdsaCurve::NistP384,
160            },
161            Algorithm::Ecdsa {
162                curve: EcdsaCurve::NistP521,
163            },
164            Algorithm::Rsa {
165                hash: Some(HashAlg::Sha512),
166            },
167            Algorithm::Rsa {
168                hash: Some(HashAlg::Sha256),
169            },
170            Algorithm::Rsa { hash: None },
171        ]),
172        cipher: Cow::Borrowed(CIPHER_ORDER),
173        mac: Cow::Borrowed(HMAC_ORDER),
174        compression: Cow::Borrowed(COMPRESSION_ORDER),
175    };
176
177    pub const COMPRESSED: Preferred = Preferred {
178        kex: Cow::Borrowed(SAFE_KEX_ORDER),
179        key: Preferred::DEFAULT.key,
180        cipher: Cow::Borrowed(CIPHER_ORDER),
181        mac: Cow::Borrowed(HMAC_ORDER),
182        compression: Cow::Borrowed(COMPRESSION_ORDER),
183    };
184}
185
186impl Default for Preferred {
187    fn default() -> Preferred {
188        Preferred::DEFAULT
189    }
190}
191
192pub(crate) fn parse_kex_algo_list(list: &str) -> Vec<&str> {
193    list.split(',').collect()
194}
195
196pub(crate) trait Select {
197    fn is_server() -> bool;
198
199    fn select<S: AsRef<str> + Clone>(
200        a: &[S],
201        b: &[&str],
202        kind: AlgorithmKind,
203    ) -> Result<(bool, S), Error>;
204
205    /// `available_host_keys`, if present, is used to limit the host key algorithms to the ones we have keys for.
206    fn read_kex(
207        buffer: &[u8],
208        pref: &Preferred,
209        available_host_keys: Option<&[PrivateKey]>,
210        cause: &KexCause,
211    ) -> Result<Names, Error> {
212        let &Some(mut r) = &buffer.get(17..) else {
213            return Err(Error::Inconsistent);
214        };
215
216        // Key exchange
217
218        let kex_string = String::decode(&mut r)?;
219        // Filter out extension kex names from both lists before selecting
220        let _local_kexes_no_ext = pref
221            .kex
222            .iter()
223            .filter(|k| !KEX_EXTENSION_NAMES.contains(k))
224            .cloned()
225            .collect::<Vec<_>>();
226        let _remote_kexes_no_ext = parse_kex_algo_list(&kex_string)
227            .into_iter()
228            .filter(|k| {
229                kex::Name::try_from(*k)
230                    .ok()
231                    .map(|k| !KEX_EXTENSION_NAMES.contains(&k))
232                    .unwrap_or(false)
233            })
234            .collect::<Vec<_>>();
235        let (kex_both_first, kex_algorithm) = Self::select(
236            &_local_kexes_no_ext,
237            &_remote_kexes_no_ext,
238            AlgorithmKind::Kex,
239        )?;
240
241        // Strict kex detection
242
243        let strict_kex_requested = pref.kex.contains(if Self::is_server() {
244            &EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER
245        } else {
246            &EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT
247        });
248        let strict_kex_provided = Self::select(
249            &[if Self::is_server() {
250                EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT
251            } else {
252                EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER
253            }],
254            &parse_kex_algo_list(&kex_string),
255            AlgorithmKind::Kex,
256        )
257        .is_ok();
258
259        if strict_kex_requested && strict_kex_provided {
260            debug!("strict kex enabled")
261        }
262
263        // Host key
264
265        let key_string = String::decode(&mut r)?;
266        let possible_host_key_algos = match available_host_keys {
267            Some(available_host_keys) => pref.possible_host_key_algos_for_keys(available_host_keys),
268            None => pref.key.iter().map(ToOwned::to_owned).collect::<Vec<_>>(),
269        };
270
271        let (key_both_first, key_algorithm) = Self::select(
272            &possible_host_key_algos[..],
273            &parse_kex_algo_list(&key_string),
274            AlgorithmKind::Key,
275        )?;
276
277        // Cipher
278
279        let cipher_string = String::decode(&mut r)?;
280        let (_cipher_both_first, cipher) = Self::select(
281            &pref.cipher,
282            &parse_kex_algo_list(&cipher_string),
283            AlgorithmKind::Cipher,
284        )?;
285        String::decode(&mut r)?; // cipher server-to-client.
286
287        // MAC
288
289        let need_mac = CIPHERS.get(&cipher).map(|x| x.needs_mac()).unwrap_or(false);
290
291        let client_mac = match Self::select(
292            &pref.mac,
293            &parse_kex_algo_list(&String::decode(&mut r)?),
294            AlgorithmKind::Mac,
295        ) {
296            Ok((_, m)) => m,
297            Err(e) => {
298                if need_mac {
299                    return Err(e);
300                } else {
301                    mac::NONE
302                }
303            }
304        };
305        let server_mac = match Self::select(
306            &pref.mac,
307            &parse_kex_algo_list(&String::decode(&mut r)?),
308            AlgorithmKind::Mac,
309        ) {
310            Ok((_, m)) => m,
311            Err(e) => {
312                if need_mac {
313                    return Err(e);
314                } else {
315                    mac::NONE
316                }
317            }
318        };
319
320        // Compression
321
322        // client-to-server compression.
323        let client_compression = compression::Compression::new(
324            &Self::select(
325                &pref.compression,
326                &parse_kex_algo_list(&String::decode(&mut r)?),
327                AlgorithmKind::Compression,
328            )?
329            .1,
330        );
331
332        // server-to-client compression.
333        let server_compression = compression::Compression::new(
334            &Self::select(
335                &pref.compression,
336                &parse_kex_algo_list(&String::decode(&mut r)?),
337                AlgorithmKind::Compression,
338            )?
339            .1,
340        );
341        String::decode(&mut r)?; // languages client-to-server
342        String::decode(&mut r)?; // languages server-to-client
343
344        let follows = u8::decode(&mut r)? != 0;
345        Ok(Names {
346            kex: kex_algorithm,
347            key: key_algorithm,
348            cipher,
349            client_mac,
350            server_mac,
351            client_compression,
352            server_compression,
353            // Ignore the next packet if (1) it follows and (2) it's not the correct guess.
354            ignore_guessed: follows && !(kex_both_first && key_both_first),
355            strict_kex: (strict_kex_requested && strict_kex_provided) || cause.is_strict_rekey(),
356        })
357    }
358}
359
360pub struct Server;
361pub struct Client;
362
363impl Select for Server {
364    fn is_server() -> bool {
365        true
366    }
367
368    fn select<S: AsRef<str> + Clone>(
369        server_list: &[S],
370        client_list: &[&str],
371        kind: AlgorithmKind,
372    ) -> Result<(bool, S), Error> {
373        let mut both_first_choice = true;
374        for c in client_list {
375            for s in server_list {
376                if c == &s.as_ref() {
377                    return Ok((both_first_choice, s.clone()));
378                }
379                both_first_choice = false
380            }
381        }
382        Err(Error::NoCommonAlgo {
383            kind,
384            ours: server_list.iter().map(|x| x.as_ref().to_owned()).collect(),
385            theirs: client_list.iter().map(|x| (*x).to_owned()).collect(),
386        })
387    }
388}
389
390impl Select for Client {
391    fn is_server() -> bool {
392        false
393    }
394
395    fn select<S: AsRef<str> + Clone>(
396        client_list: &[S],
397        server_list: &[&str],
398        kind: AlgorithmKind,
399    ) -> Result<(bool, S), Error> {
400        let mut both_first_choice = true;
401        for c in client_list {
402            for s in server_list {
403                if s == &c.as_ref() {
404                    return Ok((both_first_choice, c.clone()));
405                }
406                both_first_choice = false
407            }
408        }
409        Err(Error::NoCommonAlgo {
410            kind,
411            ours: client_list.iter().map(|x| x.as_ref().to_owned()).collect(),
412            theirs: server_list.iter().map(|x| (*x).to_owned()).collect(),
413        })
414    }
415}
416
417pub(crate) fn write_kex(
418    prefs: &Preferred,
419    writer: &mut PacketWriter,
420    server_config: Option<&Config>,
421) -> Result<CryptoVec, Error> {
422    writer.packet(|w| {
423        // buf.clear();
424        msg::KEXINIT.encode(w)?;
425
426        let mut cookie = [0; 16];
427        rand::thread_rng().fill_bytes(&mut cookie);
428        for b in cookie {
429            b.encode(w)?;
430        }
431
432        NameList(
433            prefs
434                .kex
435                .iter()
436                .filter(|k| {
437                    !(if server_config.is_some() {
438                        [
439                            crate::kex::EXTENSION_SUPPORT_AS_CLIENT,
440                            crate::kex::EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT,
441                        ]
442                    } else {
443                        [
444                            crate::kex::EXTENSION_SUPPORT_AS_SERVER,
445                            crate::kex::EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER,
446                        ]
447                    })
448                    .contains(*k)
449                })
450                .map(|x| x.as_ref().to_owned())
451                .collect(),
452        )
453        .encode(w)?; // kex algo
454
455        if let Some(server_config) = server_config {
456            // Only advertise host key algorithms that we have keys for.
457            NameList(
458                prefs
459                    .key
460                    .iter()
461                    .filter(|algo| {
462                        server_config
463                            .keys
464                            .iter()
465                            .any(|k| is_key_compatible_with_algo(k, algo))
466                    })
467                    .map(|x| x.to_string())
468                    .collect(),
469            )
470            .encode(w)?;
471        } else {
472            NameList(prefs.key.iter().map(ToString::to_string).collect()).encode(w)?;
473        }
474
475        // cipher client to server
476        NameList(
477            prefs
478                .cipher
479                .iter()
480                .map(|x| x.as_ref().to_string())
481                .collect(),
482        )
483        .encode(w)?;
484
485        // cipher server to client
486        NameList(
487            prefs
488                .cipher
489                .iter()
490                .map(|x| x.as_ref().to_string())
491                .collect(),
492        )
493        .encode(w)?;
494
495        // mac client to server
496        NameList(prefs.mac.iter().map(|x| x.as_ref().to_string()).collect()).encode(w)?;
497
498        // mac server to client
499        NameList(prefs.mac.iter().map(|x| x.as_ref().to_string()).collect()).encode(w)?;
500
501        // compress client to server
502        NameList(
503            prefs
504                .compression
505                .iter()
506                .map(|x| x.as_ref().to_string())
507                .collect(),
508        )
509        .encode(w)?;
510
511        // compress server to client
512        NameList(
513            prefs
514                .compression
515                .iter()
516                .map(|x| x.as_ref().to_string())
517                .collect(),
518        )
519        .encode(w)?;
520
521        Vec::<String>::new().encode(w)?; // languages client to server
522        Vec::<String>::new().encode(w)?; // languages server to client
523
524        0u8.encode(w)?; // doesn't follow
525        0u32.encode(w)?; // reserved
526        Ok(())
527    })
528}