Skip to content

Commit 9568441

Browse files
krutonEugeny
andauthored
fix: strict kex sequence number check should only apply to initial exchange (#577)
Co-authored-by: Eugene <inbox@null.page>
1 parent ed78d80 commit 9568441

4 files changed

Lines changed: 164 additions & 6 deletions

File tree

pageant/src/pageant_impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use byteorder::{BigEndian, ByteOrder};
21
use std::io::IoSlice;
32
use std::mem::size_of;
43
use std::pin::Pin;
54
use std::task::{Context, Poll};
65

6+
use byteorder::{BigEndian, ByteOrder};
77
use bytes::BytesMut;
88
use delegate::delegate;
99
use log::debug;

russh/src/client/kex.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,7 @@ impl ClientKex {
127127
debug!("negotiated algorithms: {names:?}");
128128

129129
// seqno has already been incremented after read()
130-
if (names.strict_kex() || self.cause.is_strict_rekey())
131-
&& !self.cause.is_rekey()
132-
&& input.seqn.0 != 1
133-
{
130+
if names.strict_kex() && !self.cause.is_rekey() && input.seqn.0 != 1 {
134131
return Err(strict_kex_violation(
135132
msg::KEXINIT,
136133
input.seqn.0 as usize - 1,

russh/src/server/kex.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ impl ServerKex {
120120
debug!("negotiated: {names:?}");
121121

122122
// seqno has already been incremented after read()
123-
if names.strict_kex() && input.seqn.0 != 1 {
123+
if names.strict_kex() && !self.cause.is_rekey() && input.seqn.0 != 1 {
124124
return Err(strict_kex_violation(
125125
msg::KEXINIT,
126126
input.seqn.0 as usize - 1,
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
2+
3+
//! Test that strict key exchange is works during initial kex and rekey
4+
//! kex. This test ensures that strict_kex sequence number checking is
5+
//! only applied to the initial key exchange, not to rekey operations.
6+
7+
use std::borrow::Cow;
8+
use std::sync::Arc;
9+
10+
use russh::keys::PrivateKeyWithHashAlg;
11+
use russh::*;
12+
use ssh_key::PrivateKey;
13+
14+
#[tokio::test]
15+
async fn test_rekey_with_strict_kex() {
16+
let _ = env_logger::try_init();
17+
18+
// Generate keys
19+
let client_key =
20+
PrivateKey::random(&mut rand_core::OsRng, ssh_key::Algorithm::Ed25519).unwrap();
21+
22+
// Server config with strict kex enabled
23+
let mut server_config = server::Config::default();
24+
server_config.inactivity_timeout = None;
25+
server_config.auth_rejection_time = std::time::Duration::from_secs(3);
26+
server_config
27+
.keys
28+
.push(PrivateKey::random(&mut rand_core::OsRng, ssh_key::Algorithm::Ed25519).unwrap());
29+
30+
// Enable strict kex by including the strict kex extension
31+
server_config.preferred = {
32+
let mut p = Preferred::default();
33+
// Include the strict kex extension marker for server
34+
p.kex = Cow::Borrowed(&[kex::CURVE25519, kex::EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER]);
35+
p
36+
};
37+
38+
let server_config = Arc::new(server_config);
39+
40+
// Setup server
41+
let socket = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
42+
let addr = socket.local_addr().unwrap();
43+
44+
tokio::spawn(async move {
45+
let (socket, _) = socket.accept().await.unwrap();
46+
server::run_stream(server_config, socket, TestServer {})
47+
.await
48+
.unwrap();
49+
});
50+
51+
// Client config with strict kex enabled
52+
let mut client_config = client::Config::default();
53+
client_config.preferred = {
54+
let mut p = Preferred::default();
55+
// Include the strict kex extension marker for client
56+
p.kex = Cow::Borrowed(&[kex::CURVE25519, kex::EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT]);
57+
p
58+
};
59+
let client_config = Arc::new(client_config);
60+
61+
// Connect and authenticate
62+
let mut session = client::connect(client_config, addr, TestClient {})
63+
.await
64+
.unwrap();
65+
66+
let authenticated = session
67+
.authenticate_publickey(
68+
std::env::var("USER").unwrap_or("user".to_owned()),
69+
PrivateKeyWithHashAlg::new(Arc::new(client_key), None),
70+
)
71+
.await
72+
.unwrap()
73+
.success();
74+
assert!(authenticated);
75+
76+
// Open a channel and send some data
77+
let mut channel = session.channel_open_session().await.unwrap();
78+
channel.data(&b"before rekey"[..]).await.unwrap();
79+
80+
// Wait for response
81+
let msg = channel.wait().await.unwrap();
82+
match msg {
83+
ChannelMsg::Data { data } => {
84+
assert_eq!(&*data, b"before rekey");
85+
}
86+
msg => panic!("Unexpected message before rekey: {:?}", msg),
87+
}
88+
89+
session.rekey_soon().await.unwrap();
90+
91+
// Give rekey time to complete
92+
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
93+
94+
// Send data after rekey to ensure connection still works
95+
// If the rekey failed due to strict_kex violation, this would fail
96+
channel.data(&b"after rekey"[..]).await.unwrap();
97+
98+
let msg = channel.wait().await.unwrap();
99+
match msg {
100+
ChannelMsg::Data { data } => {
101+
assert_eq!(&*data, b"after rekey");
102+
}
103+
msg => panic!("Unexpected message after rekey: {:?}", msg),
104+
}
105+
106+
// Close the channel
107+
channel.eof().await.unwrap();
108+
session
109+
.disconnect(Disconnect::ByApplication, "", "")
110+
.await
111+
.unwrap();
112+
}
113+
114+
#[derive(Clone)]
115+
struct TestServer {}
116+
117+
// Insecure server that accepts any public key and echos back data it receives; ONLY FOR TESTS
118+
impl server::Handler for TestServer {
119+
type Error = russh::Error;
120+
121+
async fn auth_publickey(
122+
&mut self,
123+
_user: &str,
124+
_public_key: &ssh_key::PublicKey,
125+
) -> Result<server::Auth, Self::Error> {
126+
Ok(server::Auth::Accept)
127+
}
128+
129+
async fn channel_open_session(
130+
&mut self,
131+
_channel: Channel<server::Msg>,
132+
_session: &mut server::Session,
133+
) -> Result<bool, Self::Error> {
134+
Ok(true)
135+
}
136+
137+
async fn data(
138+
&mut self,
139+
channel: ChannelId,
140+
data: &[u8],
141+
session: &mut server::Session,
142+
) -> Result<(), Self::Error> {
143+
// Echo back the data
144+
session.data(channel, CryptoVec::from_slice(data))?;
145+
Ok(())
146+
}
147+
}
148+
149+
struct TestClient {}
150+
151+
// Insecure client that accept any server key; ONLY FOR TEST
152+
impl client::Handler for TestClient {
153+
type Error = russh::Error;
154+
155+
async fn check_server_key(
156+
&mut self,
157+
_server_public_key: &ssh_key::PublicKey,
158+
) -> Result<bool, Self::Error> {
159+
Ok(true)
160+
}
161+
}

0 commit comments

Comments
 (0)