refactor: encrypt the sensitive data

This commit is contained in:
Kevin Yue
2023-07-22 07:33:53 +08:00
parent bf96a88e21
commit 601f422863
40 changed files with 1274 additions and 275 deletions

View File

@@ -23,7 +23,6 @@ tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", br
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
env_logger = "0.10"
webkit2gtk = "0.18.2"
regex = "1"
url = "2.3"
@@ -32,6 +31,10 @@ veil = "0.1.6"
whoami = "1.4.1"
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
openssl = "0.10"
keyring = "2"
aes-gcm = { version = "0.10", features = ["std"] }
hex = "0.4"
anyhow = "1.0"
[features]
# by default Tauri runs in production mode

View File

@@ -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)

View File

@@ -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(())
}

View 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())
}

View File

@@ -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");

View 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)
}

View 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(())
}

View 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(())
})
}
}