1use 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")]
36pub 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 strict_kex: bool,
56}
57
58impl Names {
59 pub fn strict_kex(&self) -> bool {
60 self.strict_kex
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct Preferred {
67 pub kex: Cow<'static, [kex::Name]>,
69 pub key: Cow<'static, [Algorithm]>,
71 pub cipher: Cow<'static, [cipher::Name]>,
73 pub mac: Cow<'static, [mac::Name]>,
75 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 Algorithm::Rsa { .. } => key.algorithm().is_rsa(),
83 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
136const 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 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 let kex_list = NameList::decode(&mut r)?;
217 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 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 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 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)?; 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 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 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)?; String::decode(&mut r)?; 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_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)?; if let Some(server_config) = server_config {
443 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 NameList(
464 prefs
465 .cipher
466 .iter()
467 .map(|x| x.as_ref().to_string())
468 .collect(),
469 )
470 .encode(w)?;
471
472 NameList(
474 prefs
475 .cipher
476 .iter()
477 .map(|x| x.as_ref().to_string())
478 .collect(),
479 )
480 .encode(w)?;
481
482 NameList(prefs.mac.iter().map(|x| x.as_ref().to_string()).collect()).encode(w)?;
484
485 NameList(prefs.mac.iter().map(|x| x.as_ref().to_string()).collect()).encode(w)?;
487
488 NameList(
490 prefs
491 .compression
492 .iter()
493 .map(|x| x.as_ref().to_string())
494 .collect(),
495 )
496 .encode(w)?;
497
498 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)?; Vec::<String>::new().encode(w)?; 0u8.encode(w)?; 0u32.encode(w)?; Ok(())
514 })
515}