mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-08 21:41:02 +03:00
port forward: a UI login answers the challenge its own connection was given
`handle_login_from_ui` hashed the typed password against `lc.hash`, the window's shared handler field, and the window's password prompt is broadcast to every listener. With two mappings both waiting on that prompt, the `Hash` that arrived last had overwritten the other's, so one of the two answered the wrong challenge and failed to log in. Master shares the same state and broadcasts the same way. The `Hash` is now a parameter of the login; `Session` keeps it beside the connection it belongs to, and the per-accept clone that `with_port_forward` makes gets a slot of its own. `lc.hash` stays for `handle_peer_info`, which only needs the salt, and that is per peer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
This commit is contained in:
@@ -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<RwLock<LoginConfigHandler>>,
|
||||
@@ -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(
|
||||
|
||||
@@ -60,6 +60,10 @@ pub struct Session<T: InvokeUiSession> {
|
||||
/// 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<RwLock<Hash>>,
|
||||
pub lc: Arc<RwLock<LoginConfigHandler>>,
|
||||
pub sender: Arc<RwLock<Option<mpsc::UnboundedSender<Data>>>>,
|
||||
pub thread: Arc<Mutex<Option<std::thread::JoinHandle<()>>>>,
|
||||
@@ -1874,10 +1878,12 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
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<T: InvokeUiSession> Interface for Session<T> {
|
||||
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<T: InvokeUiSession> Interface for Session<T> {
|
||||
password,
|
||||
remember,
|
||||
self.port_forward.clone(),
|
||||
hash,
|
||||
peer,
|
||||
)
|
||||
.await;
|
||||
|
||||
Reference in New Issue
Block a user