mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 04:53:44 +08:00
feat: encryption configuration properties
This commit is contained in:
parent
bb44fc51bd
commit
15bd7324fe
471
src-tauri/Cargo.lock
generated
471
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -55,6 +55,10 @@ tauri-plugin-devtools = "2.0.0-rc"
|
||||
url = "2.5.2"
|
||||
zip = "2.2.0"
|
||||
reqwest_dav = "0.1.14"
|
||||
aes-gcm = { version = "0.10.3", features = ["std"] }
|
||||
base64 = "0.22.1"
|
||||
getrandom = "0.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
runas = "=1.2.0"
|
||||
deelevate = "0.2.0"
|
||||
|
98
src-tauri/src/config/encrypt.rs
Normal file
98
src-tauri/src/config/encrypt.rs
Normal 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()),
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ mod clash;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod config;
|
||||
mod draft;
|
||||
mod encrypt;
|
||||
mod prfitem;
|
||||
mod profiles;
|
||||
mod runtime;
|
||||
@ -10,6 +11,7 @@ mod verge;
|
||||
pub use self::clash::*;
|
||||
pub use self::config::*;
|
||||
pub use self::draft::*;
|
||||
pub use self::encrypt::*;
|
||||
pub use self::prfitem::*;
|
||||
pub use self::profiles::*;
|
||||
pub use self::runtime::*;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::config::DEFAULT_PAC;
|
||||
use crate::config::{deserialize_encrypted, serialize_encrypted};
|
||||
use crate::utils::{dirs, help};
|
||||
use anyhow::Result;
|
||||
use log::LevelFilter;
|
||||
@ -148,8 +149,28 @@ pub struct IVerge {
|
||||
|
||||
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>,
|
||||
|
||||
/// WebDAV 用户名 (加密存储)
|
||||
#[serde(
|
||||
serialize_with = "serialize_encrypted",
|
||||
deserialize_with = "deserialize_encrypted",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
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>,
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::core::handle;
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
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(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)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user