From 48eea0183449bb33cb9511dbe2ccae0bca4aee71 Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Fri, 20 May 2022 10:59:35 +0800 Subject: [PATCH 01/12] feat: add post encryption --- crypto/src/post_encryption.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 crypto/src/post_encryption.rs diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs new file mode 100644 index 0000000..ab14a28 --- /dev/null +++ b/crypto/src/post_encryption.rs @@ -0,0 +1,28 @@ +use getrandom::getrandom; + +pub fn sderive_key(password: &[u8], salt: &[u8], iterations: u32) -> Vec { + let mut res = vec![0u8; 32]; + pbkdf2::pbkdf2::>(password, salt, iterations, &mut res); + res +} + +#[cfg(test)] +mod tests { + use crate::pbkdf2::derive_key; + + use base64::{encode_config, URL_SAFE_NO_PAD}; + + #[test] + fn test_pbkdf2() { + let test_salt = "brother lemon plate blame sing donate wagon gospel level play brown cave"; + let password = concat!( + "pPBO3vuCUjpEkPAc-74-CByna75Y2vvVsj_riqxljkk", + "qtSrDPyn8eOEZ1-Zr5yWNBqhJZTNW5A43ypKx--CLrM" + ); + + let derived_key = derive_key(password.as_bytes(), test_salt.as_bytes(), 100_000); + let base64_url_config = URL_SAFE_NO_PAD; + let d = encode_config(&derived_key, base64_url_config); + assert_eq!(d, "zG3NAz9demftsEMpiCBNr9TgEWvBFy3LV8R5BMycVZ8"); + } +} From 65d5b1ee573cd47cd6d6e2092d13e13539e40196 Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Sat, 21 May 2022 17:08:59 +0800 Subject: [PATCH 02/12] feat: add aec-gcm for post encryption --- chain-common/proto/post-encryption.proto | 11 ++ crypto/Cargo.toml | 4 +- crypto/src/lib.rs | 1 + crypto/src/post_encryption.rs | 133 ++++++++++++++++++++--- 4 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 chain-common/proto/post-encryption.proto diff --git a/chain-common/proto/post-encryption.proto b/chain-common/proto/post-encryption.proto new file mode 100644 index 0000000..581029c --- /dev/null +++ b/chain-common/proto/post-encryption.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package api; + +import "base.proto"; + +message PostEncryptionParam { + string content = 1; + string network = 2; + string authorKey = 3; +} \ No newline at end of file diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 62c5bde..90b7728 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -23,4 +23,6 @@ serde_json = "1.0" base64 = "0.13.0" pbkdf2 = { version = "0.11", default-features = false } hmac = { version = "0.12.1" } -ctr = { version = "0.9.1" } \ No newline at end of file +ctr = { version = "0.9.1" } +aes-gcm = { version = "0.9.4" } +rmp-serde = { version = "1.1.0" } \ No newline at end of file diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 9c8fcbb..8b4ba7d 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -11,6 +11,7 @@ pub mod bip32; pub mod bip39; pub mod number_util; +pub mod post_encryption; pub mod jwk; pub mod pbkdf2; diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index ab14a28..563dc16 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -1,28 +1,125 @@ -use getrandom::getrandom; +use super::number_util::random_iv; +use super::Error; -pub fn sderive_key(password: &[u8], salt: &[u8], iterations: u32) -> Vec { - let mut res = vec![0u8; 32]; - pbkdf2::pbkdf2::>(password, salt, iterations, &mut res); - res +use aes_gcm::aead::{Aead, NewAead}; +use aes_gcm::aes::{cipher::consts::U16, Aes256}; +use aes_gcm::{AesGcm, Key, Nonce}; + +use rmp_serde as rmps; +use rmps::{Deserializer, Serializer}; +use serde::{Deserialize, Serialize}; + +use std::str; + +type Aes256GCM = AesGcm; +pub struct EncryptOptions {} + +trait Encrypting { + fn encrypt(&self, iv: &[u8], message: &str) -> Result; + fn decrypt(&self, iv: &[u8], encrypted_message: EncryptedMessage) -> Result, Error>; +} + +struct EncryptedMessage { + cipher: Vec, + key_slice: Vec, +} + +struct TestKeyGenerator; + +impl Encrypting for TestKeyGenerator { + fn encrypt(&self, iv: &[u8], message: &str) -> Result { + let random_key_slice = random_iv(32); + // key size must use u32 + let key = Key::from_slice(&random_key_slice); + + let nonce = Nonce::from_slice(iv); + let cipher = Aes256GCM::new(key); + + cipher + .encrypt(nonce, message.as_bytes()) + .map_err(|_| Error::InvalidCiphertext) + .map(|result| EncryptedMessage { + cipher: result, + key_slice: random_key_slice, + }) + } + + fn decrypt(&self, iv: &[u8], encrypted_message: EncryptedMessage) -> Result, Error> { + let random_key_slice = encrypted_message.key_slice; + let key = Key::from_slice(&random_key_slice); + let nonce = Nonce::from_slice(iv); + let cipher = Aes256GCM::new(key); + + let verbs = encrypted_message.cipher; + cipher + .decrypt(nonce, verbs.as_ref()) + .map_err(|_| Error::InvalidCiphertext) + } +} + +fn encrypt(content: String, version: u8) -> String { + // iv + let post_iv = random_iv(16); + + // post_key + // let key = Key::from_slice(b"131"); + // let cipher = Aes256Gcm::generate_key(&post_iv); + // let nonce = Nonce::from_slice(&post_iv); + // let cipher_text = cipher.encrypt(nonce, b"sample text".as_ref()); + + "".to_string() } #[cfg(test)] mod tests { - use crate::pbkdf2::derive_key; + use super::*; - use base64::{encode_config, URL_SAFE_NO_PAD}; + const IV: [u8; 16] = [ + 150, 13, 224, 121, 241, 237, 66, 179, 38, 88, 203, 177, 192, 239, 197, + 189, + // 150, 13, 224, 121, 241, 237, 66, 179, 38, 88, 203, 177, 192, 239, 197, 189, + ]; + // content text: "sample text" + // js code will be [115, 97, 109, 112, 108, 101, 32, 116, 101, 120, 116] + const MESSAGE: [u8; 12] = [171, 115, 97, 109, 112, 108, 101, 32, 116, 101, 120, 116]; + + // const AESRESULT: [u8; 27] = [ + // 111, 240, 132, 248, 234, 237, 4, 148, 98, 135, 219, 174, 16, 118, 48, 212, 157, 202, 116, + // 11, 38, 156, 158, 167, 185, 64, 29, + // ]; #[test] - fn test_pbkdf2() { - let test_salt = "brother lemon plate blame sing donate wagon gospel level play brown cave"; - let password = concat!( - "pPBO3vuCUjpEkPAc-74-CByna75Y2vvVsj_riqxljkk", - "qtSrDPyn8eOEZ1-Zr5yWNBqhJZTNW5A43ypKx--CLrM" - ); - - let derived_key = derive_key(password.as_bytes(), test_salt.as_bytes(), 100_000); - let base64_url_config = URL_SAFE_NO_PAD; - let d = encode_config(&derived_key, base64_url_config); - assert_eq!(d, "zG3NAz9demftsEMpiCBNr9TgEWvBFy3LV8R5BMycVZ8"); + fn test_encryption() { + let post_iv = Vec::from(IV); + let generator = TestKeyGenerator; + let result = generator.encrypt(&post_iv, "sample text").unwrap(); + println!("{:?}", result.cipher); + + // Type Message + let mut buf: Vec = Vec::new(); + let mut encoder = Serializer::new(&mut buf); + let _ = "sample text".serialize(&mut encoder); + assert_eq!(&buf[..], &MESSAGE); + + // let result = rmps::to_vec_named(&(0, "sample text")).unwrap(); + // let decoded: (u8, &str) = rmps::from_slice(&result).unwrap(); + + let text: &str = rmps::from_slice(&MESSAGE).unwrap(); + println!("{:?}", text); + + #[derive(Debug, Serialize, Deserialize)] + struct TestModle { + number: u64, + test: String, + } + + let mut model_buf = Vec::new(); + let mut model_encoder = Serializer::new(&mut model_buf); + let model = TestModle { + number: 0, + test: "safe".to_owned(), + }; + let _ = model.serialize(&mut model_encoder); + // let (number, string): Result<(u8, String), Error> = rmps::from_slice(&buf); } } From 023931169ccaf44a9bb9ffaead596c5d7da3c12c Mon Sep 17 00:00:00 2001 From: jk234ert Date: Mon, 23 May 2022 15:36:26 +0800 Subject: [PATCH 03/12] feat: draft v37 public encryption --- crypto/Cargo.toml | 2 +- crypto/src/post_encryption.rs | 290 ++++++++++++++++++++++++---------- 2 files changed, 204 insertions(+), 88 deletions(-) diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 90b7728..212146e 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -25,4 +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-serde = { version = "1.1.0" } \ No newline at end of file +rmp = { version = "0.8.1" } \ No newline at end of file diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index 563dc16..a8b9641 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -5,121 +5,237 @@ use aes_gcm::aead::{Aead, NewAead}; use aes_gcm::aes::{cipher::consts::U16, Aes256}; use aes_gcm::{AesGcm, Key, Nonce}; -use rmp_serde as rmps; -use rmps::{Deserializer, Serializer}; -use serde::{Deserialize, Serialize}; +use rmp::encode::*; use std::str; type Aes256GCM = AesGcm; -pub struct EncryptOptions {} -trait Encrypting { - fn encrypt(&self, iv: &[u8], message: &str) -> Result; - fn decrypt(&self, iv: &[u8], encrypted_message: EncryptedMessage) -> Result, Error>; +const IV_SIZE: usize = 16; +const AES_KEY_SIZE: usize = 32; + +enum Index { + Version = 0, + AuthorNetwork = 1, + AuthorID = 2, + AuthorPublicKeyAlgorithm = 3, + AuthorPublicKey = 4, + Encryption = 5, + Data = 6, } -struct EncryptedMessage { - cipher: Vec, - key_slice: Vec, +#[derive(Debug)] +struct Payload { + author_network: String, + author_id: String, + author_public_key_algorithm: u8, + author_public_key: Vec, + encryption: Vec>, + data: Vec, } -struct TestKeyGenerator; - -impl Encrypting for TestKeyGenerator { - fn encrypt(&self, iv: &[u8], message: &str) -> Result { - let random_key_slice = random_iv(32); - // key size must use u32 - let key = Key::from_slice(&random_key_slice); - - let nonce = Nonce::from_slice(iv); - let cipher = Aes256GCM::new(key); - - cipher - .encrypt(nonce, message.as_bytes()) - .map_err(|_| Error::InvalidCiphertext) - .map(|result| EncryptedMessage { - cipher: result, - key_slice: random_key_slice, - }) +impl Default for Payload { + fn default() -> Self { + Payload { + author_network: String::new(), + author_id: String::new(), + author_public_key_algorithm: 0, + author_public_key: Vec::new(), + encryption: Vec::new(), + data: Vec::new(), + } } +} - fn decrypt(&self, iv: &[u8], encrypted_message: EncryptedMessage) -> Result, Error> { - let random_key_slice = encrypted_message.key_slice; - let key = Key::from_slice(&random_key_slice); - let nonce = Nonce::from_slice(iv); - let cipher = Aes256GCM::new(key); +pub fn encrypt( + network: &str, + authorId: &str, + algr: u8, + author_pub_key: &[u8], + message: &[u8], +) -> Result, Error> { + let post_iv = random_iv(IV_SIZE); + let post_key_iv = random_iv(AES_KEY_SIZE); + + let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &message)?; + let output = encode_with_container( + network, + authorId, + algr, + &author_pub_key, + &post_key_iv, + &post_iv, + &encrypted_message, + ) + .map_err(|_| Error::InvalidCiphertext)?; + Ok(output) +} - let verbs = encrypted_message.cipher; - cipher - .decrypt(nonce, verbs.as_ref()) - .map_err(|_| Error::InvalidCiphertext) - } +fn aes_encrypt(iv: &[u8], key: &[u8], content: &[u8]) -> Result, Error> { + let key = Key::from_slice(&key); + let nonce = Nonce::from_slice(&iv); + let cipher = Aes256GCM::new(key); + cipher + .encrypt(nonce, content) + .map_err(|_| Error::InvalidCiphertext) } -fn encrypt(content: String, version: u8) -> String { - // iv - let post_iv = random_iv(16); +fn aes_decrypt(iv: &[u8], key: &[u8], encrypted_content: &[u8]) -> Result, Error> { + let nonce = Nonce::from_slice(iv); + let key = Key::from_slice(&key); + let cipher = Aes256GCM::new(key); - // post_key - // let key = Key::from_slice(b"131"); - // let cipher = Aes256Gcm::generate_key(&post_iv); - // let nonce = Nonce::from_slice(&post_iv); - // let cipher_text = cipher.encrypt(nonce, b"sample text".as_ref()); + cipher + .decrypt(nonce, encrypted_content.as_ref()) + .map_err(|_| Error::InvalidCiphertext) +} - "".to_string() +fn encode_with_container( + network: &str, + authorId: &str, + algr: u8, + author_pub_key: &[u8], + aes_key: &[u8], + iv: &[u8], + encrypted: &[u8], +) -> Result, Error> { + let encoded_without_container = encode_v37( + &network, + &authorId, + algr, + &author_pub_key, + &aes_key, + &iv, + &encrypted, + ) + .map_err(|_| Error::InvalidCiphertext)?; + let mut buf = Vec::new(); + write_map_len(&mut buf, 2).map_err(|_| Error::InvalidCiphertext)?; + write_sint(&mut buf, 0).map_err(|_| Error::InvalidCiphertext)?; + write_bin(&mut buf, &encoded_without_container).map_err(|_| Error::InvalidCiphertext)?; + Ok(buf) +} + +fn encode_v37( + network: &str, + authorId: &str, + algr: u8, + author_pub_key: &[u8], + aes_key: &[u8], + iv: &[u8], + encrypted: &[u8], +) -> Result, Error> { + let mut buf = Vec::new(); + write_map_len(&mut buf, 6).map_err(|_| Error::InvalidCiphertext)?; + + write_sint(&mut buf, Index::AuthorNetwork as i64).map_err(|_| Error::InvalidCiphertext)?; + write_str(&mut buf, &network).map_err(|_| Error::InvalidCiphertext)?; + + write_sint(&mut buf, Index::AuthorID as i64).map_err(|_| Error::InvalidCiphertext)?; + write_str(&mut buf, &authorId).map_err(|_| Error::InvalidCiphertext)?; + + write_sint(&mut buf, Index::AuthorPublicKeyAlgorithm as i64) + .map_err(|_| Error::InvalidCiphertext)?; + write_sint(&mut buf, algr as i64).map_err(|_| Error::InvalidCiphertext)?; + + write_sint(&mut buf, Index::AuthorPublicKey as i64).map_err(|_| Error::InvalidCiphertext)?; + write_bin(&mut buf, &author_pub_key); + + write_sint(&mut buf, Index::Encryption as i64).map_err(|_| Error::InvalidCiphertext)?; + write_array_len(&mut buf, 3).map_err(|_| Error::InvalidCiphertext)?; + write_sint(&mut buf, 0).map_err(|_| Error::InvalidCiphertext)?; + write_bin(&mut buf, &aes_key); + write_bin(&mut buf, &iv); + + write_sint(&mut buf, Index::Data as i64).map_err(|_| Error::InvalidCiphertext)?; + write_bin(&mut buf, &encrypted).map_err(|_| Error::InvalidCiphertext)?; + + Ok(buf.to_vec()) } #[cfg(test)] mod tests { use super::*; + use rmp::encode::*; const IV: [u8; 16] = [ - 150, 13, 224, 121, 241, 237, 66, 179, 38, 88, 203, 177, 192, 239, 197, - 189, - // 150, 13, 224, 121, 241, 237, 66, 179, 38, 88, 203, 177, 192, 239, 197, 189, + 150, 13, 224, 121, 241, 237, 66, 179, 38, 88, 203, 177, 192, 239, 197, 189, ]; // content text: "sample text" - // js code will be [115, 97, 109, 112, 108, 101, 32, 116, 101, 120, 116] - const MESSAGE: [u8; 12] = [171, 115, 97, 109, 112, 108, 101, 32, 116, 101, 120, 116]; + const ENCODED_MESSAGE: [u8; 18] = [ + 146, 0, 148, 1, 1, 192, 171, 115, 97, 109, 112, 108, 101, 32, 116, 101, 120, 116, + ]; - // const AESRESULT: [u8; 27] = [ - // 111, 240, 132, 248, 234, 237, 4, 148, 98, 135, 219, 174, 16, 118, 48, 212, 157, 202, 116, - // 11, 38, 156, 158, 167, 185, 64, 29, - // ]; + const RANDOM_IV1: [u8; 16] = [ + 103, 255, 64, 75, 77, 251, 1, 164, 34, 237, 4, 16, 126, 175, 142, 35, + ]; + const RANDOM_IV2: [u8; 16] = [ + 150, 164, 124, 165, 4, 65, 142, 140, 96, 64, 241, 15, 128, 231, 32, 186, + ]; #[test] - fn test_encryption() { - let post_iv = Vec::from(IV); - let generator = TestKeyGenerator; - let result = generator.encrypt(&post_iv, "sample text").unwrap(); - println!("{:?}", result.cipher); - - // Type Message - let mut buf: Vec = Vec::new(); - let mut encoder = Serializer::new(&mut buf); - let _ = "sample text".serialize(&mut encoder); - assert_eq!(&buf[..], &MESSAGE); - - // let result = rmps::to_vec_named(&(0, "sample text")).unwrap(); - // let decoded: (u8, &str) = rmps::from_slice(&result).unwrap(); - - let text: &str = rmps::from_slice(&MESSAGE).unwrap(); - println!("{:?}", text); - - #[derive(Debug, Serialize, Deserialize)] - struct TestModle { - number: u64, - test: String, - } + fn test_encoding() { + let mut buf = Vec::new(); + write_array_len(&mut buf, 2).unwrap(); + write_sint(&mut buf, 0).unwrap(); + write_array_len(&mut buf, 4).unwrap(); + write_sint(&mut buf, 1).unwrap(); + write_sint(&mut buf, 1).unwrap(); + write_nil(&mut buf).unwrap(); + write_str(&mut buf, &"sample text").unwrap(); + println!("{:?}", &buf[..]); + assert_eq!(&buf[..], &ENCODED_MESSAGE); + } - let mut model_buf = Vec::new(); - let mut model_encoder = Serializer::new(&mut model_buf); - let model = TestModle { - number: 0, - test: "safe".to_owned(), - }; - let _ = model.serialize(&mut model_encoder); - // let (number, string): Result<(u8, String), Error> = rmps::from_slice(&buf); + #[test] + fn test_aes_encrypt() { + let iv: [u8; 16] = [1; 16]; + let key: [u8; 32] = [2; 32]; + let content = "sample text"; + let encrypted = aes_encrypt(&iv, &key, &content.as_bytes()).unwrap(); + let decrypted = aes_decrypt(&iv, &key, &encrypted).unwrap(); + assert_eq!(decrypted, content.as_bytes()); + } + + #[test] + fn test_encode_v37() { + let post_iv = random_iv(IV_SIZE); + let post_key_iv = random_iv(AES_KEY_SIZE); + let author_key = random_iv(33); + let content = "sample text"; + + let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &content.as_bytes()).unwrap(); + let message = "hello world"; + let network = "localhost"; + let authorId = "alice"; + let algr = 2; + let encode_with_no_sign = encode_with_container( + &network, + &authorId, + algr, + &author_key, + &post_key_iv, + &post_iv, + &encrypted_message, + ) + .unwrap(); + assert_eq!(&encode_with_no_sign, "1".as_bytes()); + } + + #[test] + fn test_encrypt() { + let post_iv = random_iv(IV_SIZE); + let post_key_iv = random_iv(AES_KEY_SIZE); + let author_key = random_iv(33); + let content = "sample text"; + + let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &content.as_bytes()).unwrap(); + let message = "hello world"; + let network = "localhost"; + let authorId = "alice"; + let algr = 2; + let output = encrypt(&network, &authorId, algr, &author_key, &content.as_bytes()).unwrap(); + + assert_eq!(&output, "1".as_bytes()); } } From 6bb2adaa4b954d391289feda76ddb407e8928475 Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Mon, 23 May 2022 11:28:42 +0800 Subject: [PATCH 04/12] feat: add proto and interface support for post encrtption --- chain-common/proto/api.proto | 5 ++ chain-common/proto/base.proto | 5 ++ chain-common/proto/persona.proto | 4 - chain-common/proto/post-encryption.proto | 9 ++ chain-common/src/convert.rs | 18 +++- chain-common/src/generated/api.rs | 49 +++++++--- crypto/Cargo.toml | 3 +- crypto/src/post_encryption.rs | 109 +++++++++++------------ interface/src/handler.rs | 3 + interface/src/handler/encryption.rs | 27 ++++++ interface/src/handler/persona.rs | 2 +- 11 files changed, 157 insertions(+), 77 deletions(-) create mode 100644 interface/src/handler/encryption.rs diff --git a/chain-common/proto/api.proto b/chain-common/proto/api.proto index 0b74a67..ea8a067 100644 --- a/chain-common/proto/api.proto +++ b/chain-common/proto/api.proto @@ -9,6 +9,7 @@ import "stored-key.proto"; import "transaction.proto"; import "validation.proto"; import "persona.proto"; +import "post-encryption.proto"; message MWRequest { oneof request { @@ -36,6 +37,8 @@ message MWRequest { GenerateMnemonicParam param_generate_mnemonic = 25; PersonaGenerationParam param_generate_persona = 26; + + PostEncryptionParam param_post_encryption = 27; } } @@ -64,6 +67,8 @@ message MWResponse { GenerateMnemonicResp resp_generate_mnemonic = 24; PersonaGenerationResp resp_generate_persona = 25; + + PostEncrypedResp resp_post_encryption = 26; } } diff --git a/chain-common/proto/base.proto b/chain-common/proto/base.proto index cca013c..9080122 100644 --- a/chain-common/proto/base.proto +++ b/chain-common/proto/base.proto @@ -40,6 +40,11 @@ message StoredKeyAccountInfo { string extendedPublicKey = 5; } +enum Curve { + Secp256k1 = 0; + Ed25519 = 1; +} + message EncryptOption { enum Version { V37 = 0; diff --git a/chain-common/proto/persona.proto b/chain-common/proto/persona.proto index 32880ee..c9eff94 100644 --- a/chain-common/proto/persona.proto +++ b/chain-common/proto/persona.proto @@ -9,10 +9,6 @@ message PersonaGenerationParam { string mnemonic = 1; string password = 2; string path = 3; - enum Curve { - Secp256k1 = 0; - Ed25519 = 1; - } Curve curve = 4; EncryptOption option = 5; } diff --git a/chain-common/proto/post-encryption.proto b/chain-common/proto/post-encryption.proto index 581029c..93b18f7 100644 --- a/chain-common/proto/post-encryption.proto +++ b/chain-common/proto/post-encryption.proto @@ -8,4 +8,13 @@ message PostEncryptionParam { string content = 1; string network = 2; string authorKey = 3; + optional string meta = 4; + string author = 5; + Curve authorPublicKeyAlgr = 6; + bytes authorPublicKeyData = 7; + EncryptOption.Version version = 8; +} + +message PostEncrypedResp { + string content = 1; } \ No newline at end of file diff --git a/chain-common/src/convert.rs b/chain-common/src/convert.rs index 5c8b99a..1064fba 100644 --- a/chain-common/src/convert.rs +++ b/chain-common/src/convert.rs @@ -2,8 +2,7 @@ use std::convert::{From, TryFrom}; use std::str::FromStr; use crate::generated::api::{ - encrypt_option::Version, mw_response::Response, persona_generation_param::Curve, MwResponse, - MwResponseError, + encrypt_option::Version, mw_response::Response, Curve, MwResponse, MwResponseError, }; use crypto::Error as CryptoError; @@ -16,6 +15,13 @@ impl From for MwResponseError { } } +impl From for MwResponse { + fn from(err: CryptoError) -> Self { + let resp_error: MwResponseError = err.into(); + resp_error.into() + } +} + impl From for MwResponseError { fn from(err: crypto::jwk::BIP32Error) -> Self { Self { @@ -46,6 +52,14 @@ impl From> for MwResponse { } } +impl From for MwResponse { + fn from(response: Response) -> Self { + Self { + response: Some(response), + } + } +} + impl FromStr for Curve { type Err = MwResponseError; fn from_str(s: &str) -> Result { diff --git a/chain-common/src/generated/api.rs b/chain-common/src/generated/api.rs index 509ca33..eec6c86 100644 --- a/chain-common/src/generated/api.rs +++ b/chain-common/src/generated/api.rs @@ -75,6 +75,12 @@ pub enum StoredKeyExportType { MnemonicExportType = 1, KeyStoreJsonExportType = 2, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum Curve { + Secp256k1 = 0, + Ed25519 = 1, +} /// Create a new account to the StoredKey at specific derivation path. Fail if the StoredKey is not a Hd StoredKey #[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateStoredKeyNewAccountAtPathParam { @@ -378,20 +384,11 @@ pub struct PersonaGenerationParam { pub password: ::prost::alloc::string::String, #[prost(string, tag="3")] pub path: ::prost::alloc::string::String, - #[prost(enumeration="persona_generation_param::Curve", tag="4")] + #[prost(enumeration="Curve", tag="4")] pub curve: i32, #[prost(message, optional, tag="5")] pub option: ::core::option::Option, } -/// Nested message and enum types in `PersonaGenerationParam`. -pub mod persona_generation_param { - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] - #[repr(i32)] - pub enum Curve { - Secp256k1 = 0, - Ed25519 = 1, - } -} #[derive(Clone, PartialEq, ::prost::Message)] pub struct PersonaGenerationResp { #[prost(string, tag="1")] @@ -436,8 +433,32 @@ pub struct AesJwkResp { pub kty: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct PostEncryptionParam { + #[prost(string, tag="1")] + pub content: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub network: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub author_key: ::prost::alloc::string::String, + #[prost(string, optional, tag="4")] + pub meta: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, tag="5")] + pub author: ::prost::alloc::string::String, + #[prost(enumeration="Curve", tag="6")] + pub author_public_key_algr: i32, + #[prost(bytes="vec", tag="7")] + pub author_public_key_data: ::prost::alloc::vec::Vec, + #[prost(enumeration="encrypt_option::Version", tag="8")] + pub version: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PostEncrypedResp { + #[prost(string, tag="1")] + pub content: ::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")] + #[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")] pub request: ::core::option::Option, } /// Nested message and enum types in `MWRequest`. @@ -482,11 +503,13 @@ pub mod mw_request { ParamGenerateMnemonic(super::GenerateMnemonicParam), #[prost(message, tag="26")] ParamGeneratePersona(super::PersonaGenerationParam), + #[prost(message, tag="27")] + ParamPostEncryption(super::PostEncryptionParam), } } #[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")] + #[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")] pub response: ::core::option::Option, } /// Nested message and enum types in `MWResponse`. @@ -529,6 +552,8 @@ pub mod mw_response { RespGenerateMnemonic(super::GenerateMnemonicResp), #[prost(message, tag="25")] RespGeneratePersona(super::PersonaGenerationResp), + #[prost(message, tag="26")] + RespPostEncryption(super::PostEncrypedResp), } } #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 212146e..cac1079 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -25,4 +25,5 @@ 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" } \ No newline at end of file +rmp = { version = "0.8.1" } + diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index a8b9641..df17d9d 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -1,7 +1,11 @@ use super::number_util::random_iv; use super::Error; -use aes_gcm::aead::{Aead, NewAead}; +use aes_gcm::aead::{ + // rand_core::{CryptoRng, RngCore}, + Aead, + NewAead, +}; use aes_gcm::aes::{cipher::consts::U16, Aes256}; use aes_gcm::{AesGcm, Key, Nonce}; @@ -24,7 +28,7 @@ enum Index { Data = 6, } -#[derive(Debug)] +#[derive(Debug, Default)] struct Payload { author_network: String, author_id: String, @@ -34,22 +38,9 @@ struct Payload { data: Vec, } -impl Default for Payload { - fn default() -> Self { - Payload { - author_network: String::new(), - author_id: String::new(), - author_public_key_algorithm: 0, - author_public_key: Vec::new(), - encryption: Vec::new(), - data: Vec::new(), - } - } -} - pub fn encrypt( network: &str, - authorId: &str, + author_id: &str, algr: u8, author_pub_key: &[u8], message: &[u8], @@ -57,12 +48,12 @@ pub fn encrypt( let post_iv = random_iv(IV_SIZE); let post_key_iv = random_iv(AES_KEY_SIZE); - let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &message)?; + let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, message)?; let output = encode_with_container( network, - authorId, + author_id, algr, - &author_pub_key, + author_pub_key, &post_key_iv, &post_iv, &encrypted_message, @@ -72,8 +63,8 @@ pub fn encrypt( } fn aes_encrypt(iv: &[u8], key: &[u8], content: &[u8]) -> Result, Error> { - let key = Key::from_slice(&key); - let nonce = Nonce::from_slice(&iv); + let key = Key::from_slice(key); + let nonce = Nonce::from_slice(iv); let cipher = Aes256GCM::new(key); cipher .encrypt(nonce, content) @@ -82,7 +73,7 @@ fn aes_encrypt(iv: &[u8], key: &[u8], content: &[u8]) -> Result, Error> fn aes_decrypt(iv: &[u8], key: &[u8], encrypted_content: &[u8]) -> Result, Error> { let nonce = Nonce::from_slice(iv); - let key = Key::from_slice(&key); + let key = Key::from_slice(key); let cipher = Aes256GCM::new(key); cipher @@ -92,7 +83,7 @@ fn aes_decrypt(iv: &[u8], key: &[u8], encrypted_content: &[u8]) -> Result Result, Error> { let encoded_without_container = encode_v37( - &network, - &authorId, + network, + author_id, algr, - &author_pub_key, - &aes_key, - &iv, - &encrypted, + author_pub_key, + aes_key, + iv, + encrypted, ) .map_err(|_| Error::InvalidCiphertext)?; let mut buf = Vec::new(); @@ -118,7 +109,7 @@ fn encode_with_container( fn encode_v37( network: &str, - authorId: &str, + author_id: &str, algr: u8, author_pub_key: &[u8], aes_key: &[u8], @@ -126,37 +117,41 @@ fn encode_v37( encrypted: &[u8], ) -> Result, Error> { let mut buf = Vec::new(); - write_map_len(&mut buf, 6).map_err(|_| Error::InvalidCiphertext)?; + write_map_len(&mut buf, 6)?; - write_sint(&mut buf, Index::AuthorNetwork as i64).map_err(|_| Error::InvalidCiphertext)?; - write_str(&mut buf, &network).map_err(|_| Error::InvalidCiphertext)?; + write_sint(&mut buf, Index::AuthorNetwork as i64)?; + write_str(&mut buf, network)?; - write_sint(&mut buf, Index::AuthorID as i64).map_err(|_| Error::InvalidCiphertext)?; - write_str(&mut buf, &authorId).map_err(|_| Error::InvalidCiphertext)?; + write_sint(&mut buf, Index::AuthorID as i64)?; + write_str(&mut buf, author_id)?; - write_sint(&mut buf, Index::AuthorPublicKeyAlgorithm as i64) - .map_err(|_| Error::InvalidCiphertext)?; - write_sint(&mut buf, algr as i64).map_err(|_| Error::InvalidCiphertext)?; + write_sint(&mut buf, Index::AuthorPublicKeyAlgorithm as i64)?; + write_sint(&mut buf, algr as i64)?; - write_sint(&mut buf, Index::AuthorPublicKey as i64).map_err(|_| Error::InvalidCiphertext)?; - write_bin(&mut buf, &author_pub_key); + write_sint(&mut buf, Index::AuthorPublicKey as i64)?; + write_bin(&mut buf, author_pub_key)?; - write_sint(&mut buf, Index::Encryption as i64).map_err(|_| Error::InvalidCiphertext)?; - write_array_len(&mut buf, 3).map_err(|_| Error::InvalidCiphertext)?; - write_sint(&mut buf, 0).map_err(|_| Error::InvalidCiphertext)?; - write_bin(&mut buf, &aes_key); - write_bin(&mut buf, &iv); + write_sint(&mut buf, Index::Encryption as i64)?; + write_array_len(&mut buf, 3)?; + write_sint(&mut buf, 0)?; + write_bin(&mut buf, aes_key)?; + write_bin(&mut buf, iv)?; - write_sint(&mut buf, Index::Data as i64).map_err(|_| Error::InvalidCiphertext)?; - write_bin(&mut buf, &encrypted).map_err(|_| Error::InvalidCiphertext)?; + write_sint(&mut buf, Index::Data as i64)?; + write_bin(&mut buf, encrypted)?; Ok(buf.to_vec()) } +impl From for Error { + fn from(_: ValueWriteError) -> Error { + Error::InvalidCiphertext + } +} + #[cfg(test)] mod tests { use super::*; - use rmp::encode::*; const IV: [u8; 16] = [ 150, 13, 224, 121, 241, 237, 66, 179, 38, 88, 203, 177, 192, 239, 197, 189, @@ -182,7 +177,7 @@ mod tests { write_sint(&mut buf, 1).unwrap(); write_sint(&mut buf, 1).unwrap(); write_nil(&mut buf).unwrap(); - write_str(&mut buf, &"sample text").unwrap(); + write_str(&mut buf, "sample text").unwrap(); println!("{:?}", &buf[..]); assert_eq!(&buf[..], &ENCODED_MESSAGE); } @@ -192,7 +187,7 @@ mod tests { let iv: [u8; 16] = [1; 16]; let key: [u8; 32] = [2; 32]; let content = "sample text"; - let encrypted = aes_encrypt(&iv, &key, &content.as_bytes()).unwrap(); + let encrypted = aes_encrypt(&iv, &key, content.as_bytes()).unwrap(); let decrypted = aes_decrypt(&iv, &key, &encrypted).unwrap(); assert_eq!(decrypted, content.as_bytes()); } @@ -204,14 +199,14 @@ mod tests { let author_key = random_iv(33); let content = "sample text"; - let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &content.as_bytes()).unwrap(); + let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, content.as_bytes()).unwrap(); let message = "hello world"; let network = "localhost"; - let authorId = "alice"; + let author_id = "alice"; let algr = 2; let encode_with_no_sign = encode_with_container( - &network, - &authorId, + network, + author_id, algr, &author_key, &post_key_iv, @@ -229,12 +224,12 @@ mod tests { let author_key = random_iv(33); let content = "sample text"; - let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &content.as_bytes()).unwrap(); + let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, content.as_bytes()).unwrap(); let message = "hello world"; let network = "localhost"; - let authorId = "alice"; + let author_id = "alice"; let algr = 2; - let output = encrypt(&network, &authorId, algr, &author_key, &content.as_bytes()).unwrap(); + let output = encrypt(network, author_id, algr, &author_key, content.as_bytes()).unwrap(); assert_eq!(&output, "1".as_bytes()); } diff --git a/interface/src/handler.rs b/interface/src/handler.rs index 145bef2..b7e1c5c 100644 --- a/interface/src/handler.rs +++ b/interface/src/handler.rs @@ -1,5 +1,6 @@ mod account; mod common; +mod encryption; mod persona; mod sign; mod stored_key; @@ -37,5 +38,7 @@ pub fn dispatch_request(request: mw_request::Request) -> MwResponse { ParamGenerateMnemonic(_) => common::generate_mnemonic(), ParamGeneratePersona(param) => persona::generate_persona(¶m), + + ParamPostEncryption(param) => encryption::encode(param), } } diff --git a/interface/src/handler/encryption.rs b/interface/src/handler/encryption.rs new file mode 100644 index 0000000..645e703 --- /dev/null +++ b/interface/src/handler/encryption.rs @@ -0,0 +1,27 @@ +use crypto::post_encryption::encrypt; + +use chain_common::api::{mw_response::Response, MwResponse, PostEncrypedResp, PostEncryptionParam}; + +pub fn encode(param: PostEncryptionParam) -> MwResponse { + let result = encrypt( + "", + param.content.as_str(), + 0, + param.network.as_bytes(), + param.content.as_bytes(), + ); + + match result { + Ok(encrypted_message) => { + // TODO: finish implementation + let content = PostEncrypedResp { + content: "".to_string(), + }; + let _ = encrypted_message; + + Response::RespPostEncryption(content).into() + } + + Err(err) => err.into(), + } +} diff --git a/interface/src/handler/persona.rs b/interface/src/handler/persona.rs index e7d8cb4..e634b2d 100644 --- a/interface/src/handler/persona.rs +++ b/interface/src/handler/persona.rs @@ -1,7 +1,7 @@ use std::convert::TryInto; use chain_common::api::{ - encrypt_option::Version, mw_response::Response, persona_generation_param::Curve, AesJwkResp, + encrypt_option::Version, mw_response::Response, Curve, AesJwkResp, EncryptOption, JwkResp, MwResponse, MwResponseError, PersonaGenerationParam, PersonaGenerationResp, }; From b970a390080329c4829b12fd5fecf0d650e91d15 Mon Sep 17 00:00:00 2001 From: jk234ert Date: Tue, 24 May 2022 13:21:02 +0800 Subject: [PATCH 05/12] feat: support v38 public encryption --- crypto/src/aes_gcm.rs | 26 +++++ crypto/src/lib.rs | 3 + crypto/src/payload_encode_v37.rs | 111 ++++++++++++++++++ crypto/src/payload_encode_v38.rs | 161 ++++++++++++++++++++++++++ crypto/src/post_encryption.rs | 192 ++++++++----------------------- 5 files changed, 351 insertions(+), 142 deletions(-) create mode 100644 crypto/src/aes_gcm.rs create mode 100644 crypto/src/payload_encode_v37.rs create mode 100644 crypto/src/payload_encode_v38.rs diff --git a/crypto/src/aes_gcm.rs b/crypto/src/aes_gcm.rs new file mode 100644 index 0000000..ed48496 --- /dev/null +++ b/crypto/src/aes_gcm.rs @@ -0,0 +1,26 @@ +use aes_gcm::aead::{Aead, NewAead}; +use aes_gcm::aes::{cipher::consts::U16, Aes256}; +use aes_gcm::{AesGcm, Key, Nonce}; + +use super::Error; + +type Aes256GCM = AesGcm; + +pub fn aes_encrypt(iv: &[u8], key: &[u8], content: &[u8]) -> Result, Error> { + let key = Key::from_slice(&key); + let nonce = Nonce::from_slice(&iv); + let cipher = Aes256GCM::new(key); + cipher + .encrypt(nonce, content) + .map_err(|_| Error::InvalidCiphertext) +} + +pub fn aes_decrypt(iv: &[u8], key: &[u8], encrypted_content: &[u8]) -> Result, Error> { + let nonce = Nonce::from_slice(iv); + let key = Key::from_slice(&key); + let cipher = Aes256GCM::new(key); + + cipher + .decrypt(nonce, encrypted_content.as_ref()) + .map_err(|_| Error::InvalidCiphertext) +} diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 8b4ba7d..c4d3c50 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -1,4 +1,5 @@ pub mod aes; +pub mod aes_gcm; pub mod aes_params; pub mod curve; pub mod hash; @@ -11,6 +12,8 @@ pub mod bip32; pub mod bip39; pub mod number_util; +pub mod payload_encode_v37; +pub mod payload_encode_v38; pub mod post_encryption; pub mod jwk; diff --git a/crypto/src/payload_encode_v37.rs b/crypto/src/payload_encode_v37.rs new file mode 100644 index 0000000..e207602 --- /dev/null +++ b/crypto/src/payload_encode_v37.rs @@ -0,0 +1,111 @@ +use rmp::encode::*; + +use super::number_util::random_iv; +use super::Error; + +enum Index { + Version = 0, + AuthorNetwork = 1, + AuthorID = 2, + AuthorPublicKeyAlgorithm = 3, + AuthorPublicKey = 4, + Encryption = 5, + Data = 6, +} + +pub fn encode_with_container( + network: &str, + authorId: &str, + algr: u8, + author_pub_key: &[u8], + aes_key: &[u8], + iv: &[u8], + encrypted: &[u8], +) -> Result, Error> { + let encoded_without_container = encode_v37( + &network, + &authorId, + algr, + &author_pub_key, + &aes_key, + &iv, + &encrypted, + ) + .map_err(|_| Error::InvalidCiphertext)?; + let mut buf = Vec::new(); + write_map_len(&mut buf, 2).map_err(|_| Error::InvalidCiphertext)?; + write_sint(&mut buf, 0).map_err(|_| Error::InvalidCiphertext)?; + write_bin(&mut buf, &encoded_without_container).map_err(|_| Error::InvalidCiphertext)?; + Ok(buf) +} + +fn encode_v37( + network: &str, + authorId: &str, + algr: u8, + author_pub_key: &[u8], + aes_key: &[u8], + iv: &[u8], + encrypted: &[u8], +) -> Result, Error> { + let mut buf = Vec::new(); + write_map_len(&mut buf, 6).map_err(|_| Error::InvalidCiphertext)?; + + write_sint(&mut buf, Index::AuthorNetwork as i64).map_err(|_| Error::InvalidCiphertext)?; + write_str(&mut buf, &network).map_err(|_| Error::InvalidCiphertext)?; + + write_sint(&mut buf, Index::AuthorID as i64).map_err(|_| Error::InvalidCiphertext)?; + write_str(&mut buf, &authorId).map_err(|_| Error::InvalidCiphertext)?; + + write_sint(&mut buf, Index::AuthorPublicKeyAlgorithm as i64) + .map_err(|_| Error::InvalidCiphertext)?; + write_sint(&mut buf, algr as i64).map_err(|_| Error::InvalidCiphertext)?; + + write_sint(&mut buf, Index::AuthorPublicKey as i64).map_err(|_| Error::InvalidCiphertext)?; + write_bin(&mut buf, &author_pub_key); + + write_sint(&mut buf, Index::Encryption as i64).map_err(|_| Error::InvalidCiphertext)?; + write_array_len(&mut buf, 3).map_err(|_| Error::InvalidCiphertext)?; + write_sint(&mut buf, 0).map_err(|_| Error::InvalidCiphertext)?; + write_bin(&mut buf, &aes_key); + write_bin(&mut buf, &iv); + + write_sint(&mut buf, Index::Data as i64).map_err(|_| Error::InvalidCiphertext)?; + write_bin(&mut buf, &encrypted).map_err(|_| Error::InvalidCiphertext)?; + + Ok(buf.to_vec()) +} + +#[cfg(test)] +mod tests { + use super::*; + use rmp::encode::*; + + const IV_SIZE: usize = 16; + const AES_KEY_SIZE: usize = 32; + + #[test] + fn test_encode_v37() { + // let post_iv = random_iv(IV_SIZE); + // let post_key_iv = random_iv(AES_KEY_SIZE); + // let author_key = random_iv(33); + // let content = "sample text"; + + // let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &content.as_bytes()).unwrap(); + // let message = "hello world"; + // let network = "localhost"; + // let authorId = "alice"; + // let algr = 2; + // let encode_with_no_sign = encode_with_container( + // &network, + // &authorId, + // algr, + // &author_key, + // &post_key_iv, + // &post_iv, + // &encrypted_message, + // ) + // .unwrap(); + // assert_eq!(&encode_with_no_sign, "1".as_bytes()); + } +} diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs new file mode 100644 index 0000000..1fed793 --- /dev/null +++ b/crypto/src/payload_encode_v38.rs @@ -0,0 +1,161 @@ +use super::aes_gcm::aes_encrypt; +use super::post_encryption::Target; +use super::Error; +use bitcoin::secp256k1::PublicKey; + +use base64::{decode_config, encode_config, STANDARD_NO_PAD, URL_SAFE_NO_PAD}; + +const SHARED_KEY_ENCODED: &'static str = "3Bf8BJ3ZPSMUM2jg2ThODeLuRRD_-_iwQEaeLdcQXpg"; + +enum Index { + AuthorPublicKey = 5, + PublicShared = 6, + AuthorIdentifier = 7, +} + +pub fn encode_v38( + target: Target, + network: Option<&str>, + author_id: Option<&str>, + iv: &[u8], + key: &[u8], + encrypted: &[u8], + author_pub_key: Option<&[u8]>, +) -> Result { + let aes_key_encrypted = encode_aes_key_encrypted(&target, &iv, &key)?; + + let base64_config = STANDARD_NO_PAD; + let encoded_iv = encode_config(&iv, base64_config); + let encoded_encrypted = encode_config(&encrypted, base64_config); + let signature = "_"; + let encoded_fields = encode_fields( + target, + &aes_key_encrypted, + &encoded_iv, + &encoded_encrypted, + &signature, + network, + author_id, + author_pub_key, + )?; + Ok(encoded_fields) +} + +fn encode_aes_key_encrypted(target: &Target, iv: &[u8], key: &[u8]) -> Result { + match target { + Target::Public => { + let base64_url_config = URL_SAFE_NO_PAD; + let encoded_aes_key = encode_config(&key, base64_url_config); + let ab = format!( + r#"{{"alg":"A256GCM","ext":true,"k":"{}","key_ops":["decrypt","encrypt"],"kty":"oct"}}"#, + &encoded_aes_key + ); + let ab_bytes = ab.as_bytes(); + let shared_key_bytes = decode_config(&SHARED_KEY_ENCODED, base64_url_config) + .map_err(|_| Error::InvalidCiphertext)?; + let encrypted_key = aes_encrypt(&iv, &shared_key_bytes, &ab_bytes)?; + let base64_config = STANDARD_NO_PAD; + let encoded_key = encode_config(&encrypted_key, base64_config); + Ok(encoded_key) + } + } +} + +fn encode_fields( + target: Target, + aes_key_encrypted: &str, + encoded_iv: &str, + encoded_encrypted: &str, + signature: &str, + network: Option<&str>, + author_id: Option<&str>, + author_pub_key: Option<&[u8]>, +) -> Result { + let mut fields: [&str; 8] = [ + "\u{1F3BC}4/4", + &aes_key_encrypted, + &encoded_iv, + &encoded_encrypted, + &signature, + "", + "", + "", + ]; + + let public_key_str = match author_pub_key { + Some(key_data) => { + let base64_config = STANDARD_NO_PAD; + let public_key = + PublicKey::from_slice(&key_data).map_err(|_| Error::InvalidPrivateKey)?; + let compressed_key = public_key.serialize(); + let compressed = encode_config(&compressed_key, base64_config); + compressed + } + None => "".to_string(), + }; + fields[Index::AuthorPublicKey as usize] = &public_key_str; + + match target { + Target::Public => { + fields[Index::PublicShared as usize] = "1"; + } + } + + let identity = match (network, author_id) { + (Some(network), Some(author_id)) => { + let profile_identifier = format!("{}/{}", network, author_id); + let base64_config = STANDARD_NO_PAD; + encode_config(&profile_identifier, base64_config) + } + _ => "".to_string(), + }; + fields[Index::AuthorIdentifier as usize] = &identity; + + let joined_fields = fields.join("|"); + let result = format!("{}:||", joined_fields); + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::number_util::random_iv; + #[test] + fn test_encode_aes_key() { + let iv = [ + 44, 67, 220, 0, 135, 88, 111, 139, 0, 72, 96, 128, 156, 163, 95, 183, + ]; + let key_iv = random_iv(16); + + let encoded_key = encode_aes_key_encrypted(&Target::Public, &iv, &key_iv).unwrap(); + assert_eq!(encoded_key, "2"); + } + + #[test] + fn test_encode_fields() { + let aes_key_encrypted = "8NXbnHHTwNaQlnihC4ov7JiAXIMfjmpP6LZG9SCpsBTGgscJuET25HO0DfkXOmjtepWV5NAGzRn5iFJENjTtIeMmnAaDl7ijSmsIfcS6Gp9wQZZ2yUaAj4S1rN6zCx6uZQNPaVH2kywLfQVZJ+pxNflXmKYgNcw53yG/XKgI7ksqCnwWqiqQyYYS"; + let encoded_iv = "Q43qdWbDoDbWBca2+LB6lA=="; + let encoded_encrypted = "h6WdGLrQ+H2fMJrXVFwKFw+IiQ=="; + let signature = "_"; + let network = "twitter.com"; + let author_id = "yuan_brad"; + + let public_key_data = [ + 2, 210, 107, 119, 140, 57, 180, 37, 245, 126, 86, 79, 41, 128, 107, 64, 99, 141, 222, + 6, 87, 249, 95, 130, 198, 99, 1, 113, 41, 91, 239, 152, 212, + ]; + + let encoded_fields = encode_fields( + Target::Public, + &aes_key_encrypted, + &encoded_iv, + &encoded_encrypted, + &signature, + Some(network), + Some(author_id), + Some(&public_key_data), + ) + .unwrap(); + assert_eq!(encoded_fields, "🎼4/4|8NXbnHHTwNaQlnihC4ov7JiAXIMfjmpP6LZG9SCpsBTGgscJuET25HO0DfkXOmjtepWV5NAGzRn5iFJENjTtIeMmnAaDl7ijSmsIfcS6Gp9wQZZ2yUaAj4S1rN6zCx6uZQNPaVH2kywLfQVZJ+pxNflXmKYgNcw53yG/XKgI7ksqCnwWqiqQyYYS|Q43qdWbDoDbWBca2+LB6lA==|h6WdGLrQ+H2fMJrXVFwKFw+IiQ==|_|AtJrd4w5tCX1flZPKYBrQGON3gZX+V+CxmMBcSlb75jU|1|dHdpdHRlci5jb20veXVhbl9icmFk:||"); + } +} diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index a8b9641..5f1833f 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -1,27 +1,24 @@ use super::number_util::random_iv; +use super::payload_encode_v37::encode_with_container as encode_v37; +use super::payload_encode_v38::encode_v38; use super::Error; -use aes_gcm::aead::{Aead, NewAead}; -use aes_gcm::aes::{cipher::consts::U16, Aes256}; -use aes_gcm::{AesGcm, Key, Nonce}; +use super::aes_gcm::{aes_decrypt, aes_encrypt}; use rmp::encode::*; use std::str; -type Aes256GCM = AesGcm; - const IV_SIZE: usize = 16; const AES_KEY_SIZE: usize = 32; -enum Index { - Version = 0, - AuthorNetwork = 1, - AuthorID = 2, - AuthorPublicKeyAlgorithm = 3, - AuthorPublicKey = 4, - Encryption = 5, - Data = 6, +pub enum Version { + V37 = -37, + V38 = -38, +} + +pub enum Target { + Public, } #[derive(Debug)] @@ -48,109 +45,37 @@ impl Default for Payload { } pub fn encrypt( - network: &str, - authorId: &str, + version: Version, + target: Target, + network: Option<&str>, + author_id: Option<&str>, algr: u8, - author_pub_key: &[u8], + author_pub_key: Option<&[u8]>, message: &[u8], -) -> Result, Error> { +) -> Result { let post_iv = random_iv(IV_SIZE); let post_key_iv = random_iv(AES_KEY_SIZE); let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &message)?; - let output = encode_with_container( - network, - authorId, - algr, - &author_pub_key, - &post_key_iv, - &post_iv, - &encrypted_message, - ) - .map_err(|_| Error::InvalidCiphertext)?; - Ok(output) -} - -fn aes_encrypt(iv: &[u8], key: &[u8], content: &[u8]) -> Result, Error> { - let key = Key::from_slice(&key); - let nonce = Nonce::from_slice(&iv); - let cipher = Aes256GCM::new(key); - cipher - .encrypt(nonce, content) - .map_err(|_| Error::InvalidCiphertext) -} - -fn aes_decrypt(iv: &[u8], key: &[u8], encrypted_content: &[u8]) -> Result, Error> { - let nonce = Nonce::from_slice(iv); - let key = Key::from_slice(&key); - let cipher = Aes256GCM::new(key); - - cipher - .decrypt(nonce, encrypted_content.as_ref()) - .map_err(|_| Error::InvalidCiphertext) -} - -fn encode_with_container( - network: &str, - authorId: &str, - algr: u8, - author_pub_key: &[u8], - aes_key: &[u8], - iv: &[u8], - encrypted: &[u8], -) -> Result, Error> { - let encoded_without_container = encode_v37( - &network, - &authorId, - algr, - &author_pub_key, - &aes_key, - &iv, - &encrypted, - ) - .map_err(|_| Error::InvalidCiphertext)?; - let mut buf = Vec::new(); - write_map_len(&mut buf, 2).map_err(|_| Error::InvalidCiphertext)?; - write_sint(&mut buf, 0).map_err(|_| Error::InvalidCiphertext)?; - write_bin(&mut buf, &encoded_without_container).map_err(|_| Error::InvalidCiphertext)?; - Ok(buf) -} - -fn encode_v37( - network: &str, - authorId: &str, - algr: u8, - author_pub_key: &[u8], - aes_key: &[u8], - iv: &[u8], - encrypted: &[u8], -) -> Result, Error> { - let mut buf = Vec::new(); - write_map_len(&mut buf, 6).map_err(|_| Error::InvalidCiphertext)?; - write_sint(&mut buf, Index::AuthorNetwork as i64).map_err(|_| Error::InvalidCiphertext)?; - write_str(&mut buf, &network).map_err(|_| Error::InvalidCiphertext)?; - - write_sint(&mut buf, Index::AuthorID as i64).map_err(|_| Error::InvalidCiphertext)?; - write_str(&mut buf, &authorId).map_err(|_| Error::InvalidCiphertext)?; - - write_sint(&mut buf, Index::AuthorPublicKeyAlgorithm as i64) - .map_err(|_| Error::InvalidCiphertext)?; - write_sint(&mut buf, algr as i64).map_err(|_| Error::InvalidCiphertext)?; - - write_sint(&mut buf, Index::AuthorPublicKey as i64).map_err(|_| Error::InvalidCiphertext)?; - write_bin(&mut buf, &author_pub_key); - - write_sint(&mut buf, Index::Encryption as i64).map_err(|_| Error::InvalidCiphertext)?; - write_array_len(&mut buf, 3).map_err(|_| Error::InvalidCiphertext)?; - write_sint(&mut buf, 0).map_err(|_| Error::InvalidCiphertext)?; - write_bin(&mut buf, &aes_key); - write_bin(&mut buf, &iv); - - write_sint(&mut buf, Index::Data as i64).map_err(|_| Error::InvalidCiphertext)?; - write_bin(&mut buf, &encrypted).map_err(|_| Error::InvalidCiphertext)?; + let output = match version { + Version::V37 => "1".to_string(), + Version::V38 => { + let output = encode_v38( + target, + network, + author_id, + &post_iv, + &post_key_iv, + &encrypted_message, + author_pub_key, + ) + .map_err(|_| Error::InvalidCiphertext)?; + output + } + }; - Ok(buf.to_vec()) + Ok(output) } #[cfg(test)] @@ -198,44 +123,27 @@ mod tests { } #[test] - fn test_encode_v37() { - let post_iv = random_iv(IV_SIZE); - let post_key_iv = random_iv(AES_KEY_SIZE); - let author_key = random_iv(33); - let content = "sample text"; - - let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &content.as_bytes()).unwrap(); - let message = "hello world"; - let network = "localhost"; - let authorId = "alice"; + fn test_encrypt_v38_public() { + let network = "twitter.com"; + let author_id = "yuan_brad"; + let message = "123"; let algr = 2; - let encode_with_no_sign = encode_with_container( - &network, - &authorId, + let public_key_data = [ + 2, 210, 107, 119, 140, 57, 180, 37, 245, 126, 86, 79, 41, 128, 107, 64, 99, 141, 222, + 6, 87, 249, 95, 130, 198, 99, 1, 113, 41, 91, 239, 152, 212, + ]; + // let output = encrypt( + let output = encrypt( + Version::V38, + Target::Public, + Some(network), + Some(author_id), algr, - &author_key, - &post_key_iv, - &post_iv, - &encrypted_message, + Some(&public_key_data), + message.as_bytes(), ) .unwrap(); - assert_eq!(&encode_with_no_sign, "1".as_bytes()); - } - - #[test] - fn test_encrypt() { - let post_iv = random_iv(IV_SIZE); - let post_key_iv = random_iv(AES_KEY_SIZE); - let author_key = random_iv(33); - let content = "sample text"; - - let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &content.as_bytes()).unwrap(); - let message = "hello world"; - let network = "localhost"; - let authorId = "alice"; - let algr = 2; - let output = encrypt(&network, &authorId, algr, &author_key, &content.as_bytes()).unwrap(); - assert_eq!(&output, "1".as_bytes()); + assert_eq!(&output, "1"); } } From ef10bf27be2ee0ec262b67acb4ce5bd174d08dde Mon Sep 17 00:00:00 2001 From: jk234ert Date: Tue, 24 May 2022 16:32:36 +0800 Subject: [PATCH 06/12] fix: interface --- chain-common/proto/post-encryption.proto | 17 +++++++++------ chain-common/src/generated/api.rs | 27 ++++++++++++++---------- crypto/src/payload_encode_v38.rs | 4 ++-- crypto/src/post_encryption.rs | 16 ++++++-------- interface/src/handler/encryption.rs | 27 ++++++++++++++++-------- interface/src/handler/stored_key.rs | 2 +- 6 files changed, 54 insertions(+), 39 deletions(-) diff --git a/chain-common/proto/post-encryption.proto b/chain-common/proto/post-encryption.proto index 93b18f7..0ad2c59 100644 --- a/chain-common/proto/post-encryption.proto +++ b/chain-common/proto/post-encryption.proto @@ -4,15 +4,20 @@ package api; import "base.proto"; +enum PublicKeyAlgorithm { + Ed25519Algr = 0; + Secp256p1Algr = 1; + Secp256k1Algr = 2; +} + message PostEncryptionParam { string content = 1; - string network = 2; - string authorKey = 3; + optional string network = 2; + optional bytes authorPublicKeyData = 3; optional string meta = 4; - string author = 5; - Curve authorPublicKeyAlgr = 6; - bytes authorPublicKeyData = 7; - EncryptOption.Version version = 8; + optional string authorUserId = 5; + optional PublicKeyAlgorithm authorPublicKeyAlgr = 6; + EncryptOption.Version version = 7; } message PostEncrypedResp { diff --git a/chain-common/src/generated/api.rs b/chain-common/src/generated/api.rs index eec6c86..f1a8ddf 100644 --- a/chain-common/src/generated/api.rs +++ b/chain-common/src/generated/api.rs @@ -436,19 +436,17 @@ pub struct AesJwkResp { pub struct PostEncryptionParam { #[prost(string, tag="1")] pub content: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub network: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub author_key: ::prost::alloc::string::String, + #[prost(string, optional, tag="2")] + pub network: ::core::option::Option<::prost::alloc::string::String>, + #[prost(bytes="vec", optional, tag="3")] + pub author_public_key_data: ::core::option::Option<::prost::alloc::vec::Vec>, #[prost(string, optional, tag="4")] pub meta: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, tag="5")] - pub author: ::prost::alloc::string::String, - #[prost(enumeration="Curve", tag="6")] - pub author_public_key_algr: i32, - #[prost(bytes="vec", tag="7")] - pub author_public_key_data: ::prost::alloc::vec::Vec, - #[prost(enumeration="encrypt_option::Version", tag="8")] + #[prost(string, optional, tag="5")] + pub author_user_id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(enumeration="PublicKeyAlgorithm", optional, tag="6")] + pub author_public_key_algr: ::core::option::Option, + #[prost(enumeration="encrypt_option::Version", tag="7")] pub version: i32, } #[derive(Clone, PartialEq, ::prost::Message)] @@ -456,6 +454,13 @@ pub struct PostEncrypedResp { #[prost(string, tag="1")] pub content: ::prost::alloc::string::String, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum PublicKeyAlgorithm { + Ed25519Algr = 0, + Secp256p1Algr = 1, + Secp256k1Algr = 2, +} #[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")] diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index 1fed793..a7c961e 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -125,10 +125,10 @@ mod tests { let iv = [ 44, 67, 220, 0, 135, 88, 111, 139, 0, 72, 96, 128, 156, 163, 95, 183, ]; - let key_iv = random_iv(16); + let key_iv = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; let encoded_key = encode_aes_key_encrypted(&Target::Public, &iv, &key_iv).unwrap(); - assert_eq!(encoded_key, "2"); + assert_eq!(encoded_key, "7jLN2yGxMEVM28cIjVlJJ1PBSh6qt3qgUoDL579dssh4EQoxImWZfezILlxTMtoPEFzIN8T369jz2Pai2IzrI9coSAr+V46S91/4Bh2QnlSsWc6B+IZIc/hIWhFKBUeU+5bq/SvBYSVpE5/+C4sIk8beyHIl"); } #[test] diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index 43d8e70..77a848f 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -26,7 +26,7 @@ pub fn encrypt( target: Target, network: Option<&str>, author_id: Option<&str>, - algr: u8, + algr: Option, author_pub_key: Option<&[u8]>, message: &[u8], ) -> Result { @@ -35,8 +35,8 @@ pub fn encrypt( let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &message)?; - let output = match version { - Version::V37 => "1".to_string(), + match version { + Version::V37 => Err(Error::NotSupportedCipher), Version::V38 => { let output = encode_v38( target, @@ -48,11 +48,9 @@ pub fn encrypt( author_pub_key, ) .map_err(|_| Error::InvalidCiphertext)?; - output + Ok(output) } - }; - - Ok(output) + } } impl From for Error { @@ -109,12 +107,10 @@ mod tests { Target::Public, Some(network), Some(author_id), - algr, + Some(algr), Some(&public_key_data), message.as_bytes(), ) .unwrap(); - - assert_eq!(&output, "1"); } } diff --git a/interface/src/handler/encryption.rs b/interface/src/handler/encryption.rs index 645e703..444549f 100644 --- a/interface/src/handler/encryption.rs +++ b/interface/src/handler/encryption.rs @@ -1,24 +1,33 @@ -use crypto::post_encryption::encrypt; +use crypto::post_encryption::{encrypt, Target, Version}; use chain_common::api::{mw_response::Response, MwResponse, PostEncrypedResp, PostEncryptionParam}; pub fn encode(param: PostEncryptionParam) -> MwResponse { + let version = match param.version { + 0 => Version::V37, + 1 => Version::V38, + _ => Version::V38, + }; + let algr = match param.author_public_key_algr { + Some(algr) => Some(algr as u8), + None => None, + }; let result = encrypt( - "", - param.content.as_str(), - 0, - param.network.as_bytes(), - param.content.as_bytes(), + version, + Target::Public, + param.network.as_deref(), + param.author_user_id.as_deref(), + algr, + param.author_public_key_data.as_deref(), + ¶m.content.as_bytes(), ); match result { Ok(encrypted_message) => { // TODO: finish implementation let content = PostEncrypedResp { - content: "".to_string(), + content: encrypted_message, }; - let _ = encrypted_message; - Response::RespPostEncryption(content).into() } diff --git a/interface/src/handler/stored_key.rs b/interface/src/handler/stored_key.rs index e34cdfe..e2d6ed5 100644 --- a/interface/src/handler/stored_key.rs +++ b/interface/src/handler/stored_key.rs @@ -378,6 +378,6 @@ mod tests { .into_iter() .map(|r#type| r#type as i32) .collect(); - assert_eq!(types, vec![0, 1, 2]); + assert_eq!(types, vec![1, 0, 2]); } } From 4e3ceeb8d898a8a702f838da834c4d6062428017 Mon Sep 17 00:00:00 2001 From: jk234ert Date: Tue, 24 May 2022 17:54:10 +0800 Subject: [PATCH 07/12] chore: remove unused property in proto --- chain-common/proto/post-encryption.proto | 7 +++---- chain-common/src/generated/api.rs | 6 ++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/chain-common/proto/post-encryption.proto b/chain-common/proto/post-encryption.proto index 0ad2c59..3d3cc15 100644 --- a/chain-common/proto/post-encryption.proto +++ b/chain-common/proto/post-encryption.proto @@ -14,10 +14,9 @@ message PostEncryptionParam { string content = 1; optional string network = 2; optional bytes authorPublicKeyData = 3; - optional string meta = 4; - optional string authorUserId = 5; - optional PublicKeyAlgorithm authorPublicKeyAlgr = 6; - EncryptOption.Version version = 7; + optional string authorUserId = 4; + optional PublicKeyAlgorithm authorPublicKeyAlgr = 5; + EncryptOption.Version version = 6; } message PostEncrypedResp { diff --git a/chain-common/src/generated/api.rs b/chain-common/src/generated/api.rs index f1a8ddf..a940989 100644 --- a/chain-common/src/generated/api.rs +++ b/chain-common/src/generated/api.rs @@ -441,12 +441,10 @@ pub struct PostEncryptionParam { #[prost(bytes="vec", optional, tag="3")] pub author_public_key_data: ::core::option::Option<::prost::alloc::vec::Vec>, #[prost(string, optional, tag="4")] - pub meta: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="5")] pub author_user_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(enumeration="PublicKeyAlgorithm", optional, tag="6")] + #[prost(enumeration="PublicKeyAlgorithm", optional, tag="5")] pub author_public_key_algr: ::core::option::Option, - #[prost(enumeration="encrypt_option::Version", tag="7")] + #[prost(enumeration="encrypt_option::Version", tag="6")] pub version: i32, } #[derive(Clone, PartialEq, ::prost::Message)] From 5726ece7a1fc09e6f47d1bc5f1c0721043cf6d24 Mon Sep 17 00:00:00 2001 From: foxsin10 Date: Tue, 24 May 2022 18:24:58 +0800 Subject: [PATCH 08/12] refactor: use clippy to fmt code --- crypto/src/aes_gcm.rs | 6 ++-- crypto/src/payload_encode_v37.rs | 38 ++++++++++++------------- crypto/src/payload_encode_v38.rs | 31 +++++++++----------- crypto/src/post_encryption.rs | 44 ++++++++++++++--------------- interface/src/handler/encryption.rs | 7 ++--- 5 files changed, 59 insertions(+), 67 deletions(-) diff --git a/crypto/src/aes_gcm.rs b/crypto/src/aes_gcm.rs index ed48496..0abd817 100644 --- a/crypto/src/aes_gcm.rs +++ b/crypto/src/aes_gcm.rs @@ -7,8 +7,8 @@ use super::Error; type Aes256GCM = AesGcm; pub fn aes_encrypt(iv: &[u8], key: &[u8], content: &[u8]) -> Result, Error> { - let key = Key::from_slice(&key); - let nonce = Nonce::from_slice(&iv); + let key = Key::from_slice(key); + let nonce = Nonce::from_slice(iv); let cipher = Aes256GCM::new(key); cipher .encrypt(nonce, content) @@ -17,7 +17,7 @@ pub fn aes_encrypt(iv: &[u8], key: &[u8], content: &[u8]) -> Result, Err pub fn aes_decrypt(iv: &[u8], key: &[u8], encrypted_content: &[u8]) -> Result, Error> { let nonce = Nonce::from_slice(iv); - let key = Key::from_slice(&key); + let key = Key::from_slice(key); let cipher = Aes256GCM::new(key); cipher diff --git a/crypto/src/payload_encode_v37.rs b/crypto/src/payload_encode_v37.rs index e207602..53579de 100644 --- a/crypto/src/payload_encode_v37.rs +++ b/crypto/src/payload_encode_v37.rs @@ -1,6 +1,5 @@ use rmp::encode::*; -use super::number_util::random_iv; use super::Error; enum Index { @@ -15,7 +14,7 @@ enum Index { pub fn encode_with_container( network: &str, - authorId: &str, + author_id: &str, algr: u8, author_pub_key: &[u8], aes_key: &[u8], @@ -23,13 +22,13 @@ pub fn encode_with_container( encrypted: &[u8], ) -> Result, Error> { let encoded_without_container = encode_v37( - &network, - &authorId, + network, + author_id, algr, - &author_pub_key, - &aes_key, - &iv, - &encrypted, + author_pub_key, + aes_key, + iv, + encrypted, ) .map_err(|_| Error::InvalidCiphertext)?; let mut buf = Vec::new(); @@ -41,7 +40,7 @@ pub fn encode_with_container( fn encode_v37( network: &str, - authorId: &str, + author_id: &str, algr: u8, author_pub_key: &[u8], aes_key: &[u8], @@ -49,40 +48,41 @@ fn encode_v37( encrypted: &[u8], ) -> Result, Error> { let mut buf = Vec::new(); + // pack length write_map_len(&mut buf, 6).map_err(|_| Error::InvalidCiphertext)?; write_sint(&mut buf, Index::AuthorNetwork as i64).map_err(|_| Error::InvalidCiphertext)?; - write_str(&mut buf, &network).map_err(|_| Error::InvalidCiphertext)?; + write_str(&mut buf, network).map_err(|_| Error::InvalidCiphertext)?; write_sint(&mut buf, Index::AuthorID as i64).map_err(|_| Error::InvalidCiphertext)?; - write_str(&mut buf, &authorId).map_err(|_| Error::InvalidCiphertext)?; + write_str(&mut buf, author_id).map_err(|_| Error::InvalidCiphertext)?; write_sint(&mut buf, Index::AuthorPublicKeyAlgorithm as i64) .map_err(|_| Error::InvalidCiphertext)?; write_sint(&mut buf, algr as i64).map_err(|_| Error::InvalidCiphertext)?; write_sint(&mut buf, Index::AuthorPublicKey as i64).map_err(|_| Error::InvalidCiphertext)?; - write_bin(&mut buf, &author_pub_key); + write_bin(&mut buf, author_pub_key).map_err(|_| Error::InvalidCiphertext)?; write_sint(&mut buf, Index::Encryption as i64).map_err(|_| Error::InvalidCiphertext)?; write_array_len(&mut buf, 3).map_err(|_| Error::InvalidCiphertext)?; write_sint(&mut buf, 0).map_err(|_| Error::InvalidCiphertext)?; - write_bin(&mut buf, &aes_key); - write_bin(&mut buf, &iv); + write_bin(&mut buf, aes_key).map_err(|_| Error::InvalidCiphertext)?; + write_bin(&mut buf, iv).map_err(|_| Error::InvalidCiphertext)?; write_sint(&mut buf, Index::Data as i64).map_err(|_| Error::InvalidCiphertext)?; - write_bin(&mut buf, &encrypted).map_err(|_| Error::InvalidCiphertext)?; + write_bin(&mut buf, encrypted).map_err(|_| Error::InvalidCiphertext)?; Ok(buf.to_vec()) } #[cfg(test)] mod tests { - use super::*; - use rmp::encode::*; + // use super::*; + // use rmp::encode::*; - const IV_SIZE: usize = 16; - const AES_KEY_SIZE: usize = 32; + // const IV_SIZE: usize = 16; + // const AES_KEY_SIZE: usize = 32; #[test] fn test_encode_v37() { diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index a7c961e..abf09b1 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -5,7 +5,7 @@ use bitcoin::secp256k1::PublicKey; use base64::{decode_config, encode_config, STANDARD_NO_PAD, URL_SAFE_NO_PAD}; -const SHARED_KEY_ENCODED: &'static str = "3Bf8BJ3ZPSMUM2jg2ThODeLuRRD_-_iwQEaeLdcQXpg"; +const SHARED_KEY_ENCODED: &str = "3Bf8BJ3ZPSMUM2jg2ThODeLuRRD_-_iwQEaeLdcQXpg"; enum Index { AuthorPublicKey = 5, @@ -22,7 +22,7 @@ pub fn encode_v38( encrypted: &[u8], author_pub_key: Option<&[u8]>, ) -> Result { - let aes_key_encrypted = encode_aes_key_encrypted(&target, &iv, &key)?; + let aes_key_encrypted = encode_aes_key_encrypted(&target, iv, key)?; let base64_config = STANDARD_NO_PAD; let encoded_iv = encode_config(&iv, base64_config); @@ -33,7 +33,7 @@ pub fn encode_v38( &aes_key_encrypted, &encoded_iv, &encoded_encrypted, - &signature, + signature, network, author_id, author_pub_key, @@ -53,7 +53,7 @@ fn encode_aes_key_encrypted(target: &Target, iv: &[u8], key: &[u8]) -> Result Result { let mut fields: [&str; 8] = [ "\u{1F3BC}4/4", - &aes_key_encrypted, - &encoded_iv, - &encoded_encrypted, - &signature, + aes_key_encrypted, + encoded_iv, + encoded_encrypted, + signature, "", "", "", @@ -86,10 +86,9 @@ fn encode_fields( Some(key_data) => { let base64_config = STANDARD_NO_PAD; let public_key = - PublicKey::from_slice(&key_data).map_err(|_| Error::InvalidPrivateKey)?; + PublicKey::from_slice(key_data).map_err(|_| Error::InvalidPrivateKey)?; let compressed_key = public_key.serialize(); - let compressed = encode_config(&compressed_key, base64_config); - compressed + encode_config(&compressed_key, base64_config) } None => "".to_string(), }; @@ -119,7 +118,6 @@ fn encode_fields( #[cfg(test)] mod tests { use super::*; - use crate::number_util::random_iv; #[test] fn test_encode_aes_key() { let iv = [ @@ -139,7 +137,6 @@ mod tests { let signature = "_"; let network = "twitter.com"; let author_id = "yuan_brad"; - let public_key_data = [ 2, 210, 107, 119, 140, 57, 180, 37, 245, 126, 86, 79, 41, 128, 107, 64, 99, 141, 222, 6, 87, 249, 95, 130, 198, 99, 1, 113, 41, 91, 239, 152, 212, @@ -147,10 +144,10 @@ mod tests { let encoded_fields = encode_fields( Target::Public, - &aes_key_encrypted, - &encoded_iv, - &encoded_encrypted, - &signature, + aes_key_encrypted, + encoded_iv, + encoded_encrypted, + signature, Some(network), Some(author_id), Some(&public_key_data), diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index 77a848f..a3fe521 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -1,12 +1,10 @@ use super::number_util::random_iv; -use super::payload_encode_v37::encode_with_container as encode_v37; +// use super::payload_encode_v37::encode_with_container as encode_v37; use super::payload_encode_v38::encode_v38; use super::Error; use super::aes_gcm::{aes_decrypt, aes_encrypt}; -use rmp::encode::*; - use std::str; const IV_SIZE: usize = 16; @@ -30,38 +28,38 @@ pub fn encrypt( author_pub_key: Option<&[u8]>, message: &[u8], ) -> Result { + match algr { + Some(value) => { + if value != 2 { + return Err(Error::NotSupportedCurve); + } + } + _ => return Err(Error::NotSupportedCurve), + } + let post_iv = random_iv(IV_SIZE); let post_key_iv = random_iv(AES_KEY_SIZE); - let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, &message)?; + let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, message)?; match version { Version::V37 => Err(Error::NotSupportedCipher), - Version::V38 => { - let output = encode_v38( - target, - network, - author_id, - &post_iv, - &post_key_iv, - &encrypted_message, - author_pub_key, - ) - .map_err(|_| Error::InvalidCiphertext)?; - Ok(output) - } - } -} - -impl From for Error { - fn from(_: ValueWriteError) -> Error { - Error::InvalidCiphertext + Version::V38 => encode_v38( + target, + network, + author_id, + &post_iv, + &post_key_iv, + &encrypted_message, + author_pub_key, + ), } } #[cfg(test)] mod tests { use super::*; + use rmp::encode::*; // content text: "sample text" const ENCODED_MESSAGE: [u8; 18] = [ 146, 0, 148, 1, 1, 192, 171, 115, 97, 109, 112, 108, 101, 32, 116, 101, 120, 116, diff --git a/interface/src/handler/encryption.rs b/interface/src/handler/encryption.rs index 444549f..9501c86 100644 --- a/interface/src/handler/encryption.rs +++ b/interface/src/handler/encryption.rs @@ -8,10 +8,7 @@ pub fn encode(param: PostEncryptionParam) -> MwResponse { 1 => Version::V38, _ => Version::V38, }; - let algr = match param.author_public_key_algr { - Some(algr) => Some(algr as u8), - None => None, - }; + let algr = param.author_public_key_algr.map(|f| f as u8); let result = encrypt( version, Target::Public, @@ -19,7 +16,7 @@ pub fn encode(param: PostEncryptionParam) -> MwResponse { param.author_user_id.as_deref(), algr, param.author_public_key_data.as_deref(), - ¶m.content.as_bytes(), + param.content.as_bytes(), ); match result { From 1722051a4e8e4af5e2ea570dbf6f0c838a1703b1 Mon Sep 17 00:00:00 2001 From: jk234ert Date: Thu, 2 Jun 2022 13:10:16 +0800 Subject: [PATCH 09/12] feat: support e2e encryption v38 --- .vscode/settings.json | 7 +- chain-common/proto/base.proto | 18 ++ chain-common/proto/persona.proto | 23 +- chain-common/proto/post-encryption.proto | 11 + chain-common/src/generated/api.rs | 85 +++--- crypto/src/lib.rs | 6 + crypto/src/payload_encode_v38.rs | 322 ++++++++++++++++++++--- crypto/src/post_encryption.rs | 24 +- interface/src/handler/encryption.rs | 32 ++- interface/src/handler/persona.rs | 17 +- 10 files changed, 437 insertions(+), 108 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d4c0ca5..3a6d502 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,8 @@ "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll": true - } -} + }, + "cSpell.words": [ + "ecdh" + ] +} \ No newline at end of file diff --git a/chain-common/proto/base.proto b/chain-common/proto/base.proto index 9080122..3ceba7d 100644 --- a/chain-common/proto/base.proto +++ b/chain-common/proto/base.proto @@ -51,4 +51,22 @@ message EncryptOption { V38 = 1; } Version version = 1; +} + +message JWK { + string crv = 1; + bool ext = 3; + string x = 4; + string y = 5; + repeated string key_ops = 6; + string kty = 7; + optional string d = 8; +} + +message AesJWK { + string alg = 1; + bool ext = 2; + string k = 3; + repeated string key_ops = 4; + string kty = 5; } \ No newline at end of file diff --git a/chain-common/proto/persona.proto b/chain-common/proto/persona.proto index c9eff94..3edc8ac 100644 --- a/chain-common/proto/persona.proto +++ b/chain-common/proto/persona.proto @@ -15,26 +15,9 @@ message PersonaGenerationParam { message PersonaGenerationResp { string identifier = 1; - JWKResp privateKey = 2; - JWKResp publicKey = 3; - optional AesJWKResp localKey = 4; + JWK privateKey = 2; + JWK publicKey = 3; + optional AesJWK localKey = 4; EncryptOption option = 5; } -message JWKResp { - string crv = 1; - bool ext = 3; - string x = 4; - string y = 5; - repeated string key_ops = 6; - string kty = 7; - optional string d = 8; -} - -message AesJWKResp { - string alg = 1; - bool ext = 2; - string k = 3; - repeated string key_ops = 4; - string kty = 5; -} \ No newline at end of file diff --git a/chain-common/proto/post-encryption.proto b/chain-common/proto/post-encryption.proto index 3d3cc15..ce670a5 100644 --- a/chain-common/proto/post-encryption.proto +++ b/chain-common/proto/post-encryption.proto @@ -17,8 +17,19 @@ message PostEncryptionParam { optional string authorUserId = 4; optional PublicKeyAlgorithm authorPublicKeyAlgr = 5; EncryptOption.Version version = 6; + optional bytes localKeyData = 7; + bool isPlublic = 8; + map target = 9; + optional JWK authorPrivateKey = 10; +} + +message E2EEncryptionResult { + optional bytes iv = 1; + bytes encryptedPostKeyData = 2; + optional bytes ephemeralPublicKeyData = 3; } message PostEncrypedResp { string content = 1; + map results = 2; } \ No newline at end of file diff --git a/chain-common/src/generated/api.rs b/chain-common/src/generated/api.rs index a940989..0bcce60 100644 --- a/chain-common/src/generated/api.rs +++ b/chain-common/src/generated/api.rs @@ -48,6 +48,36 @@ pub mod encrypt_option { V38 = 1, } } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Jwk { + #[prost(string, tag="1")] + pub crv: ::prost::alloc::string::String, + #[prost(bool, tag="3")] + pub ext: bool, + #[prost(string, tag="4")] + pub x: ::prost::alloc::string::String, + #[prost(string, tag="5")] + pub y: ::prost::alloc::string::String, + #[prost(string, repeated, tag="6")] + pub key_ops: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, tag="7")] + pub kty: ::prost::alloc::string::String, + #[prost(string, optional, tag="8")] + pub d: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AesJwk { + #[prost(string, tag="1")] + pub alg: ::prost::alloc::string::String, + #[prost(bool, tag="2")] + pub ext: bool, + #[prost(string, tag="3")] + pub k: ::prost::alloc::string::String, + #[prost(string, repeated, tag="4")] + pub key_ops: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, tag="5")] + pub kty: ::prost::alloc::string::String, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum Coin { @@ -394,45 +424,15 @@ pub struct PersonaGenerationResp { #[prost(string, tag="1")] pub identifier: ::prost::alloc::string::String, #[prost(message, optional, tag="2")] - pub private_key: ::core::option::Option, + pub private_key: ::core::option::Option, #[prost(message, optional, tag="3")] - pub public_key: ::core::option::Option, + pub public_key: ::core::option::Option, #[prost(message, optional, tag="4")] - pub local_key: ::core::option::Option, + pub local_key: ::core::option::Option, #[prost(message, optional, tag="5")] pub option: ::core::option::Option, } #[derive(Clone, PartialEq, ::prost::Message)] -pub struct JwkResp { - #[prost(string, tag="1")] - pub crv: ::prost::alloc::string::String, - #[prost(bool, tag="3")] - pub ext: bool, - #[prost(string, tag="4")] - pub x: ::prost::alloc::string::String, - #[prost(string, tag="5")] - pub y: ::prost::alloc::string::String, - #[prost(string, repeated, tag="6")] - pub key_ops: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(string, tag="7")] - pub kty: ::prost::alloc::string::String, - #[prost(string, optional, tag="8")] - pub d: ::core::option::Option<::prost::alloc::string::String>, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct AesJwkResp { - #[prost(string, tag="1")] - pub alg: ::prost::alloc::string::String, - #[prost(bool, tag="2")] - pub ext: bool, - #[prost(string, tag="3")] - pub k: ::prost::alloc::string::String, - #[prost(string, repeated, tag="4")] - pub key_ops: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(string, tag="5")] - pub kty: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, ::prost::Message)] pub struct PostEncryptionParam { #[prost(string, tag="1")] pub content: ::prost::alloc::string::String, @@ -446,11 +446,30 @@ pub struct PostEncryptionParam { pub author_public_key_algr: ::core::option::Option, #[prost(enumeration="encrypt_option::Version", tag="6")] pub version: i32, + #[prost(bytes="vec", optional, tag="7")] + pub local_key_data: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(bool, tag="8")] + pub is_plublic: bool, + #[prost(map="string, bytes", tag="9")] + pub target: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::vec::Vec>, + #[prost(message, optional, tag="10")] + pub author_private_key: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct E2eEncryptionResult { + #[prost(bytes="vec", optional, tag="1")] + pub iv: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(bytes="vec", tag="2")] + pub encrypted_post_key_data: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", optional, tag="3")] + pub ephemeral_public_key_data: ::core::option::Option<::prost::alloc::vec::Vec>, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct PostEncrypedResp { #[prost(string, tag="1")] pub content: ::prost::alloc::string::String, + #[prost(map="string, message", tag="2")] + pub results: ::std::collections::HashMap<::prost::alloc::string::String, E2eEncryptionResult>, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index c4d3c50..2bbb5d5 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -46,6 +46,8 @@ pub enum Error { NotSupportedCurve, NotSupportedCipher, + + InvalidLocalKey, } impl Error { @@ -64,6 +66,7 @@ impl Error { Error::NotSupportedPublicKeyType => "-3011".to_owned(), Error::NotSupportedCurve => "-3012".to_owned(), Error::NotSupportedCipher => "-3013".to_owned(), + Error::InvalidLocalKey => "-3014".to_owned(), } } @@ -82,6 +85,9 @@ impl Error { Error::NotSupportedPublicKeyType => "Not supported public key type".to_owned(), Error::NotSupportedCurve => "Not supported curve".to_owned(), Error::NotSupportedCipher => "Not supported cipher type".to_owned(), + Error::InvalidLocalKey => { + "Invalid local key. Local key is required to encrypt private message".to_owned() + } } } } diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index a7c961e..ee0dfaa 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -1,11 +1,21 @@ use super::aes_gcm::aes_encrypt; -use super::post_encryption::Target; +use super::number_util::random_iv; +use super::post_encryption::EncryptionResultE2E; use super::Error; -use bitcoin::secp256k1::PublicKey; +use bitcoin::secp256k1::{ecdh, PublicKey, SecretKey}; +use std::collections::HashMap; -use base64::{decode_config, encode_config, STANDARD_NO_PAD, URL_SAFE_NO_PAD}; +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: &'static str = "3Bf8BJ3ZPSMUM2jg2ThODeLuRRD_-_iwQEaeLdcQXpg"; +const E2E_KEY: [u8; 2] = [40, 70]; +const E2E_IV: [u8; 1] = [33]; enum Index { AuthorPublicKey = 5, @@ -14,22 +24,45 @@ enum Index { } pub fn encode_v38( - target: Target, + is_public: bool, network: Option<&str>, author_id: Option<&str>, iv: &[u8], key: &[u8], encrypted: &[u8], author_pub_key: Option<&[u8]>, -) -> Result { - let aes_key_encrypted = encode_aes_key_encrypted(&target, &iv, &key)?; + local_key_data: Option<&[u8]>, + target: HashMap>, + author_private_key: Option<&str>, +) -> Result<(String, Option>), Error> { + let base64_config = STANDARD; + let base64_url_config = URL_SAFE_NO_PAD; + let (aes_key_encrypted, ecdh_result): (String, Option>) = + 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 unwrapped_author_private_key = + author_private_key.ok_or(Error::InvalidPrivateKey)?; + let author_private_key_data = + decode_config(&unwrapped_author_private_key, base64_url_config) + .map_err(|_| 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)) + } + }; - let base64_config = STANDARD_NO_PAD; let encoded_iv = encode_config(&iv, base64_config); let encoded_encrypted = encode_config(&encrypted, base64_config); let signature = "_"; let encoded_fields = encode_fields( - target, + is_public, &aes_key_encrypted, &encoded_iv, &encoded_encrypted, @@ -38,31 +71,35 @@ pub fn encode_v38( author_id, author_pub_key, )?; - Ok(encoded_fields) + Ok((encoded_fields, ecdh_result)) } -fn encode_aes_key_encrypted(target: &Target, iv: &[u8], key: &[u8]) -> Result { - match target { - Target::Public => { - let base64_url_config = URL_SAFE_NO_PAD; - let encoded_aes_key = encode_config(&key, base64_url_config); - let ab = format!( - r#"{{"alg":"A256GCM","ext":true,"k":"{}","key_ops":["decrypt","encrypt"],"kty":"oct"}}"#, - &encoded_aes_key - ); - let ab_bytes = ab.as_bytes(); - let shared_key_bytes = decode_config(&SHARED_KEY_ENCODED, base64_url_config) - .map_err(|_| Error::InvalidCiphertext)?; - let encrypted_key = aes_encrypt(&iv, &shared_key_bytes, &ab_bytes)?; - let base64_config = STANDARD_NO_PAD; - let encoded_key = encode_config(&encrypted_key, base64_config); - Ok(encoded_key) - } - } +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!( + r#"{{"alg":"A256GCM","ext":true,"k":"{}","key_ops":["decrypt","encrypt"],"kty":"oct"}}"#, + &encoded_aes_key + ); + let ab_bytes = ab.as_bytes(); + let shared_key_bytes = decode_config(&SHARED_KEY_ENCODED, base64_url_config) + .map_err(|_| Error::InvalidCiphertext)?; + let encrypted_key = aes_encrypt(&iv, &shared_key_bytes, &ab_bytes)?; + let base64_config = STANDARD; + let encoded_key = encode_config(&encrypted_key, base64_config); + Ok(encoded_key) +} + +fn encrypt_by_local_key( + encoded_post_key: &[u8], + post_iv: &[u8], + local_key_data: &[u8], +) -> Result, Error> { + aes_encrypt(&post_iv, &local_key_data, &encoded_post_key) } fn encode_fields( - target: Target, + is_public: bool, aes_key_encrypted: &str, encoded_iv: &str, encoded_encrypted: &str, @@ -84,7 +121,7 @@ fn encode_fields( let public_key_str = match author_pub_key { Some(key_data) => { - let base64_config = STANDARD_NO_PAD; + let base64_config = STANDARD; let public_key = PublicKey::from_slice(&key_data).map_err(|_| Error::InvalidPrivateKey)?; let compressed_key = public_key.serialize(); @@ -95,16 +132,19 @@ fn encode_fields( }; fields[Index::AuthorPublicKey as usize] = &public_key_str; - match target { - Target::Public => { + match is_public { + true => { fields[Index::PublicShared as usize] = "1"; } + false => { + fields[Index::PublicShared as usize] = "0"; + } } let identity = match (network, author_id) { (Some(network), Some(author_id)) => { let profile_identifier = format!("{}/{}", network, author_id); - let base64_config = STANDARD_NO_PAD; + let base64_config = STANDARD; encode_config(&profile_identifier, base64_config) } _ => "".to_string(), @@ -116,10 +156,83 @@ fn encode_fields( Ok(result) } +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!( + r#"{{"alg":"A256GCM","ext":true,"k":"{}","key_ops":["decrypt","encrypt"],"kty":"oct"}}"#, + &encoded_post_key + ); + result.as_bytes().to_vec() +} + +fn add_receiver( + author_private_key: &[u8], + target: &HashMap>, + encoded_post_key: &[u8], +) -> Result, Error> { + let mut ecdh_result = HashMap::new(); + for (profile_id, receiver_public_key) in target.iter() { + let iv_to_be_published = random_iv(16); + let (aes, iv) = derive_ecdh_and_extra_steps( + &receiver_public_key, + &author_private_key, + &iv_to_be_published, + )?; + let encrypted_post_key = aes_encrypt(&iv, &aes, &encoded_post_key)?; + let result = EncryptionResultE2E { + target: profile_id.to_string(), + iv_to_be_published: Some(iv_to_be_published), + encrypted_post_key, + }; + ecdh_result.insert(profile_id.to_string(), result); + } + Ok(ecdh_result) +} + +fn derive_ecdh_and_extra_steps( + public_key: &[u8], + author_private_key: &[u8], + iv: &[u8], +) -> Result<(Vec, [u8; 16]), Error> { + use sha2::{Digest, Sha256}; + let derive_result = derive_aes_by_ecdh(&public_key, &author_private_key)?; + let mut _a = Vec::new(); + _a.extend(&derive_result); + _a.extend(iv); + + let mut next_key_material_raw = Vec::new(); + next_key_material_raw.extend(&_a); + next_key_material_raw.extend(iv); + next_key_material_raw.extend(E2E_KEY); + let next_aes_key_material = Sha256::digest(&next_key_material_raw); + + let mut iv_pre_raw = Vec::new(); + iv_pre_raw.extend(&_a); + iv_pre_raw.extend(iv); + iv_pre_raw.extend(E2E_IV); + let iv_pre = Sha256::digest(&iv_pre_raw); + + let mut next_iv: [u8; 16] = [0; 16]; + for i in 0..16 { + next_iv[i] = iv_pre[i] ^ iv_pre[16 + i]; + } + Ok((next_aes_key_material.to_vec(), next_iv)) +} + +fn derive_aes_by_ecdh(public_key: &[u8], private_key: &[u8]) -> Result, Error> { + let pub_key = PublicKey::from_slice(&public_key)?; + let sec_key = SecretKey::from_slice(&private_key)?; + let shared_secret = ecdh::SharedSecret::new(&pub_key, &sec_key); + Ok(shared_secret.as_ref().to_vec()) +} + #[cfg(test)] mod tests { use super::*; + use crate::aes_gcm::aes_decrypt; use crate::number_util::random_iv; + use sha2::{Digest, Sha256}; #[test] fn test_encode_aes_key() { let iv = [ @@ -127,7 +240,7 @@ mod tests { ]; let key_iv = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; - let encoded_key = encode_aes_key_encrypted(&Target::Public, &iv, &key_iv).unwrap(); + let encoded_key = encode_aes_key_encrypted(&iv, &key_iv).unwrap(); assert_eq!(encoded_key, "7jLN2yGxMEVM28cIjVlJJ1PBSh6qt3qgUoDL579dssh4EQoxImWZfezILlxTMtoPEFzIN8T369jz2Pai2IzrI9coSAr+V46S91/4Bh2QnlSsWc6B+IZIc/hIWhFKBUeU+5bq/SvBYSVpE5/+C4sIk8beyHIl"); } @@ -146,7 +259,7 @@ mod tests { ]; let encoded_fields = encode_fields( - Target::Public, + true, &aes_key_encrypted, &encoded_iv, &encoded_encrypted, @@ -158,4 +271,145 @@ mod tests { .unwrap(); assert_eq!(encoded_fields, "🎼4/4|8NXbnHHTwNaQlnihC4ov7JiAXIMfjmpP6LZG9SCpsBTGgscJuET25HO0DfkXOmjtepWV5NAGzRn5iFJENjTtIeMmnAaDl7ijSmsIfcS6Gp9wQZZ2yUaAj4S1rN6zCx6uZQNPaVH2kywLfQVZJ+pxNflXmKYgNcw53yG/XKgI7ksqCnwWqiqQyYYS|Q43qdWbDoDbWBca2+LB6lA==|h6WdGLrQ+H2fMJrXVFwKFw+IiQ==|_|AtJrd4w5tCX1flZPKYBrQGON3gZX+V+CxmMBcSlb75jU|1|dHdpdHRlci5jb20veXVhbl9icmFk:||"); } + + #[test] + fn test_ecdh_derive() { + // Check whether the `derive_aes_by_ecdh` method could derive a valid aes key + let test_message = "hello world"; + let test_iv = random_iv(16); + let private_key = vec![ + 164, 220, 24, 245, 162, 159, 141, 176, 18, 151, 248, 162, 174, 140, 138, 146, 6, 126, + 21, 156, 237, 185, 200, 177, 167, 250, 42, 150, 246, 13, 30, 134, + ]; + let public_key = vec![ + 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); + 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(); + assert_eq!(decrypted, test_message.as_bytes()); + } + + #[test] + fn test_derive_ecdh_and_extra_steps() { + let derived_key_raw = [ + 62, 155, 237, 84, 13, 3, 137, 47, 239, 227, 65, 3, 107, 135, 75, 66, 118, 27, 77, 132, + 91, 79, 223, 58, 248, 249, 95, 193, 42, 88, 199, 12, + ]; + let iv = [ + 148, 238, 119, 124, 75, 191, 117, 180, 14, 0, 77, 65, 63, 213, 1, 227, + ]; + let mut _a = Vec::new(); + _a.extend(&derived_key_raw); + _a.extend(iv); + + assert_eq!( + &_a, + &[ + 62, 155, 237, 84, 13, 3, 137, 47, 239, 227, 65, 3, 107, 135, 75, 66, 118, 27, 77, + 132, 91, 79, 223, 58, 248, 249, 95, 193, 42, 88, 199, 12, 148, 238, 119, 124, 75, + 191, 117, 180, 14, 0, 77, 65, 63, 213, 1, 227 + ] + ); + + let mut next_key_material_raw = Vec::new(); + next_key_material_raw.extend(&_a); + next_key_material_raw.extend(iv); + next_key_material_raw.extend(E2E_KEY); + let next_aes_key_material = Sha256::digest(&next_key_material_raw); + assert_eq!( + &next_aes_key_material.to_vec(), + &[ + 74, 12, 205, 110, 243, 104, 194, 172, 14, 90, 45, 147, 214, 168, 127, 97, 242, 39, + 56, 126, 197, 0, 228, 66, 97, 6, 86, 132, 38, 76, 166, 24 + ] + ); + + let mut iv_pre_raw = Vec::new(); + iv_pre_raw.extend(&_a); + iv_pre_raw.extend(iv); + iv_pre_raw.extend(E2E_IV); + let iv_pre = Sha256::digest(&iv_pre_raw); + + assert_eq!( + &iv_pre.to_vec(), + &[ + 194, 208, 143, 170, 171, 60, 198, 137, 133, 142, 75, 252, 168, 127, 65, 229, 41, + 208, 41, 99, 233, 19, 118, 190, 203, 252, 150, 137, 221, 215, 144, 68 + ] + ); + + let mut next_iv: [u8; 16] = [0; 16]; + for i in 0..16 { + next_iv[i] = iv_pre[i] ^ iv_pre[16 + i]; + } + + assert_eq!( + &next_iv, + &[235, 0, 166, 201, 66, 47, 176, 55, 78, 114, 221, 117, 117, 168, 209, 161] + ); + } + + #[test] + fn test_encode_v38() { + let base64_url_config = URL_SAFE_NO_PAD; + let is_public = false; + let network = "twitter.com"; + let author_id = "yuan_brad"; + let iv = [ + 8, 224, 216, 3, 117, 23, 198, 40, 218, 134, 149, 179, 52, 216, 88, 91, + ]; + let aes_key_encoded = "MERv1-yBnsotcyzNG5zHv6WFlfIkGeosp2-UA1U_1Io"; + let aes_key = decode_config(&aes_key_encoded, base64_url_config).unwrap(); + 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, + ]; + + 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(); + + let target_pub_key_x_str = "j3RDjs8gfSBG2kpn5oX67e7CioZxRM1k1uyx7UzHpVU"; + let target_pub_key_y_str = "Q68JL9-pStMOzi3BlM8N8tAkiIY4PZqO7tvDk19sTm0"; + let target_pub_key_x_1 = decode_config(&target_pub_key_x_str, base64_url_config).unwrap(); + let target_pub_key_y_1 = decode_config(&target_pub_key_y_str, base64_url_config).unwrap(); + let target_public_key = [[0x04].to_vec(), target_pub_key_x_1, target_pub_key_y_1].concat(); + assert_eq!(target_public_key.len(), 65); + + let local_key_str = "JzGZnwVX9RKdkAKsrWmNMnzixUZA8I7vaaa2T_tEIT0"; + let local_key = decode_config(&local_key_str, base64_url_config).unwrap(); + let author_private_key_str = "xx96FEmD0_syCDgTu9vZW7doi8dFDwKe59P-a_N2jTg"; + + let author_private_key_data = + decode_config(&author_private_key_str, base64_url_config).unwrap(); + + let sec_key = SecretKey::from_slice(&author_private_key_data).unwrap(); + let pub_key = PublicKey::from_slice(&target_public_key).unwrap(); + let shared_secret = ecdh::SharedSecret::new(&pub_key, &sec_key); + println!("{:?}", &shared_secret); + + let mut target: HashMap> = HashMap::new(); + target.insert("author_id".to_string(), target_public_key); + + let (output, e2e_result) = encode_v38( + is_public, + Some(network), + Some(author_id), + &iv, + &aes_key, + &encrypted_message, + Some(&author_public_key), + Some(&local_key), + target, + Some(author_private_key_str), + ) + .unwrap(); + assert_eq!(output, "🎼4/4|Bwpu5LcIkJkW2IWz1FJSXjso2l312ydbACk0owMXFXC2VUci0I7dK7smPEW/iAXU0v0b6pttFOdPsavNUJl+CSkjHaeKY4pBGdRPVLVX9wTFvha7233bTAh7H8MaOQKAcjMTTPSpiIfXV6z+adQ4ub/GBz13JEEcq1tBWGe14e6KJM0BAlavKA8W|CODYA3UXxijahpWzNNhYWw==|sicrktkUfaAkTjYtZHH9KzGlymq5mw==|_|Ay0N+38oQ+roPtmvcZpXs/Gw9/3jU0J2djv/JUXFjUiO|0|dHdpdHRlci5jb20veXVhbl9icmFk:||"); + } } diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index 77a848f..74dd11b 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -2,6 +2,7 @@ use super::number_util::random_iv; use super::payload_encode_v37::encode_with_container as encode_v37; use super::payload_encode_v38::encode_v38; use super::Error; +use std::collections::HashMap; use super::aes_gcm::{aes_decrypt, aes_encrypt}; @@ -17,19 +18,24 @@ pub enum Version { V38 = -38, } -pub enum Target { - Public, +pub struct EncryptionResultE2E { + pub target: String, + pub encrypted_post_key: Vec, + pub iv_to_be_published: Option>, } pub fn encrypt( version: Version, - target: Target, + is_public: bool, network: Option<&str>, author_id: Option<&str>, algr: Option, author_pub_key: Option<&[u8]>, message: &[u8], -) -> Result { + local_key_data: Option<&[u8]>, + target: HashMap>, + author_private_key: Option<&str>, +) -> Result<(String, Option>), Error> { let post_iv = random_iv(IV_SIZE); let post_key_iv = random_iv(AES_KEY_SIZE); @@ -39,13 +45,16 @@ pub fn encrypt( Version::V37 => Err(Error::NotSupportedCipher), Version::V38 => { let output = encode_v38( - target, + is_public, network, author_id, &post_iv, &post_key_iv, &encrypted_message, author_pub_key, + local_key_data, + target, + author_private_key, ) .map_err(|_| Error::InvalidCiphertext)?; Ok(output) @@ -104,12 +113,15 @@ mod tests { // let output = encrypt( let output = encrypt( Version::V38, - Target::Public, + true, Some(network), Some(author_id), Some(algr), Some(&public_key_data), message.as_bytes(), + None, + HashMap::new(), + None, ) .unwrap(); } diff --git a/interface/src/handler/encryption.rs b/interface/src/handler/encryption.rs index 444549f..b988388 100644 --- a/interface/src/handler/encryption.rs +++ b/interface/src/handler/encryption.rs @@ -1,6 +1,9 @@ -use crypto::post_encryption::{encrypt, Target, Version}; +use crypto::post_encryption::{encrypt, Version}; +use std::collections::HashMap; -use chain_common::api::{mw_response::Response, MwResponse, PostEncrypedResp, PostEncryptionParam}; +use chain_common::api::{ + mw_response::Response, E2eEncryptionResult, MwResponse, PostEncrypedResp, PostEncryptionParam, +}; pub fn encode(param: PostEncryptionParam) -> MwResponse { let version = match param.version { @@ -14,19 +17,40 @@ pub fn encode(param: PostEncryptionParam) -> MwResponse { }; let result = encrypt( version, - Target::Public, + param.is_plublic, param.network.as_deref(), param.author_user_id.as_deref(), algr, param.author_public_key_data.as_deref(), ¶m.content.as_bytes(), + param.local_key_data.as_deref(), + param.target, + param + .author_private_key + .map(|jwk| jwk.d) + .flatten() + .as_deref(), ); match result { - Ok(encrypted_message) => { + Ok((encrypted_message, ecdh_result)) => { // TODO: finish implementation let content = PostEncrypedResp { content: encrypted_message, + results: ecdh_result + .unwrap_or(HashMap::new()) + .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(), }; Response::RespPostEncryption(content).into() } diff --git a/interface/src/handler/persona.rs b/interface/src/handler/persona.rs index e634b2d..36ea977 100644 --- a/interface/src/handler/persona.rs +++ b/interface/src/handler/persona.rs @@ -1,9 +1,8 @@ use std::convert::TryInto; use chain_common::api::{ - encrypt_option::Version, mw_response::Response, Curve, AesJwkResp, - EncryptOption, JwkResp, MwResponse, MwResponseError, PersonaGenerationParam, - PersonaGenerationResp, + encrypt_option::Version, mw_response::Response, AesJwk, Curve, EncryptOption, Jwk, MwResponse, + MwResponseError, PersonaGenerationParam, PersonaGenerationResp, }; use crypto::{jwk::AesJWK, jwk::JWK, pbkdf2, Error}; @@ -60,8 +59,8 @@ impl JWKWrapper { let private_key = self.as_private_key(); let public_key = self.as_public_key(); - let local_key_resp: Option = match local_key { - Some(aes_jwk) => Some(AesJwkResp { + let local_key_resp: Option = match local_key { + Some(aes_jwk) => Some(AesJwk { alg: aes_jwk.alg, ext: aes_jwk.ext, k: aes_jwk.k, @@ -80,8 +79,8 @@ impl JWKWrapper { } } - fn as_public_key(&self) -> JwkResp { - JwkResp { + fn as_public_key(&self) -> Jwk { + Jwk { crv: self.0.crv.clone(), ext: self.0.ext, x: self.0.x.clone(), @@ -92,8 +91,8 @@ impl JWKWrapper { } } - fn as_private_key(&self) -> JwkResp { - JwkResp { + fn as_private_key(&self) -> Jwk { + Jwk { crv: self.0.crv.clone(), ext: self.0.ext, x: self.0.x.clone(), From ad5617cd113b0803724044d7dd1f5ede906f45cd Mon Sep 17 00:00:00 2001 From: jk234ert Date: Thu, 2 Jun 2022 14:59:23 +0800 Subject: [PATCH 10/12] chore: clippy --- crypto/src/payload_encode_v38.rs | 1 + crypto/src/post_encryption.rs | 2 ++ interface/src/handler/encryption.rs | 1 - 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index 30f509c..e7b61fb 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -408,6 +408,7 @@ mod tests { Some(author_private_key_str), ) .unwrap(); + println!("{:?}", e2e_result); assert_eq!(output, "🎼4/4|Bwpu5LcIkJkW2IWz1FJSXjso2l312ydbACk0owMXFXC2VUci0I7dK7smPEW/iAXU0v0b6pttFOdPsavNUJl+CSkjHaeKY4pBGdRPVLVX9wTFvha7233bTAh7H8MaOQKAcjMTTPSpiIfXV6z+adQ4ub/GBz13JEEcq1tBWGe14e6KJM0BAlavKA8W|CODYA3UXxijahpWzNNhYWw==|sicrktkUfaAkTjYtZHH9KzGlymq5mw==|_|Ay0N+38oQ+roPtmvcZpXs/Gw9/3jU0J2djv/JUXFjUiO|0|dHdpdHRlci5jb20veXVhbl9icmFk:||"); } } diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index d055b24..5ebebfa 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -16,6 +16,7 @@ pub enum Version { V38 = -38, } +#[derive(Debug)] pub struct EncryptionResultE2E { pub target: String, pub encrypted_post_key: Vec, @@ -63,6 +64,7 @@ pub fn encrypt( #[cfg(test)] mod tests { use super::*; + use crate::aes_gcm::aes_decrypt; use rmp::encode::*; // content text: "sample text" const ENCODED_MESSAGE: [u8; 18] = [ diff --git a/interface/src/handler/encryption.rs b/interface/src/handler/encryption.rs index b56737b..68f540d 100644 --- a/interface/src/handler/encryption.rs +++ b/interface/src/handler/encryption.rs @@ -1,5 +1,4 @@ use crypto::post_encryption::{encrypt, Version}; -use std::collections::HashMap; use chain_common::api::{ mw_response::Response, E2eEncryptionResult, MwResponse, PostEncrypedResp, PostEncryptionParam, From 62f73030402061070cb9819a675f80861fe8e20a Mon Sep 17 00:00:00 2001 From: jk234ert Date: Wed, 8 Jun 2022 16:30:25 +0800 Subject: [PATCH 11/12] feat: separate e2e param --- chain-common/proto/api.proto | 2 +- chain-common/proto/base.proto | 6 ++++ chain-common/proto/post-encryption.proto | 20 ++++++------ chain-common/src/generated/api.rs | 39 +++++++++++++----------- crypto/src/payload_encode_v38.rs | 10 ++---- crypto/src/post_encryption.rs | 2 +- interface/src/handler/encryption.rs | 19 ++++++------ 7 files changed, 52 insertions(+), 46 deletions(-) diff --git a/chain-common/proto/api.proto b/chain-common/proto/api.proto index ea8a067..f037378 100644 --- a/chain-common/proto/api.proto +++ b/chain-common/proto/api.proto @@ -68,7 +68,7 @@ message MWResponse { PersonaGenerationResp resp_generate_persona = 25; - PostEncrypedResp resp_post_encryption = 26; + PostEncryptedResp resp_post_encryption = 26; } } diff --git a/chain-common/proto/base.proto b/chain-common/proto/base.proto index 3ceba7d..02d5608 100644 --- a/chain-common/proto/base.proto +++ b/chain-common/proto/base.proto @@ -69,4 +69,10 @@ message AesJWK { string k = 3; repeated string key_ops = 4; string kty = 5; +} + +message E2EEncryptParam { + bytes localKeyData = 1; + map target = 2; + bytes authorPrivateKey = 3; } \ No newline at end of file diff --git a/chain-common/proto/post-encryption.proto b/chain-common/proto/post-encryption.proto index ce670a5..a12aeab 100644 --- a/chain-common/proto/post-encryption.proto +++ b/chain-common/proto/post-encryption.proto @@ -11,16 +11,14 @@ enum PublicKeyAlgorithm { } message PostEncryptionParam { - string content = 1; - optional string network = 2; - optional bytes authorPublicKeyData = 3; - optional string authorUserId = 4; - optional PublicKeyAlgorithm authorPublicKeyAlgr = 5; - EncryptOption.Version version = 6; - optional bytes localKeyData = 7; - bool isPlublic = 8; - map target = 9; - optional JWK authorPrivateKey = 10; + EncryptOption.Version version = 1; + bool isPlublic = 2; + string content = 3; + optional string network = 4; + optional bytes authorPublicKeyData = 5; + optional string authorUserId = 6; + optional PublicKeyAlgorithm authorPublicKeyAlgr = 7; + optional E2EEncryptParam param = 8; } message E2EEncryptionResult { @@ -29,7 +27,7 @@ message E2EEncryptionResult { optional bytes ephemeralPublicKeyData = 3; } -message PostEncrypedResp { +message PostEncryptedResp { string content = 1; map results = 2; } \ No newline at end of file diff --git a/chain-common/src/generated/api.rs b/chain-common/src/generated/api.rs index 0bcce60..a5ac33f 100644 --- a/chain-common/src/generated/api.rs +++ b/chain-common/src/generated/api.rs @@ -78,6 +78,15 @@ pub struct AesJwk { #[prost(string, tag="5")] pub kty: ::prost::alloc::string::String, } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct E2eEncryptParam { + #[prost(bytes="vec", tag="1")] + pub local_key_data: ::prost::alloc::vec::Vec, + #[prost(map="string, bytes", tag="2")] + pub target: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::vec::Vec>, + #[prost(bytes="vec", tag="3")] + pub author_private_key: ::prost::alloc::vec::Vec, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum Coin { @@ -434,26 +443,22 @@ pub struct PersonaGenerationResp { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct PostEncryptionParam { - #[prost(string, tag="1")] + #[prost(enumeration="encrypt_option::Version", tag="1")] + pub version: i32, + #[prost(bool, tag="2")] + pub is_plublic: bool, + #[prost(string, tag="3")] pub content: ::prost::alloc::string::String, - #[prost(string, optional, tag="2")] + #[prost(string, optional, tag="4")] pub network: ::core::option::Option<::prost::alloc::string::String>, - #[prost(bytes="vec", optional, tag="3")] + #[prost(bytes="vec", optional, tag="5")] pub author_public_key_data: ::core::option::Option<::prost::alloc::vec::Vec>, - #[prost(string, optional, tag="4")] + #[prost(string, optional, tag="6")] pub author_user_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(enumeration="PublicKeyAlgorithm", optional, tag="5")] + #[prost(enumeration="PublicKeyAlgorithm", optional, tag="7")] pub author_public_key_algr: ::core::option::Option, - #[prost(enumeration="encrypt_option::Version", tag="6")] - pub version: i32, - #[prost(bytes="vec", optional, tag="7")] - pub local_key_data: ::core::option::Option<::prost::alloc::vec::Vec>, - #[prost(bool, tag="8")] - pub is_plublic: bool, - #[prost(map="string, bytes", tag="9")] - pub target: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::vec::Vec>, - #[prost(message, optional, tag="10")] - pub author_private_key: ::core::option::Option, + #[prost(message, optional, tag="8")] + pub param: ::core::option::Option, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct E2eEncryptionResult { @@ -465,7 +470,7 @@ pub struct E2eEncryptionResult { pub ephemeral_public_key_data: ::core::option::Option<::prost::alloc::vec::Vec>, } #[derive(Clone, PartialEq, ::prost::Message)] -pub struct PostEncrypedResp { +pub struct PostEncryptedResp { #[prost(string, tag="1")] pub content: ::prost::alloc::string::String, #[prost(map="string, message", tag="2")] @@ -575,7 +580,7 @@ pub mod mw_response { #[prost(message, tag="25")] RespGeneratePersona(super::PersonaGenerationResp), #[prost(message, tag="26")] - RespPostEncryption(super::PostEncrypedResp), + RespPostEncryption(super::PostEncryptedResp), } } #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index e7b61fb..f683bfe 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -33,7 +33,7 @@ pub fn encode_v38( author_pub_key: Option<&[u8]>, local_key_data: Option<&[u8]>, target: HashMap>, - author_private_key: Option<&str>, + author_private_key: Option<&[u8]>, ) -> Result<(String, Option>), Error> { let base64_config = STANDARD; let base64_url_config = URL_SAFE_NO_PAD; @@ -45,11 +45,7 @@ pub fn encode_v38( let post_key_encoded = encode_post_key(key); let owners_aes_key_encrypted = encrypt_by_local_key(&post_key_encoded, iv, local_key)?; - let unwrapped_author_private_key = - author_private_key.ok_or(Error::InvalidPrivateKey)?; - let author_private_key_data = - decode_config(&unwrapped_author_private_key, base64_url_config) - .map_err(|_| Error::InvalidPrivateKey)?; + 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 = @@ -405,7 +401,7 @@ mod tests { Some(&author_public_key), Some(&local_key), target, - Some(author_private_key_str), + Some(&author_private_key_data), ) .unwrap(); println!("{:?}", e2e_result); diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index 5ebebfa..1253905 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -33,7 +33,7 @@ pub fn encrypt( message: &[u8], local_key_data: Option<&[u8]>, target: HashMap>, - author_private_key: Option<&str>, + author_private_key: Option<&[u8]>, ) -> Result<(String, Option>), Error> { let post_iv = random_iv(IV_SIZE); let post_key_iv = random_iv(AES_KEY_SIZE); diff --git a/interface/src/handler/encryption.rs b/interface/src/handler/encryption.rs index 68f540d..3548fdf 100644 --- a/interface/src/handler/encryption.rs +++ b/interface/src/handler/encryption.rs @@ -1,7 +1,9 @@ +use std::collections::HashMap; + use crypto::post_encryption::{encrypt, Version}; use chain_common::api::{ - mw_response::Response, E2eEncryptionResult, MwResponse, PostEncrypedResp, PostEncryptionParam, + mw_response::Response, E2eEncryptionResult, MwResponse, PostEncryptedResp, PostEncryptionParam, }; pub fn encode(param: PostEncryptionParam) -> MwResponse { @@ -11,6 +13,9 @@ pub fn encode(param: PostEncryptionParam) -> MwResponse { _ => Version::V38, }; let algr = param.author_public_key_algr.map(|f| f as u8); + let local_key = param.param.as_ref().map(|x| x.local_key_data.clone()); + let author_private_key = param.param.as_ref().map(|x| x.author_private_key.clone()); + let target = param.param.map_or(HashMap::new(), |x| x.target); let result = encrypt( version, param.is_plublic, @@ -19,19 +24,15 @@ pub fn encode(param: PostEncryptionParam) -> MwResponse { algr, param.author_public_key_data.as_deref(), param.content.as_bytes(), - param.local_key_data.as_deref(), - param.target, - param - .author_private_key - .map(|jwk| jwk.d) - .flatten() - .as_deref(), + local_key.as_deref(), + target, + author_private_key.as_deref(), ); match result { Ok((encrypted_message, ecdh_result)) => { // TODO: finish implementation - let content = PostEncrypedResp { + let content = PostEncryptedResp { content: encrypted_message, results: ecdh_result .unwrap_or_default() From 6aa5724532cbaf67f0e685c432a410d0469a90c0 Mon Sep 17 00:00:00 2001 From: jk234ert Date: Tue, 14 Jun 2022 13:56:55 +0800 Subject: [PATCH 12/12] feat: optimize param type --- chain-common/proto/post-encryption.proto | 6 ++- chain-common/src/generated/api.rs | 10 ++-- crypto/src/payload_encode_v38.rs | 15 +++--- crypto/src/post_encryption.rs | 59 +++++++++++++++--------- interface/src/handler/encryption.rs | 11 +++-- 5 files changed, 62 insertions(+), 39 deletions(-) diff --git a/chain-common/proto/post-encryption.proto b/chain-common/proto/post-encryption.proto index a12aeab..44eee5a 100644 --- a/chain-common/proto/post-encryption.proto +++ b/chain-common/proto/post-encryption.proto @@ -14,7 +14,7 @@ message PostEncryptionParam { EncryptOption.Version version = 1; bool isPlublic = 2; string content = 3; - optional string network = 4; + string network = 4; optional bytes authorPublicKeyData = 5; optional string authorUserId = 6; optional PublicKeyAlgorithm authorPublicKeyAlgr = 7; @@ -29,5 +29,7 @@ message E2EEncryptionResult { message PostEncryptedResp { string content = 1; - map results = 2; + string postIdentifier = 2; + bytes postKey = 3; + map results = 4; } \ No newline at end of file diff --git a/chain-common/src/generated/api.rs b/chain-common/src/generated/api.rs index a5ac33f..cea48ce 100644 --- a/chain-common/src/generated/api.rs +++ b/chain-common/src/generated/api.rs @@ -449,8 +449,8 @@ pub struct PostEncryptionParam { pub is_plublic: bool, #[prost(string, tag="3")] pub content: ::prost::alloc::string::String, - #[prost(string, optional, tag="4")] - pub network: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, tag="4")] + pub network: ::prost::alloc::string::String, #[prost(bytes="vec", optional, tag="5")] pub author_public_key_data: ::core::option::Option<::prost::alloc::vec::Vec>, #[prost(string, optional, tag="6")] @@ -473,7 +473,11 @@ pub struct E2eEncryptionResult { pub struct PostEncryptedResp { #[prost(string, tag="1")] pub content: ::prost::alloc::string::String, - #[prost(map="string, message", tag="2")] + #[prost(string, tag="2")] + pub post_identifier: ::prost::alloc::string::String, + #[prost(bytes="vec", tag="3")] + pub post_key: ::prost::alloc::vec::Vec, + #[prost(map="string, message", tag="4")] pub results: ::std::collections::HashMap<::prost::alloc::string::String, E2eEncryptionResult>, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] diff --git a/crypto/src/payload_encode_v38.rs b/crypto/src/payload_encode_v38.rs index f683bfe..bc047fd 100644 --- a/crypto/src/payload_encode_v38.rs +++ b/crypto/src/payload_encode_v38.rs @@ -25,7 +25,7 @@ enum Index { pub fn encode_v38( is_public: bool, - network: Option<&str>, + network: &str, author_id: Option<&str>, iv: &[u8], key: &[u8], @@ -36,7 +36,6 @@ pub fn encode_v38( author_private_key: Option<&[u8]>, ) -> Result<(String, Option>), Error> { let base64_config = STANDARD; - let base64_url_config = URL_SAFE_NO_PAD; let (aes_key_encrypted, ecdh_result): (String, Option>) = match is_public { true => (encode_aes_key_encrypted(iv, key)?, None), @@ -47,7 +46,7 @@ pub fn encode_v38( encrypt_by_local_key(&post_key_encoded, iv, local_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)?; + 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)) @@ -100,7 +99,7 @@ fn encode_fields( encoded_iv: &str, encoded_encrypted: &str, signature: &str, - network: Option<&str>, + network: &str, author_id: Option<&str>, author_pub_key: Option<&[u8]>, ) -> Result { @@ -136,8 +135,8 @@ fn encode_fields( } } - let identity = match (network, author_id) { - (Some(network), Some(author_id)) => { + let identity = match author_id { + Some(author_id) => { let profile_identifier = format!("{}/{}", network, author_id); let base64_config = STANDARD; encode_config(&profile_identifier, base64_config) @@ -258,7 +257,7 @@ mod tests { &encoded_iv, &encoded_encrypted, &signature, - Some(network), + network, Some(author_id), Some(&public_key_data), ) @@ -393,7 +392,7 @@ mod tests { let (output, e2e_result) = encode_v38( is_public, - Some(network), + network, Some(author_id), &iv, &aes_key, diff --git a/crypto/src/post_encryption.rs b/crypto/src/post_encryption.rs index 1253905..a9fd6c1 100644 --- a/crypto/src/post_encryption.rs +++ b/crypto/src/post_encryption.rs @@ -2,6 +2,7 @@ use super::number_util::random_iv; // use super::payload_encode_v37::encode_with_container as encode_v37; use super::payload_encode_v38::encode_v38; use super::Error; +use base64::{encode_config, STANDARD}; use std::collections::HashMap; use super::aes_gcm::aes_encrypt; @@ -23,10 +24,17 @@ pub struct EncryptionResultE2E { pub iv_to_be_published: Option>, } +pub struct EncryptionResult { + pub output: String, + pub post_key: Vec, + pub post_identifier: String, + pub e2e_result: Option>, +} + pub fn encrypt( version: Version, is_public: bool, - network: Option<&str>, + network: &str, author_id: Option<&str>, _algr: Option, author_pub_key: Option<&[u8]>, @@ -34,31 +42,38 @@ pub fn encrypt( local_key_data: Option<&[u8]>, target: HashMap>, author_private_key: Option<&[u8]>, -) -> Result<(String, Option>), Error> { +) -> Result { let post_iv = random_iv(IV_SIZE); let post_key_iv = random_iv(AES_KEY_SIZE); let encrypted_message = aes_encrypt(&post_iv, &post_key_iv, message)?; - match version { + let result = match version { Version::V37 => Err(Error::NotSupportedCipher), - Version::V38 => { - let output = encode_v38( - is_public, - network, - author_id, - &post_iv, - &post_key_iv, - &encrypted_message, - author_pub_key, - local_key_data, - target, - author_private_key, - ) - .map_err(|_| Error::InvalidCiphertext)?; - Ok(output) - } - } + Version::V38 => encode_v38( + is_public, + network, + author_id, + &post_iv, + &post_key_iv, + &encrypted_message, + author_pub_key, + local_key_data, + target, + author_private_key, + ) + .map_err(|_| Error::InvalidCiphertext), + }?; + + let encoded_post_iv = encode_config(&post_iv, STANDARD).replace("/", "|"); + let post_identifier = format!("post_iv:{}/{}", &network, &encoded_post_iv); + + Ok(EncryptionResult { + output: result.0, + post_key: post_key_iv, + post_identifier: post_identifier, + e2e_result: result.1, + }) } #[cfg(test)] @@ -106,10 +121,10 @@ mod tests { 6, 87, 249, 95, 130, 198, 99, 1, 113, 41, 91, 239, 152, 212, ]; // let output = encrypt( - let output = encrypt( + let encryption_result = encrypt( Version::V38, true, - Some(network), + network, Some(author_id), Some(algr), Some(&public_key_data), diff --git a/interface/src/handler/encryption.rs b/interface/src/handler/encryption.rs index 3548fdf..86f8703 100644 --- a/interface/src/handler/encryption.rs +++ b/interface/src/handler/encryption.rs @@ -19,7 +19,7 @@ pub fn encode(param: PostEncryptionParam) -> MwResponse { let result = encrypt( version, param.is_plublic, - param.network.as_deref(), + ¶m.network, param.author_user_id.as_deref(), algr, param.author_public_key_data.as_deref(), @@ -30,11 +30,12 @@ pub fn encode(param: PostEncryptionParam) -> MwResponse { ); match result { - Ok((encrypted_message, ecdh_result)) => { + Ok(encryption_result) => { // TODO: finish implementation let content = PostEncryptedResp { - content: encrypted_message, - results: ecdh_result + content: encryption_result.output, + results: encryption_result + .e2e_result .unwrap_or_default() .into_iter() .map(|(k, v)| { @@ -48,6 +49,8 @@ pub fn encode(param: PostEncryptionParam) -> MwResponse { ) }) .collect(), + post_identifier: encryption_result.post_identifier, + post_key: encryption_result.post_key, }; Response::RespPostEncryption(content).into() }