From ab692bc9c0b067bcb070ee079bcef8e474436de5 Mon Sep 17 00:00:00 2001 From: Matt Johnston <matt@ucc.asn.au> Date: Sun, 28 Aug 2022 00:10:48 +0800 Subject: [PATCH] progress on servauth --- .rustfmt.toml | 5 +- async/examples/con1.rs | 15 ++-- async/examples/serv1.rs | 12 ++-- async/src/async_door.rs | 1 - async/src/cmdline_client.rs | 133 ++++++++++++++++++------------------ async/src/lib.rs | 3 +- async/src/pty.rs | 2 +- sshproto/src/behaviour.rs | 15 ++-- sshproto/src/client.rs | 29 ++++---- sshproto/src/config.rs | 2 +- sshproto/src/conn.rs | 28 +++++--- sshproto/src/error.rs | 14 ++-- sshproto/src/kex.rs | 32 ++++----- sshproto/src/lib.rs | 4 +- sshproto/src/packets.rs | 6 +- sshproto/src/runner.rs | 6 +- sshproto/src/servauth.rs | 125 ++++++++++++++++++++++++++++++++- sshproto/src/server.rs | 31 +++++++-- sshproto/src/sign.rs | 7 +- sshproto/src/sshnames.rs | 1 + sshproto/src/traffic.rs | 44 ++---------- 21 files changed, 325 insertions(+), 190 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index 54026e1..f5ac0a7 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,7 +1,8 @@ max_width = 85 use_small_heuristics = "Max" -# fn_args_layout = "Compressed" edition = "2021" -# unstable + +## perhaps use once stabilised + # trailing_semicolon = false # overflow_delimited_expr = true diff --git a/async/examples/con1.rs b/async/examples/con1.rs index 673e673..e903f9c 100644 --- a/async/examples/con1.rs +++ b/async/examples/con1.rs @@ -145,15 +145,20 @@ async fn run(args: &Args) -> Result<()> { // Connect to a peer let mut stream = TcpStream::connect((args.host.as_str(), args.port)).await?; + let mut rxbuf = vec![0; 3000]; + let mut txbuf = vec![0; 3000]; + let mut cli = SSHClient::new(&mut rxbuf, &mut txbuf)?; + // app is a Behaviour - let mut app = door_async::CmdlineClient::new(args.username.as_ref().unwrap()); + let mut app = door_async::CmdlineClient::new( + args.username.as_ref().unwrap(), + cmd, + wantpty, + ); for i in &args.identityfile { app.add_authkey(read_key(&i).with_context(|| format!("loading key {i}"))?); } - let mut rxbuf = vec![0; 3000]; - let mut txbuf = vec![0; 3000]; - let mut cli = SSHClient::new(&mut rxbuf, &mut txbuf)?; let mut s = cli.socket(); @@ -164,6 +169,8 @@ async fn run(args: &Args) -> Result<()> { loop { cli.progress(&mut app).await.context("progress loop")?; + app.progress(&mut cli).await?; + // match ev { // Some(Event::CliAuthed) => { // let mut raw_pty_guard = None; diff --git a/async/examples/serv1.rs b/async/examples/serv1.rs index 031e822..663b1bd 100644 --- a/async/examples/serv1.rs +++ b/async/examples/serv1.rs @@ -115,9 +115,8 @@ impl DemoServer { } } -#[async_trait] -impl AsyncServBehaviour for DemoServer { - async fn hostkeys(&mut self) -> BhResult<&[SignKey]> { +impl ServBehaviour for DemoServer { + fn hostkeys(&mut self) -> BhResult<&[SignKey]> { Ok(&self.keys) } @@ -130,7 +129,7 @@ impl AsyncServBehaviour for DemoServer { false } - async fn auth_password(&mut self, user: &str, password: &str) -> bool { + fn auth_password(&mut self, user: &str, password: &str) -> bool { user == "matt" && password == "pw" } @@ -168,10 +167,7 @@ fn run_session<'a, R: Send>(args: &'a Args, scope: &'a moro::Scope<'a, '_, R>, m scope.spawn(async { loop { - let ev = serv.progress(&mut app, |ev| { - trace!("progress event {ev:?}"); - Ok(Some(())) - }).await.context("progress loop")?; + let ev = serv.progress(&mut app).await.context("progress loop")?; } #[allow(unreachable_code)] Ok::<_, anyhow::Error>(()) diff --git a/async/src/async_door.rs b/async/src/async_door.rs index 4306c51..c1231e9 100644 --- a/async/src/async_door.rs +++ b/async/src/async_door.rs @@ -131,7 +131,6 @@ pub(crate) fn poll_lock<'a>(inner: Arc<Mutex<Inner<'a>>>, cx: &mut Context<'_>, Poll::Ready(_) => None, Poll::Pending => Some(g), }; - trace!("poll_lock returned {:?}", p); p } diff --git a/async/src/cmdline_client.rs b/async/src/cmdline_client.rs index a1e40ce..4564e72 100644 --- a/async/src/cmdline_client.rs +++ b/async/src/cmdline_client.rs @@ -12,32 +12,85 @@ use std::collections::VecDeque; use async_trait::async_trait; +use crate::*; +use crate::{raw_pty, RawPtyGuard, SSHClient, ChanInOut, ChanExtIn}; + +enum CmdlineState<'a> { + PreAuth, + Authed, + // TODO split sending the channel open and the request strings + _ChanOpen, + _ChanReq, + Ready { io: ChanInOut<'a>, extin: Option<ChanExtIn<'a>> }, +} + /// Command line interface SSH client behaviour -pub struct CmdlineClient { - auth_done: bool, +pub struct CmdlineClient<'a> { + state: CmdlineState<'a>, main_ch: Option<u32>, + pty_guard: Option<RawPtyGuard>, + authkeys: VecDeque<SignKey>, username: String, + cmd: Option<String>, + want_pty: bool, } -impl CmdlineClient { - pub fn new(username: &impl AsRef<str>) -> Self { +impl<'a> CmdlineClient<'a> { + pub fn new(username: impl AsRef<str>, cmd: Option<impl AsRef<str>>, want_pty: bool) -> Self { CmdlineClient { - auth_done: false, + state: CmdlineState::PreAuth, main_ch: None, + pty_guard: None, + authkeys: VecDeque::new(), username: username.as_ref().into(), + // TODO: shorthand for this? + cmd: cmd.map(|c| c.as_ref().into()), + want_pty, + } + } + + pub async fn progress(&mut self, cli: &mut SSHClient<'a>) -> Result<()> { + match self.state { + CmdlineState::Authed => { + info!("Opening a new session channel"); + self.open_session(cli).await?; + } + | CmdlineState::PreAuth => (), + | CmdlineState::Ready {..} => (), + _ => todo!(), } + Ok(()) + } pub fn add_authkey(&mut self, k: SignKey) { self.authkeys.push_back(k) } + + async fn open_session(&mut self, cli: &mut SSHClient<'a>) -> Result<()> { + debug_assert!(matches!(self.state, CmdlineState::Authed)); + + // TODO expect + // self.pty_guard = Some(raw_pty().expect("raw pty")); + + let cmd = self.cmd.as_ref().map(|s| s.as_str()); + let (io, extin) = if self.want_pty { + let io = cli.open_session_pty(cmd).await?; + (io, None) + } else { + let (io, extin) = cli.open_session_nopty(cmd).await?; + (io, Some(extin)) + }; + self.state = CmdlineState::Ready { io, extin }; + Ok(()) + } } // #[async_trait(?Send)] #[async_trait] -impl door::CliBehaviour for CmdlineClient { +impl door::CliBehaviour for CmdlineClient<'_> { fn username(&mut self) -> BhResult<door::ResponseString> { door::ResponseString::from_str(&self.username).map_err(|_| BhError::Fail) } @@ -69,66 +122,12 @@ impl door::CliBehaviour for CmdlineClient { } fn authenticated(&mut self) { - info!("Authentication succeeded"); - self.auth_done = true; + match self.state { + CmdlineState::PreAuth => { + info!("Authentication succeeded"); + self.state = CmdlineState::Authed; + } + _ => warn!("Unexpected auth response") + } } - // } - - // impl door::BlockCliBehaviour for CmdlineClient { - // fn chan_handler<'f>(&mut self, resp: &mut RespPackets, chan_msg: ChanMsg<'f>) -> Result<()> { - // if Some(chan_msg.num) != self.main_ch { - // return Err(Error::SSHProtoError) - // } - - // match chan_msg.msg { - // ChanMsgDetails::Data(buf) => { - // let _ = std::io::stdout().write_all(buf); - // }, - // ChanMsgDetails::ExtData{..} => { - // } - // ChanMsgDetails::Req{..} => { - // } - // _ => {} - // } - // Ok(()) - // } - - // fn progress(&mut self, runner: &mut Runner) -> Result<()> { - // if self.auth_done { - // if self.main_ch.is_none() { - // let ch = runner.open_client_session(Some("cowsay it works"), false)?; - // self.main_ch = Some(ch); - // } - // } - // Ok(()) - // } - - // fn username(&mut self) -> BhResult<door::ResponseString> { - // // TODO unwrap - // let mut p = door::ResponseString::new(); - // p.push_str("matt").unwrap(); - // Ok(p) - // } - - // fn valid_hostkey(&mut self, key: &door::PubKey) -> BhResult<bool> { - // trace!("valid_hostkey for {key:?}"); - // Ok(true) - // } - - // fn auth_password(&mut self, pwbuf: &mut door::ResponseString) -> BhResult<bool> { - // let pw = rpassword::prompt_password("password: ").map_err(|e| { - // warn!("read_password failed {e:}"); - // BhError::Fail - // })?; - // if pwbuf.push_str(&pw).is_err() { - // Err(BhError::Fail) - // } else { - // Ok(true) - // } - // } - - // fn authenticated(&mut self) { - // info!("Authentication succeeded"); - // self.auth_done = true; - // } } diff --git a/async/src/lib.rs b/async/src/lib.rs index f802913..bda500e 100644 --- a/async/src/lib.rs +++ b/async/src/lib.rs @@ -11,10 +11,11 @@ pub use async_door::AsyncDoor; pub use client::SSHClient; pub use server::SSHServer; pub use cmdline_client::CmdlineClient; +pub use async_channel::{ChanInOut, ChanExtIn, ChanExtOut}; #[cfg(unix)] mod fdio; #[cfg(unix)] pub use fdio::{stdin, stdout, stderr}; -pub use pty::raw_pty; +pub use pty::{raw_pty, RawPtyGuard}; diff --git a/async/src/pty.rs b/async/src/pty.rs index 10a3fa2..b1004ff 100644 --- a/async/src/pty.rs +++ b/async/src/pty.rs @@ -32,7 +32,7 @@ pub fn current_pty() -> Result<Pty, IoError> { let mut term = heapless::String::<MAX_TERM>::new(); let t = std::env::var("TERM").unwrap_or(DEFAULT_TERM.into()); // XXX error - term.push_str(&t).expect("TERM fits buffer"); + term.push_str(&t).expect("$TERM is too long"); let wc = win_size()?; diff --git a/sshproto/src/behaviour.rs b/sshproto/src/behaviour.rs index 75f67af..71e580b 100644 --- a/sshproto/src/behaviour.rs +++ b/sshproto/src/behaviour.rs @@ -43,7 +43,7 @@ pub enum Behaviour<'a> { } impl<'a> Behaviour<'a> { - // TODO take and store a value not a reference + // TODO: make these From<> pub fn new_client(b: &'a mut (dyn CliBehaviour + Send)) -> Self { Self::Client(b) } @@ -164,20 +164,25 @@ pub trait ServBehaviour: Sync+Send { fn hostkeys(&mut self) -> BhResult<&[sign::SignKey]>; // TODO: or return a slice of enums - fn have_auth_password(&self, user: &str) -> bool; - fn have_auth_pubkey(&self, user: &str) -> bool; + fn have_auth_password(&self, username: TextString) -> bool; + fn have_auth_pubkey(&self, username: TextString) -> bool; + #[allow(unused)] + /// Return true to allow the user to log in with no authentication + fn auth_unchallenged(&mut self, username: TextString) -> bool { + false + } #[allow(unused)] // TODO: change password - fn auth_password(&mut self, user: &str, password: &str) -> bool { + fn auth_password(&mut self, username: TextString, password: TextString) -> bool { false } /// Returns true if the pubkey can be used to log in. /// TODO: allow returning pubkey restriction options #[allow(unused)] - fn auth_pubkey(&mut self, user: &str, pubkey: &sign::SignKey) -> bool { + fn auth_pubkey(&mut self, username: TextString, pubkey: &PubKey) -> bool { false } diff --git a/sshproto/src/client.rs b/sshproto/src/client.rs index 2b17b8f..8226692 100644 --- a/sshproto/src/client.rs +++ b/sshproto/src/client.rs @@ -6,14 +6,14 @@ use { use snafu::prelude::*; -use crate::{*, packets::ChannelOpen}; -use packets::{Packet, PubKey, ParseContext}; -use traffic::TrafSend; -use sshnames::*; -use cliauth::CliAuth; -use sign::SignKey; +use crate::{packets::ChannelOpen, *}; use behaviour::CliBehaviour; +use cliauth::CliAuth; use heapless::String; +use packets::{Packet, ParseContext, PubKey}; +use sign::SignKey; +use sshnames::*; +use traffic::TrafSend; pub(crate) struct Client { pub auth: CliAuth, @@ -21,24 +21,27 @@ pub(crate) struct Client { impl Client { pub fn new() -> Self { - Client { - auth: CliAuth::new(), - } + Client { auth: CliAuth::new() } } // pub fn check_hostkey(hostkey: ) - pub(crate) fn auth_success(&mut self, + pub(crate) fn auth_success( + &mut self, parse_ctx: &mut ParseContext, s: &mut TrafSend, - b: &mut dyn CliBehaviour) -> Result<()> { - + b: &mut dyn CliBehaviour, + ) -> Result<()> { parse_ctx.cli_auth_type = None; s.send(packets::ServiceRequest { name: SSH_SERVICE_CONNECTION })?; self.auth.success(b) } - pub(crate) fn banner(&mut self, banner: &packets::UserauthBanner<'_>, b: &mut dyn CliBehaviour) { + pub(crate) fn banner( + &mut self, + banner: &packets::UserauthBanner<'_>, + b: &mut dyn CliBehaviour, + ) { b.show_banner(banner.message, banner.lang) } } diff --git a/sshproto/src/config.rs b/sshproto/src/config.rs index 03ff3bb..3c61839 100644 --- a/sshproto/src/config.rs +++ b/sshproto/src/config.rs @@ -13,5 +13,5 @@ pub const MAX_EXEC: usize = 200; // Unsure if this is specified somewhere pub const MAX_TERM: usize = 32; -pub const DEFAULT_TERM: &str = "vt220"; +pub const DEFAULT_TERM: &str = "xterm"; diff --git a/sshproto/src/conn.rs b/sshproto/src/conn.rs index b11396f..e646783 100644 --- a/sshproto/src/conn.rs +++ b/sshproto/src/conn.rs @@ -103,8 +103,10 @@ impl<'a> Conn<'a> { Self::new(ClientServer::Client(client::Client::new())) } - pub fn new_server() -> Result<Self> { - Self::new(ClientServer::Server(server::Server::new())) + pub fn new_server( + b: &mut dyn ServBehaviour, + ) -> Result<Self> { + Self::new(ClientServer::Server(server::Server::new(b))) } fn new(cliserv: ClientServer) -> Result<Self, Error> { @@ -267,9 +269,13 @@ impl<'a> Conn<'a> { _ => return Err(Error::PacketWrong), } } - Packet::ServiceRequest(_p) => { - // TODO: this is server only - todo!("service request"); + Packet::ServiceRequest(p) => { + if let ClientServer::Server(serv) = &mut self.cliserv { + serv.service_request(&p, s)?; + } else { + debug!("Server sent a service request"); + return Err(Error::SSHProtoError) + } } Packet::ServiceAccept(p) => { // Don't need to do anything, if a request failed the server disconnects @@ -288,12 +294,15 @@ impl<'a> Conn<'a> { // TODO: SSH2_DISCONNECT_BY_APPLICATION is normal, sent by openssh client. info!("Received disconnect: {:?}", p.desc); } - Packet::UserauthRequest(_p) => { - // TODO: this is server only - todo!("userauth request"); + Packet::UserauthRequest(p) => { + if let ClientServer::Server(serv) = &mut self.cliserv { + serv.auth.request(p, s, b.server()?)?; + } else { + debug!("Server sent an auth request"); + return Err(Error::SSHProtoError) + } } Packet::UserauthFailure(p) => { - // TODO: client only if let ClientServer::Client(cli) = &mut self.cliserv { cli.auth.failure(&p, &mut self.parse_ctx, s, b.client()?).await?; } else { @@ -302,7 +311,6 @@ impl<'a> Conn<'a> { } } Packet::UserauthSuccess(_) => { - // TODO: client only if let ClientServer::Client(cli) = &mut self.cliserv { if matches!(self.state, ConnState::PreAuth) { self.state = ConnState::Authed; diff --git a/sshproto/src/error.rs b/sshproto/src/error.rs index bfbb14b..e519c3e 100644 --- a/sshproto/src/error.rs +++ b/sshproto/src/error.rs @@ -38,10 +38,6 @@ pub enum Error { /// Signature is incorrect BadSig, - // TODO: should this just be badsig? as long as we catch UnknownMethod first - /// Signature doesn't match key type - SigMismatch, - /// Error in received SSH protocol. Will disconnect. SSHProtoError, @@ -148,7 +144,7 @@ impl Error { /// Like [`bug()`] but with a message /// The message can be used instead of a code comment, is logged at `debug` level. #[cold] - pub fn bug_args(args: Arguments) -> Error { + pub fn bug_fmt(args: Arguments) -> Error { // Easier to track the source of errors in development, // but release builds shouldn't panic. if cfg!(debug_assertions) { @@ -172,12 +168,12 @@ impl Error { #[cold] /// TODO: is the generic `T` going to make it bloat? pub fn bug_msg<T>(msg: &str) -> Result<T, Error> { - Err(Self::bug_args(format_args!("{}", msg))) + Err(Self::bug_fmt(format_args!("{}", msg))) } #[cold] pub fn bug_err_msg(msg: &str) -> Error { - Self::bug_args(format_args!("{}", msg)) + Self::bug_fmt(format_args!("{}", msg)) } } @@ -209,7 +205,7 @@ impl<T, E> TrapBug<T> for Result<T, E> { if let Ok(i) = self { Ok(i) } else { - Err(Error::bug_args(args)) + Err(Error::bug_fmt(args)) } } } @@ -228,7 +224,7 @@ impl<T> TrapBug<T> for Option<T> { if let Some(i) = self { Ok(i) } else { - Err(Error::bug_args(args)) + Err(Error::bug_fmt(args)) } } } diff --git a/sshproto/src/kex.rs b/sshproto/src/kex.rs index aceeb22..6e6f65d 100644 --- a/sshproto/src/kex.rs +++ b/sshproto/src/kex.rs @@ -233,7 +233,7 @@ impl Kex { packets::KexInit { cookie: self.our_cookie, kex: (&conf.kexs).into(), - hostkey: (&conf.hostsig).into(), + hostsig: (&conf.hostsig).into(), cipher_c2s: (&conf.ciphers).into(), cipher_s2c: (&conf.ciphers).into(), mac_c2s: (&conf.macs).into(), @@ -311,14 +311,14 @@ impl Kex { }; let hostsig_method = p - .hostkey + .hostsig .first_match(is_client, &conf.hostsig)? .ok_or(Error::AlgoNoMatch { algo: "hostkey" })?; let hostsig = SigType::from_name(hostsig_method)?; let goodguess_hostkey = if kexguess2 { - p.hostkey.first() == hostsig_method + p.hostsig.first() == hostsig_method } else { - p.hostkey.first() == conf.hostsig.first() + p.hostsig.first() == conf.hostsig.first() }; // Switch between client/server tx/rx @@ -434,6 +434,7 @@ impl SharedSecret { } }; + // TODO: error message on signature failure. algos.hostsig.verify(&p.k_s.0, kex_out.h.as_ref(), &p.sig.0)?; debug!("Hostkey signature is valid"); if matches!(b.valid_hostkey(&p.k_s.0), Ok(true)) { @@ -448,12 +449,12 @@ impl SharedSecret { mut kex: Kex, p: &packets::KexDHInit, sess_id: &Option<SessId>, s: &mut TrafSend, b: &mut dyn ServBehaviour, ) -> Result<KexOutput> { - // let mut algos = kex.algos.take().trap()?; let mut algos = kex.algos.trap()?; + // hostkeys list must contain the signature type + let hostkey = b.hostkeys()?.iter().find(|k| k.can_sign(algos.hostsig)).trap()?; + let mut kex_hash = kex.kex_hash.take().trap()?; - // TODO - let fake_hostkey = PubKey::Ed25519(packets::Ed25519PubKey{ key: BinString(&[]) }); - kex_hash.prefinish(&fake_hostkey, p.q_c.0, algos.kex.pubkey())?; + kex_hash.prefinish(&hostkey.pubkey(), p.q_c.0, algos.kex.pubkey())?; let (kex_out, kex_pub) = match algos.kex { SharedSecret::KexCurve25519(_) => { let kex_out = KexCurve25519::secret(&mut algos, p.q_c.0, kex_hash, sess_id)?; @@ -461,18 +462,16 @@ impl SharedSecret { } }; - Self::send_kexdhreply(&kex_out, kex_pub, algos.hostsig, s, b)?; + Self::send_kexdhreply(&kex_out, kex_pub, hostkey, s)?; Ok(kex_out) } // server only - pub fn send_kexdhreply(ko: &KexOutput, kex_pub: &[u8], sig_type: SigType, s: &mut TrafSend, b: &mut dyn ServBehaviour) -> Result<()> { + pub fn send_kexdhreply(ko: &KexOutput, kex_pub: &[u8], hostkey: &SignKey, s: &mut TrafSend) -> Result<()> { let q_s = BinString(kex_pub); - // hostkeys list must contain the signature type - let key = b.hostkeys()?.iter().find(|k| k.can_sign(&sig_type)).trap()?; - let k_s = Blob(key.pubkey()); - let sig = key.sign(&ko.h.as_slice(), None)?; + let k_s = Blob(hostkey.pubkey()); + let sig = hostkey.sign(&ko.h.as_slice(), None)?; let sig: Signature = (&sig).into(); let sig = Blob(sig); s.send(packets::KexDHReply { k_s, q_s, sig }) @@ -573,7 +572,6 @@ mod tests { use crate::*; use crate::doorlog::init_test_log; - use super::SSH_NAME_CURVE25519; #[test] @@ -631,11 +629,11 @@ mod tests { Ok(self.keys.as_slice()) } - fn have_auth_pubkey(&self, userame: &str) -> bool { + fn have_auth_pubkey(&self, userame: TextString) -> bool { false } - fn have_auth_password(&self, userame: &str) -> bool { + fn have_auth_password(&self, userame: TextString) -> bool { false } diff --git a/sshproto/src/lib.rs b/sshproto/src/lib.rs index 9dac0a9..b77e37a 100644 --- a/sshproto/src/lib.rs +++ b/sshproto/src/lib.rs @@ -44,7 +44,9 @@ pub mod sshwire; pub mod config; // Application API -pub use behaviour::{Behaviour, ServBehaviour, CliBehaviour, BhError, BhResult, ResponseString}; +pub use behaviour::{Behaviour, ServBehaviour, CliBehaviour, + BhError, BhResult, ResponseString}; +pub use sshwire::{TextString}; pub use runner::Runner; pub use sign::SignKey; diff --git a/sshproto/src/packets.rs b/sshproto/src/packets.rs index 1b3292f..e4bd7c8 100644 --- a/sshproto/src/packets.rs +++ b/sshproto/src/packets.rs @@ -32,7 +32,11 @@ use sshwire::{SSHEncodeEnum, SSHDecodeEnum}; pub struct KexInit<'a> { pub cookie: [u8; 16], pub kex: NameList<'a>, - pub hostkey: NameList<'a>, // is actually a signature type, not a key type + /// A list of signature algorithms + /// + /// RFC4253 refers to this as the host key algorithms, but actually they + /// are signature algorithms. + pub hostsig: NameList<'a>, pub cipher_c2s: NameList<'a>, pub cipher_s2c: NameList<'a>, pub mac_c2s: NameList<'a>, diff --git a/sshproto/src/runner.rs b/sshproto/src/runner.rs index a1954d2..3c26752 100644 --- a/sshproto/src/runner.rs +++ b/sshproto/src/runner.rs @@ -64,8 +64,10 @@ impl<'a> Runner<'a> { pub fn new_server( inbuf: &'a mut [u8], outbuf: &'a mut [u8], + // TODO: can probably get rid of b argument here (and in callees) + b: &mut dyn ServBehaviour, ) -> Result<Runner<'a>, Error> { - let conn = Conn::new_server()?; + let conn = Conn::new_server(b)?; let runner = Runner { conn, traf_in: TrafIn::new(inbuf), @@ -110,7 +112,6 @@ impl<'a> Runner<'a> { if let Some(d) = d.0 { // incoming channel data, we haven't finished with payload trace!("handle_payload chan input"); - self.traf_in.handled_payload()?; self.traf_in.set_channel_input(d)?; } else { // other packets have been completed @@ -118,6 +119,7 @@ impl<'a> Runner<'a> { self.traf_in.done_payload()?; } } + self.conn.progress(&mut s, behaviour).await?; self.wake(); diff --git a/sshproto/src/servauth.rs b/sshproto/src/servauth.rs index 364658d..325660c 100644 --- a/sshproto/src/servauth.rs +++ b/sshproto/src/servauth.rs @@ -1,11 +1,132 @@ +#[allow(unused_imports)] +use { + crate::error::{Error, Result, TrapBug}, + log::{debug, error, info, log, trace, warn}, +}; + +use heapless::Vec; + +use crate::sshnames::*; use crate::*; +use packets::{AuthMethod, Userauth60, UserauthPkOk}; +use traffic::TrafSend; pub(crate) struct ServAuth { + pub authed: bool, } +// for auth_inner() +enum AuthResp { + // success + Success, + // failed, send a response + Failure, + // failure, a response has already been send + FailNoReply, +} impl ServAuth { - pub fn new() -> Self { - Self {} + pub fn new(b: &mut dyn ServBehaviour) -> Self { + Self { authed: false } + } + + /// Returns `true` if auth succeeds + pub fn request( + &self, + p: packets::UserauthRequest, + s: &mut TrafSend, + b: &mut dyn ServBehaviour, + ) -> Result<bool> { + let r = self.auth_inner(p, s, b)?; + + match r { + AuthResp::Success => { + s.send(packets::UserauthSuccess {})?; + Ok(true) + } + AuthResp::Failure => { + let mut n: Vec<&str, NUM_AUTHMETHOD> = Vec::new(); + let methods = self.avail_methods(&mut n); + let methods = (&methods).into(); + + s.send(packets::UserauthFailure { methods, partial: false })?; + Ok(false) + } + AuthResp::FailNoReply => Ok(false), + } + } + + pub fn auth_inner( + &self, + p: packets::UserauthRequest, + s: &mut TrafSend, + b: &mut dyn ServBehaviour, + ) -> Result<AuthResp> { + // even allows "none" auth + if b.auth_unchallenged(p.username) { + return Ok(AuthResp::Success); + } + + let success = match p.method { + AuthMethod::Password(m) => b.auth_password(p.username, m.password), + AuthMethod::PubKey(m) => { + let allowed_key = b.auth_pubkey(p.username, &m.pubkey.0); + if allowed_key { + if m.sig.is_none() { + s.send(Userauth60::PkOk(UserauthPkOk { + algo: m.sig_algo, + key: m.pubkey, + }))?; + return Ok(AuthResp::FailNoReply); + } else { + self.verify_pubkey(&m) + } + } else { + false + } + } + AuthMethod::None => { + // nothing to do + false + } + AuthMethod::Unknown(u) => { + debug!("Request for unknown auth method {}", u); + false + } + }; + + if success { + Ok(AuthResp::Success) + } else { + Ok(AuthResp::Failure) + } + } + + /// Returns `true` on successful signature verifcation. `false` on bad signature. + fn verify_pubkey(&self, m: &packets::MethodPubKey) -> bool { + let sig = match m.sig.as_ref() { + Some(s) => &s.0, + None => return false, + }; + + let sig_type = match sig.sig_type() { + Ok(t) => t, + Err(_) => return false, + }; + + false + // XXX + // sig_type.verify(&m.pubkey.0, sess_id.as) + + // m.pubkey. + } + + fn avail_methods<'f>( + &self, + buf: &'f mut Vec<&str, NUM_AUTHMETHOD>, + ) -> namelist::LocalNames<'f> { + buf.clear(); + // for + buf.as_slice().into() } } diff --git a/sshproto/src/server.rs b/sshproto/src/server.rs index a507787..cc1bfa4 100644 --- a/sshproto/src/server.rs +++ b/sshproto/src/server.rs @@ -1,14 +1,37 @@ +#[allow(unused_imports)] +use { + crate::error::{Error, Result, TrapBug}, + log::{debug, error, info, log, trace, warn}, +}; + use crate::*; +use crate::packets::{ServiceAccept, ServiceRequest}; use crate::servauth::ServAuth; +use crate::sshnames::{SSH_SERVICE_CONNECTION, SSH_SERVICE_USERAUTH}; +use traffic::TrafSend; pub(crate) struct Server { - auth: ServAuth, + pub(crate) auth: ServAuth, } impl Server { - pub fn new() -> Self { - Server { - auth: ServAuth::new(), + pub fn new( + b: &mut dyn ServBehaviour, + ) -> Self { + Server { auth: ServAuth::new(b) } + } + + pub fn service_request(&self, p: &ServiceRequest, s: &mut TrafSend) -> Result<()> { + let success = match p.name { + SSH_SERVICE_USERAUTH => true, + SSH_SERVICE_CONNECTION => self.auth.authed, + _ => false, + }; + if success { + s.send(ServiceAccept { name: p.name }) + } else { + warn!("Received unexpected service request '{}'", p.name); + Err(Error::SSHProtoError) } } } diff --git a/sshproto/src/sign.rs b/sshproto/src/sign.rs index 7ec846c..d156893 100644 --- a/sshproto/src/sign.rs +++ b/sshproto/src/sign.rs @@ -45,11 +45,12 @@ impl SigType { } } + /// Returns `Ok(())` on success pub fn verify( &self, pubkey: &PubKey, message: &[u8], sig: &Signature) -> Result<()> { // Check that the signature type is known - let sig_type = sig.sig_type()?; + let sig_type = sig.sig_type().map_err(|_| Error::BadSig)?; // `self` is the expected signature type from kex/auth packet // This would also get caught by SignatureMismatch below @@ -92,7 +93,7 @@ impl SigType { sig.algorithm_name(), pubkey.algorithm_name(), ); - Err(Error::SigMismatch) + Err(Error::BadSig) } } } @@ -138,7 +139,7 @@ impl SignKey { } /// Returns whether this `SignKey` can create a given signature type - pub(crate) fn can_sign(&self, sig_type: &SigType) -> bool { + pub(crate) fn can_sign(&self, sig_type: SigType) -> bool { match self { SignKey::Ed25519(_) => matches!(sig_type, SigType::Ed25519), } diff --git a/sshproto/src/sshnames.rs b/sshproto/src/sshnames.rs index 97b179b..5a73a73 100644 --- a/sshproto/src/sshnames.rs +++ b/sshproto/src/sshnames.rs @@ -50,6 +50,7 @@ pub const SSH_AUTHMETHOD_PASSWORD: &str = "password"; pub const SSH_AUTHMETHOD_PUBLICKEY: &str = "publickey"; /// [RFC4256](https://tools.ietf.org/html/rfc4256) pub const SSH_AUTHMETHOD_INTERACTIVE: &str = "keyboard-interactive"; +pub(crate) const NUM_AUTHMETHOD: usize = 3; /// [RFC4254](https://tools.ietf.org/html/rfc4254) pub const SSH_EXTENDED_DATA_STDERR: u32 = 1; diff --git a/sshproto/src/traffic.rs b/sshproto/src/traffic.rs index 0fae0f5..110a17e 100644 --- a/sshproto/src/traffic.rs +++ b/sshproto/src/traffic.rs @@ -75,9 +75,6 @@ enum RxState { ReadComplete { len: usize }, /// Decrypted complete input payload InPayload { len: usize, seq: u32 }, - /// Decrypted complete input payload. It has been dispatched by handle_payload(), - /// remains "borrowed" for use by a progress() Event. - BorrowPayload { len: usize }, /// Decrypted incoming channel data InChannelData { /// channel number @@ -104,7 +101,6 @@ impl<'a> TrafIn<'a> { | RxState::Read { .. } => true, RxState::ReadComplete { .. } | RxState::InPayload { .. } - | RxState::BorrowPayload { .. } | RxState::InChannelData { .. } => false, } @@ -130,38 +126,10 @@ impl<'a> TrafIn<'a> { Ok(inlen) } - pub(crate) fn payload_reborrow(&mut self) -> Option<&[u8]> { - match self.state { - | RxState::InPayload { len, .. } - | RxState::BorrowPayload { len, .. } - => { - let payload = &self.buf[SSH_PAYLOAD_START..SSH_PAYLOAD_START + len]; - Some(payload) - } - _ => None, - } - } - - /// Called when `payload()` has been handled once, can still be - /// `payload_reborrow()`ed later. - pub(crate) fn handled_payload(&mut self) -> Result<(), Error> { - match self.state { - | RxState::InPayload { len, .. } - | RxState::BorrowPayload { len } - => { - self.state = RxState::BorrowPayload { len }; - Ok(()) - } - _ => Err(Error::bug()) - } - } - /// Called when `payload()` and `payload_reborrow()` are complete. pub(crate) fn done_payload(&mut self) -> Result<(), Error> { match self.state { - | RxState::InPayload { .. } - | RxState::BorrowPayload { .. } - => { + RxState::InPayload { .. } => { self.state = RxState::Idle; Ok(()) } @@ -257,14 +225,14 @@ impl<'a> TrafIn<'a> { pub fn set_channel_input(&mut self, di: channel::DataIn) -> Result<()> { trace!("traf chan input state {:?}", self.state); match self.state { - RxState::Idle => { + RxState::InPayload { .. } => { let idx = SSH_PAYLOAD_START + di.offset; self.state = RxState::InChannelData { chan: di.num, ext: di.ext, idx, len: idx + di.len }; // error!("set input {:?}", self.state); - trace!("all buf {:?}", self.buf[..32].hex_dump()); - trace!("set chan input offset {} idx {} {:?}", - di.offset, idx, - self.buf[idx..idx + di.len].hex_dump()); + // trace!("all buf {:?}", self.buf[..32].hex_dump()); + // trace!("set chan input offset {} idx {} {:?}", + // di.offset, idx, + // self.buf[idx..idx + di.len].hex_dump()); Ok(()) } _ => Err(Error::bug()), -- GitLab