mirror of
				https://github.com/yuezk/GlobalProtect-openconnect.git
				synced 2025-05-20 07:26:58 -04:00 
			
		
		
		
	refactor: encrypt the sensitive data
This commit is contained in:
		| @@ -133,7 +133,7 @@ fn build_window(app_handle: &AppHandle, ua: &str) -> tauri::Result<Window> { | ||||
|     Window::builder(app_handle, AUTH_WINDOW_LABEL, url) | ||||
|         .visible(false) | ||||
|         .title("GlobalProtect Login") | ||||
|         .inner_size(400.0, 647.0) | ||||
|         .inner_size(600.0, 500.0) | ||||
|         .min_inner_size(370.0, 600.0) | ||||
|         .user_agent(ua) | ||||
|         .always_on_top(true) | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| use crate::{ | ||||
|     auth::{self, AuthData, AuthRequest, SamlBinding, SamlLoginParams}, | ||||
|     storage::{AppStorage, KeyHint}, | ||||
|     utils::get_openssl_conf, | ||||
|     utils::get_openssl_conf_path, | ||||
| }; | ||||
| use gpcommon::{Client, ServerApiError, VpnStatus}; | ||||
| use serde_json::Value; | ||||
| use std::sync::Arc; | ||||
| use tauri::{AppHandle, State}; | ||||
| use tokio::fs; | ||||
| @@ -24,9 +26,10 @@ pub(crate) async fn vpn_status<'a>( | ||||
| pub(crate) async fn vpn_connect<'a>( | ||||
|     server: String, | ||||
|     cookie: String, | ||||
|     user_agent: String, | ||||
|     client: State<'a, Arc<Client>>, | ||||
| ) -> Result<(), ServerApiError> { | ||||
|     client.connect(server, cookie).await | ||||
|     client.connect(server, cookie, user_agent).await | ||||
| } | ||||
|  | ||||
| #[tauri::command] | ||||
| @@ -40,10 +43,10 @@ pub(crate) async fn vpn_disconnect<'a>( | ||||
| pub(crate) async fn saml_login( | ||||
|     binding: SamlBinding, | ||||
|     request: String, | ||||
|     user_agent: String, | ||||
|     clear_cookies: bool, | ||||
|     app_handle: AppHandle, | ||||
| ) -> tauri::Result<Option<AuthData>> { | ||||
|     let user_agent = String::from("PAN GlobalProtect"); | ||||
|     let params = SamlLoginParams { | ||||
|         auth_request: AuthRequest::new(binding, request), | ||||
|         user_agent, | ||||
| @@ -71,3 +74,29 @@ pub(crate) async fn update_openssl_config(app_handle: AppHandle) -> tauri::Resul | ||||
|     fs::write(openssl_conf_path, openssl_conf).await?; | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[tauri::command] | ||||
| pub(crate) async fn store_get<'a>( | ||||
|     hint: KeyHint<'_>, | ||||
|     app_storage: State<'_, AppStorage<'_>>, | ||||
| ) -> Result<Option<Value>, ()> { | ||||
|     Ok(app_storage.get(hint)) | ||||
| } | ||||
|  | ||||
| #[tauri::command] | ||||
| pub(crate) fn store_set( | ||||
|     hint: KeyHint, | ||||
|     value: Value, | ||||
|     app_storage: State<'_, AppStorage>, | ||||
| ) -> Result<(), tauri_plugin_store::Error> { | ||||
|     app_storage.set(hint, &value)?; | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[tauri::command] | ||||
| pub(crate) fn store_save( | ||||
|     app_storage: State<'_, AppStorage>, | ||||
| ) -> Result<(), tauri_plugin_store::Error> { | ||||
|     app_storage.save()?; | ||||
|     Ok(()) | ||||
| } | ||||
|   | ||||
							
								
								
									
										46
									
								
								gpgui/src-tauri/src/crypto.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								gpgui/src-tauri/src/crypto.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| use aes_gcm::{ | ||||
|     aead::{consts::U12, Aead, OsRng}, | ||||
|     AeadCore, Aes256Gcm, Key, KeyInit, Nonce, | ||||
| }; | ||||
| use keyring::Entry; | ||||
|  | ||||
| const SERVICE_NAME: &str = "GlobalProtect-openconnect"; | ||||
| const ENTRY_KEY: &str = "master-key"; | ||||
|  | ||||
| fn get_master_key() -> Result<Key<Aes256Gcm>, anyhow::Error> { | ||||
|     let key_entry = Entry::new(SERVICE_NAME, ENTRY_KEY)?; | ||||
|  | ||||
|     if let Ok(key) = key_entry.get_password() { | ||||
|         let key = hex::decode(key)?; | ||||
|         return Ok(Key::<Aes256Gcm>::clone_from_slice(&key)); | ||||
|     } | ||||
|  | ||||
|     let key = Aes256Gcm::generate_key(OsRng); | ||||
|     let encoded_key = hex::encode(key); | ||||
|  | ||||
|     key_entry.set_password(&encoded_key)?; | ||||
|  | ||||
|     Ok(key) | ||||
| } | ||||
|  | ||||
| pub(crate) fn encrypt(data: &str) -> Result<String, anyhow::Error> { | ||||
|     let master_key = get_master_key()?; | ||||
|     let cipher = Aes256Gcm::new(&master_key); | ||||
|     let nonce = Aes256Gcm::generate_nonce(&mut OsRng); | ||||
|     let cipher_text = cipher.encrypt(&nonce, data.as_bytes())?; | ||||
|  | ||||
|     let mut encrypted = nonce.to_vec(); | ||||
|     encrypted.extend_from_slice(&cipher_text); | ||||
|     Ok(hex::encode(encrypted)) | ||||
| } | ||||
|  | ||||
| pub(crate) fn decrypt(encrypted: &str) -> Result<String, anyhow::Error> { | ||||
|     let master_key = get_master_key()?; | ||||
|     let encrypted = hex::decode(encrypted)?; | ||||
|     let nonce = Nonce::<U12>::from_slice(&encrypted[..12]); | ||||
|     let cipher_text = &encrypted[12..]; | ||||
|     let cipher = Aes256Gcm::new(&master_key); | ||||
|     let plain_text = cipher.decrypt(nonce, cipher_text)?; | ||||
|  | ||||
|     String::from_utf8(plain_text).map_err(|err| err.into()) | ||||
| } | ||||
| @@ -2,93 +2,26 @@ | ||||
|     all(not(debug_assertions), target_os = "windows"), | ||||
|     windows_subsystem = "windows" | ||||
| )] | ||||
|  | ||||
| use crate::utils::get_openssl_conf_path; | ||||
| use env_logger::Env; | ||||
| use gpcommon::{Client, ClientStatus, VpnStatus}; | ||||
| use log::{info, warn}; | ||||
| use serde::Serialize; | ||||
| use std::{path::PathBuf, sync::Arc}; | ||||
| use tauri::{Manager, Wry}; | ||||
| use tauri_plugin_log::LogTarget; | ||||
| use tauri_plugin_store::{with_store, StoreCollection}; | ||||
|  | ||||
| mod auth; | ||||
| mod commands; | ||||
| mod crypto; | ||||
| mod settings; | ||||
| mod setup; | ||||
| mod storage; | ||||
| mod utils; | ||||
|  | ||||
| #[derive(Debug, Clone, Serialize)] | ||||
| struct VpnStatusPayload { | ||||
|     status: VpnStatus, | ||||
| } | ||||
|  | ||||
| fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let client = Arc::new(Client::default()); | ||||
|     let client_clone = client.clone(); | ||||
|     let app_handle = app.handle(); | ||||
|  | ||||
|     let stores = app.state::<StoreCollection<Wry>>(); | ||||
|     let path = PathBuf::from(".settings.dat"); | ||||
|     let _ = with_store(app_handle.clone(), stores, path, |store| { | ||||
|         let settings_data = store.get("SETTINGS_DATA"); | ||||
|         let custom_openssl = settings_data.map_or(false, |data| { | ||||
|             data["customOpenSSL"].as_bool().unwrap_or(false) | ||||
|         }); | ||||
|  | ||||
|         if custom_openssl { | ||||
|             info!("Using custom OpenSSL config"); | ||||
|             let openssl_conf = get_openssl_conf_path(&app_handle).into_os_string(); | ||||
|             std::env::set_var("OPENSSL_CONF", openssl_conf); | ||||
|         } | ||||
|         Ok(()) | ||||
|     }); | ||||
|  | ||||
|     tauri::async_runtime::spawn(async move { | ||||
|         client_clone.subscribe_status(move |client_status| match client_status { | ||||
|             ClientStatus::Vpn(vpn_status) => { | ||||
|                 let payload = VpnStatusPayload { status: vpn_status }; | ||||
|                 if let Err(err) = app_handle.emit_all("vpn-status-received", payload) { | ||||
|                     warn!("Error emitting event: {}", err); | ||||
|                 } | ||||
|             } | ||||
|             ClientStatus::Service(is_online) => { | ||||
|                 if let Err(err) = app_handle.emit_all("service-status-changed", is_online) { | ||||
|                     warn!("Error emitting event: {}", err); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         let _ = client_clone.run().await; | ||||
|     }); | ||||
|  | ||||
|     app.manage(client); | ||||
|  | ||||
|     if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") { | ||||
|         if desktop == "KDE" { | ||||
|             if let Some(main_window) = app.get_window("main") { | ||||
|                 let _ = main_window.set_decorations(false); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn main() { | ||||
|     // env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); | ||||
|     tauri::Builder::default() | ||||
|         .plugin( | ||||
|             tauri_plugin_log::Builder::default() | ||||
|                 .targets([ | ||||
|                     LogTarget::LogDir, | ||||
|                     LogTarget::Stdout, /*LogTarget::Webview*/ | ||||
|                 ]) | ||||
|                 .targets([LogTarget::LogDir, LogTarget::Stdout]) | ||||
|                 .level(log::LevelFilter::Info) | ||||
|                 .with_colors(Default::default()) | ||||
|                 .build(), | ||||
|         ) | ||||
|         .plugin(tauri_plugin_store::Builder::default().build()) | ||||
|         .setup(setup) | ||||
|         .setup(setup::setup) | ||||
|         .invoke_handler(tauri::generate_handler![ | ||||
|             commands::service_online, | ||||
|             commands::vpn_status, | ||||
| @@ -98,6 +31,9 @@ fn main() { | ||||
|             commands::os_version, | ||||
|             commands::openssl_config, | ||||
|             commands::update_openssl_config, | ||||
|             commands::store_get, | ||||
|             commands::store_set, | ||||
|             commands::store_save, | ||||
|         ]) | ||||
|         .run(tauri::generate_context!()) | ||||
|         .expect("error while running tauri application"); | ||||
|   | ||||
							
								
								
									
										19
									
								
								gpgui/src-tauri/src/settings.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								gpgui/src-tauri/src/settings.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| use crate::storage::{AppStorage, KeyHint}; | ||||
| use serde::Deserialize; | ||||
| use tauri::{AppHandle, Manager}; | ||||
|  | ||||
| const STORAGE_KEY: &str = "SETTINGS_DATA"; | ||||
|  | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct Settings { | ||||
|     #[serde(rename = "customOpenSSL")] | ||||
|     custom_openssl: bool, | ||||
| } | ||||
|  | ||||
| pub(crate) fn is_custom_openssl_enabled(app_handle: &AppHandle) -> bool { | ||||
|     let app_storage = app_handle.state::<AppStorage>(); | ||||
|     let hint = KeyHint::new(STORAGE_KEY, false); | ||||
|     let settings = app_storage.get::<Settings>(hint); | ||||
|  | ||||
|     settings.map_or(false, |settings| settings.custom_openssl) | ||||
| } | ||||
							
								
								
									
										81
									
								
								gpgui/src-tauri/src/setup.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								gpgui/src-tauri/src/setup.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| use crate::{settings, storage::AppStorage, utils::get_openssl_conf_path}; | ||||
| use gpcommon::{Client, ClientStatus, VpnStatus}; | ||||
| use log::{info, warn}; | ||||
| use serde::Serialize; | ||||
| use std::sync::Arc; | ||||
| use tauri::Manager; | ||||
|  | ||||
| #[derive(Debug, Clone, Serialize)] | ||||
| struct VpnStatusPayload { | ||||
|     status: VpnStatus, | ||||
| } | ||||
|  | ||||
| fn setup_window(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let desktop = std::env::var("XDG_CURRENT_DESKTOP")?; | ||||
|     if desktop == "KDE" { | ||||
|         if let Some(main_window) = app.get_window("main") { | ||||
|             let _ = main_window.set_decorations(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn setup_app_storage(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let app_handle = app.app_handle(); | ||||
|     let app_storage = AppStorage::new(app_handle); | ||||
|  | ||||
|     app.manage(app_storage); | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn setup_app_env(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let app_handle = app.app_handle(); | ||||
|     let use_custom_openssl = settings::is_custom_openssl_enabled(&app_handle); | ||||
|  | ||||
|     if use_custom_openssl { | ||||
|         info!("Using custom OpenSSL config"); | ||||
|  | ||||
|         let openssl_conf = get_openssl_conf_path(&app_handle).into_os_string(); | ||||
|         std::env::set_var("OPENSSL_CONF", openssl_conf); | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn setup_vpn_client(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let app_handle = app.handle(); | ||||
|     let client = Arc::new(Client::default()); | ||||
|     let client_clone = client.clone(); | ||||
|  | ||||
|     app.manage(client_clone); | ||||
|  | ||||
|     tauri::async_runtime::spawn(async move { | ||||
|         client.subscribe_status(move |client_status| match client_status { | ||||
|             ClientStatus::Vpn(vpn_status) => { | ||||
|                 let payload = VpnStatusPayload { status: vpn_status }; | ||||
|                 if let Err(err) = app_handle.emit_all("vpn-status-received", payload) { | ||||
|                     warn!("Error emitting event: {}", err); | ||||
|                 } | ||||
|             } | ||||
|             ClientStatus::Service(is_online) => { | ||||
|                 if let Err(err) = app_handle.emit_all("service-status-changed", is_online) { | ||||
|                     warn!("Error emitting event: {}", err); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         let _ = client.run().await; | ||||
|     }); | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| pub(crate) fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     setup_window(app)?; | ||||
|     setup_app_storage(app)?; | ||||
|     setup_app_env(app)?; | ||||
|     setup_vpn_client(app)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
							
								
								
									
										87
									
								
								gpgui/src-tauri/src/storage.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								gpgui/src-tauri/src/storage.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| use crate::crypto::{decrypt, encrypt}; | ||||
| use log::warn; | ||||
| use serde::{de::DeserializeOwned, Deserialize}; | ||||
| use std::fmt::Debug; | ||||
| use tauri::{AppHandle, Manager, Wry}; | ||||
| use tauri_plugin_store::{with_store, Error, StoreCollection}; | ||||
|  | ||||
| const STORE_PATH: &str = ".data.json"; | ||||
|  | ||||
| #[derive(Debug, Deserialize)] | ||||
| pub(crate) struct KeyHint<'a> { | ||||
|     key: &'a str, | ||||
|     encrypted: bool, | ||||
| } | ||||
|  | ||||
| impl<'a> KeyHint<'a> { | ||||
|     pub(crate) fn new(key: &'a str, encrypted: bool) -> Self { | ||||
|         Self { key, encrypted } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(crate) struct AppStorage<'a> { | ||||
|     path: &'a str, | ||||
|     app_handle: AppHandle<Wry>, | ||||
| } | ||||
|  | ||||
| impl AppStorage<'_> { | ||||
|     pub(crate) fn new(app_handle: AppHandle<Wry>) -> Self { | ||||
|         Self { | ||||
|             path: STORE_PATH, | ||||
|             app_handle, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn get<T: DeserializeOwned + Debug>(&self, hint: KeyHint) -> Option<T> { | ||||
|         let stores = self.app_handle.state::<StoreCollection<Wry>>(); | ||||
|         with_store(self.app_handle.clone(), stores, self.path, |store| { | ||||
|             store | ||||
|                 .get(hint.key) | ||||
|                 .ok_or_else(|| Error::Deserialize("Value not found".into())) | ||||
|                 .and_then(|value| { | ||||
|                     if !hint.encrypted { | ||||
|                         return Ok(serde_json::from_value::<T>(value.clone())?); | ||||
|                     } | ||||
|  | ||||
|                     let value = value | ||||
|                         .as_str() | ||||
|                         .ok_or_else(|| Error::Deserialize("Value is not a string".into()))?; | ||||
|                     let value = decrypt(value).map_err(|err| { | ||||
|                         Error::Deserialize(format!("Failed to decrypt value: {}", err).into()) | ||||
|                     })?; | ||||
|  | ||||
|                     Ok(serde_json::from_str::<T>(&value)?) | ||||
|                 }) | ||||
|         }) | ||||
|         .map_err(|err| warn!("Error getting value: {:?}", err)) | ||||
|         .ok() | ||||
|     } | ||||
|  | ||||
|     pub fn set<T: serde::Serialize>(&self, hint: KeyHint, value: &T) -> Result<(), Error> { | ||||
|         let stores = self.app_handle.state::<StoreCollection<Wry>>(); | ||||
|  | ||||
|         with_store(self.app_handle.clone(), stores, self.path, |store| { | ||||
|             let value = if hint.encrypted { | ||||
|                 let json_str = serde_json::to_string(value)?; | ||||
|                 let encrypted = encrypt(&json_str).map_err(|err| { | ||||
|                     Error::Serialize(format!("Failed to encrypt value: {}", err).into()) | ||||
|                 })?; | ||||
|                 serde_json::to_value(encrypted)? | ||||
|             } else { | ||||
|                 serde_json::to_value(value)? | ||||
|             }; | ||||
|  | ||||
|             store.insert(hint.key.to_string(), value)?; | ||||
|             Ok(()) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn save(&self) -> Result<(), Error> { | ||||
|         let stores = self.app_handle.state::<StoreCollection<Wry>>(); | ||||
|          | ||||
|         with_store(self.app_handle.clone(), stores, self.path, |store| { | ||||
|             store.save()?; | ||||
|             Ok(()) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user