Skip to content
This repository has been archived by the owner on Feb 3, 2025. It is now read-only.

Commit

Permalink
Swap local encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
AnthonyRonning committed Jul 8, 2023
1 parent 1699746 commit 0124cf8
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 100 deletions.
1 change: 1 addition & 0 deletions mutiny-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ nostr-sdk = { version = "0.22.0-bitcoin-v0.29", default-features = false }
cbc = { version = "0.1", features = ["alloc"] }
aes = { version = "0.8" }
jwt-compact = { version = "0.8.0-beta.1", features = ["es256k"] }
argon2 = { version = "0.5.0", features = ["password-hash", "alloc"] }

base64 = "0.13.0"
pbkdf2 = "0.11"
Expand Down
132 changes: 82 additions & 50 deletions mutiny-core/src/encrypt.rs
Original file line number Diff line number Diff line change
@@ -1,70 +1,102 @@
use crate::error::MutinyError;
use aes_gcm::aead::Aead;
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use pbkdf2::password_hash::Output;
use pbkdf2::password_hash::{PasswordHasher, Salt, SaltString};
use pbkdf2::{Params, Pbkdf2};
use aes_gcm::Aes256Gcm;
use aes_gcm::{
aead::{generic_array::GenericArray, Aead},
KeyInit,
};
use argon2::Argon2;
use base64;
use getrandom::getrandom;
use std::sync::Arc;

