diff --git a/chain-common/proto/api.proto b/chain-common/proto/api.proto index f037378..7548432 100644 --- a/chain-common/proto/api.proto +++ b/chain-common/proto/api.proto @@ -10,6 +10,7 @@ import "transaction.proto"; import "validation.proto"; import "persona.proto"; import "post-encryption.proto"; +import "post-decryption.proto"; message MWRequest { oneof request { @@ -39,6 +40,8 @@ message MWRequest { PersonaGenerationParam param_generate_persona = 26; PostEncryptionParam param_post_encryption = 27; + + PostDecryptionParam param_post_decryption = 28; } } @@ -69,6 +72,8 @@ message MWResponse { PersonaGenerationResp resp_generate_persona = 25; PostEncryptedResp resp_post_encryption = 26; + + PostDecryptionResp resp_post_decryption = 27; } } diff --git a/chain-common/proto/post-decryption.proto b/chain-common/proto/post-decryption.proto new file mode 100644 index 0000000..db064ca --- /dev/null +++ b/chain-common/proto/post-decryption.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package api; + +import "base.proto"; + +message PostDecryptionParam { + string postContent = 1; + optional string postIdentifier = 2; + optional bytes localKeyData = 3; +} + +message PostDecryptionResp { + string contentText = 1; +} \ No newline at end of file diff --git a/chain-common/src/generated/api.rs b/chain-common/src/generated/api.rs index cea48ce..47daf33 100644 --- a/chain-common/src/generated/api.rs +++ b/chain-common/src/generated/api.rs @@ -488,8 +488,22 @@ pub enum PublicKeyAlgorithm { Secp256k1Algr = 2, } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct PostDecryptionParam { + #[prost(string, tag="1")] + pub post_content: ::prost::alloc::string::String, + #[prost(string, optional, tag="2")] + pub post_identifier: ::core::option::Option<::prost::alloc::string::String>, + #[prost(bytes="vec", optional, tag="3")] + pub local_key_data: ::core::option::Option<::prost::alloc::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PostDecryptionResp { + #[prost(string, tag="1")] + pub content_text: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] pub struct MwRequest { - #[prost(oneof="mw_request::Request", tags="1, 2, 3, 4, 5, 10, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27")] + #[prost(oneof="mw_request::Request", tags="1, 2, 3, 4, 5, 10, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28")] pub request: ::core::option::Option, } /// Nested message and enum types in `MWRequest`. @@ -536,11 +550,13 @@ pub mod mw_request { ParamGeneratePersona(super::PersonaGenerationParam), #[prost(message, tag="27")] ParamPostEncryption(super::PostEncryptionParam), + #[prost(message, tag="28")] + ParamPostDecryption(super::PostDecryptionParam), } } #[derive(Clone, PartialEq, ::prost::Message)] pub struct MwResponse { - #[prost(oneof="mw_response::Response", tags="1, 2, 3, 4, 5, 6, 11, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 25, 26")] + #[prost(oneof="mw_response::Response", tags="1, 2, 3, 4, 5, 6, 11, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27")] pub response: ::core::option::Option, } /// Nested message and enum types in `MWResponse`. @@ -585,6 +601,8 @@ pub mod mw_response { RespGeneratePersona(super::PersonaGenerationResp), #[prost(message, tag="26")] RespPostEncryption(super::PostEncryptedResp), + #[prost(message, tag="27")] + RespPostDecryption(super::PostDecryptionResp), } } #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index cac1079..5bbf5f6 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -25,5 +25,4 @@ pbkdf2 = { version = "0.11", default-features = false } hmac = { version = "0.12.1" } ctr = { version = "0.9.1" } aes-gcm = { version = "0.9.4" } -rmp = { version = "0.8.1" } - +rmp = { version = "0.8.11" } \ No newline at end of file diff --git a/crypto/src/encryption_constants.rs b/crypto/src/encryption_constants.rs new file mode 100644 index 0000000..3556f83 --- /dev/null +++ b/crypto/src/encryption_constants.rs @@ -0,0 +1,3 @@ +pub(crate) const SHARED_KEY_ENCODED: &str = "3Bf8BJ3ZPSMUM2jg2ThODeLuRRD_-_iwQEaeLdcQXpg"; +pub(crate) const IV_SIZE: usize = 16; +pub(crate) const AES_KEY_SIZE: usize = 32; diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 2bbb5d5..0973347 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -19,6 +19,10 @@ pub mod post_encryption; pub mod jwk; pub mod pbkdf2; +mod encryption_constants; +mod payload_decode_v37; +pub mod payload_decode_v38; + #[derive(Debug, PartialOrd, PartialEq)] pub enum Error { KdfParamsInvalid, @@ -48,6 +52,8 @@ pub enum Error { NotSupportedCipher, InvalidLocalKey, + + DecryptContentFailed, } impl Error { @@ -67,6 +73,7 @@ impl Error { Error::NotSupportedCurve => "-3012".to_owned(), Error::NotSupportedCipher => "-3013".to_owned(), Error::InvalidLocalKey => "-3014".to_owned(), + Error::DecryptContentFailed => "-3015".to_owned(), } } @@ -88,6 +95,8 @@ impl Error { Error::InvalidLocalKey => { "Invalid local key. Local key is required to encrypt private message".to_owned() } + + Error::DecryptContentFailed => "Failed to decrypt post content".to_owned(), } } } diff --git a/crypto/src/payload_decode_v37.rs b/crypto/src/payload_decode_v37.rs new file mode 100644 index 0000000..ec4a797 --- /dev/null +++ b/crypto/src/payload_decode_v37.rs @@ -0,0 +1,178 @@ +use std::str::from_utf8; + +use super::payload_encode_v37::Index; +use super::Error; +use rmp::decode::*; + +struct DecodedData { + network: String, + author_id: String, + algorithm: i64, + author_pub_key: Vec, + aes_key: Vec, + iv: Vec, + encrypted_content: Vec, +} + +fn decode_with_container(encrypted: &[u8]) -> Result { + let mut content = Bytes::new(encrypted); + let map_len = read_map_len(&mut content).map_err(|_| Error::InvalidCiphertext)?; + if map_len != 2 { + return Err(Error::InvalidCiphertext); + } + + let flag: i64 = read_int(&mut content).map_err(|_| Error::InvalidCiphertext)?; + if flag != 0 { + return Err(Error::InvalidCiphertext); + } + let _ = read_bin_len(&mut content).map_err(|_| Error::InvalidCiphertext)?; + + let data_map_len = read_map_len(&mut content).map_err(|_| Error::InvalidCiphertext)?; + if data_map_len != 6 { + return Err(Error::InvalidCiphertext); + } + + // network + let author_network = + decode_str(&mut content, Index::AuthorNetwork).map_err(|_| Error::InvalidCiphertext)?; + + // author_id + let author_decoded_id = + decode_str(&mut content, Index::AuthorID).map_err(|_| Error::InvalidCiphertext)?; + + // algorithm + let author_pub_key_algorithm: i64 = decode_int64(&mut content, Index::AuthorPublicKeyAlgorithm) + .map_err(|_| Error::InvalidCiphertext)?; + + // author_pub_key + let author_pub_key = decode_bin(&mut content, Option::Some(Index::AuthorPublicKey)) + .map_err(|_| Error::InvalidCiphertext)?; + + // iv and aes_key + let encryption_index: i64 = read_int(&mut content).map_err(|_| Error::InvalidCiphertext)?; + match encryption_index.try_into()? { + Index::Encryption => {} + _ => return Err(Error::InvalidCiphertext), + } + let array_len = read_array_len(&mut content).map_err(|_| Error::InvalidCiphertext)?; + if array_len != 3 { + return Err(Error::InvalidCiphertext); + } + let flag: i64 = read_int(&mut content).map_err(|_| Error::InvalidCiphertext)?; + if flag != 0 { + return Err(Error::InvalidCiphertext); + } + let aes_key = decode_bin(&mut content, Option::None).map_err(|_| Error::InvalidCiphertext)?; + let decoded_iv = + decode_bin(&mut content, Option::None).map_err(|_| Error::InvalidCiphertext)?; + + // encrypted text + let decoded_data = decode_bin(&mut content, Option::Some(Index::Data)) + .map_err(|_| Error::InvalidCiphertext)?; + + Ok(DecodedData { + network: author_network, + author_id: author_decoded_id, + algorithm: author_pub_key_algorithm, + author_pub_key, + aes_key, + iv: decoded_iv, + encrypted_content: decoded_data, + }) +} + +fn decode_str(bytes: &mut Bytes, index: Index) -> Result { + let index_value: i64 = rmp::decode::read_int(bytes).map_err(|_| Error::InvalidCiphertext)?; + let index_value: Index = index_value.try_into()?; + + if index_value != index { + return Err(Error::InvalidCiphertext); + } + + let str_len = read_str_len(bytes).map_err(|_| Error::InvalidCiphertext)?; + let mut str_buf = [0u8; 1].repeat(str_len as usize); + let _ = bytes + .read_exact_buf(&mut str_buf) + .map_err(|_| Error::InvalidCiphertext)?; + + match from_utf8(&str_buf) { + Ok(decoded) => Ok(decoded.to_owned()), + _ => Err(Error::InvalidCiphertext), + } +} + +fn decode_int64(bytes: &mut Bytes, index: Index) -> Result { + let decoded_index: i64 = rmp::decode::read_int(bytes).map_err(|_| Error::InvalidCiphertext)?; + let decoded_index: Index = decoded_index.try_into()?; + if decoded_index != index { + return Err(Error::InvalidCiphertext); + } + + match read_int(bytes) { + Ok(decoded) => Ok(decoded), + _ => Err(Error::InvalidCiphertext), + } +} + +fn decode_bin(bytes: &mut Bytes, index: Option) -> Result, Error> { + if let Some(index) = index { + let data_index: i64 = read_int(bytes).map_err(|_| Error::InvalidCiphertext)?; + let data_index: Index = data_index.try_into()?; + if data_index != index { + return Err(Error::InvalidCiphertext); + } + } + + let data_len = read_bin_len(bytes).map_err(|_| Error::InvalidCiphertext)?; + let mut decoded_data = [0u8; 1].repeat(data_len as usize); + bytes + .read_exact_buf(&mut decoded_data) + .map_err(|_| Error::InvalidCiphertext)?; + + Ok(decoded_data.to_vec()) +} + +#[cfg(test)] +mod tests { + use super::super::number_util::random_iv; + use super::super::payload_encode_v37::*; + use super::super::Error; + use super::*; + + const IV_SIZE: usize = 16; + const AES_KEY_SIZE: usize = 32; + + #[test] + fn test_decode_v37() -> Result<(), Error> { + let post_iv = random_iv(IV_SIZE); + let post_key_iv = random_iv(AES_KEY_SIZE); + let author_key = random_iv(33); + let text_content = random_iv(32); + let network = "eht"; + let author_id = "1331444"; + let algr = 2; + + let encrypted = encode_with_container( + network, + author_id, + algr, + &post_key_iv, + &author_key, + &post_iv, + &text_content, + ) + .unwrap(); + + let result = decode_with_container(&encrypted).map_err(|_| Error::InvalidCiphertext)?; + + assert_eq!(result.network, network); + assert_eq!(result.author_id, author_id); + assert_eq!(result.algorithm, algr as i64); + assert_eq!(result.author_pub_key, post_key_iv); + assert_eq!(result.aes_key, author_key); + assert_eq!(result.iv, post_iv); + assert_eq!(result.encrypted_content, text_content); + + Ok(()) + } +} diff --git a/crypto/src/payload_decode_v38.rs b/crypto/src/payload_decode_v38.rs new file mode 100644 index 0000000..d9b3067 --- /dev/null +++ b/crypto/src/payload_decode_v38.rs @@ -0,0 +1,381 @@ +use base64::{decode_config, STANDARD, URL_SAFE_NO_PAD}; +use serde::{Deserialize, Serialize}; + +use crate::aes_gcm::aes_decrypt; +use crate::encryption_constants::{IV_SIZE, SHARED_KEY_ENCODED}; +use crate::payload_encode_v38::Index; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum ParseError { + Fields, + AuthorPubKey, + Identity, + PostIdentifier, + AesKey, + PostKey, + PostMessage, + LocalKeyIsNil, + IvIsInvalid, +} + +#[allow(dead_code)] +#[derive(Debug)] +struct EncrtyptionParam { + is_public: bool, + aes_key_encrypted: String, + encoded_iv: String, + encoded_encrypted: String, + signature: String, + network: Option, + author_id: Option, + author_serialized_pub_key: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +struct JWKFormatObject { + alg: String, + ext: bool, + k: String, + key_ops: Vec, + kty: String, +} + +pub fn decode_payload_v38( + post_identifier: Option<&str>, + local_key_data: Option<&[u8]>, + post_content: &str, +) -> Result { + let parsed_enctypted_info = parse_and_decode_fields_to_encrypt_info(post_content)?; + let encrypted_text = decode_config(&parsed_enctypted_info.encoded_encrypted, STANDARD) + .map_err(|_| ParseError::Fields)?; + + let decoded_iv = decode_config(parsed_enctypted_info.encoded_iv, STANDARD) + .map_err(|_| ParseError::Fields)?; + if decoded_iv.len() != IV_SIZE { + return Err(ParseError::IvIsInvalid); + } + + let network_and_post_iv = parse_post_identifier(post_identifier)?; + match network_and_post_iv { + None => {} + Some((_, post_iv_from_identifier)) => { + if decoded_iv != post_iv_from_identifier { + return Err(ParseError::Fields); + } + } + } + + // use decoded_iv and post_key to decrypt text content + let post_key = match parsed_enctypted_info.is_public { + false => decode_aes_key_encrypted_with_local_key( + &parsed_enctypted_info.aes_key_encrypted, + local_key_data, + &decoded_iv, + )?, + true => decode_aes_key_encrypted(&decoded_iv, &parsed_enctypted_info.aes_key_encrypted) + .map_err(|_| ParseError::PostKey)?, + }; + + let base64_encrypted_text = + String::from_utf8(encrypted_text).map_err(|_| ParseError::Fields)?; + let encrypted_text_buf = + decode_config(&base64_encrypted_text, STANDARD).map_err(|_| ParseError::Fields)?; + + let decoded_message = aes_decrypt(&decoded_iv, &post_key, &encrypted_text_buf) + .map_err(|_| ParseError::PostMessage)?; + + String::from_utf8(decoded_message).map_err(|_| ParseError::PostMessage) +} + +fn decode_aes_key_encrypted(iv: &[u8], encrypted_ase_key: &str) -> Result, ParseError> { + let base64_url_config = URL_SAFE_NO_PAD; + let base64_config = STANDARD; + + let base64_decoded_ase_key = + decode_config(encrypted_ase_key, base64_config).map_err(|_| ParseError::AesKey)?; + let shared_key_bytes = + decode_config(&SHARED_KEY_ENCODED, base64_url_config).map_err(|_| ParseError::AesKey)?; + let decrypted_key = aes_decrypt(iv, &shared_key_bytes, &base64_decoded_ase_key) + .map_err(|_| ParseError::AesKey)?; + + let ab = serde_json::from_slice::(&decrypted_key); + match ab { + Ok(decrypted_object) => { + let encoded_aes_key = decrypted_object.k; + let decoded_aes_key = decode_config(encoded_aes_key, base64_url_config) + .map_err(|_| ParseError::PostKey)?; + Ok(decoded_aes_key) + } + Err(_) => Err(ParseError::PostKey), + } +} + +fn decode_aes_key_encrypted_with_local_key( + aes_key_encrypted: &str, + local_key_data: Option<&[u8]>, + iv: &[u8], +) -> Result, ParseError> { + let owners_aes_key_decoded = + decode_config(aes_key_encrypted, STANDARD).map_err(|_| ParseError::PostKey)?; + let local_key_data = local_key_data.ok_or(ParseError::LocalKeyIsNil)?; + let encoded_post_key = aes_decrypt(iv, local_key_data, &owners_aes_key_decoded) + .map_err(|_| ParseError::PostKey)?; + let jwk_object = serde_json::from_slice::(&encoded_post_key) + .map_err(|_| ParseError::PostKey)?; + + decode_config(jwk_object.k, URL_SAFE_NO_PAD).map_err(|_| ParseError::PostKey) +} + +fn parse_post_identifier(formatted: Option<&str>) -> Result)>, ParseError> { + let prefix = "post_iv:"; + + match formatted { + None => Ok(None), + Some(formatted) => match formatted.starts_with(prefix) { + false => Err(ParseError::PostIdentifier), + true => { + let mut network_and_encode_iv = formatted + .split(prefix) + .find(|x| x.contains('/')) + .map(|x| x.split('/')) + .ok_or(ParseError::Fields)?; + + let network = network_and_encode_iv + .next() + .ok_or(ParseError::PostIdentifier)?; + let post_iv = network_and_encode_iv + .next() + .ok_or(ParseError::PostIdentifier)? + .replace("|", "/"); + + let post_iv = + decode_config(&post_iv, STANDARD).map_err(|_| ParseError::PostIdentifier)?; + + Ok(Option::Some((network.to_owned(), post_iv))) + } + }, + } +} + +fn parse_post_fields(given_content: &str) -> Result, ParseError> { + let prefix = "\u{1F3BC}4/4"; + if !given_content.starts_with(prefix) { + return Err(ParseError::Fields); + } + + if !given_content.ends_with(":||") { + return Err(ParseError::Fields); + } + + let compace_fields = given_content.split(":||").next().unwrap_or(""); + if compace_fields.is_empty() { + return Err(ParseError::Fields); + } + + let fields = compace_fields.split('|').collect::>(); + if fields.len() as usize != 8 { + return Err(ParseError::Fields); + } + + let is_public = fields[Index::PublicShared as usize]; + if !["0", "1"].contains(&is_public) { + return Err(ParseError::Fields); + } + + Ok(fields) +} + +fn parse_and_decode_fields_to_encrypt_info( + given_content: &str, +) -> Result { + let fields = parse_post_fields(given_content)?; + + let aes_key_encrypted = fields[1].to_owned(); + + let identity = fields[Index::AuthorIdentifier as usize]; + + let (network, author_id) = match identity.is_empty() { + true => (Option::None, Option::None), + false => { + let decoded_identity = + decode_config(identity, STANDARD).map_err(|_| ParseError::Identity)?; + let string = String::from_utf8(decoded_identity).map_err(|_| ParseError::Identity)?; + let mut splits = string.split('/'); + + let network = splits.next(); + let author_id = splits.next(); + + ( + network.map(|x| x.to_owned()), + author_id.map(|x| x.to_owned()), + ) + } + }; + + let pub_key = fields[Index::AuthorPublicKey as usize]; + let pub_key_serialized = match pub_key.is_empty() { + true => Option::None, + false => { + let key = decode_config(pub_key, STANDARD).map_err(|_| ParseError::AuthorPubKey)?; + Option::Some(key) + } + }; + + let is_public = fields[Index::PublicShared as usize]; + + Ok(EncrtyptionParam { + is_public: is_public == "1", + aes_key_encrypted, + encoded_iv: fields[2].to_owned(), + encoded_encrypted: fields[3].to_owned(), + signature: fields[4].to_owned(), + network, + author_id, + author_serialized_pub_key: pub_key_serialized, + }) +} + +#[cfg(test)] +mod tests { + use base64::encode_config; + + use super::*; + + use crate::number_util::random_iv; + use crate::payload_encode_v38::{encode_aes_key_encrypted, eocode_post_key_and_aes_key}; + use crate::encryption_constants::AES_KEY_SIZE; + + const PUB_KEY_SIZE: usize = 33; + + #[test] + fn test_get_post_identifier_and_post_iv() { + let post_iv = random_iv(IV_SIZE); + let iv_str = encode_config(&post_iv, STANDARD); + let post_identifier = format!("post_iv:eth/{}", iv_str); + let decoded = parse_post_identifier(Some(&post_identifier)) + .expect("Invalid post identifier") + .unwrap(); + let (network, iv) = decoded; + assert_eq!(network, "eth"); + assert_eq!(&iv, &post_iv) + } + + #[test] + fn test_decode_aes_key_encrypted_public() { + let iv = random_iv(IV_SIZE); + let key = random_iv(AES_KEY_SIZE); + let encoded_aes_key = encode_aes_key_encrypted(&iv, &key).unwrap(); + + let decoded_key = decode_aes_key_encrypted(&iv, &encoded_aes_key); + // let ad = Vec::from_iter(key.into_iter()); + assert!(decoded_key.is_ok()); + assert_eq!(decoded_key.unwrap(), key); + } + + #[test] + fn test_decode_aes_key_encrypted() { + let iv = random_iv(IV_SIZE); + let key = random_iv(AES_KEY_SIZE); + let local_key_data = random_iv(AES_KEY_SIZE); + let (_, owners_aes_key_encrypted_string) = + eocode_post_key_and_aes_key(Some(&local_key_data), &iv, &key).unwrap(); + + let result = decode_aes_key_encrypted_with_local_key( + &owners_aes_key_encrypted_string, + Some(&local_key_data), + &iv, + ); + + match result { + Err(err) => panic!("decoding failed {err:?}"), + Ok(result) => { + assert_eq!(result, key); + } + } + } + + #[test] + fn test_decode_fields() { + let public_fields = + parse_post_fields("\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|12adad|1|dad:||"); + let privte_fields = + parse_post_fields("\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|01131|0|dad:||"); + let faild_public_fields = + parse_post_fields("11dd|13add|1333dad|1318dadss_dad|juudad|1qe1dda|1q|dad:||"); + let faild_fields = + parse_post_fields("\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|1qe1dda|1q|dad:||"); + + assert!(public_fields.is_ok()); + assert!(privte_fields.is_ok()); + assert!(faild_public_fields.is_err()); + assert!(faild_fields.is_err()); + } + + #[test] + fn test_decode_base64_encoded_encryped() { + let base64_url_config = URL_SAFE_NO_PAD; + let encrypted_result = "🎼4/4|dPsavNUJl+CSkjHaeKY4pBGdRPVLVX9wTFvha7233bTAh7H8MaOQKAcjMTTPSpiIfXV6z+adQ4ub/GBz13JEEcq1tBWGe14e6KJM0BAlavKA8W|CODYA3UXxijahpWzNNhYWw==|sicrktkUfaAkTjYtZHH9KzGlymq5mw==|_|Ay0N+38oQ+roPtmvcZpXs/Gw9/3jU0J2djv/JUXFjUiO|0|dHdpdHRlci5jb20veXVhbl9icmFk:||"; + let encryped_info = parse_and_decode_fields_to_encrypt_info(encrypted_result) + .expect("parse_fields_to_encrypt_info failed"); + + let network = "twitter.com"; + let author_id = "yuan_brad"; + let is_public = false; + let iv = [ + 8, 224, 216, 3, 117, 23, 198, 40, 218, 134, 149, 179, 52, 216, 88, 91, + ]; + + assert_eq!(encryped_info.is_public, is_public); + assert_eq!(encryped_info.author_id.as_ref().unwrap(), author_id); + assert_eq!(encryped_info.network.as_deref().unwrap(), network); + assert_eq!(encryped_info.signature, "_"); + + let decoded_iv = + decode_config(&encryped_info.encoded_iv, STANDARD).expect("iv decode failed"); + assert_eq!(decoded_iv, iv); + + let author_pub_key_x_str = "LQ37fyhD6ug-2a9xmlez8bD3_eNTQnZ2O_8lRcWNSI4"; + let author_pub_key_y_str = "yoWbbZIyR-8dwLivurXT4fwD3QqP4sZ329jan3fp4I0"; + let author_pub_key_x_1 = decode_config(&author_pub_key_x_str, base64_url_config).unwrap(); + let author_pub_key_y_1 = decode_config(&author_pub_key_y_str, base64_url_config).unwrap(); + let author_public_key = [[0x04].to_vec(), author_pub_key_x_1, author_pub_key_y_1].concat(); + + // skip flag u8 + assert_eq!( + encryped_info.author_serialized_pub_key.unwrap()[1..], + author_public_key[1..PUB_KEY_SIZE] + ); + + let encrypted_message = [ + 178, 39, 43, 146, 217, 20, 125, 160, 36, 78, 54, 45, 100, 113, 253, 43, 49, 165, 202, + 106, 185, 155, + ]; + + assert_eq!( + decode_config(encryped_info.encoded_encrypted, STANDARD) + .expect("decode encrypted_message failed"), + encrypted_message + ); + + assert_eq!(encryped_info.aes_key_encrypted, "dPsavNUJl+CSkjHaeKY4pBGdRPVLVX9wTFvha7233bTAh7H8MaOQKAcjMTTPSpiIfXV6z+adQ4ub/GBz13JEEcq1tBWGe14e6KJM0BAlavKA8W"); + } + + #[test] + fn test_decode_proccess() { + let ownersAESKeyEncrypted: [u8; 138] = [ + 144, 174, 122, 212, 72, 176, 255, 251, 188, 174, 28, 233, 204, 151, 80, 93, 36, 109, + 165, 126, 119, 100, 64, 93, 55, 94, 37, 149, 89, 113, 222, 62, 245, 197, 232, 114, 255, + 100, 89, 118, 11, 128, 59, 223, 17, 148, 137, 1, 215, 188, 44, 12, 211, 86, 246, 197, + 59, 183, 149, 125, 172, 106, 225, 119, 222, 221, 214, 135, 53, 104, 255, 122, 113, 71, + 62, 77, 164, 13, 18, 104, 245, 100, 241, 139, 50, 193, 151, 200, 82, 143, 94, 68, 56, + 246, 42, 75, 223, 44, 208, 251, 9, 195, 249, 131, 84, 175, 78, 237, 23, 26, 34, 33, + 115, 248, 238, 80, 144, 64, 120, 103, 23, 251, 177, 78, 222, 231, 250, 67, 214, 25, + 214, 99, 187, 76, 2, 197, 220, 30, + ]; + + let content_text = "🎼4/4|kK561Eiw//u8rhzpzJdQXSRtpX53ZEBdN14llVlx3j71xehy/2RZdguAO98RlIkB17wsDNNW9sU7t5V9rGrhd97d1oc1aP96cUc+TaQNEmj1ZPGLMsGXyFKPXkQ49ipL3yzQ+wnD+YNUr07tFxoiIXP47lCQQHhnF/uxTt7n+kPWGdZju0wCxdwe|tAnANz4j+aOOGkSAMTXbrA==|F8rREysE4XdmNXqvD5GKrbHA2k9IOR9s8dNW2QAA2MPOVw==|_|Asbei5hqEcr1HoHONBdPQugbBvw/EYZej6O+IKZeF8m2|0|dHdpdHRlci5jb20vZm94X3dlMTA=:||"; + let fields = parse_and_decode_fields_to_encrypt_info(content_text).unwrap(); + println!("{:?}", fields); + } +} diff --git a/crypto/src/payload_encode_v37.rs b/crypto/src/payload_encode_v37.rs index 53579de..38527aa 100644 --- a/crypto/src/payload_encode_v37.rs +++ b/crypto/src/payload_encode_v37.rs @@ -1,8 +1,13 @@ +use std::convert::TryFrom; + use rmp::encode::*; use super::Error; -enum Index { +#[allow(dead_code)] +#[repr(i64)] +#[derive(Debug, PartialEq, Eq)] +pub enum Index { Version = 0, AuthorNetwork = 1, AuthorID = 2, @@ -12,6 +17,22 @@ enum Index { Data = 6, } +impl TryFrom for Index { + type Error = (); + fn try_from(value: i64) -> Result { + match value { + x if x == Index::Version as i64 => Ok(Index::Version), + x if x == Index::AuthorNetwork as i64 => Ok(Index::AuthorNetwork), + x if x == Index::AuthorID as i64 => Ok(Index::AuthorID), + x if x == Index::AuthorPublicKeyAlgorithm as i64 => Ok(Index::AuthorPublicKeyAlgorithm), + x if x == Index::AuthorPublicKey as i64 => Ok(Index::AuthorPublicKey), + x if x == Index::Encryption as i64 => Ok(Index::Encryption), + x if x == Index::Data as i64 => Ok(Index::Data), + _ => Err(()), + } + } +} + pub fn encode_with_container( network: &str, author_id: &str, diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index bc047fd..188409b 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -5,24 +5,26 @@ use super::Error; use bitcoin::secp256k1::{ecdh, PublicKey, SecretKey}; use std::collections::HashMap; +use base64::{decode_config, encode_config, STANDARD, URL_SAFE_NO_PAD}; + +use crate::encryption_constants::SHARED_KEY_ENCODED; + impl From for Error { fn from(_err: bitcoin::secp256k1::Error) -> Error { Error::InvalidPrivateKey } } -use base64::{decode_config, encode_config, STANDARD, URL_SAFE_NO_PAD}; - -const SHARED_KEY_ENCODED: &str = "3Bf8BJ3ZPSMUM2jg2ThODeLuRRD_-_iwQEaeLdcQXpg"; const E2E_KEY: [u8; 2] = [40, 70]; const E2E_IV: [u8; 1] = [33]; -enum Index { +pub enum Index { AuthorPublicKey = 5, PublicShared = 6, AuthorIdentifier = 7, } +#[allow(clippy::too_many_arguments)] pub fn encode_v38( is_public: bool, network: &str, @@ -40,15 +42,13 @@ pub fn encode_v38( match is_public { true => (encode_aes_key_encrypted(iv, key)?, None), false => { - let local_key = local_key_data.ok_or(Error::InvalidLocalKey)?; - let post_key_encoded = encode_post_key(key); - let owners_aes_key_encrypted = - encrypt_by_local_key(&post_key_encoded, iv, local_key)?; + let (post_key_encoded, owners_aes_key_encrypted_string) = + eocode_post_key_and_aes_key(local_key_data, iv, key)?; + let author_private_key_data = author_private_key.ok_or(Error::InvalidPrivateKey)?; let ecdh_result = add_receiver(author_private_key_data, &target, &post_key_encoded)?; - let owners_aes_key_encrypted_string = - encode_config(&owners_aes_key_encrypted, base64_config); + (owners_aes_key_encrypted_string, Some(ecdh_result)) } }; @@ -69,7 +69,7 @@ pub fn encode_v38( Ok((encoded_fields, ecdh_result)) } -fn encode_aes_key_encrypted(iv: &[u8], key: &[u8]) -> Result { +pub(crate) fn encode_aes_key_encrypted(iv: &[u8], key: &[u8]) -> Result { let base64_url_config = URL_SAFE_NO_PAD; let encoded_aes_key = encode_config(&key, base64_url_config); let ab = format!( @@ -93,6 +93,7 @@ fn encrypt_by_local_key( aes_encrypt(post_iv, local_key_data, encoded_post_key) } +#[allow(clippy::too_many_arguments)] fn encode_fields( is_public: bool, aes_key_encrypted: &str, @@ -150,6 +151,20 @@ fn encode_fields( Ok(result) } +pub(crate) fn eocode_post_key_and_aes_key( + local_key_data: Option<&[u8]>, + iv: &[u8], + key: &[u8], +) -> Result<(Vec, String), Error> { + let base64_config = STANDARD; + let local_key = local_key_data.ok_or(Error::InvalidLocalKey)?; + let post_key_encoded = encode_post_key(key); + let owners_aes_key_encrypted = encrypt_by_local_key(&post_key_encoded, iv, local_key)?; + let owners_aes_key_encrypted_string = encode_config(&owners_aes_key_encrypted, base64_config); + + Ok((post_key_encoded, owners_aes_key_encrypted_string)) +} + fn encode_post_key(post_key: &[u8]) -> Vec { let base64_url_config = URL_SAFE_NO_PAD; let encoded_post_key = encode_config(&post_key, base64_url_config); @@ -253,10 +268,10 @@ mod tests { let encoded_fields = encode_fields( true, - &aes_key_encrypted, - &encoded_iv, - &encoded_encrypted, - &signature, + aes_key_encrypted, + encoded_iv, + encoded_encrypted, + signature, network, Some(author_id), Some(&public_key_data), @@ -278,8 +293,9 @@ mod tests { 2, 170, 10, 30, 27, 232, 4, 43, 63, 50, 63, 249, 34, 255, 147, 179, 179, 85, 203, 103, 115, 52, 111, 166, 140, 56, 20, 223, 54, 25, 143, 49, 28, ]; - assert_eq!(PublicKey::from_slice(&public_key).is_ok(), true); - assert_eq!(SecretKey::from_slice(&private_key).is_ok(), true); + + assert!(PublicKey::from_slice(&public_key).is_ok()); + assert!(SecretKey::from_slice(&private_key).is_ok()); let shared_secret = derive_aes_by_ecdh(&public_key, &private_key).unwrap(); let encrypted = aes_encrypt(&test_iv, &shared_secret, test_message.as_bytes()).unwrap(); let decrypted = aes_decrypt(&test_iv, &shared_secret, &encrypted).unwrap(); diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index a9fd6c1..007da99 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -6,12 +6,10 @@ use base64::{encode_config, STANDARD}; use std::collections::HashMap; use super::aes_gcm::aes_encrypt; +use crate::encryption_constants::{AES_KEY_SIZE, IV_SIZE}; use std::str; -const IV_SIZE: usize = 16; -const AES_KEY_SIZE: usize = 32; - pub enum Version { V37 = -37, V38 = -38, @@ -24,6 +22,7 @@ pub struct EncryptionResultE2E { pub iv_to_be_published: Option>, } +#[derive(Debug)] pub struct EncryptionResult { pub output: String, pub post_key: Vec, @@ -31,6 +30,7 @@ pub struct EncryptionResult { pub e2e_result: Option>, } +#[allow(clippy::too_many_arguments)] pub fn encrypt( version: Version, is_public: bool, @@ -71,7 +71,7 @@ pub fn encrypt( Ok(EncryptionResult { output: result.0, post_key: post_key_iv, - post_identifier: post_identifier, + post_identifier, e2e_result: result.1, }) } @@ -134,5 +134,7 @@ mod tests { None, ) .unwrap(); + + println!("{:?}", encryption_result); } } diff --git a/interface/src/handler.rs b/interface/src/handler.rs index b7e1c5c..cfcf0e1 100644 --- a/interface/src/handler.rs +++ b/interface/src/handler.rs @@ -1,5 +1,6 @@ mod account; mod common; +mod decryption; mod encryption; mod persona; mod sign; @@ -40,5 +41,7 @@ pub fn dispatch_request(request: mw_request::Request) -> MwResponse { ParamGeneratePersona(param) => persona::generate_persona(¶m), ParamPostEncryption(param) => encryption::encode(param), + + ParamPostDecryption(param) => decryption::decode_post(param), } } diff --git a/interface/src/handler/decryption.rs b/interface/src/handler/decryption.rs new file mode 100644 index 0000000..ec7e0d7 --- /dev/null +++ b/interface/src/handler/decryption.rs @@ -0,0 +1,21 @@ +use crypto::{payload_decode_v38::decode_payload_v38, Error}; + +use chain_common::api::{ + mw_response::Response, MwResponse, PostDecryptionParam, PostDecryptionResp, +}; + +pub fn decode_post(param: PostDecryptionParam) -> MwResponse { + let post_identifier = param.post_identifier.as_deref(); + let local_key = param.local_key_data; + + let decoded_result = + decode_payload_v38(post_identifier, local_key.as_deref(), ¶m.post_content); + + match decoded_result { + Err(_) => Error::DecryptContentFailed.into(), + Ok(decoded_text) => Response::RespPostDecryption(PostDecryptionResp { + content_text: decoded_text, + }) + .into(), + } +} diff --git a/interface/src/handler/encryption.rs b/interface/src/handler/encryption.rs index 86f8703..4759520 100644 --- a/interface/src/handler/encryption.rs +++ b/interface/src/handler/encryption.rs @@ -32,26 +32,30 @@ pub fn encode(param: PostEncryptionParam) -> MwResponse { match result { Ok(encryption_result) => { // TODO: finish implementation + + let encryption_results = encryption_result + .e2e_result + .unwrap_or_default() + .into_iter() + .map(|(k, v)| { + ( + k, + E2eEncryptionResult { + iv: v.iv_to_be_published, + encrypted_post_key_data: v.encrypted_post_key, + ephemeral_public_key_data: None, + }, + ) + }) + .collect(); + let content = PostEncryptedResp { content: encryption_result.output, - results: encryption_result - .e2e_result - .unwrap_or_default() - .into_iter() - .map(|(k, v)| { - ( - k, - E2eEncryptionResult { - iv: v.iv_to_be_published, - encrypted_post_key_data: v.encrypted_post_key, - ephemeral_public_key_data: None, - }, - ) - }) - .collect(), + results: encryption_results, post_identifier: encryption_result.post_identifier, post_key: encryption_result.post_key, }; + Response::RespPostEncryption(content).into() }