Skip to content

Commit

Permalink
feat: introduce a secret key for encryption type into context identity
Browse files Browse the repository at this point in the history
  • Loading branch information
chefsale committed Oct 25, 2024
1 parent fd4fdc9 commit 0a44932
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 4 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"./crates/config",
"./crates/context",
"./crates/context/config",
"./crates/crypto",
"./crates/meroctl",
"./crates/merod",
"./crates/network",
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
14 changes: 11 additions & 3 deletions crates/context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

Expand All @@ -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)
};
Expand Down Expand Up @@ -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()),
},
)?;

Expand Down Expand Up @@ -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,
},
)?;
}
}
}
Expand Down Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions crates/crypto/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
127 changes: 127 additions & 0 deletions crates/crypto/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
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<u8>,
nonce: [u8; aead::NONCE_LEN],
) -> eyre::Result<Vec<u8>, ()> {
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<u8>,
nonce: [u8; aead::NONCE_LEN],
) -> eyre::Result<Vec<u8>, ()> {
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(())
}
}
1 change: 1 addition & 0 deletions crates/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
2 changes: 1 addition & 1 deletion crates/node/src/interactive_cli/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
1 change: 1 addition & 0 deletions crates/node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ impl Node {
}
Ok(())
}
PeerAction::RequestSenderKey(request_sender_key_message) => todo!(),
}
}

Expand Down
10 changes: 10 additions & 0 deletions crates/node/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use calimero_crypto::SharedKey;
use calimero_primitives::application::ApplicationId;
use calimero_primitives::context::ContextId;
use calimero_primitives::hash::Hash;
Expand All @@ -13,6 +14,7 @@ use thiserror::Error as ThisError;
pub enum PeerAction {
ActionList(ActionMessage),
Sync(SyncMessage),
RequestSenderKey(RequestSenderKeyMessage),
}

#[derive(Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -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,
}
8 changes: 8 additions & 0 deletions crates/store/src/types/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,21 @@ 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,
reason = "This is not expected to have additional fields"
)]
pub struct ContextIdentity {
pub private_key: Option<[u8; 32]>,
pub sender_key: Option<[u8; 32]>,
}

impl PredefinedEntry for ContextIdentityKey {
Expand Down

0 comments on commit 0a44932

Please sign in to comment.