From 397262cb4dd15e92410b518e42b1ac514465b7a2 Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Tue, 2 Aug 2022 14:53:14 +0800 Subject: [PATCH 01/10] style: check code style with clippy and slience the warnings in `payload_encode_v37.rs`, `payload_encode_v38.rs` and `encryption_.rs` --- crypto/src/payload_encode_v37.rs | 1 + crypto/src/payload_encode_v38.rs | 15 +++++++++------ crypto/src/post_encryption.rs | 6 +++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/crypto/src/payload_encode_v37.rs b/crypto/src/payload_encode_v37.rs index 53579de..906fdff 100644 --- a/crypto/src/payload_encode_v37.rs +++ b/crypto/src/payload_encode_v37.rs @@ -2,6 +2,7 @@ use rmp::encode::*; use super::Error; +#[allow(dead_code)] enum Index { Version = 0, AuthorNetwork = 1, diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index bc047fd..4521c80 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -23,6 +23,7 @@ enum Index { AuthorIdentifier = 7, } +#[allow(clippy::too_many_arguments)] pub fn encode_v38( is_public: bool, network: &str, @@ -93,6 +94,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, @@ -253,10 +255,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 +280,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..ceb88b4 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -24,6 +24,7 @@ pub struct EncryptionResultE2E { pub iv_to_be_published: Option>, } +#[derive(Debug)] pub struct EncryptionResult { pub output: String, pub post_key: Vec, @@ -31,6 +32,7 @@ pub struct EncryptionResult { pub e2e_result: Option>, } +#[allow(clippy::too_many_arguments)] pub fn encrypt( version: Version, is_public: bool, @@ -71,7 +73,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 +136,7 @@ mod tests { None, ) .unwrap(); + + println!("{:?}", encryption_result); } } From 4f16a8d2e5cc49ad8a9654fb4cc0e68d8c35c15e Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Tue, 2 Aug 2022 23:57:08 +0800 Subject: [PATCH 02/10] feat: add decode function for v37 decryption --- crypto/Cargo.toml | 3 +- crypto/src/lib.rs | 3 + crypto/src/payload_decode_v37.rs | 178 +++++++++++++++++++++++++++++++ crypto/src/payload_encode_v37.rs | 12 +-- crypto/src/payload_index_v37.rs | 30 ++++++ 5 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 crypto/src/payload_decode_v37.rs create mode 100644 crypto/src/payload_index_v37.rs 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/lib.rs b/crypto/src/lib.rs index 2bbb5d5..1cced3d 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -19,6 +19,9 @@ pub mod post_encryption; pub mod jwk; pub mod pbkdf2; +mod payload_index_v37; +mod payload_decode_v37; + #[derive(Debug, PartialOrd, PartialEq)] pub enum Error { KdfParamsInvalid, diff --git a/crypto/src/payload_decode_v37.rs b/crypto/src/payload_decode_v37.rs new file mode 100644 index 0000000..82be26a --- /dev/null +++ b/crypto/src/payload_decode_v37.rs @@ -0,0 +1,178 @@ +use std::str::from_utf8; + +use super::payload_index_v37::*; +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_encode_v37.rs b/crypto/src/payload_encode_v37.rs index 906fdff..6318356 100644 --- a/crypto/src/payload_encode_v37.rs +++ b/crypto/src/payload_encode_v37.rs @@ -1,18 +1,8 @@ use rmp::encode::*; +use super::payload_index_v37::Index; use super::Error; -#[allow(dead_code)] -enum Index { - Version = 0, - AuthorNetwork = 1, - AuthorID = 2, - AuthorPublicKeyAlgorithm = 3, - AuthorPublicKey = 4, - Encryption = 5, - Data = 6, -} - pub fn encode_with_container( network: &str, author_id: &str, diff --git a/crypto/src/payload_index_v37.rs b/crypto/src/payload_index_v37.rs new file mode 100644 index 0000000..018bf26 --- /dev/null +++ b/crypto/src/payload_index_v37.rs @@ -0,0 +1,30 @@ +use std::convert::TryFrom; + +#[allow(dead_code)] +#[repr(i64)] +#[derive(Debug, PartialEq, Eq)] +pub enum Index { + Version = 0, + AuthorNetwork = 1, + AuthorID = 2, + AuthorPublicKeyAlgorithm = 3, + AuthorPublicKey = 4, + Encryption = 5, + 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(()), + } + } +} From 244886a978a841f2a4cc7718309bf934d6d0872a Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Wed, 3 Aug 2022 16:08:17 +0800 Subject: [PATCH 03/10] feat: add test for implementing decode_v38 --- crypto/src/lib.rs | 2 +- crypto/src/payload_decode_v37.rs | 2 +- crypto/src/payload_decode_v38.rs | 46 +++++++++++++++++++++++++++++ crypto/src/payload_encode_v37.rs | 32 +++++++++++++++++++- crypto/src/payload_encode_v38.rs | 2 +- crypto/src/payload_index_v37.rs | 30 ------------------- interface/src/handler/encryption.rs | 34 +++++++++++---------- 7 files changed, 99 insertions(+), 49 deletions(-) create mode 100644 crypto/src/payload_decode_v38.rs delete mode 100644 crypto/src/payload_index_v37.rs diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 1cced3d..94ab1fc 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -19,8 +19,8 @@ pub mod post_encryption; pub mod jwk; pub mod pbkdf2; -mod payload_index_v37; mod payload_decode_v37; +mod payload_decode_v38; #[derive(Debug, PartialOrd, PartialEq)] pub enum Error { diff --git a/crypto/src/payload_decode_v37.rs b/crypto/src/payload_decode_v37.rs index 82be26a..ec4a797 100644 --- a/crypto/src/payload_decode_v37.rs +++ b/crypto/src/payload_decode_v37.rs @@ -1,6 +1,6 @@ use std::str::from_utf8; -use super::payload_index_v37::*; +use super::payload_encode_v37::Index; use super::Error; use rmp::decode::*; diff --git a/crypto/src/payload_decode_v38.rs b/crypto/src/payload_decode_v38.rs new file mode 100644 index 0000000..510aec9 --- /dev/null +++ b/crypto/src/payload_decode_v38.rs @@ -0,0 +1,46 @@ + +use super::Error; + +fn parse_post_identifier(fomatted: &str) -> Result { + todo!() +} + +#[cfg(test)] +mod tests { + use super::super::number_util::random_iv; + use super::super::Error; + + // use + + const IV_SIZE: usize = 16; + const AES_KEY_SIZE: usize = 32; + + #[test] + fn test_decode_v37() -> Result<(), Error> { + todo!() + } + + #[test] + fn test_decode_post_iv() { + todo!() + } + + #[test] + fn test_get_post_identifier_and_post_iv() { + todo!() + } + + fn test_get_encrypted_message() { + todo!() + } + + fn test_decode_fields() { + todo!() + } + + fn test_base64_decode_encoded_encryped() { + todo!() + } + + +} diff --git a/crypto/src/payload_encode_v37.rs b/crypto/src/payload_encode_v37.rs index 6318356..38527aa 100644 --- a/crypto/src/payload_encode_v37.rs +++ b/crypto/src/payload_encode_v37.rs @@ -1,8 +1,38 @@ +use std::convert::TryFrom; + use rmp::encode::*; -use super::payload_index_v37::Index; use super::Error; +#[allow(dead_code)] +#[repr(i64)] +#[derive(Debug, PartialEq, Eq)] +pub enum Index { + Version = 0, + AuthorNetwork = 1, + AuthorID = 2, + AuthorPublicKeyAlgorithm = 3, + AuthorPublicKey = 4, + Encryption = 5, + 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 4521c80..9c6e062 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -17,7 +17,7 @@ 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, diff --git a/crypto/src/payload_index_v37.rs b/crypto/src/payload_index_v37.rs deleted file mode 100644 index 018bf26..0000000 --- a/crypto/src/payload_index_v37.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::convert::TryFrom; - -#[allow(dead_code)] -#[repr(i64)] -#[derive(Debug, PartialEq, Eq)] -pub enum Index { - Version = 0, - AuthorNetwork = 1, - AuthorID = 2, - AuthorPublicKeyAlgorithm = 3, - AuthorPublicKey = 4, - Encryption = 5, - 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(()), - } - } -} 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() } From 3f86181784858db32a2f8ae48392dc8f3f139da6 Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Thu, 4 Aug 2022 17:25:50 +0800 Subject: [PATCH 04/10] feat: extract and expose `eocode_post_key_and_aes_key` function --- crypto/src/payload_encode_v38.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index 9c6e062..22330cc 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -13,7 +13,7 @@ impl From for Error { use base64::{decode_config, encode_config, STANDARD, URL_SAFE_NO_PAD}; -const SHARED_KEY_ENCODED: &str = "3Bf8BJ3ZPSMUM2jg2ThODeLuRRD_-_iwQEaeLdcQXpg"; +pub(crate) const SHARED_KEY_ENCODED: &str = "3Bf8BJ3ZPSMUM2jg2ThODeLuRRD_-_iwQEaeLdcQXpg"; const E2E_KEY: [u8; 2] = [40, 70]; const E2E_IV: [u8; 1] = [33]; @@ -41,15 +41,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)) } }; @@ -152,7 +150,21 @@ fn encode_fields( Ok(result) } -fn encode_post_key(post_key: &[u8]) -> Vec { +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)) +} + +pub(crate) 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); let result = format!( From f56a6c0bef6f51ca667b247d35e595dd4c0eb2ec Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Thu, 4 Aug 2022 17:26:15 +0800 Subject: [PATCH 05/10] feat: add post_identifier parsing --- crypto/src/payload_decode_v38.rs | 217 +++++++++++++++++++++++++++++-- 1 file changed, 207 insertions(+), 10 deletions(-) diff --git a/crypto/src/payload_decode_v38.rs b/crypto/src/payload_decode_v38.rs index 510aec9..46bf206 100644 --- a/crypto/src/payload_decode_v38.rs +++ b/crypto/src/payload_decode_v38.rs @@ -1,20 +1,156 @@ +use base64::{decode_config, STANDARD}; -use super::Error; +use super::payload_encode_v38::Index; -fn parse_post_identifier(fomatted: &str) -> Result { - todo!() +#[derive(Debug)] +enum ParseError { + InvalidField, + InvalidAuthorPubKey, + InvalidIdentity, + InvalidPostIdentifier, +} + +struct EncrtyptionParam { + is_public: bool, + aes_key_encrypted: String, + encoded_iv: String, + encoded_encrypted: String, + signature: String, + network: Option, + author_id: Option, + author_pub_key: Option>, +} + +fn decode_post_e2e_v38( + post_identifier: &str, + local_key_data: Option<&[u8]>, + author_private_key: Option<&[u8]>, + post_content: &str, +) -> Result<(), ParseError> { + let parsed_fields = parse_fields(post_content)?; + let parsed_enctypted_info = parse_and_decode_fields_to_encrypt_info(parsed_fields)?; + + Ok(()) +} + +fn parse_post_identifier(formated: &str) -> Result<(String, String), ParseError> { + let prefix = "post_iv:"; + + match formated.starts_with(prefix) { + false => Err(ParseError::InvalidPostIdentifier), + true => { + let mut network_and_encode_iv = formated + .split(prefix) + .find(|x| x.contains('/')) + .map(|x| x.split('/')) + .ok_or(ParseError::InvalidField)?; + + let network = network_and_encode_iv + .next() + .ok_or(ParseError::InvalidPostIdentifier)?; + let encode_iv = network_and_encode_iv + .next() + .ok_or(ParseError::InvalidPostIdentifier)?; + + Ok((network.to_owned(), encode_iv.to_owned())) + } + } +} + +fn parse_fields(given_content: &str) -> Result, ParseError> { + let prefix = "\u{1F3BC}4/4"; + if !given_content.starts_with(prefix) { + return Err(ParseError::InvalidField); + } + + if !given_content.ends_with(":||") { + return Err(ParseError::InvalidField); + } + + let compace_fields = given_content.split(":||").next().unwrap_or(""); + if compace_fields.is_empty() { + return Err(ParseError::InvalidField); + } + + let fields = compace_fields.split('|').collect::>(); + if fields.len() != 8 { + return Err(ParseError::InvalidField); + } + + Ok(fields) +} + +fn parse_and_decode_fields_to_encrypt_info( + fields: Vec<&str>, +) -> Result { + if fields[0] != "\u{1F3BC}4/4" { + return Err(ParseError::InvalidField); + } + + let is_public = fields[Index::PublicShared as usize]; + if !["0", "1"].contains(&is_public) { + return Err(ParseError::InvalidField); + } + + 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::InvalidIdentity)?; + let string = + String::from_utf8(decoded_identity).map_err(|_| ParseError::InvalidIdentity)?; + 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::InvalidAuthorPubKey)?; + Option::Some(key) + } + }; + + 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_pub_key: pub_key_serialized, + }) } #[cfg(test)] mod tests { - use super::super::number_util::random_iv; use super::super::Error; + use super::*; + use crate::payload_encode_v38::*; + use base64::URL_SAFE_NO_PAD; // use const IV_SIZE: usize = 16; const AES_KEY_SIZE: usize = 32; + const PUB_KEY_SIZE: usize = 33; + #[test] fn test_decode_v37() -> Result<(), Error> { todo!() @@ -27,20 +163,81 @@ mod tests { #[test] fn test_get_post_identifier_and_post_iv() { - todo!() + let post_identifier = "post_iv:eth/randomiv"; + let (network, iv) = + parse_post_identifier(post_identifier).expect("Invalid post identifier"); + assert_eq!(network, "eth"); + assert_eq!(iv, "randomiv"); } - + #[test] fn test_get_encrypted_message() { todo!() } + #[test] fn test_decode_fields() { - todo!() + let public_fields = + parse_fields("\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|12adad|1|dad:||"); + let privte_fields = + parse_fields("\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|01131|0|dad:||"); + let faild_public_fields = + parse_fields("11dd|13add|1333dad|1318dadss_dad|juudad|1qe1dda|1q|dad:||"); + let faild_fields = + parse_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_ok()); } + #[test] fn test_base64_decode_encoded_encryped() { - todo!() - } + 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 parsed_field = parse_fields(encrypted_result).expect("parse failed"); + let encryped_info = parse_and_decode_fields_to_encrypt_info(parsed_field) + .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_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"); + } } From 2156d8ced0eec3035b836af58ebefb6e1c6daa81 Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Thu, 4 Aug 2022 18:15:00 +0800 Subject: [PATCH 06/10] refactor: rename `parse_fields` to `check_and_parse_fields` --- crypto/src/payload_decode_v38.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/crypto/src/payload_decode_v38.rs b/crypto/src/payload_decode_v38.rs index 46bf206..d633a05 100644 --- a/crypto/src/payload_decode_v38.rs +++ b/crypto/src/payload_decode_v38.rs @@ -27,7 +27,7 @@ fn decode_post_e2e_v38( author_private_key: Option<&[u8]>, post_content: &str, ) -> Result<(), ParseError> { - let parsed_fields = parse_fields(post_content)?; + let parsed_fields = check_and_parse_fields(post_content)?; let parsed_enctypted_info = parse_and_decode_fields_to_encrypt_info(parsed_fields)?; Ok(()) @@ -57,7 +57,7 @@ fn parse_post_identifier(formated: &str) -> Result<(String, String), ParseError> } } -fn parse_fields(given_content: &str) -> Result, ParseError> { +fn check_and_parse_fields(given_content: &str) -> Result, ParseError> { let prefix = "\u{1F3BC}4/4"; if !given_content.starts_with(prefix) { return Err(ParseError::InvalidField); @@ -176,14 +176,17 @@ mod tests { #[test] fn test_decode_fields() { - let public_fields = - parse_fields("\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|12adad|1|dad:||"); - let privte_fields = - parse_fields("\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|01131|0|dad:||"); + let public_fields = check_and_parse_fields( + "\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|12adad|1|dad:||", + ); + let privte_fields = check_and_parse_fields( + "\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|01131|0|dad:||", + ); let faild_public_fields = - parse_fields("11dd|13add|1333dad|1318dadss_dad|juudad|1qe1dda|1q|dad:||"); - let faild_fields = - parse_fields("\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|1qe1dda|1q|dad:||"); + check_and_parse_fields("11dd|13add|1333dad|1318dadss_dad|juudad|1qe1dda|1q|dad:||"); + let faild_fields = check_and_parse_fields( + "\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|1qe1dda|1q|dad:||", + ); assert!(public_fields.is_ok()); assert!(privte_fields.is_ok()); @@ -195,7 +198,7 @@ mod tests { fn test_base64_decode_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 parsed_field = parse_fields(encrypted_result).expect("parse failed"); + let parsed_field = check_and_parse_fields(encrypted_result).expect("parse failed"); let encryped_info = parse_and_decode_fields_to_encrypt_info(parsed_field) .expect("parse_fields_to_encrypt_info failed"); From 01915c36b8807da556107dfe6350ec5975a2a45d Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Thu, 4 Aug 2022 18:19:32 +0800 Subject: [PATCH 07/10] refactor: remove unnecessary pub function --- crypto/src/payload_encode_v38.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index 22330cc..61c6e88 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -150,7 +150,7 @@ fn encode_fields( Ok(result) } -pub(crate) fn eocode_post_key_and_aes_key( +fn eocode_post_key_and_aes_key( local_key_data: Option<&[u8]>, iv: &[u8], key: &[u8], @@ -164,7 +164,7 @@ pub(crate) fn eocode_post_key_and_aes_key( Ok((post_key_encoded, owners_aes_key_encrypted_string)) } -pub(crate) fn encode_post_key(post_key: &[u8]) -> Vec { +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); let result = format!( From 40ce3dc38c0cffd2f6e114db4bd24564f55481c6 Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Fri, 5 Aug 2022 11:34:09 +0800 Subject: [PATCH 08/10] feat: move `SHARED_KEY_ENCODED`, `IV_SIZE`, `AES_KEY_SIZE` to `encryption_constants.rs` --- crypto/src/encryption_constants.rs | 3 +++ crypto/src/lib.rs | 1 + crypto/src/post_encryption.rs | 4 +--- 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 crypto/src/encryption_constants.rs 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 94ab1fc..b2b1f97 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -19,6 +19,7 @@ pub mod post_encryption; pub mod jwk; pub mod pbkdf2; +mod encryption_constants; mod payload_decode_v37; mod payload_decode_v38; diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index ceb88b4..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, From ff395ab91f4b4bdfd27b2c3264c5cbe19b94a30e Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Fri, 5 Aug 2022 15:12:43 +0800 Subject: [PATCH 09/10] feat: add decoding for post_key --- crypto/src/payload_decode_v38.rs | 292 +++++++++++++++++++++---------- crypto/src/payload_encode_v38.rs | 11 +- 2 files changed, 210 insertions(+), 93 deletions(-) diff --git a/crypto/src/payload_decode_v38.rs b/crypto/src/payload_decode_v38.rs index d633a05..1c29de3 100644 --- a/crypto/src/payload_decode_v38.rs +++ b/crypto/src/payload_decode_v38.rs @@ -1,13 +1,21 @@ -use base64::{decode_config, STANDARD}; +use base64::{decode_config, STANDARD, URL_SAFE_NO_PAD}; +use serde::{Deserialize, Serialize}; -use super::payload_encode_v38::Index; +use crate::aes_gcm::aes_decrypt; +use crate::encryption_constants::SHARED_KEY_ENCODED; +use crate::payload_encode_v38::Index; +#[allow(dead_code)] #[derive(Debug)] enum ParseError { - InvalidField, - InvalidAuthorPubKey, - InvalidIdentity, - InvalidPostIdentifier, + Fields, + AuthorPubKey, + Identity, + PostIdentifier, + AesKey, + PostKey, + PostMessage, + LocalKeyIsNil, } struct EncrtyptionParam { @@ -18,79 +26,164 @@ struct EncrtyptionParam { signature: String, network: Option, author_id: Option, - author_pub_key: Option>, + author_serialized_pub_key: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +struct JWKFormtaObject { + alg: String, + ext: bool, + k: String, + key_ops: Vec, + kty: String, } fn decode_post_e2e_v38( - post_identifier: &str, + post_identifier: Option<&str>, local_key_data: Option<&[u8]>, author_private_key: Option<&[u8]>, post_content: &str, -) -> Result<(), ParseError> { - let parsed_fields = check_and_parse_fields(post_content)?; - let parsed_enctypted_info = parse_and_decode_fields_to_encrypt_info(parsed_fields)?; +) -> 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)?; + 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 { + true => decode_aes_key_encrypted_with_local_key( + &parsed_enctypted_info.aes_key_encrypted, + local_key_data, + &decoded_iv, + )?, + false => decode_aes_key_encrypted(&decoded_iv, &parsed_enctypted_info.aes_key_encrypted) + .map_err(|_| ParseError::PostKey)?, + }; - Ok(()) + 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 parse_post_identifier(formated: &str) -> Result<(String, String), ParseError> { +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 formated.starts_with(prefix) { - false => Err(ParseError::InvalidPostIdentifier), - true => { - let mut network_and_encode_iv = formated - .split(prefix) - .find(|x| x.contains('/')) - .map(|x| x.split('/')) - .ok_or(ParseError::InvalidField)?; - - let network = network_and_encode_iv - .next() - .ok_or(ParseError::InvalidPostIdentifier)?; - let encode_iv = network_and_encode_iv - .next() - .ok_or(ParseError::InvalidPostIdentifier)?; - - Ok((network.to_owned(), encode_iv.to_owned())) - } + 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 check_and_parse_fields(given_content: &str) -> Result, ParseError> { +fn parse_post_fields(given_content: &str) -> Result, ParseError> { let prefix = "\u{1F3BC}4/4"; if !given_content.starts_with(prefix) { - return Err(ParseError::InvalidField); + return Err(ParseError::Fields); } if !given_content.ends_with(":||") { - return Err(ParseError::InvalidField); + return Err(ParseError::Fields); } let compace_fields = given_content.split(":||").next().unwrap_or(""); if compace_fields.is_empty() { - return Err(ParseError::InvalidField); + return Err(ParseError::Fields); } let fields = compace_fields.split('|').collect::>(); - if fields.len() != 8 { - return Err(ParseError::InvalidField); + 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( - fields: Vec<&str>, + given_content: &str, ) -> Result { - if fields[0] != "\u{1F3BC}4/4" { - return Err(ParseError::InvalidField); - } - - let is_public = fields[Index::PublicShared as usize]; - if !["0", "1"].contains(&is_public) { - return Err(ParseError::InvalidField); - } + let fields = parse_post_fields(given_content)?; let aes_key_encrypted = fields[1].to_owned(); @@ -100,9 +193,8 @@ fn parse_and_decode_fields_to_encrypt_info( true => (Option::None, Option::None), false => { let decoded_identity = - decode_config(identity, STANDARD).map_err(|_| ParseError::InvalidIdentity)?; - let string = - String::from_utf8(decoded_identity).map_err(|_| ParseError::InvalidIdentity)?; + 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(); @@ -119,12 +211,13 @@ fn parse_and_decode_fields_to_encrypt_info( let pub_key_serialized = match pub_key.is_empty() { true => Option::None, false => { - let key = - decode_config(pub_key, STANDARD).map_err(|_| ParseError::InvalidAuthorPubKey)?; + 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, @@ -133,73 +226,96 @@ fn parse_and_decode_fields_to_encrypt_info( signature: fields[4].to_owned(), network, author_id, - author_pub_key: pub_key_serialized, + author_serialized_pub_key: pub_key_serialized, }) } #[cfg(test)] mod tests { - use super::super::Error; - use super::*; - use crate::payload_encode_v38::*; - use base64::URL_SAFE_NO_PAD; + use base64::encode_config; - // use + use super::*; - const IV_SIZE: usize = 16; - const AES_KEY_SIZE: usize = 32; + use crate::encryption_constants::{AES_KEY_SIZE, IV_SIZE}; + use crate::number_util::random_iv; + use crate::payload_encode_v38::{encode_aes_key_encrypted, eocode_post_key_and_aes_key}; const PUB_KEY_SIZE: usize = 33; - #[test] - fn test_decode_v37() -> Result<(), Error> { - todo!() - } + // #[test] + // fn test_get_encrypted_message() { + // todo!() + // } #[test] - fn test_decode_post_iv() { - todo!() + 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_get_post_identifier_and_post_iv() { - let post_identifier = "post_iv:eth/randomiv"; - let (network, iv) = - parse_post_identifier(post_identifier).expect("Invalid post identifier"); - assert_eq!(network, "eth"); - assert_eq!(iv, "randomiv"); + 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_get_encrypted_message() { - todo!() + 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 = check_and_parse_fields( - "\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|12adad|1|dad:||", - ); - let privte_fields = check_and_parse_fields( - "\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|01131|0|dad:||", - ); + 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 = - check_and_parse_fields("11dd|13add|1333dad|1318dadss_dad|juudad|1qe1dda|1q|dad:||"); - let faild_fields = check_and_parse_fields( - "\u{1F3BC}4/4|13add|1333dad|1318dadss_dad|juudad|1qe1dda|1q|dad:||", - ); + 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_ok()); + assert!(faild_fields.is_err()); } #[test] - fn test_base64_decode_encoded_encryped() { + 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 parsed_field = check_and_parse_fields(encrypted_result).expect("parse failed"); - let encryped_info = parse_and_decode_fields_to_encrypt_info(parsed_field) + let encryped_info = parse_and_decode_fields_to_encrypt_info(encrypted_result) .expect("parse_fields_to_encrypt_info failed"); let network = "twitter.com"; @@ -226,7 +342,7 @@ mod tests { // skip flag u8 assert_eq!( - encryped_info.author_pub_key.unwrap()[1..], + encryped_info.author_serialized_pub_key.unwrap()[1..], author_public_key[1..PUB_KEY_SIZE] ); diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index 61c6e88..188409b 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -5,15 +5,16 @@ 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}; - -pub(crate) const SHARED_KEY_ENCODED: &str = "3Bf8BJ3ZPSMUM2jg2ThODeLuRRD_-_iwQEaeLdcQXpg"; const E2E_KEY: [u8; 2] = [40, 70]; const E2E_IV: [u8; 1] = [33]; @@ -68,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!( @@ -150,7 +151,7 @@ fn encode_fields( Ok(result) } -fn eocode_post_key_and_aes_key( +pub(crate) fn eocode_post_key_and_aes_key( local_key_data: Option<&[u8]>, iv: &[u8], key: &[u8], From d7d5847e86727a4e1d056664c0010a82534e764b Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Thu, 11 Aug 2022 15:26:49 +0800 Subject: [PATCH 10/10] feat: add `decrypt_post` function to decrypt public post --- chain-common/proto/api.proto | 5 +++ chain-common/proto/post-decryption.proto | 15 ++++++++ chain-common/src/generated/api.rs | 22 ++++++++++- crypto/src/lib.rs | 7 +++- crypto/src/payload_decode_v38.rs | 49 ++++++++++++++++-------- interface/src/handler.rs | 3 ++ interface/src/handler/decryption.rs | 21 ++++++++++ 7 files changed, 104 insertions(+), 18 deletions(-) create mode 100644 chain-common/proto/post-decryption.proto create mode 100644 interface/src/handler/decryption.rs 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/src/lib.rs b/crypto/src/lib.rs index b2b1f97..0973347 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -21,7 +21,7 @@ pub mod pbkdf2; mod encryption_constants; mod payload_decode_v37; -mod payload_decode_v38; +pub mod payload_decode_v38; #[derive(Debug, PartialOrd, PartialEq)] pub enum Error { @@ -52,6 +52,8 @@ pub enum Error { NotSupportedCipher, InvalidLocalKey, + + DecryptContentFailed, } impl Error { @@ -71,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(), } } @@ -92,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_v38.rs b/crypto/src/payload_decode_v38.rs index 1c29de3..d9b3067 100644 --- a/crypto/src/payload_decode_v38.rs +++ b/crypto/src/payload_decode_v38.rs @@ -2,12 +2,12 @@ use base64::{decode_config, STANDARD, URL_SAFE_NO_PAD}; use serde::{Deserialize, Serialize}; use crate::aes_gcm::aes_decrypt; -use crate::encryption_constants::SHARED_KEY_ENCODED; +use crate::encryption_constants::{IV_SIZE, SHARED_KEY_ENCODED}; use crate::payload_encode_v38::Index; #[allow(dead_code)] #[derive(Debug)] -enum ParseError { +pub enum ParseError { Fields, AuthorPubKey, Identity, @@ -16,8 +16,11 @@ enum ParseError { PostKey, PostMessage, LocalKeyIsNil, + IvIsInvalid, } +#[allow(dead_code)] +#[derive(Debug)] struct EncrtyptionParam { is_public: bool, aes_key_encrypted: String, @@ -30,7 +33,7 @@ struct EncrtyptionParam { } #[derive(Debug, Serialize, Deserialize)] -struct JWKFormtaObject { +struct JWKFormatObject { alg: String, ext: bool, k: String, @@ -38,10 +41,9 @@ struct JWKFormtaObject { kty: String, } -fn decode_post_e2e_v38( +pub fn decode_payload_v38( post_identifier: Option<&str>, local_key_data: Option<&[u8]>, - author_private_key: Option<&[u8]>, post_content: &str, ) -> Result { let parsed_enctypted_info = parse_and_decode_fields_to_encrypt_info(post_content)?; @@ -50,6 +52,10 @@ fn decode_post_e2e_v38( 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 => {} @@ -62,12 +68,12 @@ fn decode_post_e2e_v38( // use decoded_iv and post_key to decrypt text content let post_key = match parsed_enctypted_info.is_public { - true => decode_aes_key_encrypted_with_local_key( + false => decode_aes_key_encrypted_with_local_key( &parsed_enctypted_info.aes_key_encrypted, local_key_data, &decoded_iv, )?, - false => decode_aes_key_encrypted(&decoded_iv, &parsed_enctypted_info.aes_key_encrypted) + true => decode_aes_key_encrypted(&decoded_iv, &parsed_enctypted_info.aes_key_encrypted) .map_err(|_| ParseError::PostKey)?, }; @@ -93,7 +99,7 @@ fn decode_aes_key_encrypted(iv: &[u8], encrypted_ase_key: &str) -> Result(&decrypted_key); + let ab = serde_json::from_slice::(&decrypted_key); match ab { Ok(decrypted_object) => { let encoded_aes_key = decrypted_object.k; @@ -115,7 +121,7 @@ fn decode_aes_key_encrypted_with_local_key( 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) + 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) @@ -236,17 +242,12 @@ mod tests { use super::*; - use crate::encryption_constants::{AES_KEY_SIZE, IV_SIZE}; 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_encrypted_message() { - // todo!() - // } - #[test] fn test_get_post_identifier_and_post_iv() { let post_iv = random_iv(IV_SIZE); @@ -359,4 +360,22 @@ mod tests { 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/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(), + } +}