From 0a4493228690994f67eb43a7c2e983c1d93664a8 Mon Sep 17 00:00:00 2001 From: chefsale Date: Fri, 25 Oct 2024 12:58:03 +0200 Subject: [PATCH] feat: introduce a secret key for encryption type into context identity --- Cargo.lock | 13 ++ Cargo.toml | 3 + crates/context/src/lib.rs | 14 ++- crates/crypto/Cargo.toml | 18 +++ crates/crypto/src/lib.rs | 127 ++++++++++++++++++++ crates/node/Cargo.toml | 1 + crates/node/src/interactive_cli/identity.rs | 2 +- crates/node/src/lib.rs | 1 + crates/node/src/types.rs | 10 ++ crates/store/src/types/context.rs | 8 ++ 10 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 crates/crypto/Cargo.toml create mode 100644 crates/crypto/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 10f0db784..72d45b173 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -909,6 +909,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "calimero-crypto" +version = "0.1.0" +dependencies = [ + "curve25519-dalek", + "ed25519-dalek", + "eyre", + "rand 0.8.5", + "ring 0.17.8", + "serde", +] + [[package]] name = "calimero-network" version = "0.1.0" @@ -937,6 +949,7 @@ dependencies = [ "borsh", "calimero-blobstore", "calimero-context", + "calimero-crypto", "calimero-network", "calimero-node-primitives", "calimero-primitives", diff --git a/Cargo.toml b/Cargo.toml index cd1b12dbe..364f559ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "./crates/config", "./crates/context", "./crates/context/config", + "./crates/crypto", "./crates/meroctl", "./crates/merod", "./crates/network", @@ -52,6 +53,7 @@ claims = "0.7.1" clap = "4.4.18" color-eyre = "0.6.2" const_format = "0.2.32" +curve25519-dalek = "4.1.3" dirs = "5.0.1" ed25519-dalek = "2.1.1" either = "1.13.0" @@ -88,6 +90,7 @@ quote = "1.0" rand = "0.8.5" rand_chacha = "0.3.1" reqwest = "0.12.2" +ring = "0.17.8" rocksdb = "0.22.0" rust-embed = "8.5.0" sha2 = "0.10.8" diff --git a/crates/context/src/lib.rs b/crates/context/src/lib.rs index 4b500f8d0..42177ba58 100644 --- a/crates/context/src/lib.rs +++ b/crates/context/src/lib.rs @@ -135,7 +135,7 @@ impl ContextManager { } #[must_use] - pub fn new_identity(&self) -> PrivateKey { + pub fn new_private_key(&self) -> PrivateKey { PrivateKey::random(&mut rand::thread_rng()) } @@ -156,7 +156,7 @@ impl ContextManager { None => PrivateKey::random(&mut rng), }; - let identity_secret = identity_secret.unwrap_or_else(|| self.new_identity()); + let identity_secret = identity_secret.unwrap_or_else(|| self.new_private_key()); (context_secret, identity_secret) }; @@ -271,6 +271,7 @@ impl ContextManager { &ContextIdentityKey::new(context.id, identity_secret.public_key()), &ContextIdentityValue { private_key: Some(*identity_secret), + sender_key: Some(*self.new_private_key()), }, )?; @@ -334,7 +335,13 @@ impl ContextManager { let key = ContextIdentityKey::new(context_id, member); if !handle.has(&key)? { - handle.put(&key, &ContextIdentityValue { private_key: None })?; + handle.put( + &key, + &ContextIdentityValue { + private_key: None, + sender_key: None, + }, + )?; } } } @@ -404,6 +411,7 @@ impl ContextManager { let Some(ContextIdentityValue { private_key: Some(requester_secret), + sender_key: None, }) = handle.get(&ContextIdentityKey::new(context_id, inviter_id))? else { return Ok(None); diff --git a/crates/crypto/Cargo.toml b/crates/crypto/Cargo.toml new file mode 100644 index 000000000..f6c71fe24 --- /dev/null +++ b/crates/crypto/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "calimero-crypto" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +curve25519-dalek.workspace = true +ed25519-dalek = { workspace = true, features = ["rand_core"] } +eyre.workspace = true +serde.workspace = true +rand.workspace = true +ring.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/crypto/src/lib.rs b/crates/crypto/src/lib.rs new file mode 100644 index 000000000..d88cfb763 --- /dev/null +++ b/crates/crypto/src/lib.rs @@ -0,0 +1,127 @@ +use rand as _; +use ring::aead; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct SharedKey { + key: ed25519_dalek::SecretKey, +} + +#[derive(Debug)] +pub struct Record { + pub token: Vec, + pub nonce: [u8; aead::NONCE_LEN], +} + +impl SharedKey { + pub fn new(sk: &ed25519_dalek::SigningKey, pk: &ed25519_dalek::VerifyingKey) -> Self { + SharedKey { + key: (sk.to_scalar() + * curve25519_dalek::edwards::CompressedEdwardsY(pk.to_bytes()) + .decompress() + .expect("pk should be guaranteed to be the y coordinate")) + .compress() + .to_bytes(), + } + } + + pub fn encrypt( + &self, + token: Vec, + nonce: [u8; aead::NONCE_LEN], + ) -> eyre::Result, ()> { + let encryption_key = + aead::LessSafeKey::new(aead::UnboundKey::new(&aead::AES_256_GCM, &self.key).unwrap()); + + let mut encrypted_token = token; + encryption_key + .seal_in_place_append_tag( + aead::Nonce::assume_unique_for_key(nonce), + aead::Aad::empty(), + &mut encrypted_token, + ) + .expect("failed to encrypt token"); + + Ok(encrypted_token) + } + + pub fn decrypt( + &self, + token: Vec, + nonce: [u8; aead::NONCE_LEN], + ) -> eyre::Result, ()> { + let mut decrypted_token = token; + let decryption_key = + aead::LessSafeKey::new(aead::UnboundKey::new(&aead::AES_256_GCM, &self.key).unwrap()); + + let decrypted_len = decryption_key + .open_in_place( + aead::Nonce::assume_unique_for_key(nonce), + aead::Aad::empty(), + &mut decrypted_token, + ) + .expect("failed to decrypt token") + .len(); + + decrypted_token.truncate(decrypted_len); + + Ok(decrypted_token) + } +} + +#[cfg(test)] +mod tests { + + use ed25519_dalek::SigningKey; + use rand::rngs::OsRng; + + use super::*; + + #[test] + fn test_encrypt_decrypt() -> eyre::Result<(), eyre::ErrReport> { + let mut csprng = OsRng {}; + let signer = SigningKey::generate(&mut csprng); + let verifier = SigningKey::generate(&mut csprng); + let signer_shared_key = SharedKey::new(&signer, &verifier.verifying_key()); + let verifier_shared_key = SharedKey::new(&verifier, &signer.verifying_key()); + + let token = b"privacy is important".to_vec(); + let nonce = [0u8; aead::NONCE_LEN]; + + let encrypted_token = signer_shared_key + .encrypt(token.clone(), nonce) + .expect("encryption failed"); + + let decrypted_token = verifier_shared_key.decrypt(encrypted_token, nonce); + + let decrypted_token = decrypted_token.unwrap(); + assert_eq!(decrypted_token, token); + assert_ne!(decrypted_token, b"privacy is not important".to_vec()); + + Ok(()) + } + + #[test] + fn test_decrypt_with_invalid_key() -> eyre::Result<(), eyre::ErrReport> { + let mut csprng = OsRng {}; + let signer = SigningKey::generate(&mut csprng); + let verifier = SigningKey::generate(&mut csprng); + let invalid = SigningKey::generate(&mut csprng); + + let signer_shared_key = SharedKey::new(&signer, &verifier.verifying_key()); + let invalid_shared_key = SharedKey::new(&invalid, &invalid.verifying_key()); + + let token = b"privacy is important".to_vec(); + let nonce = [0u8; aead::NONCE_LEN]; + + let encrypted_token = signer_shared_key + .encrypt(token.clone(), nonce) + .expect("encryption failed"); + + let decrypted_data = invalid_shared_key.decrypt(encrypted_token, nonce); + + assert!(decrypted_data.is_err()); + + Ok(()) + } +} diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 690a96e26..64f995543 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -23,6 +23,7 @@ tracing.workspace = true url.workspace = true calimero-context = { path = "../context" } +calimero-crypto = { path = "../crypto" } calimero-blobstore = { path = "../store/blobs" } calimero-network = { path = "../network" } calimero-node-primitives = { path = "../node-primitives" } diff --git a/crates/node/src/interactive_cli/identity.rs b/crates/node/src/interactive_cli/identity.rs index a1be72441..edc56b68b 100644 --- a/crates/node/src/interactive_cli/identity.rs +++ b/crates/node/src/interactive_cli/identity.rs @@ -63,7 +63,7 @@ impl IdentityCommand { } IdentitySubcommands::New => { // Handle the "new" subcommand - let identity = node.ctx_manager.new_identity(); + let identity = node.ctx_manager.new_private_key(); println!("Private Key: {}", identity.cyan()); println!("Public Key: {}", identity.public_key().cyan()); } diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs index 3814d98d6..8e35ce750 100644 --- a/crates/node/src/lib.rs +++ b/crates/node/src/lib.rs @@ -353,6 +353,7 @@ impl Node { } Ok(()) } + PeerAction::RequestSenderKey(request_sender_key_message) => todo!(), } } diff --git a/crates/node/src/types.rs b/crates/node/src/types.rs index 5f52b8afc..bc8c55d44 100644 --- a/crates/node/src/types.rs +++ b/crates/node/src/types.rs @@ -1,3 +1,4 @@ +use calimero_crypto::SharedKey; use calimero_primitives::application::ApplicationId; use calimero_primitives::context::ContextId; use calimero_primitives::hash::Hash; @@ -13,6 +14,7 @@ use thiserror::Error as ThisError; pub enum PeerAction { ActionList(ActionMessage), Sync(SyncMessage), + RequestSenderKey(RequestSenderKeyMessage), } #[derive(Debug, Deserialize, Serialize)] @@ -79,3 +81,11 @@ pub struct SyncMessage { pub public_key: PublicKey, pub root_hash: Hash, } + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct RequestSenderKeyMessage { + pub context_id: ContextId, + pub public_key: PublicKey, + pub shared_key: SharedKey, +} diff --git a/crates/store/src/types/context.rs b/crates/store/src/types/context.rs index 12b8e8733..96fdd2237 100644 --- a/crates/store/src/types/context.rs +++ b/crates/store/src/types/context.rs @@ -75,6 +75,13 @@ impl AsRef<[u8]> for ContextState<'_> { } } +/* + if private_key is Some(_), we own this identity + if we own the identity and sender_key is Some(_) we can encrypt all network messages using this key + if we don't own the identity, and sender_key is Some(_), we can decrypt any message received from this user + if sender_key is None, ask the user for their sender_key + // TODO: implement methods to make this easier to understand and use +*/ #[derive(BorshDeserialize, BorshSerialize, Clone, Copy, Debug, Eq, PartialEq)] #[expect( clippy::exhaustive_structs, @@ -82,6 +89,7 @@ impl AsRef<[u8]> for ContextState<'_> { )] pub struct ContextIdentity { pub private_key: Option<[u8; 32]>, + pub sender_key: Option<[u8; 32]>, } impl PredefinedEntry for ContextIdentityKey {