Skip to content

Commit

Permalink
Add encryption to VSS
Browse files Browse the repository at this point in the history
  • Loading branch information
benthecarman committed Jul 8, 2023
1 parent 3ff50ff commit ae11a3b
Showing 1 changed file with 89 additions and 12 deletions.
101 changes: 89 additions & 12 deletions mutiny-core/src/vss.rs
Original file line number Diff line number Diff line change
@@ -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<Aes256>;
type Aes256CbcDec = Decryptor<Aes256>;

pub struct MutinyVssClient {
auth_client: Arc<MutinyAuthClient>,
url: String,
encryption_key: SecretKey,
logger: Arc<MutinyLogger>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VssKeyValueItem {
pub key: String,
pub value: Option<String>,
pub value: Option<Value>,
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<u8> = cipher.encrypt_padded_vec_mut::<Pkcs7>(&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<u8> = cipher.decrypt_padded_vec_mut::<Pkcs7>(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<MutinyAuthClient>,
url: String,
encryption_key: SecretKey,
logger: Arc<MutinyLogger>,
) -> Self {
Self {
auth_client,
url,
encryption_key,
logger,
}
}
Expand All @@ -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::<Vec<_>>();

// todo do we need global version here?
let body = json!({ "transaction_items": items });

Expand All @@ -51,14 +125,14 @@ impl MutinyVssClient {
Ok(())
}

pub async fn get_objects(&self, key: &str) -> Result<VssKeyValueItem, MutinyError> {
pub async fn get_object(&self, key: &str) -> Result<VssKeyValueItem, MutinyError> {
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?
Expand All @@ -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(
Expand Down Expand Up @@ -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,
)
}
Expand All @@ -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()),
Expand All @@ -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();
Expand All @@ -166,31 +243,31 @@ 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()),
version: 0,
};

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()),
version: 1,
};

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);
}
}

0 comments on commit ae11a3b

Please sign in to comment.