diff --git a/src/client.rs b/src/client.rs index b8ab0f1c2..874d20d23 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3681,6 +3681,7 @@ async fn send_login( /// * `password` - Password. /// * `remember` - Whether to remember password. /// * `port_forward` - Target of a port-forward login; ignored by other types. +/// * `hash` - The challenge this connection was given. /// * `peer` - [`Stream`] for communicating with peer. pub async fn handle_login_from_ui( lc: Arc>, @@ -3689,6 +3690,7 @@ pub async fn handle_login_from_ui( password: String, remember: bool, port_forward: PortForward, + hash: Hash, peer: &mut Stream, ) { let mut hash_password = if password.is_empty() { @@ -3704,7 +3706,7 @@ pub async fn handle_login_from_ui( lc.write().unwrap().password_source = Default::default(); let mut hasher = Sha256::new(); hasher.update(password); - hasher.update(&lc.read().unwrap().hash.salt); + hasher.update(&hash.salt); let res = hasher.finalize(); lc.write().unwrap().remember = remember; res[..].into() @@ -3712,7 +3714,7 @@ pub async fn handle_login_from_ui( lc.write().unwrap().password = hash_password.clone(); let mut hasher2 = Sha256::new(); hasher2.update(&hash_password[..]); - hasher2.update(&lc.read().unwrap().hash.challenge); + hasher2.update(&hash.challenge); hash_password = hasher2.finalize()[..].to_vec(); send_login(lc.clone(), os_username, os_password, hash_password, port_forward, peer).await; @@ -4091,6 +4093,57 @@ mod login_scope_tests { assert_eq!((pf(&b).host.as_str(), pf(&b).port), ("b", 2)); assert!(pf(&a).multiplex); } + + /// Each connection answers its own challenge: the `Hash` is a parameter + /// of the login, not a field two accepts could overwrite in the handler. + #[test] + fn a_ui_login_answers_the_challenge_it_was_given() { + use hbb_common::{protobuf::Message as _, tcp::FramedStream, tokio, Stream}; + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + rt.block_on(async { + let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = l.local_addr().unwrap(); + let client = tokio::net::TcpStream::connect(addr).await.unwrap(); + let (server, _) = l.accept().await.unwrap(); + let mut ours = Stream::Tcp(FramedStream::from(client, addr)); + let mut peer = Stream::Tcp(FramedStream::from(server, addr)); + let lc = Arc::new(RwLock::new(LoginConfigHandler::default())); + let hash = |challenge: &str| Hash { + salt: "salt".to_owned(), + challenge: challenge.to_owned(), + ..Default::default() + }; + let expected = |challenge: &str| { + let mut h = Sha256::new(); + h.update("pw"); + h.update("salt"); + let salted = h.finalize(); + let mut h2 = Sha256::new(); + h2.update(&salted[..]); + h2.update(challenge); + h2.finalize()[..].to_vec() + }; + for challenge in ["a", "b"] { + handle_login_from_ui( + lc.clone(), + String::new(), + String::new(), + "pw".to_owned(), + false, + Default::default(), + hash(challenge), + &mut ours, + ) + .await; + let bytes = peer.next().await.unwrap().unwrap(); + let msg = Message::parse_from_bytes(&bytes).unwrap(); + assert_eq!(msg.login_request().password, expected(challenge)); + } + }); + } } pub async fn hc_connection( diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index b170b0f05..d2b27f00e 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -60,6 +60,10 @@ pub struct Session { /// Per clone, set by `Interface::with_port_forward`: the target a /// port-forward login asks for. pub port_forward: PortForward, + /// The `Hash` this connection was challenged with. A session's clones + /// share it, as they share the connection; a port-forward accept's clone + /// gets its own, since every accept is a connection of its own. + pub login_hash: Arc>, pub lc: Arc>, pub sender: Arc>>>, pub thread: Arc>>>, @@ -1874,10 +1878,12 @@ impl Interface for Session { fn with_port_forward(&self, port_forward: PortForward) -> Self { let mut scoped = self.clone(); scoped.port_forward = port_forward; + scoped.login_hash = Default::default(); scoped } async fn handle_hash(&self, pass: &str, hash: Hash, peer: &mut Stream) -> bool { + *self.login_hash.write().unwrap() = hash.clone(); handle_hash(self.lc.clone(), pass, hash, self.port_forward.clone(), self, peer).await } @@ -1889,6 +1895,7 @@ impl Interface for Session { remember: bool, peer: &mut Stream, ) { + let hash = self.login_hash.read().unwrap().clone(); handle_login_from_ui( self.lc.clone(), os_username, @@ -1896,6 +1903,7 @@ impl Interface for Session { password, remember, self.port_forward.clone(), + hash, peer, ) .await;