From e46019a17182f81691efa3e535e60b3211f73537 Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 20 Jun 2022 10:41:46 +0800 Subject: [PATCH] password: safe/random personal password Signed-off-by: 21pages --- Cargo.lock | 1 + libs/hbb_common/Cargo.toml | 1 + libs/hbb_common/src/config.rs | 30 ++- libs/hbb_common/src/lib.rs | 12 +- libs/hbb_common/src/password_security.rs | 330 +++++++++++++++++++++++ src/common.rs | 8 - src/ipc.rs | 207 ++++++++++++-- src/lang/cn.rs | 13 + src/lang/cs.rs | 13 + src/lang/da.rs | 13 + src/lang/de.rs | 13 + src/lang/eo.rs | 13 + src/lang/es.rs | 13 + src/lang/fr.rs | 13 + src/lang/hu.rs | 13 + src/lang/id.rs | 13 + src/lang/it.rs | 13 + src/lang/ptbr.rs | 13 + src/lang/ru.rs | 13 + src/lang/sk.rs | 13 + src/lang/template.rs | 13 + src/lang/tr.rs | 13 + src/lang/tw.rs | 13 + src/main.rs | 2 +- src/rendezvous_mediator.rs | 2 +- src/server/connection.rs | 46 +++- src/ui.rs | 99 ++++++- src/ui/common.css | 2 +- src/ui/common.tis | 2 +- src/ui/index.css | 15 ++ src/ui/index.tis | 189 ++++++++++--- 31 files changed, 1060 insertions(+), 94 deletions(-) create mode 100644 libs/hbb_common/src/password_security.rs diff --git a/Cargo.lock b/Cargo.lock index 62d1c9d84..bbd8edfba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2087,6 +2087,7 @@ dependencies = [ "lazy_static", "log", "mac_address", + "machine-uid", "protobuf", "protobuf-codegen", "quinn", diff --git a/libs/hbb_common/Cargo.toml b/libs/hbb_common/Cargo.toml index 6fec67193..b8db8d508 100644 --- a/libs/hbb_common/Cargo.toml +++ b/libs/hbb_common/Cargo.toml @@ -34,6 +34,7 @@ tokio-socks = { git = "https://github.com/open-trade/tokio-socks" } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] mac_address = "1.1" +machine-uid = "0.2" [features] quic = [] diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 340a725f8..d8502151f 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -1,4 +1,8 @@ -use crate::log; +use crate::{ + log, + password_security::config::{decrypt_str_or_original, encrypt_str_or_original}, +}; +use anyhow::Result; use directories_next::ProjectDirs; use rand::Rng; use serde_derive::{Deserialize, Serialize}; @@ -17,6 +21,7 @@ pub const CONNECT_TIMEOUT: u64 = 18_000; pub const REG_INTERVAL: i64 = 12_000; pub const COMPRESS_LEVEL: i32 = 3; const SERIAL: i32 = 3; +const PASSWORD_ENC_VERSION: &'static str = "00"; // 128x128 #[cfg(target_os = "macos")] // 128x128 on 160x160 canvas, then shrink to 128, mac looks better with padding pub const ICON: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAyVBMVEUAAAAAcf8Acf8Acf8Acv8Acf8Acf8Acf8Acf8AcP8Acf8Ab/8AcP8Acf////8AaP/z+f/o8v/k7v/5/v/T5f8AYP/u9v/X6f+hx/+Kuv95pP8Aef/B1/+TwP9xoP8BdP/g6P+Irv9ZmP8Bgf/E3f98q/9sn/+01f+Es/9nm/9Jif8hhv8off/M4P+syP+avP86iP/c7f+xy/9yqf9Om/9hk/9Rjv+60P99tv9fpf88lv8yjf8Tgf8deP+kvP8BiP8NeP8hkP80gP8oj2VLAAAADXRSTlMA7o7qLvnaxZ1FOxYPjH9HWgAABHJJREFUeNrtm+tW4jAQgBfwuu7MtIUWsOUiCCioIIgLiqvr+z/UHq/LJKVkmwTcc/r9E2nzlU4mSTP9lpGRkZGR8VX5cZjfL+yCEXYL+/nDH//U/Pd8DgyTy39Xbv7oIAcWyB0cqbW/sweW2NtRaj8H1sgpGOwUIAH7Bkd7YJW9dXFwAJY5WNP/cmCZQnJvzIN18on5LwfWySXlxEPYAIcad8D6PdiHDbCfIFCADVBIENiFDbCbIACKPPXrZ+cP8E6/0znvP4EymgIEravIRcTxu8HxNSJ60a8W0AYECKrlAN+YwAthCd9wm1Ug6wKzIn5SgRduXfwkqDasCjx0XFzi9PV6zwNcIuhcWBOg+ikySq8C9UD4dEKWBCoOcspvAuLHTo9sCDQiFPHotRM48j8G5gVur1FdAN2uaYEuiz7xFsgEJ2RUoMUakXuBTHHoGxQYOBhHjeUBAefEnMAowFhaLBOKuOemBBbxLRQrH2PBCgMvNCPQGMeevTb9zLrPxz2Mo+QbEaijzPUcOOHMQZkKGRAIPem39+bypREMPTkQW/oCfk866zAkiIFG4yIKRE/aAnfiSd0WrORY6pFdXQEqi9mvAQm0RIOSnoCcZ8vJoz3diCnjRk+g8VP4/fuQDJ2Lxr6WwG0gXs9aTpDzW0vgDBlVUpixR8gYk44AD8FrUKHr8JQJGgIDnoDqoALxmWPQSi9AVVzm8gKUuEPGr/QCvptwJkbSYT/TC4S8C96DGjTj86aHtAI0x2WaBIq0eSYYpRa4EsdWVVwWu9O0Aj6f6dyBMnwEraeOgSYu0wZlauzA47QCbT7DgAQSE+hZWoEBF/BBmWOewNMK3BsSqKUW4MGcWqCSVmDkbvkXGKQOwg6PAUO9oL3xXhA20yaiCjuwYygRVQlUOTWTCf2SuNJTxeFjgaHByGuAIvd8ItdPLTDhS7IuqEE1YSKVOgbayLhSFQhMzYh8hwfBs1r7c505YVIQYEdNoKwxK06MJiyrpUFHiF0NAfCQUVHoiRclIXJIR6C2fqG37pBHvcWpgwzvAtYwkR5UGV2e42UISdBJETl3mg8ouo54Rcnti1/vaT+iuUQBt500Cgo4U10BeHSkk57FB0JjWkKRMWgLUA0lLodtImAQdaMiiri3+gIAPZQoutHNsgKF1aaDMhMyIdBf8Th+Bh8MTjGWCpl5Wv43tDmnF+IUVMrcZgRoiAxhtrloYizNkZaAnF5leglbNhj0wYCAbCDvGb0mP4nib7O7ZlcYQ2m1gPtIZgVgGNNMeaVAaWR+57TrqgtUnm3sHQ+kYeE6fufUubG1ez50FXbPnWgBlgSABmN3TTcsRl2yWkHRrwbiunvk/W2+Mg1hPZplPDeXRbZzStFH15s1QIVd3UImP5z/bHpeeQLvRJ7XLFUffQIlCvqlXETQbgN9/rlYABGosv+Vi9m2Xs639YLGrZd0br+odetlvdsvbN56abfd4vbCzv9Q3v/ygoOV21A4OPpfXvH4Ai+5ZGRkZGRkbJA/t/I0QMzoMiEAAAAASUVORK5CYII= @@ -267,11 +272,19 @@ impl Config { } fn load() -> Config { - Config::load_::("") + let mut config = Config::load_::(""); + let (password, store) = decrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION); + config.password = password; + if store { + config.store(); + } + config } fn store(&self) { - Config::store_(self, ""); + let mut config = self.clone(); + config.password = encrypt_str_or_original(&config.password, PASSWORD_ENC_VERSION); + Config::store_(&config, ""); } pub fn file() -> PathBuf { @@ -627,7 +640,7 @@ impl Config { log::info!("id updated from {} to {}", id, new_id); } - pub fn set_password(password: &str) { + pub fn set_security_password(password: &str) { let mut config = CONFIG.write().unwrap(); if password == config.password { return; @@ -636,13 +649,8 @@ impl Config { config.store(); } - pub fn get_password() -> String { - let mut password = CONFIG.read().unwrap().password.clone(); - if password.is_empty() { - password = Config::get_auto_password(); - Config::set_password(&password); - } - password + pub fn get_security_password() -> String { + CONFIG.read().unwrap().password.clone() } pub fn set_salt(salt: &str) { diff --git a/libs/hbb_common/src/lib.rs b/libs/hbb_common/src/lib.rs index 07c6a6868..fdd32c4c7 100644 --- a/libs/hbb_common/src/lib.rs +++ b/libs/hbb_common/src/lib.rs @@ -3,6 +3,7 @@ pub mod protos; pub use protos::message as message_proto; pub use protos::rendezvous as rendezvous_proto; pub use bytes; +use config::Config; pub use futures; pub use protobuf; use std::{ @@ -26,6 +27,7 @@ pub use anyhow::{self, bail}; pub use futures_util; pub mod config; pub mod fs; +pub use lazy_static; #[cfg(not(any(target_os = "android", target_os = "ios")))] pub use mac_address; pub use rand; @@ -34,7 +36,7 @@ pub use sodiumoxide; pub use tokio_socks; pub use tokio_socks::IntoTargetAddr; pub use tokio_socks::TargetAddr; -pub use lazy_static; +pub mod password_security; #[cfg(feature = "quic")] pub type Stream = quic::Connection; @@ -199,6 +201,14 @@ pub fn get_modified_time(path: &std::path::Path) -> SystemTime { .unwrap_or(UNIX_EPOCH) } +pub fn get_uuid() -> Vec { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if let Ok(id) = machine_uid::get() { + return id.into(); + } + Config::get_key_pair().1 +} + #[cfg(test)] mod tests { use super::*; diff --git a/libs/hbb_common/src/password_security.rs b/libs/hbb_common/src/password_security.rs new file mode 100644 index 000000000..ed6376ff9 --- /dev/null +++ b/libs/hbb_common/src/password_security.rs @@ -0,0 +1,330 @@ +pub mod password { + use crate::config::Config; + use std::{ + fmt::Display, + str::FromStr, + sync::{Arc, RwLock}, + }; + + lazy_static::lazy_static! { + pub static ref RANDOM_PASSWORD:Arc> = Arc::new(RwLock::new(Config::get_auto_password())); + } + + const SECURITY_ENABLED: &'static str = "security-password-enabled"; + const RANDOM_ENABLED: &'static str = "random-password-enabled"; + const ONETIME_ENABLED: &'static str = "onetime-password-enabled"; + const ONETIME_ACTIVATED: &'static str = "onetime-password-activated"; + const UPDATE_METHOD: &'static str = "random-password-update-method"; + + #[derive(Debug, Clone, PartialEq, Eq)] + pub enum UpdateMethod { + KEEP, + UPDATE, + DISABLE, + } + + impl FromStr for UpdateMethod { + type Err = (); + + fn from_str(s: &str) -> Result { + if s == "KEEP" { + Ok(Self::KEEP) + } else if s == "UPDATE" { + Ok(Self::UPDATE) + } else if s == "DISABLE" { + Ok(Self::DISABLE) + } else { + Err(()) + } + } + } + + impl Display for UpdateMethod { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UpdateMethod::KEEP => write!(f, "KEEP"), + UpdateMethod::UPDATE => write!(f, "UPDATE"), + UpdateMethod::DISABLE => write!(f, "DISABLE"), + } + } + } + + pub fn set_random_password(password: &str) { + *RANDOM_PASSWORD.write().unwrap() = password.to_owned(); + } + + pub fn random_password() -> String { + let mut password = RANDOM_PASSWORD.read().unwrap().clone(); + if password.is_empty() { + password = Config::get_auto_password(); + set_random_password(&password); + } + password + } + + pub fn random_password_valid() -> bool { + if random_enabled() { + onetime_password_activated() || !onetime_password_enabled() + } else { + false + } + } + + pub fn passwords() -> Vec { + let mut v = vec![]; + if random_password_valid() { + v.push(random_password()); + } + if security_enabled() { + v.push(Config::get_security_password()); + } + v + } + + pub fn after_session(authorized: bool) { + if authorized && random_enabled() { + UpdateMethod::from_str(&update_method()) + .map(|method| match method { + UpdateMethod::KEEP => {} + UpdateMethod::UPDATE => set_random_password(&Config::get_auto_password()), + UpdateMethod::DISABLE => set_random_enabled(false), + }) + .ok(); + } + } + + pub fn update_method() -> String { + let mut method = Config::get_option(UPDATE_METHOD); + if UpdateMethod::from_str(&method).is_err() { + method = UpdateMethod::KEEP.to_string(); // default is keep + set_update_method(&method); + } + method + } + + pub fn set_update_method(method: &str) { + Config::set_option(UPDATE_METHOD.to_owned(), method.to_owned()); + } + + pub fn random_enabled() -> bool { + str2bool(RANDOM_ENABLED, true, || { + set_onetime_password_activated(false); + set_random_password(&Config::get_auto_password()); + }) + } + + pub fn set_random_enabled(enabled: bool) { + if enabled != random_enabled() { + Config::set_option(RANDOM_ENABLED.to_owned(), bool2str(enabled)); + set_onetime_password_activated(false); + if enabled { + set_random_password(&Config::get_auto_password()); + } + } + } + + pub fn security_enabled() -> bool { + str2bool(SECURITY_ENABLED, true, || {}) + } + + pub fn set_security_enabled(enabled: bool) { + if enabled != security_enabled() { + Config::set_option(SECURITY_ENABLED.to_owned(), bool2str(enabled)); + } + } + + pub fn onetime_password_enabled() -> bool { + str2bool(ONETIME_ENABLED, false, || { + set_onetime_password_activated(false); + set_random_password(&Config::get_auto_password()); + }) + } + + pub fn set_onetime_password_enabled(enabled: bool) { + if enabled != onetime_password_enabled() { + Config::set_option(ONETIME_ENABLED.to_owned(), bool2str(enabled)); + set_onetime_password_activated(false); + set_random_password(&Config::get_auto_password()); + } + } + + pub fn onetime_password_activated() -> bool { + str2bool(ONETIME_ACTIVATED, false, || {}) + } + + pub fn set_onetime_password_activated(activated: bool) { + if activated != onetime_password_activated() { + Config::set_option(ONETIME_ACTIVATED.to_owned(), bool2str(activated)); + if activated { + set_random_password(&Config::get_auto_password()); + } + } + } + + // notice: Function nesting + fn str2bool(key: &str, default: bool, default_set: impl Fn()) -> bool { + let option = Config::get_option(key); + if option == "Y" { + true + } else if option == "N" { + false + } else { + Config::set_option(key.to_owned(), bool2str(default)); + default_set(); + default + } + } + + fn bool2str(option: bool) -> String { + if option { "Y" } else { "N" }.to_owned() + } +} + +pub mod config { + use super::base64::decrypt as decrypt00; + use super::base64::encrypt as encrypt00; + + const VERSION_LEN: usize = 2; + + pub fn encrypt_str_or_original(s: &str, version: &str) -> String { + if version.len() == VERSION_LEN { + if version == "00" { + if let Ok(s) = encrypt00(s.as_bytes()) { + return version.to_owned() + &s; + } + } + } + + s.to_owned() + } + + // bool: whether should store to re-encrypt when load + pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool) { + if s.len() > VERSION_LEN { + let version = &s[..VERSION_LEN]; + if version == "00" { + if let Ok(v) = decrypt00(&s[VERSION_LEN..].as_bytes()) { + return ( + String::from_utf8_lossy(&v).to_string(), + version != current_version, + ); + } + } + } + + (s.to_owned(), !s.is_empty()) + } + + pub fn encrypt_vec_or_original(v: &[u8], version: &str) -> Vec { + if version.len() == VERSION_LEN { + if version == "00" { + if let Ok(s) = encrypt00(v) { + let mut version = version.to_owned().into_bytes(); + version.append(&mut s.into_bytes()); + return version; + } + } + } + + v.to_owned() + } + + // bool: whether should store to re-encrypt when load + pub fn decrypt_vec_or_original(v: &[u8], current_version: &str) -> (Vec, bool) { + if v.len() > VERSION_LEN { + let version = String::from_utf8_lossy(&v[..VERSION_LEN]); + if version == "00" { + if let Ok(v) = decrypt00(&v[VERSION_LEN..]) { + return (v, version != current_version); + } + } + } + + (v.to_owned(), !v.is_empty()) + } + + mod test { + + #[test] + fn test() { + use crate::password_security::config::*; + + println!("test str"); + let data = "Hello World"; + let encrypted = encrypt_str_or_original(data, "00"); + let (decrypted, store) = decrypt_str_or_original(&encrypted, "00"); + println!("data: {}", data); + println!("encrypted: {}", encrypted); + println!("decrypted: {}", decrypted); + assert_eq!(data, decrypted); + assert_eq!("00", &encrypted[..2]); + assert_eq!(store, false); + let (_, store2) = decrypt_str_or_original(&encrypted, "01"); + assert_eq!(store2, true); + + println!("test vec"); + let data: Vec = vec![1, 2, 3, 4]; + let encrypted = encrypt_vec_or_original(&data, "00"); + let (decrypted, store) = decrypt_vec_or_original(&encrypted, "00"); + println!("data: {:?}", data); + println!("encrypted: {:?}", encrypted); + println!("decrypted: {:?}", decrypted); + assert_eq!(data, decrypted); + assert_eq!("00".as_bytes(), &encrypted[..2]); + assert_eq!(store, false); + let (_, store2) = decrypt_vec_or_original(&encrypted, "01"); + assert_eq!(store2, true); + + println!("test old"); + let data = "00Hello World"; + let (decrypted, store) = decrypt_str_or_original(&data, "00"); + assert_eq!(data, decrypted); + assert_eq!(store, true); + let data: Vec = vec!['0' as u8, '0' as u8, 1, 2, 3, 4]; + let (decrypted, store) = decrypt_vec_or_original(&data, "00"); + assert_eq!(data, decrypted); + assert_eq!(store, true); + let (_, store) = decrypt_str_or_original("", "00"); + assert_eq!(store, false); + let (_, store) = decrypt_vec_or_original(&vec![], "00"); + assert_eq!(store, false); + } + } +} + +mod base64 { + use super::symmetric_crypt; + use sodiumoxide::base64; + + pub fn encrypt(v: &[u8]) -> Result { + if v.len() > 0 { + symmetric_crypt(v, true).map(|v| base64::encode(v, base64::Variant::Original)) + } else { + Err(()) + } + } + + pub fn decrypt(v: &[u8]) -> Result, ()> { + if v.len() > 0 { + base64::decode(v, base64::Variant::Original).and_then(|v| symmetric_crypt(&v, false)) + } else { + Err(()) + } + } +} + +fn symmetric_crypt(data: &[u8], encrypt: bool) -> Result, ()> { + use sodiumoxide::crypto::secretbox; + use std::convert::TryInto; + + let mut keybuf = crate::get_uuid(); + keybuf.resize(secretbox::KEYBYTES, 0); + let key = secretbox::Key(keybuf.try_into().map_err(|_| ())?); + let nonce = secretbox::Nonce([0; secretbox::NONCEBYTES]); + + if encrypt { + Ok(secretbox::seal(data, &nonce, &key)) + } else { + secretbox::open(data, &nonce, &key) + } +} diff --git a/src/common.rs b/src/common.rs index 19af5986b..d9eab5d99 100644 --- a/src/common.rs +++ b/src/common.rs @@ -537,14 +537,6 @@ pub fn is_setup(name: &str) -> bool { name.to_lowercase().ends_with("setdown.exe") || name.to_lowercase().ends_with("安装.exe") } -pub fn get_uuid() -> Vec { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if let Ok(id) = machine_uid::get() { - return id.into(); - } - Config::get_key_pair().1 -} - pub fn get_custom_rendezvous_server(custom: String) -> String { if !custom.is_empty() { return custom; diff --git a/src/ipc.rs b/src/ipc.rs index 7df06cd22..5f2f83b89 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -7,7 +7,9 @@ use hbb_common::{ config::{self, Config, Config2}, futures::StreamExt as _, futures_util::sink::SinkExt, - log, timeout, tokio, + log, + password_security::password, + timeout, tokio, tokio::io::{AsyncRead, AsyncWrite}, tokio_util::codec::Framed, ResultType, @@ -20,6 +22,16 @@ use std::{collections::HashMap, sync::atomic::Ordering}; #[cfg(not(windows))] use std::{fs::File, io::prelude::*}; +const STR_RANDOM_PASSWORD: &'static str = "random-password"; +const STR_SECURITY_PASSWORD: &'static str = "security-password"; +const STR_RANDOM_PASSWORD_UPDATE_METHOD: &'static str = "random-password-update-method"; +const STR_RANDOM_PASSWORD_ENABLED: &'static str = "random-password-enabled"; +const STR_SECURITY_PASSWORD_ENABLED: &'static str = "security-password-enabled"; +const STR_ONETIME_PASSWORD_ENABLED: &'static str = "onetime-password-enabled"; +const STR_ONETIME_PASSWORD_ACTIVATED: &'static str = "onetime-password-activated"; +const STR_RANDOM_PASSWORD_VALID: &'static str = "random-password-valid"; +pub const STR_PASSWORD_DESCRIPTION: &'static str = "password-description"; + // State with timestamp, because std::time::Instant cannot be serialized #[derive(Debug, Serialize, Deserialize, Copy, Clone)] #[serde(tag = "t", content = "c")] @@ -128,6 +140,7 @@ pub enum Data { ClipboardFileEnabled(bool), PrivacyModeState((i32, PrivacyModeState)), TestRendezvousServer, + Bool((String, Option)), } #[tokio::main(flavor = "current_thread")] @@ -282,8 +295,20 @@ async fn handle(data: Data, stream: &mut Connection) { let value; if name == "id" { value = Some(Config::get_id()); - } else if name == "password" { - value = Some(Config::get_password()); + } else if name == STR_RANDOM_PASSWORD { + value = Some(password::random_password()); + } else if name == STR_SECURITY_PASSWORD { + value = Some(Config::get_security_password()); + } else if name == STR_RANDOM_PASSWORD_UPDATE_METHOD { + value = Some(password::update_method().to_string()); + } else if name == STR_PASSWORD_DESCRIPTION { + value = Some( + password::random_password() + + &password::security_enabled().to_string() + + &password::random_enabled().to_string() + + &password::onetime_password_enabled().to_string() + + &password::onetime_password_activated().to_string(), + ); } else if name == "salt" { value = Some(Config::get_salt()); } else if name == "rendezvous_server" { @@ -303,8 +328,12 @@ async fn handle(data: Data, stream: &mut Connection) { if name == "id" { Config::set_key_confirmed(false); Config::set_id(&value); - } else if name == "password" { - Config::set_password(&value); + } else if name == STR_RANDOM_PASSWORD { + password::set_random_password(&value); + } else if name == STR_SECURITY_PASSWORD { + Config::set_security_password(&value); + } else if name == STR_RANDOM_PASSWORD_UPDATE_METHOD { + password::set_update_method(&value); } else if name == "salt" { Config::set_salt(&value); } else { @@ -344,6 +373,36 @@ async fn handle(data: Data, stream: &mut Connection) { Data::TestRendezvousServer => { crate::test_rendezvous_server(); } + Data::Bool((name, value)) => match value { + None => { + let value; + if name == STR_SECURITY_PASSWORD_ENABLED { + value = Some(password::security_enabled()); + } else if name == STR_RANDOM_PASSWORD_ENABLED { + value = Some(password::random_enabled()); + } else if name == STR_ONETIME_PASSWORD_ENABLED { + value = Some(password::onetime_password_enabled()); + } else if name == STR_ONETIME_PASSWORD_ACTIVATED { + value = Some(password::onetime_password_activated()); + } else if name == STR_RANDOM_PASSWORD_VALID { + value = Some(password::random_password_valid()); + } else { + return; + } + allow_err!(stream.send(&Data::Bool((name, value))).await); + } + Some(value) => { + if name == STR_SECURITY_PASSWORD_ENABLED { + password::set_security_enabled(value); + } else if name == STR_RANDOM_PASSWORD_ENABLED { + password::set_random_enabled(value); + } else if name == STR_ONETIME_PASSWORD_ENABLED { + password::set_onetime_password_enabled(value); + } else if name == STR_ONETIME_PASSWORD_ACTIVATED { + password::set_onetime_password_activated(value); + } + } + }, _ => {} } } @@ -426,6 +485,10 @@ where .await } + async fn send_bool(&mut self, name: &str, value: bool) -> ResultType<()> { + self.send(&Data::Bool((name.to_owned(), Some(value)))).await + } + pub async fn next_timeout(&mut self, ms_timeout: u64) -> ResultType> { Ok(timeout(ms_timeout, self.next()).await??) } @@ -497,9 +560,128 @@ pub async fn set_config(name: &str, value: String) -> ResultType<()> { set_config_async(name, value).await } -pub fn set_password(v: String) -> ResultType<()> { - Config::set_password(&v); - set_config("password", v) +#[tokio::main(flavor = "current_thread")] +async fn get_bool(name: &str) -> ResultType> { + get_bool_async(name, 1_000).await +} + +async fn get_bool_async(name: &str, ms_timeout: u64) -> ResultType> { + let mut c = connect(ms_timeout, "").await?; + c.send(&Data::Bool((name.to_owned(), None))).await?; + if let Some(Data::Bool((name2, value))) = c.next_timeout(ms_timeout).await? { + if name == name2 { + return Ok(value); + } + } + return Ok(None); +} + +pub async fn set_bool_async(name: &str, value: bool) -> ResultType<()> { + let mut c = connect(1000, "").await?; + c.send_bool(name, value).await?; + Ok(()) +} + +#[tokio::main(flavor = "current_thread")] +pub async fn set_bool(name: &str, value: bool) -> ResultType<()> { + set_bool_async(name, value).await +} + +pub fn get_random_password() -> String { + if let Ok(Some(password)) = get_config(STR_RANDOM_PASSWORD) { + password::set_random_password(&password); + password + } else { + password::random_password() + } +} + +pub fn set_random_password(v: String) -> ResultType<()> { + password::set_random_password(&v); + set_config(STR_RANDOM_PASSWORD, v) +} + +pub fn set_security_password(v: String) -> ResultType<()> { + Config::set_security_password(&v); + set_config(STR_SECURITY_PASSWORD, v) +} + +pub fn random_password_update_method() -> String { + if let Ok(Some(method)) = get_config(STR_RANDOM_PASSWORD_UPDATE_METHOD) { + password::set_update_method(&method); + method + } else { + password::update_method() + } +} + +pub fn set_random_password_update_method(method: String) -> ResultType<()> { + password::set_update_method(&method); + set_config(STR_RANDOM_PASSWORD_UPDATE_METHOD, method) +} + +pub fn is_random_password_enabled() -> bool { + if let Ok(Some(enabled)) = get_bool(STR_RANDOM_PASSWORD_ENABLED) { + password::set_random_enabled(enabled); + enabled + } else { + password::random_enabled() + } +} + +pub fn set_random_password_enabled(enabled: bool) -> ResultType<()> { + password::set_random_enabled(enabled); + set_bool(STR_RANDOM_PASSWORD_ENABLED, enabled) +} + +pub fn is_security_password_enabled() -> bool { + if let Ok(Some(enabled)) = get_bool(STR_SECURITY_PASSWORD_ENABLED) { + password::set_security_enabled(enabled); + enabled + } else { + password::security_enabled() + } +} + +pub fn set_security_password_enabled(enabled: bool) -> ResultType<()> { + password::set_security_enabled(enabled); + set_bool(STR_SECURITY_PASSWORD_ENABLED, enabled) +} + +pub fn is_onetime_password_enabled() -> bool { + if let Ok(Some(enabled)) = get_bool(STR_ONETIME_PASSWORD_ENABLED) { + password::set_onetime_password_enabled(enabled); + enabled + } else { + password::onetime_password_enabled() + } +} + +pub fn set_onetime_password_enabled(enabled: bool) -> ResultType<()> { + password::set_onetime_password_enabled(enabled); + set_bool(STR_ONETIME_PASSWORD_ENABLED, enabled) +} + +pub fn is_onetime_password_activated() -> bool { + if let Ok(Some(activated)) = get_bool(STR_ONETIME_PASSWORD_ACTIVATED) { + password::set_onetime_password_activated(activated); + activated + } else { + password::onetime_password_activated() + } +} + +pub fn set_onetime_password_activated(activated: bool) -> ResultType<()> { + password::set_onetime_password_activated(activated); + set_bool(STR_ONETIME_PASSWORD_ACTIVATED, activated) +} + +pub fn is_random_password_valid() -> bool { + if let Ok(Some(valid)) = get_bool(STR_RANDOM_PASSWORD_VALID) { + valid + } else { + password::random_password_valid() + } } pub fn get_id() -> String { @@ -518,15 +700,6 @@ pub fn get_id() -> String { } } -pub fn get_password() -> String { - if let Ok(Some(v)) = get_config("password") { - Config::set_password(&v); - v - } else { - Config::get_password() - } -} - pub async fn get_rendezvous_server(ms_timeout: u64) -> (String, Vec) { if let Ok(Some(v)) = get_config_async("rendezvous_server", ms_timeout).await { let mut urls = v.split(","); diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 58a3a6cd7..6e3d4d067 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "保持RustDesk后台服务"), ("Ignore Battery Optimizations", "忽略电池优化"), ("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的RustDesk应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"), + ("Random Password After Session", "会话结束更新随机密码"), + ("Keep", "保持"), + ("Update", "更新"), + ("Disable", "禁用"), + ("Onetime Password", "一次性口令"), + ("Verification Method", "密码验证方式"), + ("Enable security password", "启用安全密码"), + ("Enable random password", "启用随机密码"), + ("Enable onetime password", "启用一次性访问功能"), + ("Disable onetime password", "禁用一次性访问功能"), + ("Activate onetime password", "激活一次性访问功能"), + ("Set security password", "设置安全密码"), + ("Connection not allowed", "对方不允许连接"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index c5c432d46..d9ff10416 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index d13cd8550..f9776e687 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index b7f471992..c9cd243fb 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 4719a3be3..a21833559 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index adf214b97..14ba0ab57 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index e92069993..f387b550b 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index b0d1bfda0..e35ab8195 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 4cdaf8b95..6bf69e476 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 3a0c2dc2a..778313d5b 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index e3cadd204..176833501 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 7735e3db3..d8a7702f3 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index ea8d22aa8..332336007 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 9ea7146e1..7b39a2876 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 1d30425dc..02468201e 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Random Password After Session", ""), + ("Keep", ""), + ("Update", ""), + ("Disable", ""), + ("Onetime Password", ""), + ("Verification Method", ""), + ("Enable security password", ""), + ("Enable random password", ""), + ("Enable onetime password", ""), + ("Disable onetime password", ""), + ("Activate onetime password", ""), + ("Set security password", ""), + ("Connection not allowed", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 30c8f5a76..ea94d159c 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -287,5 +287,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "保持RustDesk後台服務"), ("Ignore Battery Optimizations", "忽略電池優化"), ("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的RustDesk應用設置頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"), + ("Random Password After Session", "會話結束更新隨機密碼"), + ("Keep", "保持"), + ("Update", "更新"), + ("Disable", "禁用"), + ("Onetime Password", "一次性口令"), + ("Verification Method", "密碼驗證方式"), + ("Enable security password", "啟用安全密碼"), + ("Enable random password", "啟用隨機密碼"), + ("Enable onetime password", "啟用一次性訪問功能"), + ("Disable onetime password", "禁用一次性訪問功能"), + ("Activate onetime password", "激活一次性訪問功能"), + ("Set security password", "設置安全密碼"), + ("Connection not allowed", "對方不允許連接"), ].iter().cloned().collect(); } diff --git a/src/main.rs b/src/main.rs index 2e2e4c8ac..2f30f4b4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -148,7 +148,7 @@ fn main() { return; } else if args[0] == "--password" { if args.len() == 2 { - ipc::set_password(args[1].to_owned()).unwrap(); + ipc::set_security_password(args[1].to_owned()).unwrap(); } return; } else if args[0] == "--check-hwcodec-config" { diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index 6dc7a11c9..d4b76c991 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -389,7 +389,7 @@ impl RendezvousMediator { async fn register_pk(&mut self, socket: &mut FramedSocket) -> ResultType<()> { let mut msg_out = Message::new(); let pk = Config::get_key_pair().1; - let uuid = crate::get_uuid(); + let uuid = hbb_common::get_uuid(); let id = Config::get_id(); self.last_id_pk_registry = id.clone(); msg_out.set_register_pk(RegisterPk { diff --git a/src/server/connection.rs b/src/server/connection.rs index dbf1545ae..0481126c4 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -8,6 +8,7 @@ use crate::video_service; use crate::{common::MOBILE_INFO2, mobile::connection_manager::start_channel}; use crate::{ipc, VERSION}; use hbb_common::fs::can_enable_overwrite_detection; +use hbb_common::password_security::password; use hbb_common::{ config::Config, fs, @@ -398,6 +399,7 @@ impl Connection { video_service::notify_video_frame_feched(id, None); scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove); video_service::VIDEO_QOS.lock().unwrap().reset(); + password::after_session(conn.authorized); if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await { conn.on_close(&err.to_string(), false); } @@ -571,7 +573,7 @@ impl Connection { let url = self.api_server.clone(); let mut v = v; v["id"] = json!(Config::get_id()); - v["uuid"] = json!(base64::encode(crate::get_uuid())); + v["uuid"] = json!(base64::encode(hbb_common::get_uuid())); v["Id"] = json!(self.inner.id); tokio::spawn(async move { allow_err!(Self::post_audit_async(url, v).await); @@ -778,6 +780,36 @@ impl Connection { self.tx_input.send(MessageInput::Key((msg, press))).ok(); } + fn validate_password(&mut self, lr_password: Vec) -> bool { + let validate = |password: String| { + if password.len() == 0 { + return false; + } + let mut hasher = Sha256::new(); + hasher.update(password); + hasher.update(&self.hash.salt); + let mut hasher2 = Sha256::new(); + hasher2.update(&hasher.finalize()[..]); + hasher2.update(&self.hash.challenge); + hasher2.finalize()[..] == lr_password[..] + }; + if password::security_enabled() { + if validate(Config::get_security_password()) { + return true; + } + } + if password::random_password_valid() { + if validate(password::random_password()) { + if password::onetime_password_activated() { + password::set_onetime_password_activated(false); + } + return true; + } + } + + false + } + async fn on_message(&mut self, msg: Message) -> bool { if let Some(message::Union::LoginRequest(lr)) = msg.union { if let Some(o) = lr.option.as_ref() { @@ -853,12 +885,10 @@ impl Connection { } else if lr.password.is_empty() { self.try_start_cm(lr.my_id, lr.my_name, false); } else { - let mut hasher = Sha256::new(); - hasher.update(&Config::get_password()); - hasher.update(&self.hash.salt); - let mut hasher2 = Sha256::new(); - hasher2.update(&hasher.finalize()[..]); - hasher2.update(&self.hash.challenge); + if password::passwords().len() == 0 { + self.send_login_error("Connection not allowed").await; + return false; + } let mut failure = LOGIN_FAILURES .lock() .unwrap() @@ -871,7 +901,7 @@ impl Connection { .await; } else if time == failure.0 && failure.1 > 6 { self.send_login_error("Please try 1 minute later").await; - } else if hasher2.finalize()[..] != lr.password[..] { + } else if !self.validate_password(lr.password.clone()) { if failure.0 == time { failure.1 += 1; failure.2 += 1; diff --git a/src/ui.rs b/src/ui.rs index 2b7ffcc29..3fecc33cd 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -43,6 +43,7 @@ struct UI( Arc>>, Arc>, mpsc::UnboundedSender, + Arc>, ); struct UIHostHandler; @@ -169,7 +170,7 @@ pub fn start(args: &mut [String]) { impl UI { fn new(childs: Childs) -> Self { let res = check_connect_status(true); - Self(childs, res.0, res.1, Default::default(), res.2) + Self(childs, res.0, res.1, Default::default(), res.2, res.3) } fn recent_sessions_updated(&mut self) -> bool { @@ -186,16 +187,16 @@ impl UI { ipc::get_id() } - fn get_password(&mut self) -> String { - ipc::get_password() + fn get_random_password(&self) -> String { + ipc::get_random_password() } - fn update_password(&mut self, password: String) { - if password.is_empty() { - allow_err!(ipc::set_password(Config::get_auto_password())); - } else { - allow_err!(ipc::set_password(password)); - } + fn update_random_password(&self) { + allow_err!(ipc::set_random_password(Config::get_auto_password())); + } + + fn set_security_password(&self, password: String) { + allow_err!(ipc::set_security_password(password)); } fn get_remote_id(&mut self) -> String { @@ -704,7 +705,7 @@ impl UI { } fn get_uuid(&self) -> String { - base64::encode(crate::get_uuid()) + base64::encode(hbb_common::get_uuid()) } fn open_url(&self, url: String) { @@ -774,6 +775,54 @@ impl UI { fn get_langs(&self) -> String { crate::lang::LANGS.to_string() } + + fn random_password_update_method(&self) -> String { + ipc::random_password_update_method() + } + + fn set_random_password_update_method(&self, method: String) { + allow_err!(ipc::set_random_password_update_method(method)); + } + + fn is_random_password_enabled(&self) -> bool { + ipc::is_random_password_enabled() + } + + fn set_random_password_enabled(&self, enabled: bool) { + allow_err!(ipc::set_random_password_enabled(enabled)); + } + + fn is_security_password_enabled(&self) -> bool { + ipc::is_security_password_enabled() + } + + fn set_security_password_enabled(&self, enabled: bool) { + allow_err!(ipc::set_security_password_enabled(enabled)); + } + + fn is_onetime_password_enabled(&self) -> bool { + ipc::is_onetime_password_enabled() + } + + fn set_onetime_password_enabled(&self, enabled: bool) { + allow_err!(ipc::set_onetime_password_enabled(enabled)); + } + + fn is_onetime_password_activated(&self) -> bool { + ipc::is_onetime_password_activated() + } + + fn set_onetime_password_activated(&self, activated: bool) { + allow_err!(ipc::set_onetime_password_activated(activated)); + } + + fn is_random_password_valid(&self) -> bool { + ipc::is_random_password_valid() + } + + fn password_description(&mut self) -> String { + self.5.lock().unwrap().clone() + } } impl sciter::EventHandler for UI { @@ -783,8 +832,9 @@ impl sciter::EventHandler for UI { fn is_xfce(); fn using_public_server(); fn get_id(); - fn get_password(); - fn update_password(String); + fn get_random_password(); + fn update_random_password(); + fn set_security_password(String); fn get_remote_id(); fn set_remote_id(String); fn closing(i32, i32, i32, i32); @@ -854,6 +904,18 @@ impl sciter::EventHandler for UI { fn get_uuid(); fn has_hwcodec(); fn get_langs(); + fn random_password_update_method(); + fn set_random_password_update_method(String); + fn is_random_password_enabled(); + fn set_random_password_enabled(bool); + fn is_security_password_enabled(); + fn set_security_password_enabled(bool); + fn is_onetime_password_enabled(); + fn set_onetime_password_enabled(bool); + fn is_onetime_password_activated(); + fn set_onetime_password_activated(bool); + fn is_random_password_valid(); + fn password_description(); } } @@ -893,6 +955,7 @@ async fn check_connect_status_( status: Arc>, options: Arc>>, rx: mpsc::UnboundedReceiver, + password: Arc>, ) { let mut key_confirmed = false; let mut rx = rx; @@ -919,6 +982,8 @@ async fn check_connect_status_( Ok(Some(ipc::Data::Config((name, Some(value))))) => { if name == "id" { id = value; + } else if name == ipc::STR_PASSWORD_DESCRIPTION { + *password.lock().unwrap() = value; } } Ok(Some(ipc::Data::OnlineStatus(Some((mut x, c))))) => { @@ -938,6 +1003,7 @@ async fn check_connect_status_( c.send(&ipc::Data::OnlineStatus(None)).await.ok(); c.send(&ipc::Data::Options(None)).await.ok(); c.send(&ipc::Data::Config(("id".to_owned(), None))).await.ok(); + c.send(&ipc::Data::Config((ipc::STR_PASSWORD_DESCRIPTION.to_owned(), None))).await.ok(); } } } @@ -986,14 +1052,19 @@ fn check_connect_status( Arc>, Arc>>, mpsc::UnboundedSender, + Arc>, ) { let status = Arc::new(Mutex::new((0, false, 0, "".to_owned()))); let options = Arc::new(Mutex::new(Config::get_options())); let cloned = status.clone(); let cloned_options = options.clone(); let (tx, rx) = mpsc::unbounded_channel::(); - std::thread::spawn(move || check_connect_status_(reconnect, cloned, cloned_options, rx)); - (status, options, tx) + let password = Arc::new(Mutex::new(String::default())); + let cloned_password = password.clone(); + std::thread::spawn(move || { + check_connect_status_(reconnect, cloned, cloned_options, rx, cloned_password) + }); + (status, options, tx, password) } const INVALID_FORMAT: &'static str = "Invalid format"; diff --git a/src/ui/common.css b/src/ui/common.css index 39427fc8a..c3f3706ef 100644 --- a/src/ui/common.css +++ b/src/ui/common.css @@ -120,7 +120,7 @@ textarea:empty { @ELLIPSIS; } -div.password svg { +div.password svg:not(.checkmark) { padding-left: 1em; size: 16px; color: #ddd; diff --git a/src/ui/common.tis b/src/ui/common.tis index 46adc3288..aae950c2d 100644 --- a/src/ui/common.tis +++ b/src/ui/common.tis @@ -141,7 +141,7 @@ function adjustBorder() { if (el) el.attributes.toggleClass("active", view.windowState == View.WINDOW_FULL_SCREEN); } -var svg_checkmark = ; +var svg_checkmark = ; var svg_edit = ; diff --git a/src/ui/index.css b/src/ui/index.css index 3c4dc6a47..f4ec4c2f5 100644 --- a/src/ui/index.css +++ b/src/ui/index.css @@ -403,3 +403,18 @@ div.remote-session svg#menu { background: none; color: white; } + +svg#refresh-password { + display: inline-block; + stroke:#ddd; +} + +svg#refresh-password:hover { + stroke:color(text); +} + +li:disabled, li:disabled:hover { + color: color(lighter-text); + background: color(menu); +} + diff --git a/src/ui/index.tis b/src/ui/index.tis index 099ca2af8..4a6004135 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -20,6 +20,7 @@ var svg_menu = ; +var svg_refresh_password = ; var my_id = ""; function get_id() { @@ -520,10 +521,6 @@ class App: Reactor.Component var is_can_screen_recording = handler.is_can_screen_recording(false); return
- -
  • {translate('Refresh random password')}
  • -
  • {translate('Set your own password')}
  • -
    {translate('Your Desktop')}
    @@ -533,8 +530,7 @@ class App: Reactor.Component {key_confirmed ? : translate("Generating ...")}
    -
    {translate('Password')}
    - +
    {!is_win || handler.is_installed() ? "": } @@ -806,44 +802,151 @@ function watch_screen_recording() { class PasswordEyeArea : Reactor.Component { render() { + var show = handler.is_random_password_valid(); + var value = show ? handler.get_random_password() : "-"; return
    - - {svg_eye} + + {svg_refresh_password}
    ; } - - event mouseenter { - var me = this; - me.leaved = false; - me.timer(300ms, function() { - if (me.leaved) return; - me.input.value = handler.get_password(); - }); - } - event mouseleave { - this.leaved = true; - this.input.value = "******"; + event click $(svg#refresh-password) (_, me) { + if (handler.is_random_password_valid()) handler.update_random_password(); + this.update(); } } -class Password: Reactor.Component { +var verificationMethodMenu; +class VerificationMethodMenu: Reactor.Component { + function this() { + verificationMethodMenu = this; + } + function render() { - return
    - - {svg_edit} + if (!this.show) return
  • ; + var me = this; + self.timer(1ms, function() { me.toggleMenuState() }); + return
  • {translate('Verification Method')} + +
  • {svg_checkmark}{translate('Enable security password')}
  • +
  • {svg_checkmark}{translate('Enable random password')}
  • +
    +
  • ; + } + + function toggleMenuState() { + var security_enabled = handler.is_security_password_enabled(); + var random_enabled = handler.is_random_password_enabled(); + var onetime_enabled = handler.is_onetime_password_enabled(); + for (var (index, el) in this.$$(menu#verification-method>li)) { + if (index == 0) el.attributes.toggleClass("selected", security_enabled); + if (index == 1) el.attributes.toggleClass("selected", random_enabled); + } + } + + event click $(menu#verification-method>li) (_, me) { + switch (me.id.substring('verification-method-'.length)) { + case 'security': + { + var security_enabled = handler.is_security_password_enabled(); + handler.set_security_password_enabled(!security_enabled); + } + break; + case 'random': + { + var random_enabled = handler.is_random_password_enabled(); + handler.set_random_password_enabled(!random_enabled); + } + break; + } + + this.toggleMenuState(); + passwordArea.update(); + } +} + +var randomPasswordUpdateMethodMenu; +class RandomPasswordUpdateMethodMenu: Reactor.Component { + function this() { + randomPasswordUpdateMethodMenu = this; + } + + function render() { + if (!this.show) return
  • ; + var me = this; + var random_enabled = handler.is_random_password_enabled(); + self.timer(1ms, function() { me.toggleMenuState() }); + return
  • {translate('Random Password After Session')} + +
  • {svg_checkmark}{translate('Keep')}
  • +
  • {svg_checkmark}{translate('Update')}
  • +
  • {svg_checkmark}{translate('Disable')}
  • +
    +
  • ; + } + + function toggleMenuState() { + var method = handler.random_password_update_method(); + for (var (index, el) in this.$$(menu#random-password-update-method>li)) { + if (index == 0) el.attributes.toggleClass("selected", method == "KEEP"); + if (index == 1) el.attributes.toggleClass("selected", method == "UPDATE"); + if (index == 2) el.attributes.toggleClass("selected", method == "DISABLE"); + } + } + + event click $(menu#random-password-update-method>li) (_, me) { + if (me.id === 'random-password-update-method-keep') handler.set_random_password_update_method("KEEP"); + if (me.id === 'random-password-update-method-update') handler.set_random_password_update_method("UPDATE"); + if (me.id === 'random-password-update-method-disable') handler.set_random_password_update_method("DISABLE"); + this.toggleMenuState(); + passwordArea.update(); + } +} + +var passwordArea; +class PasswordArea: Reactor.Component { + function this() { + passwordArea = this; + } + + function render() { + var onetime_enabled = handler.is_onetime_password_enabled(); + + return +
    +
    {translate(onetime_enabled ? 'Onetime Password' : 'Password')}
    +
    + {this.renderPop()} + + {svg_edit} +
    ; } - event click $(svg#edit) (_, me) { - var menu = $(menu#edit-password-context); - me.popup(menu); + function renderPop() { + var security_enabled = handler.is_security_password_enabled(); + var random_enabled = handler.is_random_password_enabled(); + var onetime_enabled = handler.is_onetime_password_enabled(); + var onetime_activated = handler.is_onetime_password_activated(); + + return +
  • {translate(onetime_enabled ? "Disable onetime password" : "Enable onetime password")}
  • +
  • {translate('Activate onetime password')}
  • +
    + +
    +
  • {translate('Set security password')}
  • +
    + + ; } - event click $(li#refresh-password) { - handler.update_password(""); - this.update(); + event click $(svg#edit) (_, me) { + randomPasswordUpdateMethodMenu.update({show: true }); + verificationMethodMenu.update({show: true }); + var menu = $(menu#edit-password-context); + me.popup(menu); } event click $(li#set-password) { @@ -862,12 +965,36 @@ class Password: Reactor.Component { if (p0 != p1) { return translate("The confirmation is not identical."); } - handler.update_password(p0); + handler.set_security_password(p0); me.update(); }); } + + event click $(li#enable-onetime-password) { + var onetime_enabled = handler.is_onetime_password_enabled(); + handler.set_onetime_password_enabled(!onetime_enabled); + passwordArea.update(); + } + + event click $(li#activate-onetime-password) { + handler.set_onetime_password_activated(true); + passwordArea.update(); + } } +var last_password_description = ""; +function updatePasswordArea() { + self.timer(1s, function() { + var description = handler.password_description(); + if (last_password_description != description) { + last_password_description = description + passwordArea.update(); + } + updatePasswordArea(); + }); +} +updatePasswordArea(); + class ID: Reactor.Component { function render() { return