diff --git a/russh/src/client/encrypted.rs b/russh/src/client/encrypted.rs index f070d76f..187ef1a1 100644 --- a/russh/src/client/encrypted.rs +++ b/russh/src/client/encrypted.rs @@ -60,7 +60,11 @@ impl Session { } else if let Some(exchange) = enc.exchange.take() { Some(KexInit::received_rekey( exchange, - negotiation::Client::read_kex(buf, &self.common.config.as_ref().preferred)?, + negotiation::Client::read_kex( + buf, + &self.common.config.as_ref().preferred, + None, + )?, &enc.session_id, )) } else { diff --git a/russh/src/client/kex.rs b/russh/src/client/kex.rs index 9836aa98..92de368a 100644 --- a/russh/src/client/kex.rs +++ b/russh/src/client/kex.rs @@ -21,7 +21,7 @@ impl KexInit { // read algorithms from packet. debug!("extending {:?}", &self.exchange.server_kex_init[..]); self.exchange.server_kex_init.extend(buf); - negotiation::Client::read_kex(buf, &config.preferred)? + negotiation::Client::read_kex(buf, &config.preferred, None)? }; debug!("algo = {:?}", algo); debug!("write = {:?}", &write_buffer.buffer[..]); diff --git a/russh/src/negotiation.rs b/russh/src/negotiation.rs index 845411b9..9cb72160 100644 --- a/russh/src/negotiation.rs +++ b/russh/src/negotiation.rs @@ -55,6 +55,18 @@ pub struct Preferred { pub compression: &'static [&'static str], } +impl Preferred { + pub(crate) fn possible_host_key_algos_for_keys( + &self, + available_host_keys: &[KeyPair], + ) -> Vec<&'static key::Name> { + self.key + .iter() + .filter(|n| available_host_keys.iter().any(|k| k.name() == n.0)) + .collect::>() + } +} + const SAFE_KEX_ORDER: &[kex::Name] = &[ kex::CURVE25519, kex::CURVE25519_PRE_RFC_8731, @@ -158,19 +170,28 @@ pub(crate) trait Select { fn select + Copy>(a: &[S], b: &[u8]) -> Option<(bool, S)>; - fn read_kex(buffer: &[u8], pref: &Preferred) -> Result { + /// `available_host_keys`, if present, is used to limit the host key algorithms to the ones we have keys for. + fn read_kex( + buffer: &[u8], + pref: &Preferred, + available_host_keys: Option<&[KeyPair]>, + ) -> Result { let mut r = buffer.reader(17); + + // Key exchange + let kex_string = r.read_string()?; - let (kex_both_first, kex_algorithm) = if let Some(x) = Self::select(pref.kex, kex_string) { - x - } else { + let (kex_both_first, kex_algorithm) = Self::select(pref.kex, kex_string).ok_or_else(|| + { debug!( "Could not find common kex algorithm, other side only supports {:?}, we only support {:?}", from_utf8(kex_string), pref.kex ); - return Err(Error::NoCommonKexAlgo); - }; + return Error::NoCommonKexAlgo; + })?; + + // Strict kex detection let strict_kex_requested = pref.kex.contains(if Self::is_server() { &EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER @@ -190,35 +211,42 @@ pub(crate) trait Select { debug!("strict kex enabled") } - let key_string = r.read_string()?; - let (key_both_first, key_algorithm) = if let Some(x) = Self::select(pref.key, key_string) { - x - } else { - debug!( - "Could not find common key algorithm, other side only supports {:?}, we only support {:?}", - from_utf8(key_string), - pref.key - ); - return Err(Error::NoCommonKeyAlgo); + // Host key + + let key_string: &[u8] = r.read_string()?; + let possible_host_key_algos = match available_host_keys { + Some(available_host_keys) => pref.possible_host_key_algos_for_keys(available_host_keys), + None => pref.key.iter().collect::>(), }; + let (key_both_first, key_algorithm) = + Self::select(&possible_host_key_algos[..], key_string).ok_or_else(|| { + debug!( + "Could not find common key algorithm, other side only supports {:?}, we only support {:?}", + from_utf8(key_string), + pref.key + ); + return Error::NoCommonKeyAlgo; + })?; + + // Cipher + let cipher_string = r.read_string()?; - let cipher = Self::select(pref.cipher, cipher_string); - if cipher.is_none() { - debug!( + let (_cipher_both_first, cipher) = + Self::select(pref.cipher, cipher_string).ok_or_else(|| { + debug!( "Could not find common cipher, other side only supports {:?}, we only support {:?}", from_utf8(cipher_string), pref.cipher ); - return Err(Error::NoCommonCipher); - } + Error::NoCommonCipher + })?; r.read_string()?; // cipher server-to-client. debug!("kex {}", line!()); - let need_mac = cipher - .and_then(|x| CIPHERS.get(&x.1)) - .map(|x| x.needs_mac()) - .unwrap_or(false); + // MAC + + let need_mac = CIPHERS.get(&cipher).map(|x| x.needs_mac()).unwrap_or(false); let client_mac = if let Some((_, m)) = Self::select(pref.mac, r.read_string()?) { m @@ -235,6 +263,8 @@ pub(crate) trait Select { mac::NONE }; + // Compression + debug!("kex {}", line!()); // client-to-server compression. let client_compression = @@ -256,23 +286,18 @@ pub(crate) trait Select { r.read_string()?; // languages server-to-client let follows = r.read_byte()? != 0; - match (cipher, follows) { - (Some((_, cipher)), fol) => { - Ok(Names { - kex: kex_algorithm, - key: key_algorithm, - cipher, - client_mac, - server_mac, - client_compression, - server_compression, - // Ignore the next packet if (1) it follows and (2) it's not the correct guess. - ignore_guessed: fol && !(kex_both_first && key_both_first), - strict_kex: strict_kex_requested && strict_kex_provided, - }) - } - _ => Err(Error::KexInit), - } + Ok(Names { + kex: kex_algorithm, + key: *key_algorithm, + cipher, + client_mac, + server_mac, + client_compression, + server_compression, + // Ignore the next packet if (1) it follows and (2) it's not the correct guess. + ignore_guessed: follows && !(kex_both_first && key_both_first), + strict_kex: strict_kex_requested && strict_kex_provided, + }) } } diff --git a/russh/src/server/encrypted.rs b/russh/src/server/encrypted.rs index c1d928a8..f36e5d54 100644 --- a/russh/src/server/encrypted.rs +++ b/russh/src/server/encrypted.rs @@ -61,7 +61,11 @@ impl Session { } else if let Some(exchange) = enc.exchange.take() { let kexinit = KexInit::received_rekey( exchange, - negotiation::Server::read_kex(buf, &self.common.config.as_ref().preferred)?, + negotiation::Server::read_kex( + buf, + &self.common.config.as_ref().preferred, + Some(&self.common.config.as_ref().keys), + )?, &enc.session_id, ); enc.rekey = Some(kexinit.server_parse( diff --git a/russh/src/server/kex.rs b/russh/src/server/kex.rs index c3fe1ca2..5efbbacd 100644 --- a/russh/src/server/kex.rs +++ b/russh/src/server/kex.rs @@ -26,7 +26,7 @@ impl KexInit { let algo = { // read algorithms from packet. self.exchange.client_kex_init.extend(buf); - super::negotiation::Server::read_kex(buf, &config.preferred)? + super::negotiation::Server::read_kex(buf, &config.preferred, Some(&config.keys))? }; if !self.sent { self.server_write(config, cipher, write_buffer)?