Skip to main content

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