mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-11-03 16:32:22 +00:00
* fix: secure trusted devices with host-issued V2 tokens Signed-off-by: fufesou <linlong1266@gmail.com> * fix: preserve trusted-device management and ordinary authentication Restore selected-device deletion using nonsecret record identifiers and acknowledged V2 revocation errors. Preserve password and TOTP authentication when the V2 authority or cached controller credentials are unavailable. Disable V2 for the affected attempt while retaining authentication, configuration, and revocation rejection. Validation: 20 selected Rust tests, Flutter-feature Rust compilation, focused Dart analysis, formatting, and whitespace checks passed. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: simplify trusted-device caching and stabilize tests Signed-off-by: fufesou <linlong1266@gmail.com> * fix: persist trusted-device grants in host configuration Store host grants in RustDesk2.toml and preserve revocation across configuration writes and service transitions. Use local authority on macOS when no background service is installed. Preserve both configuration imports by checking Config2 eligibility before revocation writes. Cover the password and 2FA import regression alongside the existing trusted-device tests. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: store trusted-device credentials in peer configuration Signed-off-by: fufesou <linlong1266@gmail.com> * fix: minimize trusted-device persistence changes Signed-off-by: fufesou <linlong1266@gmail.com> * refactor: minimize trusted-device integration changes Keep ordinary policy writes on the generic configuration path and document authentication updates. Restore the macOS installer to master, rename the V2 version gate, and relocate the existing client regression test. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: harden trusted-device revocation and persistence Recover corrupt grant data without skipping authentication-write validation, and avoid revoking trust when IPC synchronizes an unchanged salt. Make desktop management IPC async and keep runtime creation at the Flutter worker boundary. Update hbb_common to preserve V2 credentials when configuration reads fail, and extend existing regressions for clear, salt synchronization, and persistence failures. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: preserve trusted-device validation and no-op settings Validate trust before connection side effects and retain tickets for the final check when the authority is unavailable after TOTP. Disable enrollment on that path. Skip revocation for password operations that the existing setter treats as no-ops, extend the existing IPC regression, and clean up client state on initialization or credential errors. Validation: 36 targeted native tests, Flutter-feature Rust check, formatting, and git diff --check passed. Signed-off-by: fufesou <linlong1266@gmail.com> * feat: add trusted-device expiry and per-device renewal Keep legacy records for password-and-TOTP migration, encrypt host grant storage, and add configurable trust periods that revoke grants when changed. Allow renewing individual unexpired devices from the manager, hide renewal for never-expiring grants, and preserve the mobile table height constraints. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(macos): report trusted-device cleanup failure before uninstall Revoke local trusted-device grants before dispatching the uninstall closure so cleanup failures return false without starting the uninstall script. Keep the existing synchronous and asynchronous script execution paths. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: preserve trusted-device settings and revoke macOS service grants Default unsupported expiration values on read while keeping explicit edits strict. Allow password/TOTP when stored grants are out of sync, after validating the authentication scope. Revoke root-owned grants during macOS service removal. Bump hbb_common for malformed-file recovery and legacy-record preservation, and retain the reviewed regression coverage. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: avoid duplicate trusted-device revocation during config sync Signed-off-by: fufesou <linlong1266@gmail.com> * fix: preserve trusted-device revocation during config mismatches Allow grant clearing when authentication snapshots are unavailable and validate existing snapshots before applying authentication updates. Restore optimistic caching for non-auth bulk options and restrict the trusted-device IPC helper to desktop targets. Signed-off-by: fufesou <linlong1266@gmail.com> * feat: preserve trusted devices when trust duration changes Adjust existing grant deadlines for duration changes and startup policy overrides, support 90 days, and use an adaptive inline duration selector without the obsolete revocation warning. Consolidate management IPC and trim redundant tests while retaining authentication and revocation coverage. Validation: four existing focused Rust tests and the startup override reproduction passed; Windows default/service and Flutter-feature cargo checks passed. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: finish trusted-device notice cleanup and duration sync Remove the recurring legacy notice, unused response metadata and translation key. Allow duration-only saves with a readable inactive grant scope while preserving storage and authentication errors. Update hbb_common to 040bf3f662ae7422f2f9570701cc8451f7bb1d59, which removes the unused legacy detector. Validation: four existing focused Rust tests passed; Windows default/service and Flutter-feature cargo checks passed. No new tests were added. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: refine trusted-device duration layout Indent the duration row beneath the trusted-device option, add vertical spacing, and compact the desktop selector. Mobile sizing and other dropdowns remain unchanged. Validation: targeted Dart analysis passed with existing informational diagnostics. Actual window rendering was not exercised. Signed-off-by: fufesou <linlong1266@gmail.com> * refact: adjust indentation Signed-off-by: fufesou <linlong1266@gmail.com> * fix: preserve trusted devices across version upgrades Honor existing legacy trust without creating new legacy registrations. Import live records into encrypted grants, apply the current expiration policy, and migrate consenting controllers to V2 tokens while retaining grouped revocation. Advertise V2 capability explicitly and hide enrollment for mixed-version sessions. Update existing coverage and use the paired hbb_common migration API. Signed-off-by: fufesou <linlong1266@gmail.com> * fix: translations Signed-off-by: fufesou <linlong1266@gmail.com> * Align the compact trusted-device duration selector on mobile Signed-off-by: fufesou <linlong1266@gmail.com> * Default trusted devices to 90 days and drop unreleased V2 migrations Signed-off-by: fufesou <linlong1266@gmail.com> * Avoid masking macOS daemon stop failures during uninstall Signed-off-by: fufesou <linlong1266@gmail.com> * restore uninstall.scpt, do not clear trusted devices on uninstall Signed-off-by: fufesou <linlong1266@gmail.com> * Preserve trusted devices when uninstalling services Signed-off-by: fufesou <linlong1266@gmail.com> * Preserve trusted devices when importing host configuration Signed-off-by: fufesou <linlong1266@gmail.com> * Recover from unreadable trusted-device grants Log and persistently revoke invalid or undecryptable V2 payloads so password and TOTP can enroll a new device. Keep configuration read and write errors explicit and extend existing tests to cover recovery and rejection of old tokens. Signed-off-by: fufesou <linlong1266@gmail.com> * Preserve trusted devices across service transitions Run grant operations in the accepting server and synchronize current grants and revocations through the existing service configuration channel. Keep ordinary settings saves from restoring stale credentials and retain root peer verification. Recognize explicit configuration errors as live service responses during IPC probing, so duplicate startup does not remove the active socket. Validated with five trusted-device tests, seven Linux IPC filesystem tests, an isolated IPC regression probe, and Windows Flutter and Linux compilation checks. Installed-service transitions and macOS runtime behavior remain untested. Signed-off-by: fufesou <linlong1266@gmail.com> * Keep trusted-device grants under the service authority Preserve root-owned grants when copying service configuration and route installed-host trust operations through the root service. Keep local authentication edits available when the Linux service is explicitly stopped, then synchronize pending revocation before using service grants. Serialize pending revocation synchronization and cover service copies, stale user snapshots, and stopped-service authentication updates with the existing focused tests. Signed-off-by: fufesou <linlong1266@gmail.com> * Replace same-ID grants when enrolling trusted devices Signed-off-by: fufesou <linlong1266@gmail.com> * Allow macOS security edits after service removal Reuse pending trusted-device revocation for offline password and 2FA changes. Honor pending revocation during installation copies and before root-authority requests and configuration synchronization. Validation: five focused macOS tests, macOS and Windows cargo checks, and targeted formatting checks. Signed-off-by: fufesou <linlong1266@gmail.com> * Allow macOS security edits before daemon reinstallation Detect an absent daemon using retained service ownership instead of the incoming-connections stop flag. General Start resumes connections without reinstalling the daemon, so local authentication edits must continue using pending revocation until installation. Adjust the existing recovery test to check that toggling incoming connections does not make a standalone profile service-owned. Signed-off-by: fufesou <linlong1266@gmail.com> * Allow re-enrollment after invalid controller credentials Ignore malformed or undecryptable stored token payloads with a throttled warning while preserving file access errors and host identity checks. Fresh authorized enrollment can replace the unusable credential. Restore logged, non-propagating IPC errors for non-authentication option changes and extend the existing credential recovery test. Signed-off-by: fufesou <linlong1266@gmail.com> * Update hbb_common to serialize peer deletion Signed-off-by: fufesou <linlong1266@gmail.com> * fix(macos): preserve trusted devices across service removal Hand off the current root grant store to the removing user and restore that user's latest grants on reinstallation. Keep other retained profiles from becoming authoritative, and preserve user settings when the service has no optional configuration. Validate the macOS build, five trusted-device tests, isolated headless transfer cases, and AppleScript syntax. Real privileged service transitions remain untested. Signed-off-by: fufesou <linlong1266@gmail.com> * refactor(trusted-devices): use local grants and existing config sync Keep grant decisions in the accepting host process and transfer grants and removals through the existing background configuration sync. Remove root-only grant RPC, ownership handoff, pending revocation, and dedicated copy commands; restore the master service lifecycle on Linux and macOS. Preserve V2 credentials, authentication revocation, and ordinary-save protections. Accept the existing asynchronous sync and multiple-user limitations. Validated Windows and macOS Flutter checks, the Linux library/tests/service check, four macOS trusted-device tests, and AppleScript syntax. Real service transitions remain untested. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(trusted-devices): preserve migrated tokens at capacity Raise the shared V2 token limit to 128 and order newly migrated tokens by issuance time without extending their legacy expiration. Extend the existing migration regression coverage to verify token reuse and expiry preservation at capacity. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(windows): preserve trust across service stop and start Export current on-disk grants and explicitly synchronize them into the GUI configuration at startup and before stopping a running service. Abort the stop if synchronization fails so the next service import cannot use a stale local copy. Extend the existing synchronization regression coverage to check revoked, retained and newly enrolled devices and the actual import-source file. Remove unrelated import logging changes. Validation: trusted-device tests passed on Windows and macOS (4 each); changed-line formatting and git diff --check passed. Real Windows GUI/UAC service transitions remain untested. Signed-off-by: fufesou <linlong1266@gmail.com> * refactor(trusted-devices): simplify local authorization Replace local authorization envelopes with typed operations while retaining locked authentication checks and persistence. Remove redundant configuration and Dart wrappers without changing credential formats or management requests. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(windows): allow service removal when host is unavailable Signed-off-by: fufesou <linlong1266@gmail.com> * fix(windows): preserve trust revocations before full uninstall Signed-off-by: fufesou <linlong1266@gmail.com> * fix(windows): sync trust changes to the retained user config Signed-off-by: fufesou <linlong1266@gmail.com> * fix(windows): sync trust after security setting changes Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
205 lines
6.3 KiB
Rust
205 lines
6.3 KiB
Rust
use hbb_common::{
|
|
anyhow::anyhow,
|
|
bail,
|
|
config::Config,
|
|
get_time,
|
|
password_security::{decrypt_vec_or_original, encrypt_vec_or_original},
|
|
ResultType,
|
|
};
|
|
use serde_derive::{Deserialize, Serialize};
|
|
use std::sync::Mutex;
|
|
use totp_rs::{Algorithm, Secret, TOTP};
|
|
|
|
lazy_static::lazy_static! {
|
|
static ref CURRENT_2FA: Mutex<Option<(TOTPInfo, TOTP)>> = Mutex::new(None);
|
|
}
|
|
|
|
const ISSUER: &str = "RustDesk";
|
|
const TAG_LOGIN: &str = "Connection";
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct TOTPInfo {
|
|
pub name: String,
|
|
pub secret: Vec<u8>,
|
|
pub digits: usize,
|
|
pub created_at: i64,
|
|
}
|
|
|
|
impl TOTPInfo {
|
|
fn new_totp(&self) -> ResultType<TOTP> {
|
|
let totp = TOTP::new(
|
|
Algorithm::SHA1,
|
|
self.digits,
|
|
1,
|
|
30,
|
|
self.secret.clone(),
|
|
Some(format!("{} {}", ISSUER, TAG_LOGIN)),
|
|
self.name.clone(),
|
|
)?;
|
|
Ok(totp)
|
|
}
|
|
|
|
fn gen_totp_info(name: String, digits: usize) -> ResultType<TOTPInfo> {
|
|
let secret = Secret::generate_secret();
|
|
let totp = TOTPInfo {
|
|
secret: secret.to_bytes()?,
|
|
name,
|
|
digits,
|
|
created_at: get_time(),
|
|
..Default::default()
|
|
};
|
|
Ok(totp)
|
|
}
|
|
|
|
pub fn into_string(&self) -> ResultType<String> {
|
|
let secret = encrypt_vec_or_original(self.secret.as_slice(), "00", 1024);
|
|
let totp_info = TOTPInfo {
|
|
secret,
|
|
..self.clone()
|
|
};
|
|
let s = serde_json::to_string(&totp_info)?;
|
|
Ok(s)
|
|
}
|
|
|
|
pub fn from_str(data: &str) -> ResultType<TOTP> {
|
|
let mut totp_info = serde_json::from_str::<TOTPInfo>(data)?;
|
|
let (secret, success, _) = decrypt_vec_or_original(&totp_info.secret, "00");
|
|
if success {
|
|
totp_info.secret = secret;
|
|
return Ok(totp_info.new_totp()?);
|
|
} else {
|
|
bail!("decrypt_vec_or_original 2fa secret failed")
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn generate2fa() -> String {
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
let id = crate::ipc::get_id();
|
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
let id = Config::get_id();
|
|
if let Ok(info) = TOTPInfo::gen_totp_info(id, 6) {
|
|
if let Ok(totp) = info.new_totp() {
|
|
let code = totp.get_url();
|
|
*CURRENT_2FA.lock().unwrap() = Some((info, totp));
|
|
return code;
|
|
}
|
|
}
|
|
"".to_owned()
|
|
}
|
|
|
|
pub fn verify2fa(code: String) -> bool {
|
|
if let Some((info, totp)) = CURRENT_2FA.lock().unwrap().as_ref() {
|
|
if let Ok(res) = totp.check_current(&code) {
|
|
if res {
|
|
if let Ok(v) = info.into_string() {
|
|
if let Err(error) = crate::ui_interface::set_auth_option("2fa".to_owned(), v) {
|
|
hbb_common::log::error!("2FA configuration update rejected: {error}");
|
|
return false;
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn get_2fa(raw: Option<String>) -> Option<TOTP> {
|
|
TOTPInfo::from_str(&raw.unwrap_or(Config::get_option("2fa")))
|
|
.map(|x| Some(x))
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct TelegramBot {
|
|
#[serde(skip)]
|
|
pub token_str: String,
|
|
pub token: Vec<u8>,
|
|
pub chat_id: String,
|
|
}
|
|
|
|
impl TelegramBot {
|
|
fn into_string(&self) -> ResultType<String> {
|
|
let token = encrypt_vec_or_original(self.token_str.as_bytes(), "00", 1024);
|
|
let bot = TelegramBot {
|
|
token,
|
|
..self.clone()
|
|
};
|
|
let s = serde_json::to_string(&bot)?;
|
|
Ok(s)
|
|
}
|
|
|
|
fn save(&self) -> ResultType<()> {
|
|
let s = self.into_string()?;
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
crate::ipc::set_option("bot", &s);
|
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
Config::set_option("bot".to_owned(), s);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get() -> ResultType<Option<TelegramBot>> {
|
|
let data = Config::get_option("bot");
|
|
if data.is_empty() {
|
|
return Ok(None);
|
|
}
|
|
let mut bot = serde_json::from_str::<TelegramBot>(&data)?;
|
|
let (token, success, _) = decrypt_vec_or_original(&bot.token, "00");
|
|
if success {
|
|
bot.token_str = String::from_utf8(token)?;
|
|
return Ok(Some(bot));
|
|
}
|
|
bail!("decrypt_vec_or_original telegram bot token failed")
|
|
}
|
|
}
|
|
|
|
// https://gist.github.com/dideler/85de4d64f66c1966788c1b2304b9caf1
|
|
pub async fn send_2fa_code_to_telegram(text: &str, bot: TelegramBot) -> ResultType<()> {
|
|
let url = format!("https://api.telegram.org/bot{}/sendMessage", bot.token_str);
|
|
let params = serde_json::json!({"chat_id": bot.chat_id, "text": text});
|
|
crate::post_request(url, params.to_string(), "").await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_chatid_telegram(bot_token: &str) -> ResultType<Option<String>> {
|
|
let url = format!("https://api.telegram.org/bot{}/getUpdates", bot_token);
|
|
// because caller is in tokio runtime, so we must call post_request_sync in new thread.
|
|
let handle = std::thread::spawn(move || crate::post_request_sync(url, "".to_owned(), ""));
|
|
let resp = handle.join().map_err(|_| anyhow!("Thread panicked"))??;
|
|
let value = serde_json::from_str::<serde_json::Value>(&resp).map_err(|e| anyhow!(e))?;
|
|
|
|
// Check for an error_code in the response
|
|
if let Some(error_code) = value.get("error_code").and_then(|code| code.as_i64()) {
|
|
// If there's an error_code, try to use the description for the error message
|
|
let description = value["description"]
|
|
.as_str()
|
|
.unwrap_or("Unknown error occurred");
|
|
return Err(anyhow!(
|
|
"Telegram API error: {} (error_code: {})",
|
|
description,
|
|
error_code
|
|
));
|
|
}
|
|
|
|
let chat_id = &value["result"][0]["message"]["chat"]["id"];
|
|
let chat_id = if let Some(id) = chat_id.as_i64() {
|
|
Some(id.to_string())
|
|
} else if let Some(id) = chat_id.as_str() {
|
|
Some(id.to_owned())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if let Some(chat_id) = chat_id.as_ref() {
|
|
let bot = TelegramBot {
|
|
token_str: bot_token.to_owned(),
|
|
chat_id: chat_id.to_owned(),
|
|
..Default::default()
|
|
};
|
|
bot.save()?;
|
|
}
|
|
|
|
Ok(chat_id)
|
|
}
|