pub fn encrypt(content: &str, password: &str) -> Result<String, MutinyError> {
#[derive(Clone)]
pub struct Cipher {
key: Arc<Aes256Gcm>,
salt: [u8; 16],
}

pub fn encryption_key_from_pass(password: &str) -> Result<Cipher, MutinyError> {
let mut salt = [0u8; 16];
getrandom::getrandom(&mut salt).unwrap();
let derive_key = derive_key(password, &salt);
let key = derive_key.as_bytes();

let mut iv = [0u8; 12];
getrandom::getrandom(&mut iv).unwrap();

let cipher = Aes256Gcm::new_from_slice(key)?;
let nonce = Nonce::from_slice(&iv);
let mut bytes = cipher.encrypt(nonce, content.as_bytes())?;

let mut combined = vec![];
combined.append(&mut salt.to_vec());
combined.append(&mut iv.to_vec());
combined.append(&mut bytes);
Ok(base64::encode(combined.as_slice()))
getrandom(&mut salt).unwrap();

let key = get_encryption_key(password, &salt)?;

// convert key to proper format for aes_gcm
let key = GenericArray::clone_from_slice(&key);
Ok(Cipher {
key: Arc::new(Aes256Gcm::new(&key)),
salt,
})
}

pub fn encrypt(content: &str, c: Cipher) -> Result<String, MutinyError> {
// convert key and nonce to proper format for aes_gcm
let mut nonce = [0u8; 12];
getrandom(&mut nonce).unwrap();

// convert nonce to proper format for aes_gcm
let nonce = GenericArray::from_slice(&nonce);

let encrypted_data = c.key.encrypt(nonce, content.as_bytes().to_vec().as_ref())?;

let mut result: Vec<u8> = Vec::new();
result.extend(&c.salt);
result.extend(nonce);
result.extend(encrypted_data);

Ok(base64::encode(&result))
}

pub fn decrypt(encrypted: &str, password: &str) -> Result<String, MutinyError> {
let buffer = base64::decode(encrypted).map_err(|_| MutinyError::IncorrectPassword)?;
let buffer_slice = buffer.as_slice();
let salt = &buffer_slice[0..16];
let iv = &buffer_slice[16..28];
let data = &buffer_slice[28..];

let derive_key = derive_key(password, salt);
let key = derive_key.as_bytes();

let cipher = Aes256Gcm::new_from_slice(key)?;
let nonce = Nonce::from_slice(iv);
let decrypted = cipher.decrypt(nonce, data)?;
Ok(String::from_utf8(decrypted).unwrap())
let encrypted = base64::decode(encrypted).map_err(|_| MutinyError::IncorrectPassword)?;
if encrypted.len() < 12 + 16 {
return Err(MutinyError::IncorrectPassword);
}

let (rest, encrypted_bytes) = encrypted.split_at(16 + 12);
let (salt, nonce_bytes) = rest.split_at(16);

let key = get_encryption_key(password, salt)?;

// convert key and nonce to proper format for aes_gcm
let key = GenericArray::clone_from_slice(&key);
let nonce = GenericArray::from_slice(nonce_bytes);

let cipher = Aes256Gcm::new(&key);

let decrypted_data = cipher.decrypt(nonce, encrypted_bytes)?;

let decrypted_string =
String::from_utf8(decrypted_data).map_err(|_| MutinyError::IncorrectPassword)?;

Ok(decrypted_string)
}

fn derive_key(password: &str, salt: &[u8]) -> Output {
let params = Params {
rounds: 2048,
output_length: 32,
};

let salt_string = SaltString::b64_encode(salt).unwrap();
let salt = Salt::from(&salt_string);
let password = password.as_bytes();
let key = Pbkdf2
.hash_password_customized(password, None, None, params, salt)
.unwrap();
key.hash.unwrap()
pub fn get_encryption_key(password: &str, salt: &[u8]) -> Result<[u8; 32], MutinyError> {
let mut key = [0u8; 32];
argon2()
.hash_password_into(password.as_bytes(), salt, &mut key)
.map_err(|_| MutinyError::IncorrectPassword)?;
Ok(key)
}

fn argon2() -> Argon2<'static> {
let mut binding = argon2::ParamsBuilder::new();
let params = binding.m_cost(7 * 1024).t_cost(1).p_cost(1);
Argon2::from(params.build().expect("valid params"))
}

#[cfg(test)]
mod tests {
use crate::encrypt::{decrypt, encrypt};
use crate::encrypt::{decrypt, encrypt, encryption_key_from_pass};

#[test]
fn test_encryption() {
let password = "password";
let content = "hello world";
let encrypted = encrypt(content, password).unwrap();
let cipher = encryption_key_from_pass(password).unwrap();

let encrypted = encrypt(content, cipher).unwrap();
println!("{encrypted}");

let decrypted = decrypt(&encrypted, password).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion mutiny-core/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ mod test {

#[cfg(not(target_arch = "wasm32"))]
async fn create_fee_estimator() -> MutinyFeeEstimator<MemoryStorage> {
let storage = MemoryStorage::new(None);
let storage = MemoryStorage::new(None, None);
let esplora = Arc::new(
Builder::new("https://mutinynet.com/api")
.build_async()
Expand Down
8 changes: 6 additions & 2 deletions mutiny-core/src/keymanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,9 @@ mod tests {

wasm_bindgen_test_configure!(run_in_browser);

use crate::{keymanager::pubkey_from_keys_manager, test_utils::*};
use crate::{
encrypt::encryption_key_from_pass, keymanager::pubkey_from_keys_manager, test_utils::*,
};

use super::create_keys_manager;
use crate::fees::MutinyFeeEstimator;
Expand All @@ -265,7 +267,9 @@ mod tests {
.build_async()
.unwrap(),
);
let db = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let db = MemoryStorage::new(Some(pass), Some(cipher));
let logger = Arc::new(MutinyLogger::default());
let fees = Arc::new(MutinyFeeEstimator::new(
db.clone(),
Expand Down
29 changes: 22 additions & 7 deletions mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,10 @@ impl<S: MutinyStorage> MutinyWallet<S> {

#[cfg(test)]
mod tests {
use crate::{nodemanager::NodeManager, MutinyWallet, MutinyWalletConfig};
use crate::{
encrypt::encryption_key_from_pass, nodemanager::NodeManager, MutinyWallet,
MutinyWalletConfig,
};
use bitcoin::Network;

use crate::test_utils::*;
Expand All @@ -386,7 +389,9 @@ mod tests {
let test_name = "create_mutiny_wallet";
log!("{}", test_name);

let storage = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let storage = MemoryStorage::new(Some(pass), Some(cipher));
assert!(!NodeManager::has_node_manager(storage.clone()));
let config = MutinyWalletConfig::new(
None,
Expand All @@ -410,7 +415,9 @@ mod tests {
let test_name = "restart_mutiny_wallet";
log!("{}", test_name);

let storage = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let storage = MemoryStorage::new(Some(pass), Some(cipher));
assert!(!NodeManager::has_node_manager(storage.clone()));
let config = MutinyWalletConfig::new(
None,
Expand Down Expand Up @@ -440,7 +447,9 @@ mod tests {
let test_name = "restart_mutiny_wallet_with_nodes";
log!("{}", test_name);

let storage = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let storage = MemoryStorage::new(Some(pass), Some(cipher));

assert!(!NodeManager::has_node_manager(storage.clone()));
let config = MutinyWalletConfig::new(
Expand Down Expand Up @@ -473,7 +482,9 @@ mod tests {
let test_name = "restore_mutiny_mnemonic";
log!("{}", test_name);

let storage = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let storage = MemoryStorage::new(Some(pass), Some(cipher));
assert!(!NodeManager::has_node_manager(storage.clone()));
let config = MutinyWalletConfig::new(
None,
Expand All @@ -493,7 +504,9 @@ mod tests {
assert_ne!(seed.to_string(), "");

// create a second mw and make sure it has a different seed
let storage2 = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let storage2 = MemoryStorage::new(Some(pass), Some(cipher));
assert!(!NodeManager::has_node_manager(storage2.clone()));
let config2 = MutinyWalletConfig::new(
None,
Expand All @@ -516,7 +529,9 @@ mod tests {
mw2.stop().await.expect("should stop");
drop(mw2);

let storage3 = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let storage3 = MemoryStorage::new(Some(pass), Some(cipher));
MutinyWallet::restore_mnemonic(storage3.clone(), seed.clone())
.await
.expect("mutiny wallet should restore");
Expand Down
19 changes: 14 additions & 5 deletions mutiny-core/src/nodemanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2387,8 +2387,11 @@ pub(crate) async fn create_new_node_from_node_manager<S: MutinyStorage>(

#[cfg(test)]
mod tests {
use crate::nodemanager::{
ActivityItem, ChannelClosure, MutinyInvoice, NodeManager, TransactionDetails,
use crate::{
encrypt::encryption_key_from_pass,
nodemanager::{
ActivityItem, ChannelClosure, MutinyInvoice, NodeManager, TransactionDetails,
},
};
use crate::{keymanager::generate_seed, MutinyWalletConfig};
use bdk::chain::ConfirmationTime;
Expand All @@ -2415,7 +2418,9 @@ mod tests {
let test_name = "create_node_manager";
log!("{}", test_name);

let storage = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let storage = MemoryStorage::new(Some(pass), Some(cipher));

assert!(!NodeManager::has_node_manager(storage.clone()));
let c = MutinyWalletConfig::new(
Expand Down Expand Up @@ -2462,7 +2467,9 @@ mod tests {
let test_name = "created_new_nodes";
log!("{}", test_name);

let storage = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let storage = MemoryStorage::new(Some(pass), Some(cipher));
let seed = generate_seed(12).expect("Failed to gen seed");
let c = MutinyWalletConfig::new(
Some(seed),
Expand Down Expand Up @@ -2508,7 +2515,9 @@ mod tests {
let test_name = "created_new_nodes";
log!("{}", test_name);

let storage = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let storage = MemoryStorage::new(Some(pass), Some(cipher));
let seed = generate_seed(12).expect("Failed to gen seed");
let c = MutinyWalletConfig::new(
Some(seed),
Expand Down
2 changes: 1 addition & 1 deletion mutiny-core/src/nostr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ mod test {
let xprivkey =
ExtendedPrivKey::new_master(Network::Bitcoin, &mnemonic.to_seed("")).unwrap();

let storage = MemoryStorage::new(None);
let storage = MemoryStorage::new(None, None);

NostrManager::from_mnemonic(xprivkey, storage).unwrap()
}
Expand Down
6 changes: 4 additions & 2 deletions mutiny-core/src/onchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,8 +553,8 @@ pub(crate) fn get_esplora_url(network: Network, user_provided_url: Option<String
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::MemoryStorage;
use crate::test_utils::*;
use crate::{encrypt::encryption_key_from_pass, storage::MemoryStorage};
use bitcoin::Address;
use esplora_client::Builder;
use std::str::FromStr;
Expand All @@ -568,7 +568,9 @@ mod tests {
.build_async()
.unwrap(),
);
let db = MemoryStorage::new(Some(uuid::Uuid::new_v4().to_string()));
let pass = uuid::Uuid::new_v4().to_string();
let cipher = encryption_key_from_pass(&pass).unwrap();
let db = MemoryStorage::new(Some(pass), Some(cipher));
let logger = Arc::new(MutinyLogger::default());
let fees = Arc::new(MutinyFeeEstimator::new(
db.clone(),
Expand Down
Loading

0 comments on commit 0124cf8

Please sign in to comment.