Skip to content

Commit 62366e9

Browse files
authored
#259, #245, ref #227 - fixed host key algo selection when Preferred::key and the available host keys don't match (#262)
1 parent 0fcb1ec commit 62366e9

File tree

8 files changed

+92
-51
lines changed

8 files changed

+92
-51
lines changed

.all-contributorsrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,15 @@
247247
"contributions": [
248248
"code"
249249
]
250+
},
251+
{
252+
"login": "T0b1-iOS",
253+
"name": "T0b1-iOS",
254+
"avatar_url": "https://avatars.githubusercontent.com/u/15174814?v=4",
255+
"profile": "https://github.com/T0b1-iOS",
256+
"contributions": [
257+
"code"
258+
]
250259
}
251260
],
252261
"contributorsPerLine": 7,

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Russh
22
[![Rust](https://github.com/warp-tech/russh/actions/workflows/rust.yml/badge.svg)](https://github.com/warp-tech/russh/actions/workflows/rust.yml) <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
3-
[![All Contributors](https://img.shields.io/badge/all_contributors-27-orange.svg?style=flat-square)](#contributors-)
3+
[![All Contributors](https://img.shields.io/badge/all_contributors-28-orange.svg?style=flat-square)](#contributors-)
44
<!-- ALL-CONTRIBUTORS-BADGE:END -->
55

66
Low-level Tokio SSH2 client and server implementation.
@@ -109,6 +109,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
109109
<td align="center" valign="top" width="14.28%"><a href="http://samlikes.pizza/"><img src="https://avatars.githubusercontent.com/u/226872?v=4?s=100" width="100px;" alt="Samuel Ainsworth"/><br /><sub><b>Samuel Ainsworth</b></sub></a><br /><a href="https://github.com/warp-tech/russh/commits?author=samuela" title="Code">💻</a></td>
110110
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Sherlock-Holo"><img src="https://avatars.githubusercontent.com/u/10096425?v=4?s=100" width="100px;" alt="Sherlock Holo"/><br /><sub><b>Sherlock Holo</b></sub></a><br /><a href="https://github.com/warp-tech/russh/commits?author=sherlock-holo" title="Code">💻</a></td>
111111
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ricott1"><img src="https://avatars.githubusercontent.com/u/16502243?v=4?s=100" width="100px;" alt="Alessandro Ricottone"/><br /><sub><b>Alessandro Ricottone</b></sub></a><br /><a href="https://github.com/warp-tech/russh/commits?author=ricott1" title="Code">💻</a></td>
112+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/T0b1-iOS"><img src="https://avatars.githubusercontent.com/u/15174814?v=4?s=100" width="100px;" alt="T0b1-iOS"/><br /><sub><b>T0b1-iOS</b></sub></a><br /><a href="https://github.com/warp-tech/russh/commits?author=T0b1-iOS" title="Code">💻</a></td>
112113
</tr>
113114
</tbody>
114115
</table>

russh/src/client/encrypted.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ impl Session {
6060
} else if let Some(exchange) = enc.exchange.take() {
6161
Some(KexInit::received_rekey(
6262
exchange,
63-
negotiation::Client::read_kex(buf, &self.common.config.as_ref().preferred)?,
63+
negotiation::Client::read_kex(
64+
buf,
65+
&self.common.config.as_ref().preferred,
66+
None,
67+
)?,
6468
&enc.session_id,
6569
))
6670
} else {

russh/src/client/kex.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl KexInit {
2121
// read algorithms from packet.
2222
debug!("extending {:?}", &self.exchange.server_kex_init[..]);
2323
self.exchange.server_kex_init.extend(buf);
24-
negotiation::Client::read_kex(buf, &config.preferred)?
24+
negotiation::Client::read_kex(buf, &config.preferred, None)?
2525
};
2626
debug!("algo = {:?}", algo);
2727
debug!("write = {:?}", &write_buffer.buffer[..]);

russh/src/lib.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,8 @@
9494
//! messages sent through a `server::Handle` are processed when there
9595
//! is no incoming packet to read.
9696
97-
use std::{
98-
convert::TryFrom,
99-
fmt::{Debug, Display, Formatter},
100-
};
97+
use std::convert::TryFrom;
98+
use std::fmt::{Debug, Display, Formatter};
10199

102100
use log::debug;
103101
use parsing::ChannelOpenConfirmation;

russh/src/negotiation.rs

Lines changed: 67 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ pub struct Preferred {
5555
pub compression: &'static [&'static str],
5656
}
5757

58+
impl Preferred {
59+
pub(crate) fn possible_host_key_algos_for_keys(
60+
&self,
61+
available_host_keys: &[KeyPair],
62+
) -> Vec<&'static key::Name> {
63+
self.key
64+
.iter()
65+
.filter(|n| available_host_keys.iter().any(|k| k.name() == n.0))
66+
.collect::<Vec<_>>()
67+
}
68+
}
69+
5870
const SAFE_KEX_ORDER: &[kex::Name] = &[
5971
kex::CURVE25519,
6072
kex::CURVE25519_PRE_RFC_8731,
@@ -158,19 +170,28 @@ pub(crate) trait Select {
158170

159171
fn select<S: AsRef<str> + Copy>(a: &[S], b: &[u8]) -> Option<(bool, S)>;
160172

161-
fn read_kex(buffer: &[u8], pref: &Preferred) -> Result<Names, Error> {
173+
/// `available_host_keys`, if present, is used to limit the host key algorithms to the ones we have keys for.
174+
fn read_kex(
175+
buffer: &[u8],
176+
pref: &Preferred,
177+
available_host_keys: Option<&[KeyPair]>,
178+
) -> Result<Names, Error> {
162179
let mut r = buffer.reader(17);
180+
181+
// Key exchange
182+
163183
let kex_string = r.read_string()?;
164-
let (kex_both_first, kex_algorithm) = if let Some(x) = Self::select(pref.kex, kex_string) {
165-
x
166-
} else {
184+
let (kex_both_first, kex_algorithm) = Self::select(pref.kex, kex_string).ok_or_else(||
185+
{
167186
debug!(
168187
"Could not find common kex algorithm, other side only supports {:?}, we only support {:?}",
169188
from_utf8(kex_string),
170189
pref.kex
171190
);
172-
return Err(Error::NoCommonKexAlgo);
173-
};
191+
Error::NoCommonKexAlgo
192+
})?;
193+
194+
// Strict kex detection
174195

175196
let strict_kex_requested = pref.kex.contains(if Self::is_server() {
176197
&EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER
@@ -190,35 +211,42 @@ pub(crate) trait Select {
190211
debug!("strict kex enabled")
191212
}
192213

193-
let key_string = r.read_string()?;
194-
let (key_both_first, key_algorithm) = if let Some(x) = Self::select(pref.key, key_string) {
195-
x
196-
} else {
197-
debug!(
198-
"Could not find common key algorithm, other side only supports {:?}, we only support {:?}",
199-
from_utf8(key_string),
200-
pref.key
201-
);
202-
return Err(Error::NoCommonKeyAlgo);
214+
// Host key
215+
216+
let key_string: &[u8] = r.read_string()?;
217+
let possible_host_key_algos = match available_host_keys {
218+
Some(available_host_keys) => pref.possible_host_key_algos_for_keys(available_host_keys),
219+
None => pref.key.iter().collect::<Vec<_>>(),
203220
};
204221

222+
let (key_both_first, key_algorithm) =
223+
Self::select(&possible_host_key_algos[..], key_string).ok_or_else(|| {
224+
debug!(
225+
"Could not find common key algorithm, other side only supports {:?}, we only support {:?}",
226+
from_utf8(key_string),
227+
pref.key
228+
);
229+
Error::NoCommonKeyAlgo
230+
})?;
231+
232+
// Cipher
233+
205234
let cipher_string = r.read_string()?;
206-
let cipher = Self::select(pref.cipher, cipher_string);
207-
if cipher.is_none() {
208-
debug!(
235+
let (_cipher_both_first, cipher) =
236+
Self::select(pref.cipher, cipher_string).ok_or_else(|| {
237+
debug!(
209238
"Could not find common cipher, other side only supports {:?}, we only support {:?}",
210239
from_utf8(cipher_string),
211240
pref.cipher
212241
);
213-
return Err(Error::NoCommonCipher);
214-
}
242+
Error::NoCommonCipher
243+
})?;
215244
r.read_string()?; // cipher server-to-client.
216245
debug!("kex {}", line!());
217246

218-
let need_mac = cipher
219-
.and_then(|x| CIPHERS.get(&x.1))
220-
.map(|x| x.needs_mac())
221-
.unwrap_or(false);
247+
// MAC
248+
249+
let need_mac = CIPHERS.get(&cipher).map(|x| x.needs_mac()).unwrap_or(false);
222250

223251
let client_mac = if let Some((_, m)) = Self::select(pref.mac, r.read_string()?) {
224252
m
@@ -235,6 +263,8 @@ pub(crate) trait Select {
235263
mac::NONE
236264
};
237265

266+
// Compression
267+
238268
debug!("kex {}", line!());
239269
// client-to-server compression.
240270
let client_compression =
@@ -256,23 +286,18 @@ pub(crate) trait Select {
256286
r.read_string()?; // languages server-to-client
257287

258288
let follows = r.read_byte()? != 0;
259-
match (cipher, follows) {
260-
(Some((_, cipher)), fol) => {
261-
Ok(Names {
262-
kex: kex_algorithm,
263-
key: key_algorithm,
264-
cipher,
265-
client_mac,
266-
server_mac,
267-
client_compression,
268-
server_compression,
269-
// Ignore the next packet if (1) it follows and (2) it's not the correct guess.
270-
ignore_guessed: fol && !(kex_both_first && key_both_first),
271-
strict_kex: strict_kex_requested && strict_kex_provided,
272-
})
273-
}
274-
_ => Err(Error::KexInit),
275-
}
289+
Ok(Names {
290+
kex: kex_algorithm,
291+
key: *key_algorithm,
292+
cipher,
293+
client_mac,
294+
server_mac,
295+
client_compression,
296+
server_compression,
297+
// Ignore the next packet if (1) it follows and (2) it's not the correct guess.
298+
ignore_guessed: follows && !(kex_both_first && key_both_first),
299+
strict_kex: strict_kex_requested && strict_kex_provided,
300+
})
276301
}
277302
}
278303

russh/src/server/encrypted.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ impl Session {
6161
} else if let Some(exchange) = enc.exchange.take() {
6262
let kexinit = KexInit::received_rekey(
6363
exchange,
64-
negotiation::Server::read_kex(buf, &self.common.config.as_ref().preferred)?,
64+
negotiation::Server::read_kex(
65+
buf,
66+
&self.common.config.as_ref().preferred,
67+
Some(&self.common.config.as_ref().keys),
68+
)?,
6569
&enc.session_id,
6670
);
6771
enc.rekey = Some(kexinit.server_parse(

russh/src/server/kex.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl KexInit {
2626
let algo = {
2727
// read algorithms from packet.
2828
self.exchange.client_kex_init.extend(buf);
29-
super::negotiation::Server::read_kex(buf, &config.preferred)?
29+
super::negotiation::Server::read_kex(buf, &config.preferred, Some(&config.keys))?
3030
};
3131
if !self.sent {
3232
self.server_write(config, cipher, write_buffer)?

0 commit comments

Comments
 (0)