feat: encryption configuration properties

This commit is contained in:
huzibaca 2024-11-20 07:27:42 +08:00
parent bb44fc51bd
commit 15bd7324fe
6 changed files with 494 additions and 127 deletions

471
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -55,6 +55,10 @@ tauri-plugin-devtools = "2.0.0-rc"
url = "2.5.2" url = "2.5.2"
zip = "2.2.0" zip = "2.2.0"
reqwest_dav = "0.1.14" reqwest_dav = "0.1.14"
aes-gcm = { version = "0.10.3", features = ["std"] }
base64 = "0.22.1"
getrandom = "0.2"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
runas = "=1.2.0" runas = "=1.2.0"
deelevate = "0.2.0" deelevate = "0.2.0"

View File

@ -0,0 +1,98 @@
use crate::utils::dirs::get_encryption_key;
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Key,
};
use base64::{engine::general_purpose::STANDARD, Engine};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
const NONCE_LENGTH: usize = 12;
/// Encrypt data
pub fn encrypt_data(data: &str) -> Result<String, Box<dyn std::error::Error>> {
let encryption_key = get_encryption_key()?;
let key = Key::<Aes256Gcm>::from_slice(&encryption_key);
let cipher = Aes256Gcm::new(key);
// Generate random nonce
let mut nonce = vec![0u8; NONCE_LENGTH];
getrandom::getrandom(&mut nonce)?;
// Encrypt data
let ciphertext = cipher
.encrypt(nonce.as_slice().into(), data.as_bytes())
.map_err(|e| format!("Encryption failed: {}", e))?;
// Concatenate nonce and ciphertext and encode them in base64
let mut combined = nonce;
combined.extend(ciphertext);
Ok(STANDARD.encode(combined))
}
/// Decrypt data
pub fn decrypt_data(encrypted: &str) -> Result<String, Box<dyn std::error::Error>> {
let encryption_key = get_encryption_key()?;
let key = Key::<Aes256Gcm>::from_slice(&encryption_key);
let cipher = Aes256Gcm::new(key);
// Decode from base64
let data = STANDARD.decode(encrypted)?;
if data.len() < NONCE_LENGTH {
return Err("Invalid encrypted data".into());
}
// Separate nonce and ciphertext
let (nonce, ciphertext) = data.split_at(NONCE_LENGTH);
// Decrypt data
let plaintext = cipher
.decrypt(nonce.into(), ciphertext)
.map_err(|e| format!("Decryption failed: {}", e))?;
String::from_utf8(plaintext).map_err(|e| e.into())
}
/// Serialize encrypted function
pub fn serialize_encrypted<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Serialize,
S: Serializer,
{
// 如果序列化失败,返回 None
let json = match serde_json::to_string(value) {
Ok(j) => j,
Err(_) => return serializer.serialize_none(),
};
// 如果加密失败,返回 None
match encrypt_data(&json) {
Ok(encrypted) => serializer.serialize_str(&encrypted),
Err(_) => serializer.serialize_none(),
}
}
/// Deserialize decrypted function
pub fn deserialize_encrypted<'a, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: for<'de> Deserialize<'de> + Default,
D: Deserializer<'a>,
{
// 如果反序列化字符串失败,返回默认值
println!("deserialize_encrypted");
let encrypted = match String::deserialize(deserializer) {
Ok(s) => s,
Err(_) => return Ok(T::default()),
};
println!("encrypted: {}", encrypted);
// 如果解密失败,返回默认值
let decrypted_string = match decrypt_data(&encrypted) {
Ok(data) => data,
Err(_) => return Ok(T::default()),
};
println!("decrypted_string: {}", decrypted_string);
// 如果 JSON 解析失败,返回默认值
match serde_json::from_str(&decrypted_string) {
Ok(value) => Ok(value),
Err(_) => Ok(T::default()),
}
}

View File

@ -2,6 +2,7 @@ mod clash;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
mod config; mod config;
mod draft; mod draft;
mod encrypt;
mod prfitem; mod prfitem;
mod profiles; mod profiles;
mod runtime; mod runtime;
@ -10,6 +11,7 @@ mod verge;
pub use self::clash::*; pub use self::clash::*;
pub use self::config::*; pub use self::config::*;
pub use self::draft::*; pub use self::draft::*;
pub use self::encrypt::*;
pub use self::prfitem::*; pub use self::prfitem::*;
pub use self::profiles::*; pub use self::profiles::*;
pub use self::runtime::*; pub use self::runtime::*;

View File

@ -1,4 +1,5 @@
use crate::config::DEFAULT_PAC; use crate::config::DEFAULT_PAC;
use crate::config::{deserialize_encrypted, serialize_encrypted};
use crate::utils::{dirs, help}; use crate::utils::{dirs, help};
use anyhow::Result; use anyhow::Result;
use log::LevelFilter; use log::LevelFilter;
@ -148,8 +149,28 @@ pub struct IVerge {
pub verge_http_enabled: Option<bool>, pub verge_http_enabled: Option<bool>,
/// WebDAV 配置 (加密存储)
#[serde(
serialize_with = "serialize_encrypted",
deserialize_with = "deserialize_encrypted",
skip_serializing_if = "Option::is_none"
)]
pub webdav_url: Option<String>, pub webdav_url: Option<String>,
/// WebDAV 用户名 (加密存储)
#[serde(
serialize_with = "serialize_encrypted",
deserialize_with = "deserialize_encrypted",
skip_serializing_if = "Option::is_none"
)]
pub webdav_username: Option<String>, pub webdav_username: Option<String>,
/// WebDAV 密码 (加密存储)
#[serde(
serialize_with = "serialize_encrypted",
deserialize_with = "deserialize_encrypted",
skip_serializing_if = "Option::is_none"
)]
pub webdav_password: Option<String>, pub webdav_password: Option<String>,
} }

View File

@ -1,6 +1,7 @@
use crate::core::handle; use crate::core::handle;
use anyhow::Result; use anyhow::Result;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use tauri::Manager; use tauri::Manager;
@ -125,3 +126,27 @@ pub fn path_to_str(path: &PathBuf) -> Result<&str> {
.ok_or(anyhow::anyhow!("failed to get path from {:?}", path))?; .ok_or(anyhow::anyhow!("failed to get path from {:?}", path))?;
Ok(path_str) Ok(path_str)
} }
pub fn get_encryption_key() -> Result<Vec<u8>> {
let app_dir = app_home_dir()?;
let key_path = app_dir.join(".encryption_key");
if key_path.exists() {
// Read existing key
fs::read(&key_path).map_err(|e| anyhow::anyhow!("Failed to read encryption key: {}", e))
} else {
// Generate and save new key
let mut key = vec![0u8; 32];
getrandom::getrandom(&mut key)?;
// Ensure directory exists
if let Some(parent) = key_path.parent() {
fs::create_dir_all(parent)
.map_err(|e| anyhow::anyhow!("Failed to create key directory: {}", e))?;
}
// Save key
fs::write(&key_path, &key)
.map_err(|e| anyhow::anyhow!("Failed to save encryption key: {}", e))?;
Ok(key)
}
}