diff --git a/Cargo.lock b/Cargo.lock index 6820f925ad..b7ec25a422 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -719,41 +719,6 @@ dependencies = [ "syn 2.0.27", ] -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - [[package]] name = "data-encoding" version = "2.4.0" @@ -782,37 +747,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_builder" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_builder_macro" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" -dependencies = [ - "derive_builder_core", - "syn 1.0.109", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -1494,12 +1428,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.4.0" @@ -1652,7 +1580,6 @@ dependencies = [ "bech32 0.9.1", "bitflags 2.3.3", "bytemuck", - "derive_builder", "derive_more", "dotenvy", "fern-logger", diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index ee99c119e8..76b8c9337c 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 1.1.0 - 2023-MM-DD + +### Changed + +- `StrongholdAdapterBuilder` updated to be slightly more ergonomic; + ## 1.0.1 - 2023-07-25 ### Fixed diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 29115cf0dd..3cfc845dc7 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -58,7 +58,6 @@ serde_json = { version = "1.0.103", default-features = false, features = [ # Optional dependencies anymap = { version = "0.12.1", default-features = false, optional = true } async-trait = { version = "0.1.72", default-features = false, optional = true } -derive_builder = { version = "0.12.0", default-features = false, optional = true } fern-logger = { version = "0.5.0", default-features = false, optional = true } futures = { version = "0.3.28", default-features = false, features = [ "thread-pool", @@ -162,7 +161,6 @@ std = [ "bitflags/std", "rand?/std_rng", "regex?/std", - "derive_builder?/std", "iota_stronghold?/std", "iota-crypto/std", "once_cell?/std", @@ -177,7 +175,6 @@ storage = [ ] stronghold = [ "iota_stronghold", - "derive_builder", "iota-crypto/chacha", "dep:time", "dep:anymap", diff --git a/sdk/src/client/stronghold/mod.rs b/sdk/src/client/stronghold/mod.rs index 9e4d3f02bf..79c6fe16a3 100644 --- a/sdk/src/client/stronghold/mod.rs +++ b/sdk/src/client/stronghold/mod.rs @@ -52,13 +52,13 @@ mod migration; mod secret; mod storage; +use alloc::sync::Weak; use std::{ path::{Path, PathBuf}, sync::Arc, time::Duration, }; -use derive_builder::Builder; use iota_stronghold::{KeyProvider, SnapshotPath, Stronghold}; use log::{debug, error, warn}; use tokio::{ @@ -74,11 +74,9 @@ use super::{storage::StorageAdapter, utils::Password}; /// A wrapper on [Stronghold]. /// /// See the [module-level documentation](self) for more details. -#[derive(Builder, Debug)] -#[builder(pattern = "owned", build_fn(skip))] +#[derive(Debug)] pub struct StrongholdAdapter { /// A stronghold instance. - #[builder(field(type = "Option"))] stronghold: Arc>, /// A key to open the Stronghold vault. @@ -88,8 +86,6 @@ pub struct StrongholdAdapter { /// derive a key from it. /// /// [`password()`]: self::StrongholdAdapterBuilder::password() - #[builder(setter(custom))] - #[builder(field(type = "Option"))] key_provider: Arc>>, /// An interval of time, after which `key` will be cleared from the memory. @@ -98,18 +94,12 @@ pub struct StrongholdAdapter { /// timer will be spawned in the background to clear ([zeroize]) the key after `timeout`. /// /// If a [`StrongholdAdapter`] is destroyed (dropped), then the timer will stop too. - #[builder(setter(strip_option))] timeout: Option, /// A handle to the timeout task. - /// - /// Note that this field doesn't actually have a custom setter; `setter(custom)` is only for skipping the setter - /// generation. - #[builder(setter(custom))] - timeout_task: Arc>>>, + timeout_task: Arc>>, /// The path to a Stronghold snapshot file. - #[builder(setter(skip))] pub(crate) snapshot_path: PathBuf, } @@ -146,15 +136,34 @@ fn check_or_create_snapshot( Ok(()) } +#[derive(Default, Debug)] +pub struct StrongholdAdapterBuilder { + stronghold: Option, + key_provider: Option, + timeout: Option, +} + /// Extra / custom builder method implementations. impl StrongholdAdapterBuilder { - /// Use an user-input password string to derive a key to use Stronghold. - pub fn password(mut self, password: impl Into) -> Self { - let password = password.into(); + pub fn stronghold(mut self, stronghold: impl Into>) -> Self { + self.stronghold = stronghold.into(); + self + } + + pub fn key_provider(mut self, key_provider: impl Into>) -> Self { + self.key_provider = key_provider.into(); + self + } - // Note that derive_builder always adds another layer of Option. - self.key_provider = Some(self::common::key_provider_from_password(password)); + pub fn timeout(mut self, timeout: impl Into>) -> Self { + self.timeout = timeout.into(); + self + } + /// Use an user-input password string to derive a key to use Stronghold. + pub fn password(mut self, password: impl Into) -> Self { + self.key_provider + .replace(self::common::key_provider_from_password(password.into())); self } @@ -172,7 +181,7 @@ impl StrongholdAdapterBuilder { /// /// [`password()`]: Self::password() /// [`timeout()`]: Self::timeout() - pub fn build>(mut self, snapshot_path: P) -> Result { + pub fn build>(self, snapshot_path: P) -> Result { // In any case, Stronghold - as a necessary component - needs to be present at this point. let stronghold = self.stronghold.unwrap_or_default(); @@ -186,39 +195,25 @@ impl StrongholdAdapterBuilder { let has_key_provider = self.key_provider.is_some(); let key_provider = Arc::new(Mutex::new(self.key_provider)); let stronghold = Arc::new(Mutex::new(stronghold)); + let timeout_task = Arc::new(Mutex::new(None)); // If both `key` and `timeout` are set, then we spawn the task and keep its join handle. - if let (true, Some(Some(timeout))) = (has_key_provider, self.timeout) { - let timeout_task = Arc::new(Mutex::new(None)); - - // The key clearing task, with the data it owns. - let task_self = timeout_task.clone(); - let key_provider = key_provider.clone(); - - // To keep this function synchronous (`fn`), we spawn a task that spawns the key clearing task here. It'll - // however panic when this function is not in a Tokio runtime context (usually in an `async fn`), albeit it - // itself is a `fn`. There is also a small delay from the return of this function to the task actually being - // spawned and set in the `struct`. - let stronghold_clone = stronghold.clone(); - tokio::spawn(async move { - *task_self.lock().await = Some(tokio::spawn(task_key_clear( - task_self.clone(), // LHS moves task_self - stronghold_clone, - key_provider, - timeout, - ))); - }); - - // Keep the task handle in the builder; the code below checks this. - self.timeout_task = Some(timeout_task); + if let (true, Some(timeout)) = (has_key_provider, self.timeout) { + let weak = Arc::downgrade(&timeout_task); + *timeout_task.try_lock().unwrap() = Some(tokio::spawn(task_key_clear( + weak, + stronghold.clone(), + key_provider.clone(), + timeout, + ))); } // Create the adapter as per configuration and return it. Ok(StrongholdAdapter { stronghold, key_provider, - timeout: self.timeout.unwrap_or(None), - timeout_task: self.timeout_task.unwrap_or_else(|| Arc::new(Mutex::new(None))), + timeout: self.timeout, + timeout_task, snapshot_path: snapshot_path.as_ref().to_path_buf(), }) } @@ -269,12 +264,10 @@ impl StrongholdAdapter { timeout_task.abort(); } - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -329,12 +322,10 @@ impl StrongholdAdapter { // Recover: restart the key clearing task if let Some(timeout) = self.timeout { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -372,12 +363,10 @@ impl StrongholdAdapter { // Recover: restart key clearing task if let Some(timeout) = self.timeout { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -393,12 +382,10 @@ impl StrongholdAdapter { // Restart the key clearing task. if let Some(timeout) = self.timeout { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -453,12 +440,10 @@ impl StrongholdAdapter { // If a new timeout is set and the key is still in the memory, spawn a new task; otherwise we do nothing. if let (Some(_), Some(timeout)) = (self.key_provider.lock().await.as_ref(), self.timeout) { - // The key clearing task, with the data it owns. - let task_self = self.timeout_task.clone(); let key_provider = self.key_provider.clone(); *self.timeout_task.lock().await = Some(tokio::spawn(task_key_clear( - task_self, + Arc::downgrade(&self.timeout_task), self.stronghold.clone(), key_provider, timeout, @@ -540,23 +525,33 @@ impl StrongholdAdapter { } } +type TaskHandle = JoinHandle<()>; + /// The asynchronous key clearing task purging `key` after `timeout` spent in Tokio. async fn task_key_clear( - task_self: Arc>>>, + task: Weak>>, stronghold: Arc>, key_provider: Arc>>, timeout: Duration, ) { tokio::time::sleep(timeout).await; - debug!("StrongholdAdapter is purging the key"); - key_provider.lock().await.take(); + // If the weak pointer cannot upgrade, that means the secret manager has been dropped, + // so we can just exit. + if let Some(task) = task.upgrade() { + // Take the join handle, but hold the lock until we're done + let mut lock = task.lock().await; + lock.take(); - // TODO handle error - stronghold.lock().await.clear().unwrap(); + debug!("StrongholdAdapter is purging the key"); + key_provider.lock().await.take(); - // Take self, but do nothing (we're exiting anyways). - task_self.lock().await.take(); + if let Err(e) = stronghold.lock().await.clear() { + log::error!("Failed to clear stronghold keys: {e}"); + } + + drop(lock); + } } #[cfg(test)]