diff --git a/Cargo.lock b/Cargo.lock index 500fcca3..6a2c8a48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,17 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +[[package]] +name = "argon2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" +dependencies = [ + "base64ct", + "blake2", + "password-hash", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -2850,6 +2861,17 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.11" @@ -4458,11 +4480,14 @@ dependencies = [ name = "tauri-plugin-stronghold" version = "0.0.0" dependencies = [ + "argon2", "hex", "iota-crypto 0.21.0", "iota_stronghold", "log", "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "rusty-fork", "serde", "serde_json", diff --git a/plugins/stronghold/Cargo.toml b/plugins/stronghold/Cargo.toml index 57a8a2c9..1c2d769a 100644 --- a/plugins/stronghold/Cargo.toml +++ b/plugins/stronghold/Cargo.toml @@ -20,6 +20,15 @@ iota-crypto = "0.21" hex = "0.4" zeroize = { version = "1", features = ["zeroize_derive"] } +# kdf dependincies +argon2 = { version = "0.5.0", optional = true } +rand_chacha = { version = "0.3.1", optional = true } +rand_core = { version = "0.6.4", features = ["getrandom"], optional = true } + [dev-dependencies] rand = "0.8" -rusty-fork = "0.3" \ No newline at end of file +rusty-fork = "0.3" + +[features] +default = ["kdf"] +kdf = ["dep:argon2", "dep:rand_chacha", "dep:rand_core"] diff --git a/plugins/stronghold/src/kdf.rs b/plugins/stronghold/src/kdf.rs new file mode 100644 index 00000000..f8ddf237 --- /dev/null +++ b/plugins/stronghold/src/kdf.rs @@ -0,0 +1,56 @@ +use argon2::Argon2; +use rand_chacha::ChaCha20Rng; +use rand_core::{RngCore, SeedableRng}; +use std::path::PathBuf; +use tauri::Config; + +/// NOTE: Hash supplied to Stronghold must be 32bits long. +/// This is a current limitation of Stronghold. +const HASH_LENGTH: usize = 32; +const SALT_FILENAME: &str = "stronghold_salt.txt"; + +pub struct KeyDerivation {} + +impl KeyDerivation { + /// Will create a key from [`password`] and a generated salt. + /// Salt will be generated to file [`salt_path`] or taken from it + /// if file already exists + pub fn argon2(password: &str, salt_path: &PathBuf) -> Vec { + let mut salt = [0u8; HASH_LENGTH]; + create_or_get_salt(&mut salt, salt_path); + + let mut encoded = [0u8; HASH_LENGTH]; + Argon2::default() + .hash_password_into(password.as_bytes(), &salt, &mut encoded) + .expect("Failed to generate hash for password"); + encoded.to_vec() + } + + /// Will create a key from [`password`] and a generated salt. + /// Salt will be generated/taken from a default file in the Tauri local + /// directory + pub fn argon2_with_config(password: &str, tauri_config: &Config) -> Vec { + let salt_dir = tauri::api::path::app_local_data_dir(tauri_config) + .expect("Application local directory not found"); + let mut salt_path = PathBuf::new(); + salt_path.push(salt_dir); + salt_path.push(SALT_FILENAME); + + KeyDerivation::argon2(password, &salt_path) + } +} + +// NOTE: this is not ideal as we produce a single salt per application +// rather than having different salt for each Stronghold snapshot/password +fn create_or_get_salt(salt: &mut [u8], salt_path: &PathBuf) { + if salt_path.is_file() { + // Get existing salt + let tmp = std::fs::read(salt_path).unwrap(); + salt.clone_from_slice(&tmp); + } else { + // Generate new salt + let mut gen = ChaCha20Rng::from_entropy(); + gen.fill_bytes(salt); + std::fs::write(salt_path, salt).expect("Failed to write salt for Stronghold") + } +} diff --git a/plugins/stronghold/src/lib.rs b/plugins/stronghold/src/lib.rs index b14b6990..0695d73d 100644 --- a/plugins/stronghold/src/lib.rs +++ b/plugins/stronghold/src/lib.rs @@ -22,6 +22,9 @@ use tauri::{ }; use zeroize::Zeroize; +#[cfg(feature = "kdf")] +pub mod kdf; + pub mod stronghold; type PasswordHashFn = dyn Fn(&str) -> Vec + Send + Sync; @@ -405,15 +408,35 @@ impl Builder { } } + /// Initializes a stronghold plugin with argon2 as a default kdf + pub fn init_and_build_with_argon2() -> TauriPlugin { + let plugin_builder = PluginBuilder::new("stronghold").setup(move |app| { + let app2 = app.clone(); + app.manage(StrongholdCollection::default()); + app.manage(PasswordHashFunction(Box::new(move |pwd: &str| { + kdf::KeyDerivation::argon2_with_config(pwd, &app2.config()) + }))); + Ok(()) + }); + Builder::invoke_stronghold_handlers_and_build(plugin_builder) + } + pub fn build(self) -> TauriPlugin { 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(()) - }) + let plugin_builder = PluginBuilder::new("stronghold").setup(move |app| { + app.manage(StrongholdCollection::default()); + app.manage(PasswordHashFunction(password_hash_function)); + Ok(()) + }); + + Builder::invoke_stronghold_handlers_and_build(plugin_builder) + } + + fn invoke_stronghold_handlers_and_build( + builder: PluginBuilder, + ) -> TauriPlugin { + builder .invoke_handler(tauri::generate_handler![ initialize, destroy,