mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-10-10 22:38:44 +02:00
copy plugin sources
This commit is contained in:
1 parent
c15990d4e2
commit
eb3495fb72
145 files changed
+16069
-22
No files matched your search
@@ -0,0 +1,432 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use iota_stronghold::{
|
||||
procedures::{
|
||||
BIP39Generate, BIP39Recover, Chain, Ed25519Sign, KeyType as StrongholdKeyType,
|
||||
MnemonicLanguage, PublicKey, Slip10Derive, Slip10DeriveInput, Slip10Generate,
|
||||
StrongholdProcedure,
|
||||
},
|
||||
Client, Location,
|
||||
};
|
||||
use serde::{de::Visitor, Deserialize, Deserializer};
|
||||
use stronghold::{Error, Result, Stronghold};
|
||||
use tauri::{
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
Manager, Runtime, State,
|
||||
};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
pub mod stronghold;
|
||||
|
||||
type PasswordHashFn = dyn Fn(&str) -> Vec<u8> + Send + Sync;
|
||||
|
||||
#[derive(Default)]
|
||||
struct StrongholdCollection(Arc<Mutex<HashMap<PathBuf, Stronghold>>>);
|
||||
|
||||
struct PasswordHashFunction(Box<PasswordHashFn>);
|
||||
|
||||
#[derive(Deserialize, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[serde(untagged)]
|
||||
enum BytesDto {
|
||||
Text(String),
|
||||
Raw(Vec<u8>),
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for BytesDto {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Text(t) => t.as_ref(),
|
||||
Self::Raw(b) => b.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BytesDto> for Vec<u8> {
|
||||
fn from(v: BytesDto) -> Self {
|
||||
match v {
|
||||
BytesDto::Text(t) => t.as_bytes().to_vec(),
|
||||
BytesDto::Raw(b) => b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", content = "payload")]
|
||||
enum LocationDto {
|
||||
Generic { vault: BytesDto, record: BytesDto },
|
||||
Counter { vault: BytesDto, counter: usize },
|
||||
}
|
||||
|
||||
impl From<LocationDto> for Location {
|
||||
fn from(dto: LocationDto) -> Location {
|
||||
match dto {
|
||||
LocationDto::Generic { vault, record } => Location::generic(vault, record),
|
||||
LocationDto::Counter { vault, counter } => Location::counter(vault, counter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", content = "payload")]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
enum Slip10DeriveInputDto {
|
||||
Seed(LocationDto),
|
||||
Key(LocationDto),
|
||||
}
|
||||
|
||||
impl From<Slip10DeriveInputDto> for Slip10DeriveInput {
|
||||
fn from(dto: Slip10DeriveInputDto) -> Slip10DeriveInput {
|
||||
match dto {
|
||||
Slip10DeriveInputDto::Seed(location) => Slip10DeriveInput::Seed(location.into()),
|
||||
Slip10DeriveInputDto::Key(location) => Slip10DeriveInput::Key(location.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum KeyType {
|
||||
Ed25519,
|
||||
X25519,
|
||||
}
|
||||
|
||||
impl From<KeyType> for StrongholdKeyType {
|
||||
fn from(ty: KeyType) -> StrongholdKeyType {
|
||||
match ty {
|
||||
KeyType::Ed25519 => StrongholdKeyType::Ed25519,
|
||||
KeyType::X25519 => StrongholdKeyType::X25519,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for KeyType {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct KeyTypeVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for KeyTypeVisitor {
|
||||
type Value = KeyType;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("ed25519 or x25519")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> std::result::Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
match value.to_lowercase().as_str() {
|
||||
"ed25519" => Ok(KeyType::Ed25519),
|
||||
"x25519" => Ok(KeyType::X25519),
|
||||
_ => Err(serde::de::Error::custom("unknown key type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(KeyTypeVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", content = "payload")]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
enum ProcedureDto {
|
||||
SLIP10Generate {
|
||||
output: LocationDto,
|
||||
#[serde(rename = "sizeBytes")]
|
||||
size_bytes: Option<usize>,
|
||||
},
|
||||
SLIP10Derive {
|
||||
chain: Vec<u32>,
|
||||
input: Slip10DeriveInputDto,
|
||||
output: LocationDto,
|
||||
},
|
||||
BIP39Recover {
|
||||
mnemonic: String,
|
||||
passphrase: Option<String>,
|
||||
output: LocationDto,
|
||||
},
|
||||
BIP39Generate {
|
||||
passphrase: Option<String>,
|
||||
output: LocationDto,
|
||||
},
|
||||
PublicKey {
|
||||
#[serde(rename = "type")]
|
||||
ty: KeyType,
|
||||
#[serde(rename = "privateKey")]
|
||||
private_key: LocationDto,
|
||||
},
|
||||
Ed25519Sign {
|
||||
#[serde(rename = "privateKey")]
|
||||
private_key: LocationDto,
|
||||
msg: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ProcedureDto> for StrongholdProcedure {
|
||||
fn from(dto: ProcedureDto) -> StrongholdProcedure {
|
||||
match dto {
|
||||
ProcedureDto::SLIP10Generate { output, size_bytes } => {
|
||||
StrongholdProcedure::Slip10Generate(Slip10Generate {
|
||||
output: output.into(),
|
||||
size_bytes,
|
||||
})
|
||||
}
|
||||
ProcedureDto::SLIP10Derive {
|
||||
chain,
|
||||
input,
|
||||
output,
|
||||
} => StrongholdProcedure::Slip10Derive(Slip10Derive {
|
||||
chain: Chain::from_u32_hardened(chain),
|
||||
input: input.into(),
|
||||
output: output.into(),
|
||||
}),
|
||||
ProcedureDto::BIP39Recover {
|
||||
mnemonic,
|
||||
passphrase,
|
||||
output,
|
||||
} => StrongholdProcedure::BIP39Recover(BIP39Recover {
|
||||
mnemonic,
|
||||
passphrase,
|
||||
output: output.into(),
|
||||
}),
|
||||
ProcedureDto::BIP39Generate { passphrase, output } => {
|
||||
StrongholdProcedure::BIP39Generate(BIP39Generate {
|
||||
passphrase,
|
||||
output: output.into(),
|
||||
language: MnemonicLanguage::English,
|
||||
})
|
||||
}
|
||||
ProcedureDto::PublicKey { ty, private_key } => {
|
||||
StrongholdProcedure::PublicKey(PublicKey {
|
||||
ty: ty.into(),
|
||||
private_key: private_key.into(),
|
||||
})
|
||||
}
|
||||
ProcedureDto::Ed25519Sign { private_key, msg } => {
|
||||
StrongholdProcedure::Ed25519Sign(Ed25519Sign {
|
||||
private_key: private_key.into(),
|
||||
msg: msg.as_bytes().to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn initialize(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
hash_function: State<'_, PasswordHashFunction>,
|
||||
snapshot_path: PathBuf,
|
||||
mut password: String,
|
||||
) -> Result<()> {
|
||||
let hash = (hash_function.0)(&password);
|
||||
password.zeroize();
|
||||
let stronghold = Stronghold::new(snapshot_path.clone(), hash)?;
|
||||
|
||||
collection
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(snapshot_path, stronghold);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn destroy(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
) -> Result<()> {
|
||||
let mut collection = collection.0.lock().unwrap();
|
||||
if let Some(stronghold) = collection.remove(&snapshot_path) {
|
||||
if let Err(e) = stronghold.save() {
|
||||
collection.insert(snapshot_path, stronghold);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn save(collection: State<'_, StrongholdCollection>, snapshot_path: PathBuf) -> Result<()> {
|
||||
let collection = collection.0.lock().unwrap();
|
||||
if let Some(stronghold) = collection.get(&snapshot_path) {
|
||||
stronghold.save()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn create_client(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
client: BytesDto,
|
||||
) -> Result<()> {
|
||||
let stronghold = get_stronghold(collection, snapshot_path)?;
|
||||
stronghold.create_client(client)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn load_client(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
client: BytesDto,
|
||||
) -> Result<()> {
|
||||
let stronghold = get_stronghold(collection, snapshot_path)?;
|
||||
stronghold.load_client(client)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_store_record(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
client: BytesDto,
|
||||
key: String,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
let client = get_client(collection, snapshot_path, client)?;
|
||||
client.store().get(key.as_ref()).map_err(Into::into)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn save_store_record(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
client: BytesDto,
|
||||
key: String,
|
||||
value: Vec<u8>,
|
||||
lifetime: Option<Duration>,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
let client = get_client(collection, snapshot_path, client)?;
|
||||
client
|
||||
.store()
|
||||
.insert(key.as_bytes().to_vec(), value, lifetime)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn remove_store_record(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
client: BytesDto,
|
||||
key: String,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
let client = get_client(collection, snapshot_path, client)?;
|
||||
client.store().delete(key.as_ref()).map_err(Into::into)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn save_secret(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
client: BytesDto,
|
||||
vault: BytesDto,
|
||||
record_path: BytesDto,
|
||||
secret: Vec<u8>,
|
||||
) -> Result<()> {
|
||||
let client = get_client(collection, snapshot_path, client)?;
|
||||
client
|
||||
.vault(&vault)
|
||||
.write_secret(Location::generic(vault, record_path), secret)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn remove_secret(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
client: BytesDto,
|
||||
vault: BytesDto,
|
||||
record_path: BytesDto,
|
||||
) -> Result<()> {
|
||||
let client = get_client(collection, snapshot_path, client)?;
|
||||
client
|
||||
.vault(vault)
|
||||
.delete_secret(record_path)
|
||||
.map(|_| ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn execute_procedure(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
client: BytesDto,
|
||||
procedure: ProcedureDto,
|
||||
) -> Result<Vec<u8>> {
|
||||
let client = get_client(collection, snapshot_path, client)?;
|
||||
client
|
||||
.execute_procedure(StrongholdProcedure::from(procedure))
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn get_stronghold(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
) -> Result<iota_stronghold::Stronghold> {
|
||||
let collection = collection.0.lock().unwrap();
|
||||
if let Some(stronghold) = collection.get(&snapshot_path) {
|
||||
Ok(stronghold.inner().clone())
|
||||
} else {
|
||||
Err(Error::StrongholdNotInitialized)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_client(
|
||||
collection: State<'_, StrongholdCollection>,
|
||||
snapshot_path: PathBuf,
|
||||
client: BytesDto,
|
||||
) -> Result<Client> {
|
||||
let collection = collection.0.lock().unwrap();
|
||||
if let Some(stronghold) = collection.get(&snapshot_path) {
|
||||
stronghold.get_client(client).map_err(Into::into)
|
||||
} else {
|
||||
Err(Error::StrongholdNotInitialized)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Builder {
|
||||
password_hash_function: Box<PasswordHashFn>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn new<F: Fn(&str) -> Vec<u8> + Send + Sync + 'static>(password_hash_function: F) -> Self {
|
||||
Self {
|
||||
password_hash_function: Box::new(password_hash_function),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
|
||||
let password_hash_function = self.password_hash_function;
|
||||
|
||||
PluginBuilder::new("stronghold")
|
||||
.setup(move |app| {
|
||||
app.manage(StrongholdCollection::default());
|
||||
app.manage(PasswordHashFunction(password_hash_function));
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
initialize,
|
||||
destroy,
|
||||
save,
|
||||
create_client,
|
||||
load_client,
|
||||
get_store_record,
|
||||
save_store_record,
|
||||
remove_store_record,
|
||||
save_secret,
|
||||
remove_secret,
|
||||
execute_procedure,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use std::{convert::TryFrom, ops::Deref, path::Path};
|
||||
|
||||
use iota_stronghold::{KeyProvider, SnapshotPath};
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("stronghold not initialized")]
|
||||
StrongholdNotInitialized,
|
||||
#[error(transparent)]
|
||||
Stronghold(#[from] iota_stronghold::ClientError),
|
||||
#[error(transparent)]
|
||||
Memory(#[from] iota_stronghold::MemoryError),
|
||||
#[error(transparent)]
|
||||
Procedure(#[from] iota_stronghold::procedures::ProcedureError),
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Stronghold {
|
||||
inner: iota_stronghold::Stronghold,
|
||||
path: SnapshotPath,
|
||||
keyprovider: KeyProvider,
|
||||
}
|
||||
|
||||
impl Stronghold {
|
||||
pub fn new<P: AsRef<Path>>(path: P, password: Vec<u8>) -> Result<Self> {
|
||||
let path = SnapshotPath::from_path(path);
|
||||
let stronghold = iota_stronghold::Stronghold::default();
|
||||
let keyprovider = KeyProvider::try_from(password)?;
|
||||
if path.exists() {
|
||||
stronghold.load_snapshot(&keyprovider, &path)?;
|
||||
}
|
||||
Ok(Self {
|
||||
inner: stronghold,
|
||||
path,
|
||||
keyprovider,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<()> {
|
||||
self.inner.commit(&self.path, &self.keyprovider)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &iota_stronghold::Stronghold {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Stronghold {
|
||||
type Target = iota_stronghold::Stronghold;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user