use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::vec::Vec;
use std::time::{Duration, Instant};
use purecrypto::rng::{CryptoRng, OsRng, RngCore};
use crate::client::AlgoOverrides;
use crate::error::{Error, Result};
use crate::hostkey::HostKeyVerify;
use crate::transport::ping::{SSH_MSG_PING, SSH_MSG_PONG, pong_for_ping};
use crate::transport::rekey::{RekeyPolicy, is_kex_msg, may_send_during_kex};
use crate::transport::{ExtInfo, KexRunner, PacketCodec, Role, VersionExchange};
use super::{
Event, MAX_BANNER_LINE, MAX_BANNER_LINES, MAX_BANNER_TOTAL_BYTES, MAX_DEFERRED_OUT_BYTES,
MAX_INBOX_BYTES, SSH_MSG_EXT_INFO, SSH_MSG_KEX_ECDH_REPLY, SSH_MSG_KEXINIT, defer_inbound,
generic_msg_during_initial_kex, keepalive_request,
};
const SSH_MSG_KEX_DH_GEX_REPLY: u8 = 33;
pub type VerifierFactory =
Box<dyn FnMut(&[u8], &KexRunner) -> Result<Box<dyn HostKeyVerify>> + Send>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
AwaitingVersion,
Kex,
PostKex,
}
pub struct ClientDriver {
phase: Phase,
codec: PacketCodec,
runner: KexRunner,
rng: OsRng,
inbox: Vec<u8>,
outbox: VecDeque<Vec<u8>>,
events: VecDeque<Event>,
deferred: VecDeque<Vec<u8>>,
deferred_bytes: usize,
deferred_out: VecDeque<Vec<u8>>,
deferred_out_bytes: usize,
v_s: Vec<u8>,
session_id: Vec<u8>,
algo_overrides: AlgoOverrides,
verifier_factory: VerifierFactory,
rekey_policy: RekeyPolicy,
last_kex: Option<Instant>,
keepalive: Option<(Duration, u32)>,
last_activity: Instant,
missed_keepalives: u32,
banner_lines: usize,
banner_total: usize,
authenticated: bool,
kex_observer: Option<KexObserver>,
}
pub type KexObserver = alloc::sync::Arc<dyn Fn(&crate::transport::Negotiated) + Send + Sync>;
impl ClientDriver {
pub fn new(algo_overrides: AlgoOverrides, verifier_factory: VerifierFactory) -> Self {
let mut rng = OsRng;
let placeholder = crate::client::build_default_kexinit(&mut rng, &algo_overrides);
Self {
phase: Phase::AwaitingVersion,
codec: PacketCodec::new(),
runner: KexRunner::new(Role::Client, placeholder),
rng,
inbox: Vec::new(),
outbox: VecDeque::new(),
events: VecDeque::new(),
authenticated: false,
deferred: VecDeque::new(),
deferred_bytes: 0,
deferred_out: VecDeque::new(),
deferred_out_bytes: 0,
v_s: Vec::new(),
session_id: Vec::new(),
algo_overrides,
verifier_factory,
rekey_policy: RekeyPolicy::default(),
last_kex: None,
keepalive: None,
last_activity: Instant::now(),
missed_keepalives: 0,
banner_lines: 0,
banner_total: 0,
kex_observer: None,
}
}
pub fn set_kex_observer(&mut self, f: KexObserver) {
self.kex_observer = Some(f);
}
pub fn set_rekey_policy(&mut self, policy: RekeyPolicy) {
self.rekey_policy = policy;
}
pub fn set_keepalive(&mut self, interval: Duration, count_max: u32) {
self.keepalive = Some((interval, count_max));
}
pub fn start(&mut self, now: Instant) -> Result<()> {
self.last_activity = now;
self.outbox.push_back(VersionExchange::outgoing_bytes());
let advert = crate::client::build_default_kexinit(&mut self.rng, &self.algo_overrides)
.with_ext_info_marker(Role::Client);
self.runner = KexRunner::new(Role::Client, advert);
let mut ext = ExtInfo::new();
ext.raw
.push(("ext-info-in-auth@openssh.com".into(), "0".into()));
self.runner.set_outbound_ext_info(ext);
let initial = self.runner.start(&mut self.rng)?;
for p in initial.outbound {
self.enqueue_payload(&p)?;
}
Ok(())
}
pub fn session_id(&self) -> &[u8] {
&self.session_id
}
pub fn initial_host_key_sig(&self) -> Option<(&[u8], &[u8])> {
self.runner.initial_host_key_sig()
}
pub fn peer_ext_info(&self) -> Option<&ExtInfo> {
self.runner.peer_ext_info()
}
pub fn traffic(&self) -> crate::transport::TrafficStats {
self.codec.traffic
}
pub fn negotiated_compression(&self) -> Option<(String, String)> {
self.runner.negotiated().map(|n| (n.comp_c2s, n.comp_s2c))
}
pub fn negotiated(&self) -> Option<crate::transport::Negotiated> {
self.runner.negotiated()
}
pub fn handshake_done(&self) -> bool {
self.phase == Phase::PostKex
}
pub fn is_kexing(&self) -> bool {
self.runner.is_kexing()
}
pub fn notify_auth_success(&mut self) {
self.codec.activate_compress();
self.authenticated = true;
self.runner.arm_ext_info_post_auth();
}
pub fn enqueue_payload(&mut self, payload: &[u8]) -> Result<()> {
let must_wait = match payload.first() {
Some(&b) => !may_send_during_kex(b),
None => false,
};
if must_wait && self.runner.is_kexing() {
self.deferred_out_bytes += payload.len();
if self.deferred_out_bytes > MAX_DEFERRED_OUT_BYTES {
return Err(Error::Protocol("re-key: deferred egress buffer too large"));
}
self.deferred_out.push_back(payload.to_vec());
return Ok(());
}
self.encode_and_queue(payload)
}
fn encode_and_queue(&mut self, payload: &[u8]) -> Result<()> {
let frame = self.codec.encode(payload, &mut self.rng)?;
self.outbox.push_back(frame);
Ok(())
}
fn drain_deferred_out(&mut self) -> Result<()> {
while let Some(payload) = self.deferred_out.pop_front() {
self.encode_and_queue(&payload)?;
}
self.deferred_out_bytes = 0;
Ok(())
}
pub fn poll_transmit(&mut self) -> Option<Vec<u8>> {
self.outbox.pop_front()
}
pub fn has_pending_events(&self) -> bool {
!self.events.is_empty()
}
pub fn poll_event(&mut self) -> Option<Event> {
self.events.pop_front()
}
pub fn handle_input(&mut self, bytes: &[u8], now: Instant) -> Result<()> {
self.inbox.extend_from_slice(bytes);
if self.inbox.len() > MAX_INBOX_BYTES {
return Err(Error::Protocol("inbound buffer too large"));
}
if self.phase == Phase::AwaitingVersion && !self.scan_peer_version()? {
return Ok(()); }
loop {
match self.codec.decode(&self.inbox)? {
Some((payload, consumed)) => {
self.inbox.drain(..consumed);
self.route_packet(&payload, now)?;
}
None => return Ok(()),
}
}
}
pub fn handle_timeout(&mut self, now: Instant) -> Result<()> {
if self.runner.is_kexing() {
return Ok(());
}
if self.authenticated
&& let Some(last) = self.last_kex
&& self.rekey_policy.should_rekey(&self.codec, last, now)
{
self.initiate_rekey()?;
return Ok(());
}
if let Some((interval, count_max)) = self.keepalive
&& now.duration_since(self.last_activity) >= interval
{
if self.missed_keepalives >= count_max {
return Err(Error::Protocol("keepalive: no response from peer"));
}
let probe = keepalive_request();
self.enqueue_payload(&probe)?;
self.missed_keepalives += 1;
self.last_activity = now;
}
Ok(())
}
pub fn next_timeout(&self) -> Option<Instant> {
self.keepalive
.map(|(interval, _)| self.last_activity + interval)
}
fn scan_peer_version(&mut self) -> Result<bool> {
loop {
let Some(pos) = self.inbox.iter().position(|&b| b == b'\n') else {
if self.inbox.len() > MAX_BANNER_LINE {
return Err(Error::Protocol("banner line too long"));
}
return Ok(false);
};
let line: Vec<u8> = self.inbox.drain(..=pos).collect();
self.banner_total = self.banner_total.saturating_add(line.len());
if self.banner_total > MAX_BANNER_TOTAL_BYTES {
return Err(Error::Protocol("banner too large"));
}
if line.starts_with(b"SSH-") {
let parsed = VersionExchange::parse_remote(&line)?;
self.v_s = parsed.into_bytes();
self.phase = Phase::Kex;
return Ok(true);
}
self.banner_lines += 1;
if self.banner_lines > MAX_BANNER_LINES {
return Err(Error::Protocol("peer banner too long"));
}
}
}
fn route_packet(&mut self, payload: &[u8], now: Instant) -> Result<()> {
self.note_activity(now);
let initial_kex = self.phase == Phase::Kex;
match payload.first().copied() {
Some(1) => Err(super::disconnect_error(payload)),
Some(2) | Some(3) | Some(4) | Some(SSH_MSG_PING) | Some(SSH_MSG_PONG)
if initial_kex =>
{
generic_msg_during_initial_kex(self.runner.strict_kex_enabled())
}
Some(2) | Some(3) | Some(4) => Ok(()),
Some(SSH_MSG_PING) => {
let pong = pong_for_ping(payload)?;
self.enqueue_payload(&pong)
}
Some(SSH_MSG_PONG) => Ok(()),
Some(SSH_MSG_EXT_INFO) => {
if !self.authenticated && self.phase != Phase::Kex {
self.runner.arm_ext_info_post_auth();
}
if !self.runner.may_accept_ext_info() {
return Err(Error::Protocol("unexpected SSH_MSG_EXT_INFO"));
}
self.runner.handle_inbound_ext_info(payload)
}
Some(b) if is_kex_msg(b) => {
if b == SSH_MSG_KEXINIT && !self.runner.is_kexing() {
self.initiate_rekey()?;
}
self.route_kex(payload)?;
if self.runner.is_completed() {
if self.phase == Phase::Kex {
self.session_id = self
.runner
.session_id()
.ok_or(Error::Protocol("kex: missing session id"))?
.to_vec();
self.phase = Phase::PostKex;
self.events.push_back(Event::HandshakeComplete);
}
self.last_kex = Some(now);
if let (Some(f), Some(n)) = (&self.kex_observer, self.runner.negotiated()) {
f(&n);
}
self.drain_deferred_out()?;
self.drain_deferred()?;
}
Ok(())
}
_ => {
if initial_kex {
return Err(Error::Protocol("non-KEX message before first NEWKEYS"));
}
if self.runner.is_kexing() {
if !self.runner.awaiting_peer_kexinit() {
return Err(Error::Protocol("non-KEX message during key re-exchange"));
}
return defer_inbound(&mut self.deferred, &mut self.deferred_bytes, payload);
}
self.runner.note_inbound_other();
self.route_app(payload)
}
}
}
fn route_kex(&mut self, payload: &[u8]) -> Result<()> {
let msg = *payload.first().ok_or(Error::Format("empty kex payload"))?;
let reply = if self.runner.uses_gex() {
SSH_MSG_KEX_DH_GEX_REPLY
} else {
SSH_MSG_KEX_ECDH_REPLY
};
let verifier: Option<Box<dyn HostKeyVerify>> = if msg == reply {
Some((self.verifier_factory)(payload, &self.runner)?)
} else {
None
};
let v_c = crate::transport::version::LOCAL_VERSION.as_bytes().to_vec();
let v_s = self.v_s.clone();
let adv = self.runner.on_packet(
&mut self.rng,
&mut self.codec,
payload,
None,
verifier.as_deref(),
&v_c,
&v_s,
)?;
for p in adv.outbound {
self.enqueue_payload(&p)?;
}
Ok(())
}
fn drain_deferred(&mut self) -> Result<()> {
while !self.runner.is_kexing() {
let Some(payload) = self.deferred.pop_front() else {
break;
};
self.deferred_bytes = self.deferred_bytes.saturating_sub(payload.len());
self.runner.note_inbound_other();
self.route_app(&payload)?;
}
Ok(())
}
fn route_app(&mut self, payload: &[u8]) -> Result<()> {
self.events.push_back(Event::AppData(payload.to_vec()));
Ok(())
}
fn initiate_rekey(&mut self) -> Result<()> {
let advert = crate::client::build_default_kexinit(&mut self.rng, &self.algo_overrides);
let adv = self.runner.restart(&mut self.rng, advert)?;
for p in adv.outbound {
self.enqueue_payload(&p)?;
}
Ok(())
}
pub fn request_rekey(&mut self) -> Result<()> {
if self.runner.is_kexing() || !self.runner.is_completed() {
return Ok(());
}
self.initiate_rekey()
}
fn note_activity(&mut self, now: Instant) {
self.last_activity = now;
self.missed_keepalives = 0;
}
#[cfg(test)]
pub(crate) fn codec_mut(&mut self) -> &mut PacketCodec {
&mut self.codec
}
#[cfg(test)]
pub(crate) fn force_rekey(&mut self) -> Result<()> {
self.initiate_rekey()
}
}
const _: fn() = || {
fn _assert<T: CryptoRng + RngCore>() {}
_assert::<OsRng>();
};
#[cfg(all(test, feature = "server"))]
mod tests {
use super::*;
use std::io::{Read as _, Write as _};
use std::net::TcpStream;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use crate::auth::{AuthAttempt, AuthDecision, Authenticator, ClientAuth, ClientCredential};
use crate::channel::{ChannelEvent, ChannelOpen, ChannelRequest, ConnectionState};
use crate::hostkey::{Ed25519HostKey, HostKey, host_key_verify_by_name};
use crate::server::{
AuthenticatorFactory, CommandHandler, Config as ServerConfig, ExecResult, Server,
SessionEnv,
};
struct OneKeyAuth {
user: String,
blob: Vec<u8>,
}
impl Authenticator for OneKeyAuth {
fn evaluate(&mut self, attempt: AuthAttempt) -> AuthDecision {
match attempt {
AuthAttempt::PublicKey {
user,
public_blob,
probe_only,
verified,
..
} => {
if user != self.user || public_blob != self.blob {
return AuthDecision::Reject;
}
if probe_only {
return AuthDecision::Accept;
}
if verified {
AuthDecision::Accept
} else {
AuthDecision::Reject
}
}
_ => AuthDecision::Reject,
}
}
}
struct StaticHandler {
out: Vec<u8>,
}
impl CommandHandler for StaticHandler {
fn handle(&self, _user: &str, _env: &SessionEnv, _command: &str) -> ExecResult {
ExecResult {
stdout: self.out.clone(),
stderr: Vec::new(),
exit_status: 0,
}
}
}
fn fresh_seed() -> [u8; 32] {
let mut s = [0u8; 32];
OsRng.fill_bytes(&mut s);
s
}
fn accept_any_factory() -> VerifierFactory {
Box::new(|reply: &[u8], runner: &KexRunner| {
if reply.len() < 5 {
return Err(Error::Format("kex-ecdh-reply too short"));
}
let k_s_len = u32::from_be_bytes([reply[1], reply[2], reply[3], reply[4]]) as usize;
if reply.len() < 5 + k_s_len {
return Err(Error::Format("kex-ecdh-reply truncated"));
}
let k_s = &reply[5..5 + k_s_len];
let neg = runner
.negotiated()
.ok_or(Error::Protocol("kex: no negotiated algorithms"))?;
host_key_verify_by_name(&neg.host_key, k_s)
})
}
#[test]
fn driver_handshake_auth_exec_round_trip() {
let host_seed = fresh_seed();
let client_seed = fresh_seed();
let client_blob = Ed25519HostKey::from_seed(client_seed).public_blob();
let user = "driver-user".to_string();
let expected = b"hello from sans-io driver\n".to_vec();
let host_key: Box<dyn HostKey + Send + Sync> =
Box::new(Ed25519HostKey::from_seed(host_seed));
let u = user.clone();
let b = client_blob.clone();
let factory: Arc<dyn AuthenticatorFactory> = Arc::new(move || {
Box::new(OneKeyAuth {
user: u.clone(),
blob: b.clone(),
}) as Box<dyn Authenticator>
});
let cfg = ServerConfig::new(
vec![host_key],
factory,
vec!["publickey"],
Arc::new(StaticHandler {
out: expected.clone(),
}),
);
let mut server = Server::bind("127.0.0.1:0", cfg).expect("bind");
let addr = server.local_addr().expect("addr");
let done = Arc::new(Mutex::new(false));
let d2 = done.clone();
let server_thread = thread::spawn(move || {
let _ = server.accept_one();
*d2.lock().unwrap() = true;
});
let mut sock = TcpStream::connect(addr).expect("connect");
sock.set_read_timeout(Some(Duration::from_millis(50)))
.unwrap();
let mut driver = ClientDriver::new(Default::default(), accept_any_factory());
let mut conn = ConnectionState::new();
driver.start(Instant::now()).expect("start");
let mut auth: Option<ClientAuth> = None;
let mut channel: Option<u32> = None;
let mut stdout = Vec::new();
let mut exit: Option<u32> = None;
let (mut eof_sent, mut close_sent, mut remote_close) = (false, false, false);
macro_rules! flush {
() => {
while let Some(frame) = driver.poll_transmit() {
sock.write_all(&frame).expect("write");
}
};
}
'pump: for _ in 0..100_000 {
flush!();
while let Some(ev) = driver.poll_event() {
match ev {
Event::HandshakeComplete => {
let mut a = ClientAuth::new(user.clone(), driver.session_id().to_vec());
a.add_credential(ClientCredential::PublicKey(Box::new(
Ed25519HostKey::from_seed(client_seed),
)));
let first = a.start();
driver.enqueue_payload(&first).expect("enq");
auth = Some(a);
}
Event::AppData(payload) if auth.is_some() => {
let a = auth.as_mut().unwrap();
match a.on_packet(&payload).expect("auth on_packet") {
crate::auth::ClientStep::Send(p) => {
driver.enqueue_payload(&p).expect("enq")
}
crate::auth::ClientStep::Success => {
driver.notify_auth_success();
auth = None;
let (id, p) = conn.open(ChannelOpen::Session).expect("open");
driver.enqueue_payload(&p).expect("enq");
channel = Some(id);
}
crate::auth::ClientStep::Failed { .. } => panic!("auth failed"),
crate::auth::ClientStep::Banner { .. }
| crate::auth::ClientStep::Idle => {}
}
}
Event::AppData(payload) => {
let ce = conn.on_packet(&payload).expect("on_packet");
match ce {
ChannelEvent::OpenConfirmed { channel: c } if Some(c) == channel => {
let p = conn
.send_request(
c,
ChannelRequest::Exec {
command: "hi".into(),
},
true,
)
.expect("exec req");
driver.enqueue_payload(&p).expect("enq");
}
ChannelEvent::Data { channel: c, data } if Some(c) == channel => {
stdout.extend_from_slice(&data);
if let Some(adj) =
conn.replenish_window(c, data.len() as u32).expect("win")
{
driver.enqueue_payload(&adj).expect("enq");
}
}
ChannelEvent::Request {
channel: c,
request,
want_reply,
} if Some(c) == channel => {
if let ChannelRequest::ExitStatus { code } = request {
exit = Some(code);
}
if want_reply {
let p = conn.send_request_failure(c).expect("rf");
driver.enqueue_payload(&p).expect("enq");
}
}
ChannelEvent::Eof { channel: c } if Some(c) == channel && !eof_sent => {
let p = conn.send_eof(c).expect("eof");
driver.enqueue_payload(&p).expect("enq");
eof_sent = true;
}
ChannelEvent::Close { channel: c } if Some(c) == channel => {
remote_close = true;
if !close_sent {
let p = conn.send_close(c).expect("close");
driver.enqueue_payload(&p).expect("enq");
close_sent = true;
}
}
_ => {}
}
}
}
}
flush!();
if remote_close && close_sent {
break 'pump;
}
let mut buf = [0u8; 16 * 1024];
match sock.read(&mut buf) {
Ok(0) => break 'pump,
Ok(n) => driver
.handle_input(&buf[..n], Instant::now())
.expect("input"),
Err(e)
if e.kind() == std::io::ErrorKind::WouldBlock
|| e.kind() == std::io::ErrorKind::TimedOut =>
{
driver.handle_timeout(Instant::now()).expect("timeout");
}
Err(e) => panic!("read error: {e}"),
}
}
assert_eq!(
stdout, expected,
"exec stdout round-trips through the driver"
);
assert_eq!(exit, Some(0), "exit status captured");
drop(sock);
let start = std::time::Instant::now();
while !*done.lock().unwrap() {
if start.elapsed() > Duration::from_secs(10) {
panic!("server did not finish");
}
thread::sleep(Duration::from_millis(20));
}
let _ = server_thread.join();
}
#[test]
fn egress_during_kex_defers_connection_layer_but_not_transport() {
let mut d = ClientDriver::new(
AlgoOverrides::default(),
Box::new(|_p, _r| Err(Error::Protocol("no verifier in this test"))),
);
d.start(Instant::now()).expect("start");
assert!(d.is_kexing(), "start() leaves a KEX in flight");
while d.poll_transmit().is_some() {}
d.enqueue_payload(&[93, 0, 0, 0, 1, 0, 0, 0x40, 0])
.expect("enqueue window adjust");
assert!(
d.poll_transmit().is_none(),
"CHANNEL_WINDOW_ADJUST must not reach the wire during a KEX"
);
d.enqueue_payload(&[2, 0, 0, 0, 0]).expect("enqueue IGNORE");
assert!(
d.poll_transmit().is_some(),
"transport-layer messages stay allowed during a KEX"
);
}
fn cleartext(payload: &[u8]) -> Vec<u8> {
PacketCodec::new()
.encode(payload, &mut OsRng)
.expect("cleartext frame")
}
fn peer_server_kexinit(strict: bool) -> Vec<u8> {
use crate::transport::KexInit;
use crate::transport::kex::{KexAlgorithms, STRICT_KEX_SERVER_MARKER};
let kex: &[&str] = if strict {
&["curve25519-sha256", STRICT_KEX_SERVER_MARKER]
} else {
&["curve25519-sha256"]
};
let algs = KexAlgorithms {
kex,
server_host_key: &["ssh-ed25519"],
ciphers_c2s: &["chacha20-poly1305@openssh.com"],
ciphers_s2c: &["chacha20-poly1305@openssh.com"],
macs_c2s: &["hmac-sha2-256"],
macs_s2c: &["hmac-sha2-256"],
comp_c2s: &["none"],
comp_s2c: &["none"],
lang_c2s: &[],
lang_s2c: &[],
};
KexInit::from_algorithms(&algs, [7u8; 16]).encode()
}
fn client_in_initial_kex() -> ClientDriver {
let mut d = ClientDriver::new(
AlgoOverrides::default(),
Box::new(|_p, _r| Err(Error::Protocol("no verifier in this test"))),
);
d.start(Instant::now()).expect("start");
while d.poll_transmit().is_some() {}
d.handle_input(b"SSH-2.0-peer\r\n", Instant::now())
.expect("version line");
assert!(d.is_kexing() && !d.handshake_done());
d
}
fn protocol_err(res: Result<()>) -> &'static str {
match res {
Err(Error::Protocol(msg)) => msg,
other => panic!("expected a protocol error, got {other:?}"),
}
}
#[test]
fn initial_kex_rejects_application_packets() {
let mut d = client_in_initial_kex();
let err = protocol_err(d.handle_input(&cleartext(&[50, 0, 0, 0, 0]), Instant::now()));
assert_eq!(err, "non-KEX message before first NEWKEYS");
assert!(
d.poll_event().is_none(),
"nothing surfaced to the application"
);
for strict in [true, false] {
let mut d = client_in_initial_kex();
d.handle_input(&cleartext(&peer_server_kexinit(strict)), Instant::now())
.expect("peer kexinit");
let channel_data = [94, 0, 0, 0, 0, 0, 0, 0, 1, b'x'];
let err = protocol_err(d.handle_input(&cleartext(&channel_data), Instant::now()));
assert_eq!(err, "non-KEX message before first NEWKEYS");
assert!(d.poll_event().is_none());
}
}
#[test]
fn initial_kex_generic_messages_follow_strict_kex() {
let ignore = [2u8, 0, 0, 0, 0];
let debug = [4u8, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let ping = [SSH_MSG_PING, 0, 0, 0, 0];
for msg in [&ignore[..], &debug[..], &ping[..]] {
let mut d = client_in_initial_kex();
d.handle_input(&cleartext(&peer_server_kexinit(true)), Instant::now())
.expect("peer kexinit");
let err = protocol_err(d.handle_input(&cleartext(msg), Instant::now()));
assert_eq!(
err,
"strict-kex: unexpected message during initial key exchange"
);
}
let mut d = client_in_initial_kex();
d.handle_input(&cleartext(&peer_server_kexinit(false)), Instant::now())
.expect("peer kexinit");
while d.poll_transmit().is_some() {} for msg in [&ignore[..], &debug[..], &ping[..]] {
d.handle_input(&cleartext(msg), Instant::now())
.expect("tolerated without strict-kex");
}
assert!(
d.poll_transmit().is_none(),
"no PONG before the connection is keyed"
);
assert!(d.poll_event().is_none());
let err = protocol_err(d.handle_input(&cleartext(&[50, 0, 0, 0, 0]), Instant::now()));
assert_eq!(err, "non-KEX message before first NEWKEYS");
}
#[test]
fn initial_kex_packet_before_kexinit_fails_strict_negotiation() {
let mut d = client_in_initial_kex();
d.handle_input(&cleartext(&[2, 0, 0, 0, 0]), Instant::now())
.expect("IGNORE before KEXINIT is not yet known to be strict");
let err =
protocol_err(d.handle_input(&cleartext(&peer_server_kexinit(true)), Instant::now()));
assert_eq!(err, "strict-kex: KEXINIT was not the first packet");
let mut d = client_in_initial_kex();
d.handle_input(&cleartext(&[2, 0, 0, 0, 0]), Instant::now())
.expect("IGNORE");
d.handle_input(&cleartext(&peer_server_kexinit(false)), Instant::now())
.expect("non-strict KEXINIT after an IGNORE");
}
}