From ae11a3bbac43b42977e14102e446ceee14b236f8 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Fri, 7 Jul 2023 15:59:30 -0500 Subject: [PATCH] Add encryption to VSS --- mutiny-core/src/vss.rs | 101 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 12 deletions(-) diff --git a/mutiny-core/src/vss.rs b/mutiny-core/src/vss.rs index fa3bbd9ab..f3bd995ec 100644 --- a/mutiny-core/src/vss.rs +++ b/mutiny-core/src/vss.rs @@ -1,36 +1,96 @@ #![allow(dead_code)] use crate::auth::MutinyAuthClient; use crate::{error::MutinyError, logging::MutinyLogger}; +use aes::cipher::block_padding::Pkcs7; +use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +use aes::Aes256; use anyhow::anyhow; +use bitcoin::secp256k1; +use bitcoin::secp256k1::SecretKey; +use cbc::{Decryptor, Encryptor}; use lightning::log_error; use lightning::util::logger::*; use reqwest::{Method, Url}; use serde::{Deserialize, Serialize}; -use serde_json::json; +use serde_json::{json, Value}; use std::sync::Arc; +type Aes256CbcEnc = Encryptor; +type Aes256CbcDec = Decryptor; + pub struct MutinyVssClient { auth_client: Arc, url: String, + encryption_key: SecretKey, logger: Arc, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct VssKeyValueItem { pub key: String, - pub value: Option, + pub value: Option, + pub version: u32, +} + +impl VssKeyValueItem { + /// Encrypts the value of the item using the encryption key + /// and returns an encrypted version of the item + pub(crate) fn encrypt(self, encryption_key: &SecretKey) -> EncryptedVssKeyValueItem { + // should we handle this unwrap better? + let bytes = self.value.unwrap().to_string().into_bytes(); + let iv: [u8; 16] = secp256k1::rand::random(); + + let cipher = Aes256CbcEnc::new(&encryption_key.secret_bytes().into(), &iv.into()); + let mut encrypted: Vec = cipher.encrypt_padded_vec_mut::(&bytes); + encrypted.extend(iv); + let encrypted_value = base64::encode(encrypted); + + EncryptedVssKeyValueItem { + key: self.key, + value: encrypted_value, + version: self.version, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct EncryptedVssKeyValueItem { + pub key: String, + pub value: String, pub version: u32, } +impl EncryptedVssKeyValueItem { + pub(crate) fn decrypt(self, encryption_key: &SecretKey) -> VssKeyValueItem { + let bytes = base64::decode(self.value).unwrap(); + // split last 16 bytes off as iv + let iv = &bytes[bytes.len() - 16..]; + let bytes = &bytes[..bytes.len() - 16]; + + let cipher = Aes256CbcDec::new(&encryption_key.secret_bytes().into(), iv.into()); + let decrypted: Vec = cipher.decrypt_padded_vec_mut::(bytes).unwrap(); + let decrypted_value = String::from_utf8(decrypted).unwrap(); + let value = serde_json::from_str(&decrypted_value).unwrap(); + + VssKeyValueItem { + key: self.key, + value: Some(value), + version: self.version, + } + } +} + impl MutinyVssClient { pub(crate) fn new( auth_client: Arc, url: String, + encryption_key: SecretKey, logger: Arc, ) -> Self { Self { auth_client, url, + encryption_key, logger, } } @@ -41,6 +101,20 @@ impl MutinyVssClient { MutinyError::Other(anyhow!("Error parsing put objects url: {e}")) })?; + // check value is defined for all items + for item in &items { + if item.value.is_none() { + return Err(MutinyError::Other(anyhow!( + "Value must be defined for all items" + ))); + } + } + + let items = items + .into_iter() + .map(|item| item.encrypt(&self.encryption_key)) + .collect::>(); + // todo do we need global version here? let body = json!({ "transaction_items": items }); @@ -51,14 +125,14 @@ impl MutinyVssClient { Ok(()) } - pub async fn get_objects(&self, key: &str) -> Result { + pub async fn get_object(&self, key: &str) -> Result { let url = Url::parse(&format!("{}/getObject", self.url)).map_err(|e| { log_error!(self.logger, "Error parsing get objects url: {e}"); MutinyError::Other(anyhow!("Error parsing get objects url: {e}")) })?; let body = json!({ "key": key }); - let result = self + let result: EncryptedVssKeyValueItem = self .auth_client .request(Method::POST, url, Some(body)) .await? @@ -69,7 +143,7 @@ impl MutinyVssClient { MutinyError::Other(anyhow!("Error parsing get objects response: {e}")) })?; - Ok(result) + Ok(result.decrypt(&self.encryption_key)) } pub async fn list_key_versions( @@ -126,9 +200,12 @@ mod tests { Err(e) => panic!("Authentication failed with error: {:?}", e), }; + let encryption_key = SecretKey::from_slice(&[2; 32]).unwrap(); + MutinyVssClient::new( Arc::new(auth_client), "https://storage-staging.mutinywallet.com".to_string(), + encryption_key, logger, ) } @@ -138,7 +215,7 @@ mod tests { let client = create_client().await; let key = "hello".to_string(); - let value = "world".to_string(); + let value: Value = serde_json::from_str("\"world\"").unwrap(); let obj = VssKeyValueItem { key: key.clone(), value: Some(value.clone()), @@ -147,7 +224,7 @@ mod tests { client.put_objects(vec![obj.clone()]).await.unwrap(); - let result = client.get_objects(&key).await.unwrap(); + let result = client.get_object(&key).await.unwrap(); assert_eq!(obj, result); let result = client.list_key_versions(None).await.unwrap(); @@ -166,7 +243,7 @@ mod tests { let client = create_client().await; let key = "hello".to_string(); - let value = "world1".to_string(); + let value: Value = serde_json::from_str("\"world\"").unwrap(); let obj = VssKeyValueItem { key: key.clone(), value: Some(value.clone()), @@ -174,10 +251,10 @@ mod tests { }; client.put_objects(vec![obj.clone()]).await.unwrap(); - let result = client.get_objects(&key).await.unwrap(); + let result = client.get_object(&key).await.unwrap(); assert_eq!(obj.clone(), result); - let value1 = "new world".to_string(); + let value1: Value = serde_json::from_str("\"new world\"").unwrap(); let obj1 = VssKeyValueItem { key: key.clone(), value: Some(value1.clone()), @@ -185,12 +262,12 @@ mod tests { }; client.put_objects(vec![obj1.clone()]).await.unwrap(); - let result = client.get_objects(&key).await.unwrap(); + let result = client.get_object(&key).await.unwrap(); assert_eq!(obj1, result); // check we get version 1 client.put_objects(vec![obj]).await.unwrap(); - let result = client.get_objects(&key).await.unwrap(); + let result = client.get_object(&key).await.unwrap(); assert_eq!(obj1, result); } }