From 68b32cecb095400768ab914ca1925f11e613d549 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Mon, 5 Dec 2022 22:38:16 +0800
Subject: [PATCH] Tidying and comments

Fix a few things from cargo clippy
---
 src/behaviour.rs | 31 +++++++++++++++++++++++--------
 src/channel.rs   | 12 +++++++-----
 src/conn.rs      |  5 +++--
 src/encrypt.rs   | 11 +++++------
 src/kex.rs       |  2 +-
 src/namelist.rs  |  1 +
 src/packets.rs   |  5 +++--
 src/servauth.rs  |  2 +-
 src/sign.rs      | 14 ++++++++++++--
 src/sshwire.rs   | 24 ++++++++++++++++++------
 10 files changed, 74 insertions(+), 33 deletions(-)

diff --git a/src/behaviour.rs b/src/behaviour.rs
index 62adb23..a126f2e 100644
--- a/src/behaviour.rs
+++ b/src/behaviour.rs
@@ -67,20 +67,18 @@ impl<'a> Behaviour<'a> {
     /// Calls either client or server
     pub(crate) fn open_tcp_forwarded(&mut self, chan: u32,
         t: &ForwardedTcpip) -> channel::ChanOpened {
-        if self.is_client() {
-            self.client().unwrap().open_tcp_forwarded(chan, t)
-        } else {
-            self.server().unwrap().open_tcp_forwarded(chan, t)
+        match self {
+            Self::Client(b) => b.open_tcp_forwarded(chan, t),
+            Self::Server(b) => b.open_tcp_forwarded(chan, t),
         }
     }
 
     /// Calls either client or server
     pub(crate) fn open_tcp_direct(&mut self, chan: u32,
         t: &DirectTcpip) -> channel::ChanOpened {
-        if self.is_client() {
-            self.client().unwrap().open_tcp_direct(chan, t)
-        } else {
-            self.server().unwrap().open_tcp_direct(chan, t)
+        match self {
+            Self::Client(b) => b.open_tcp_direct(chan, t),
+            Self::Server(b) => b.open_tcp_direct(chan, t),
         }
     }
 
@@ -178,29 +176,46 @@ pub trait ServBehaviour: Sync+Send {
 
     #[allow(unused)]
     // TODO: or return a slice of enums
+    /// Implementations should take care to avoid leaking user existence
+    /// based on timing.
     fn have_auth_password(&self, username: TextString) -> bool {
         false
     }
 
     #[allow(unused)]
+    /// Implementations should take care to avoid leaking user existence
+    /// based on timing.
     fn have_auth_pubkey(&self, username: TextString) -> bool {
         false
     }
 
     #[allow(unused)]
     /// Return true to allow the user to log in with no authentication
+    ///
+    /// Implementations may need to take care to avoid leaking user existence
+    /// based on timing.
     fn auth_unchallenged(&mut self, username: TextString) -> bool {
         false
     }
 
     #[allow(unused)]
     // TODO: change password
+    /// Implementations should perform password hash comparisons
+    /// in constant time, using 
+    /// [`subtle::ConstantTimeEq`](subtle::ConstantTimeEq) or similar.
+    ///
+    /// Implementations may need to take care to avoid leaking user existence
+    /// based on timing.
     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
+    ///
+    /// Implementations may need to take care to avoid leaking user existence
+    /// based on timing.
     #[allow(unused)]
     fn auth_pubkey(&mut self, username: TextString, pubkey: &PubKey) -> bool {
         false
diff --git a/src/channel.rs b/src/channel.rs
index c6349ea..5fef02a 100644
--- a/src/channel.rs
+++ b/src/channel.rs
@@ -67,7 +67,7 @@ impl Channels {
         ty: packets::ChannelOpenType<'b>,
         init_req: InitReqs,
     ) -> Result<(&Channel, Packet<'b>)> {
-        let (num, ch) = self.unused_chan()?;
+        let num = self.unused_chan()?;
 
         let chan = Channel::new(num, (&ty).into(), init_req);
         let p = packets::ChannelOpen {
@@ -77,6 +77,7 @@ impl Channels {
             ty,
         }
         .into();
+        let ch = &mut self.ch[num as usize];
         *ch = Some(chan);
         Ok((ch.as_ref().unwrap(), p))
     }
@@ -144,19 +145,19 @@ impl Channels {
     }
 
     /// Returns the first available channel
-    fn unused_chan(&mut self) -> Result<(u32, &mut Option<Channel>)> {
+    fn unused_chan(&self) -> Result<u32> {
         self.ch
-            .iter_mut()
+            .iter()
             .enumerate()
             .find_map(
-                |(i, ch)| if ch.as_mut().is_none() { Some((i as u32, ch)) } else { None },
+                |(i, ch)| if ch.as_ref().is_none() { Some(i as u32) } else { None },
             )
             .ok_or(Error::NoChannels)
     }
 
     /// Creates a new channel in InOpen state.
     fn reserve_chan(&mut self, co: &ChannelOpen<'_>) -> Result<&mut Channel> {
-        let (num, ch) = self.unused_chan()?;
+        let num = self.unused_chan()?;
         let mut chan = Channel::new(num, (&co.ty).into(), Vec::new());
         chan.send = Some(ChanDir {
             num: co.num,
@@ -165,6 +166,7 @@ impl Channels {
         });
         chan.state = ChanState::InOpen;
 
+        let ch = &mut self.ch[num as usize];
         *ch = Some(chan);
         Ok(ch.as_mut().unwrap())
     }
diff --git a/src/conn.rs b/src/conn.rs
index 954f771..72374cb 100644
--- a/src/conn.rs
+++ b/src/conn.rs
@@ -84,6 +84,7 @@ enum ConnState {
 }
 
 #[derive(Default)]
+/// Returned state from `handle_payload()` for `Runner` to use.
 pub(crate) struct Dispatched {
     pub data_in: Option<channel::DataIn>,
     /// set for sensitive payloads such as password auth
@@ -251,7 +252,7 @@ impl Conn {
             }
             Packet::KexDHInit(p) => {
                 match self.state {
-                    ConnState::InKex { done_auth: _, ref mut output } => {
+                    ConnState::InKex { ref mut output, .. } => {
                         if self.cliserv.is_client() {
                             // TODO: client/server validity checks should move somewhere more general
                             trace!("kexdhinit not server");
@@ -270,7 +271,7 @@ impl Conn {
             }
             Packet::KexDHReply(p) => {
                 match self.state {
-                    ConnState::InKex { done_auth: _, ref mut output } => {
+                    ConnState::InKex { ref mut output, .. } => {
                         if let ClientServer::Client(_cli) = &mut self.cliserv {
                             if self.kex.maybe_discard_packet() {
                                 // ok
diff --git a/src/encrypt.rs b/src/encrypt.rs
index 0a986f7..b1b882c 100644
--- a/src/encrypt.rs
+++ b/src/encrypt.rs
@@ -44,8 +44,7 @@ const MAX_KEY_LEN: usize = 64;
 #[derive(Debug)]
 pub(crate) struct KeyState {
     keys: Keys,
-    // Packet sequence numbers. These must be transferred to subsequent KeyState
-    // since they don't reset with rekeying.
+    // Packet sequence numbers. These don't reset with rekeying.
     seq_encrypt: Wrapping<u32>,
     seq_decrypt: Wrapping<u32>,
 }
@@ -157,7 +156,7 @@ impl Keys {
         }
     }
 
-    pub fn new_from(
+    pub fn derive(
         k: &[u8], h: &SessId, sess_id: &SessId, algos: &kex::Algos,
     ) -> Result<Self, Error> {
         let mut key = [0u8; MAX_KEY_LEN];
@@ -816,7 +815,7 @@ mod tests {
                 let sharedkey = b"hello";
 
                 trace!("algos enc {algos:?}");
-                let newkeys = Keys::new_from(sharedkey, &h, &sess_id, &algos).unwrap();
+                let newkeys = Keys::derive(sharedkey, &h, &sess_id, &algos).unwrap();
                 keys_enc.rekey(newkeys);
 
                 // client and server enc/dec keys are derived differently, we need them
@@ -825,7 +824,7 @@ mod tests {
                 core::mem::swap(&mut algos.cipher_enc, &mut algos.cipher_dec);
                 core::mem::swap(&mut algos.integ_enc, &mut algos.integ_dec);
                 trace!("algos dec {algos:?}");
-                let newkeys_b = Keys::new_from(sharedkey, &h, &sess_id, &algos).unwrap();
+                let newkeys_b = Keys::derive(sharedkey, &h, &sess_id, &algos).unwrap();
                 keys_dec.rekey(newkeys_b);
 
             } else {
@@ -852,7 +851,7 @@ mod tests {
                 let sess_id = SessId::from_slice(&Sha256::digest(b"some sessid")).unwrap();
                 let sharedkey = b"hello";
                 let newkeys =
-                    Keys::new_from(sharedkey, &h, &sess_id, &algos).unwrap();
+                    Keys::derive(sharedkey, &h, &sess_id, &algos).unwrap();
 
                 keys.rekey(newkeys);
                 trace!("algos {algos:?}");
diff --git a/src/kex.rs b/src/kex.rs
index 1b8bc63..be26b74 100644
--- a/src/kex.rs
+++ b/src/kex.rs
@@ -505,7 +505,7 @@ impl<'a> KexOutput {
         let h = kex_hash.finish(k);
 
         let sess_id = sess_id.as_ref().unwrap_or(&h);
-        let keys = Keys::new_from(k, &h, &sess_id, algos)?;
+        let keys = Keys::derive(k, &h, &sess_id, algos)?;
 
         Ok(KexOutput { h, keys })
     }
diff --git a/src/namelist.rs b/src/namelist.rs
index cbf4105..52ae7cf 100644
--- a/src/namelist.rs
+++ b/src/namelist.rs
@@ -23,6 +23,7 @@ static EMPTY_LOCALNAMES: LocalNames = LocalNames::new();
 
 /// A comma separated string, can be decoded or encoded.
 /// Used for remote name lists.
+/// Wire format is described in [RFC4251](https://tools.ietf.org/html/rfc4251) SSH Architecture "name-list"
 #[derive(SSHEncode, SSHDecode, Debug)]
 pub struct StringNames<'a>(pub &'a AsciiStr);
 
diff --git a/src/packets.rs b/src/packets.rs
index 2a46875..1bd0cb4 100644
--- a/src/packets.rs
+++ b/src/packets.rs
@@ -2,6 +2,7 @@
 //!
 //! A [`Packet`] can be encoded/decoded to the
 //! SSH Binary Packet Protocol using [`sshwire`].
+//! SSH packet format is described in [RFC4253](https://tools.ietf.org/html/rfc5643) SSH Transport
 
 use core::borrow::BorrowMut;
 use core::cell::Cell;
@@ -148,7 +149,7 @@ impl<'de: 'a, 'a> SSHDecode<'de> for Userauth60<'a> {
             Some(auth::AuthType::PubKey) => Ok(Self::PkOk(SSHDecode::dec(s)?)),
             _ => {
                 trace!("Wrong packet state for userauth60");
-                return Err(WireError::PacketWrong)
+                Err(WireError::PacketWrong)
             }
         }
     }
@@ -603,7 +604,7 @@ impl core::fmt::Display for Unknown<'_> {
 pub struct ParseContext {
     pub cli_auth_type: Option<auth::AuthType>,
 
-    // Used by sign_encode()
+    // Used by auth_sig_msg()
     pub method_pubkey_force_sig_bool: bool,
 
     // Set to true if an unknown variant is encountered.
diff --git a/src/servauth.rs b/src/servauth.rs
index b0de747..34b0a4c 100644
--- a/src/servauth.rs
+++ b/src/servauth.rs
@@ -131,7 +131,7 @@ impl ServAuth {
         let msg = auth::AuthSigMsg::new(&p, sess_id);
         match sig_type.verify(&m.pubkey.0, &msg, sig) {
             Ok(()) => true,
-            Err(e) => { trace!("sig failde  {e}"); false},
+            Err(e) => { trace!("sig failed  {e}"); false},
         }
     }
 
diff --git a/src/sign.rs b/src/sign.rs
index f0892d2..4674996 100644
--- a/src/sign.rs
+++ b/src/sign.rs
@@ -104,7 +104,7 @@ impl SigType {
 }
 
 pub(crate) enum OwnedSig {
-    // Signature doesn't let us borrow the inner bytes,
+    // salty::Signature doesn't let us borrow the inner bytes,
     // so we just store raw bytes here.
     Ed25519([u8; 64]),
     _RSA256, // TODO
@@ -168,7 +168,7 @@ impl SignKey {
     }
 
     pub(crate) fn sign(&self, msg: &impl SSHEncode, parse_ctx: Option<&ParseContext>) -> Result<OwnedSig> {
-        match self {
+        let sig: OwnedSig = match self {
             SignKey::Ed25519(k) => {
                 k.sign_parts(|h| {
                     sshwire::hash_ser(h, msg, parse_ctx).map_err(|_| salty::Error::ContextTooLong)
@@ -176,7 +176,17 @@ impl SignKey {
                 .trap()
                 .map(|s| s.into())
             }
+        }?;
+
+        {
+            // Validate to avoid letting errors/bitflips expose private key.
+            // Maybe this needs to be configurable for slow platforms?
+            let vsig: Signature = (&sig).into();
+            let sig_type = vsig.sig_type().unwrap();
+            sig_type.verify(&self.pubkey(), msg, &vsig)?;
         }
+
+        Ok(sig)
     }
 }
 
diff --git a/src/sshwire.rs b/src/sshwire.rs
index 0c5b7fc..e6a9a48 100644
--- a/src/sshwire.rs
+++ b/src/sshwire.rs
@@ -1,6 +1,9 @@
 //! SSH wire format reading/writing.
+//!
 //! Used in conjunction with [`sshwire_derive`] and the [`packet`](crate::packets) format
 //! definitions.
+//!
+//! SSH wire format is described in [RFC4251](https://tools.ietf.org/html/rfc4251) SSH Architecture
 
 #[allow(unused_imports)]
 use {
@@ -36,6 +39,10 @@ pub trait SSHSource<'de> {
 
 /// Encodes the type in SSH wire format
 pub trait SSHEncode {
+    /// Encode data
+    ///
+    /// The state of the `SSHSink` is undefined after an error is returned, data may
+    /// have been partially encoded.
     fn enc<S>(&self, s: &mut S) -> WireResult<()> where S: SSHSink;
 }
 
@@ -48,6 +55,10 @@ pub trait SSHEncodeEnum {
 
 /// Decodes `struct` and `enum`s without an externally provided enum name
 pub trait SSHDecode<'de>: Sized {
+    /// Decode data
+    ///
+    /// The state of the `SSHSource` is undefined after an error is returned, data may
+    /// have been partially consumed.
     fn dec<S>(s: &mut S) -> WireResult<Self> where S: SSHSource<'de>;
 }
 
@@ -169,11 +180,12 @@ struct EncodeBytes<'a> {
 
 impl SSHSink for EncodeBytes<'_> {
     fn push(&mut self, v: &[u8]) -> WireResult<()> {
-        if self.target.len() - self.pos < v.len() {
+        let end = self.pos.checked_add(v.len()).ok_or(WireError::NoRoom)?;
+        if end > self.target.len() {
             return Err(WireError::NoRoom);
         }
-        self.target[self.pos..self.pos + v.len()].copy_from_slice(v);
-        self.pos += v.len();
+        self.target[self.pos..end].copy_from_slice(v);
+        self.pos = end;
         Ok(())
     }
 }
@@ -438,7 +450,7 @@ impl SSHEncode for &[u8] {
     fn enc<S>(&self, s: &mut S) -> WireResult<()>
     where S: SSHSink {
         // data
-        s.push(&self)
+        s.push(self)
     }
 }
 
@@ -507,7 +519,7 @@ impl<'de> SSHDecode<'de> for u32 {
 
 /// Decodes a SSH name string. Must be ASCII
 /// without control characters. RFC4251 section 6.
-pub fn try_as_ascii<'a>(t: &'a [u8]) -> WireResult<&'a AsciiStr> {
+pub fn try_as_ascii(t: &[u8]) -> WireResult<&AsciiStr> {
     let n = t.as_ascii_str().map_err(|_| WireError::BadName)?;
     if n.chars().any(|ch| ch.is_ascii_control() || ch == AsciiChar::DEL) {
         return Err(WireError::BadName);
@@ -515,7 +527,7 @@ pub fn try_as_ascii<'a>(t: &'a [u8]) -> WireResult<&'a AsciiStr> {
     Ok(n)
 }
 
-pub fn try_as_ascii_str<'a>(t: &'a [u8]) -> WireResult<&'a str> {
+pub fn try_as_ascii_str(t: &[u8]) -> WireResult<&str> {
     try_as_ascii(t).map(AsciiStr::as_str)
 }
 
-- 
GitLab