tauri-plugin-stronghold 是一个在 Tauri 中实现安装存储的插件, 它使用IOTA Stronghold加密数据库和安全运行时存储秘密和密钥。

IOTA Stronghold 是一个使用 Rust 编写的库,其唯一目的是隔离数字机密,防止黑客入侵和意外泄露。它使用加密快照,可以轻松备份并在设备之间安全共享。具有强大的内存安全性和进程完整性保证。

插件的基本功能:

  1. 安全存储敏感数据: 如密码、密钥、token
  2. 数据加密
  3. 数据隔离存储

在前端使用

都是在 JS层实现的逻辑

在 Rust 中使用

stronghold.rs

use std::env;
use std::path::PathBuf;
use tauri::Manager;
use tauri_plugin_stronghold::stronghold::Stronghold;
 
pub fn init_stronghold(app_dir: PathBuf) -> tauri_plugin_stronghold::stronghold::Stronghold {
  let vault_name = env::var("VAULT_NAME");
  let vault_password =
    env::var("VAULT_PASSWORD");
  println!(
    "vault_name: {:?}, vault_password: {:?}",
    vault_name, vault_password
  );
 
  let vault_path = app_dir.join(vault_name);
 
  let stronghold = Stronghold::new(
    vault_path.to_str().unwrap(),
    vault_password.as_bytes().to_vec(),
  )
  .expect("Failed to initialize Stronghold");
 
  match stronghold.inner().create_client("xgist-client".as_bytes()) {
    Ok(_) => println!("Client Created"),
    Err(_) => println!("Client Already Exists"),
  }
 
  stronghold
}
 
#[tauri::command]
pub async fn save_value(
  app_handle: tauri::AppHandle,
  key: &str,
  value: &str,
) -> Result<(), String> {
  let stronghold = app_handle.state::<Stronghold>();
  let client = stronghold
    .inner()
    .get_client("xgist-client".as_bytes())
    .map_err(|e| e.to_string())?;
 
  client
    .store()
    .insert(key.as_bytes().to_vec(), value.as_bytes().to_vec(), None)
    .map_err(|e| e.to_string())?;
 
  println!("start save to file");
 
  // TODO: Save need long time?
  // https://github.com/tauri-apps/plugins-workspace/issues/2048
  stronghold.inner().save().map_err(|e| e.to_string())?;
 
  println!("save to file success");
  Ok(())
}
 
#[tauri::command]
pub async fn get_value(app_handle: tauri::AppHandle, key: &str) -> Result<String, String> {
  let stronghold = app_handle.state::<Stronghold>();
  let client = stronghold
    .inner()
    .get_client("xgist-client".as_bytes())
    .map_err(|e| e.to_string())?;
 
  let value = client
    .store()
    .get(key.as_bytes())
    .map_err(|e| e.to_string())?;
 
  match value {
    Some(bytes) => String::from_utf8(bytes).map_err(|e| e.to_string()),
    None => Err("not exist".into()),
  }
}
 
#[tauri::command]
pub async fn list_all_values(
  app_handle: tauri::AppHandle,
) -> Result<Vec<(String, String)>, String> {
  let stronghold = app_handle.state::<Stronghold>();
  let client = stronghold
    .inner()
    .get_client("xgist-client".as_bytes())
    .map_err(|e| e.to_string())?;
 
  let store = client.store();
  let mut results = Vec::new();
 
  let locations = store.keys().map_err(|e| e.to_string())?;
  for location in locations {
    if let Some(value) = store.get(location.as_slice()).map_err(|e| e.to_string())? {
      let key = String::from_utf8(location).map_err(|e| e.to_string())?;
      let value = String::from_utf8(value).map_err(|e| e.to_string())?;
      results.push((key, value));
    }
  }
 
  Ok(results)
}

lib.rs

tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![
      greet,
      gist::init_gist,
      stronghold::save_value,
      stronghold::get_value,
      stronghold::list_all_values,
    ])
    .setup(|app| {
      dotenv().ok();
      let app_dir = app.path().app_data_dir().unwrap();
 
      let salt_path = app
        .path()
        .app_local_data_dir()
        .expect("could not resolve app local data path")
        .join("salt.txt");
      app
        .handle()       .plugin(tauri_plugin_stronghold::Builder::with_argon2(&salt_path).build())?;
 
      // TODO: long time
      let stronghold_state = stronghold::init_stronghold(app_dir);
      app.manage(stronghold_state);
 
      Ok(())
    })