1use 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")]
33pub 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 strict_kex: bool,
53}
54
55impl Names {
56 pub fn strict_kex(&self) -> bool {
57 self.strict_kex
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct Preferred {
64 pub kex: Cow<'static, [kex::Name]>,
66 pub key: Cow<'static, [Algorithm]>,
68 pub cipher: Cow<'static, [cipher::Name]>,
70 pub mac: Cow<'static, [mac::Name]>,
72 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 Algorithm::Rsa { .. } => key.algorithm().is_rsa(),
80 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 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 let kex_string = String::decode(&mut r)?;
219 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 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 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 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)?; 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 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 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)?; String::decode(&mut r)?; 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_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 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)?; if let Some(server_config) = server_config {
456 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 NameList(
477 prefs
478 .cipher
479 .iter()
480 .map(|x| x.as_ref().to_string())
481 .collect(),
482 )
483 .encode(w)?;
484
485 NameList(
487 prefs
488 .cipher
489 .iter()
490 .map(|x| x.as_ref().to_string())
491 .collect(),
492 )
493 .encode(w)?;
494
495 NameList(prefs.mac.iter().map(|x| x.as_ref().to_string()).collect()).encode(w)?;
497
498 NameList(prefs.mac.iter().map(|x| x.as_ref().to_string()).collect()).encode(w)?;
500
501 NameList(
503 prefs
504 .compression
505 .iter()
506 .map(|x| x.as_ref().to_string())
507 .collect(),
508 )
509 .encode(w)?;
510
511 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)?; Vec::<String>::new().encode(w)?; 0u8.encode(w)?; 0u32.encode(w)?; Ok(())
527 })
528}