Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce a secret key for encryption type into context identity #863

Merged
merged 39 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
38fb2c9
feat: introduce a secret key for encryption type into context identity
chefsale Oct 25, 2024
592e4ff
fix tests
miraclx Oct 25, 2024
bfffe65
feat: introduce a secret key for encryption type into context identity
chefsale Oct 25, 2024
9d35b3c
fix tests
miraclx Oct 25, 2024
3f93c7f
Rebased on master
petarjuki7 Nov 5, 2024
e108eb3
Rebase
petarjuki7 Nov 5, 2024
0fb78fb
make compile
petarjuki7 Nov 5, 2024
f8947a2
Merge branch 'master' into introduce-encryption-key-broadcast2
petarjuki7 Nov 5, 2024
fdbec1b
Added get sender key
petarjuki7 Nov 7, 2024
78b28b0
Merge branch 'introduce-encryption-key-broadcast2' of github.com:cali…
petarjuki7 Nov 7, 2024
43562e4
merge master
petarjuki7 Nov 7, 2024
2d79779
Usage of SharedKeys
petarjuki7 Nov 7, 2024
579111b
Using PublicKey instead of dalek versions
petarjuki7 Nov 7, 2024
9de785d
Use SendingKey where appropriate
petarjuki7 Nov 7, 2024
423dc45
Merge branch 'master' into introduce-encryption-key-broadcast2
petarjuki7 Nov 7, 2024
c569183
Comment fixes
petarjuki7 Nov 8, 2024
d6c6afe
Merge branch 'introduce-encryption-key-broadcast2' of github.com:cali…
petarjuki7 Nov 8, 2024
c891866
Construct new sender key in initialization
petarjuki7 Nov 8, 2024
0feaf62
Fixed comments#
petarjuki7 Nov 10, 2024
9d0f433
Merge branch 'master' into introduce-encryption-key-broadcast2
petarjuki7 Nov 10, 2024
7e5c7d1
Fix merge issues
petarjuki7 Nov 10, 2024
6833399
Using PrivateKey instead of SenderKey
petarjuki7 Nov 11, 2024
d2394e9
KeyShare process
petarjuki7 Nov 12, 2024
ee2e576
Update SenderKey
petarjuki7 Nov 12, 2024
b915d70
Init handshake
petarjuki7 Nov 12, 2024
aaa794c
Merge branch 'master' into introduce-encryption-key-broadcast2
petarjuki7 Nov 12, 2024
da327c3
Remove comments
petarjuki7 Nov 12, 2024
6e9915b
remove unrelated code
petarjuki7 Nov 12, 2024
cb812dd
cleanup
petarjuki7 Nov 12, 2024
b3175b9
Update lib.rs
petarjuki7 Nov 12, 2024
ded8cbf
Merge branch 'master' into introduce-encryption-key-broadcast2
petarjuki7 Nov 13, 2024
f8a4826
Added sequencing
petarjuki7 Nov 13, 2024
aa063e2
fix key derivation
miraclx Nov 13, 2024
af59c24
Bidirectional sync method
petarjuki7 Nov 13, 2024
d833c87
further deduplicate
miraclx Nov 13, 2024
c2e2b6c
remove ProposalRequest
miraclx Nov 13, 2024
025ecd3
fix clippy
miraclx Nov 13, 2024
6033af0
remove serde on calimero-crypto
miraclx Nov 13, 2024
a7087f5
remove serde on calimero-node
miraclx Nov 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 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 @@ -56,6 +57,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 @@ -92,6 +94,7 @@ quote = "1.0.37"
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
46 changes: 43 additions & 3 deletions crates/context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,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 Down Expand Up @@ -175,7 +175,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 @@ -312,6 +312,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 @@ -400,6 +401,7 @@ impl ContextManager {

let Some(ContextIdentityValue {
private_key: Some(requester_secret),
..
}) = handle.get(&ContextIdentityKey::new(context_id, inviter_id))?
else {
return Ok(None);
Expand Down Expand Up @@ -498,7 +500,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: Some(*self.new_private_key()),
},
)?;
}
}
}
Expand Down Expand Up @@ -778,6 +786,35 @@ impl ContextManager {
Ok(ids)
}

pub fn get_sender_key(
&self,
context_id: &ContextId,
public_key: &PublicKey,
) -> EyreResult<Option<PrivateKey>> {
let handle = self.store.handle();
let key = handle
.get(&ContextIdentityKey::new(*context_id, *public_key))?
.and_then(|ctx_identity| ctx_identity.sender_key);

Ok(key.map(PrivateKey::from))
}

pub fn get_private_key(
&self,
context_id: ContextId,
public_key: PublicKey,
) -> EyreResult<Option<PrivateKey>> {
let handle = self.store.handle();

let key = ContextIdentityKey::new(context_id, public_key);

let Some(value) = handle.get(&key)? else {
return Ok(None);
};

Ok(value.private_key.map(PrivateKey::from))
}

pub fn get_context_members_identities(
&self,
context_id: ContextId,
Expand Down Expand Up @@ -854,6 +891,7 @@ impl ContextManager {

let Some(ContextIdentityValue {
private_key: Some(requester_secret),
..
}) = handle.get(&ContextIdentityKey::new(context_id, signer_id))?
else {
bail!("'{}' is not a member of '{}'", signer_id, context_id)
Expand Down Expand Up @@ -1109,6 +1147,7 @@ impl ContextManager {

let Some(ContextIdentityValue {
private_key: Some(signing_key),
..
}) = handle.get(&ContextIdentityKey::new(context_id, signer_id))?
else {
bail!("No private key found for signer");
Expand Down Expand Up @@ -1146,6 +1185,7 @@ impl ContextManager {

let Some(ContextIdentityValue {
private_key: Some(signing_key),
..
}) = handle.get(&ContextIdentityKey::new(context_id, signer_id))?
else {
bail!("No private key found for signer");
Expand Down
22 changes: 22 additions & 0 deletions crates/crypto/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[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"] }
ring.workspace = true
serde = { workspace = true, features = ["derive"] }

calimero-primitives = { path = "../primitives", features = ["rand"] }

[dev-dependencies]
eyre.workspace = true
rand.workspace = true

[lints]
workspace = true
126 changes: 126 additions & 0 deletions crates/crypto/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use calimero_primitives::identity::{PrivateKey, PublicKey};
use ed25519_dalek::{SecretKey, SigningKey};
use ring::aead;
use serde::{Deserialize, Serialize};

#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct SharedKey {
miraclx marked this conversation as resolved.
Show resolved Hide resolved
key: SecretKey,
}

#[derive(Debug)]
pub struct Record {
pub token: Vec<u8>,
pub nonce: [u8; 12],
}

impl SharedKey {
pub fn new(sk: &PrivateKey, pk: &PublicKey) -> Self {
SharedKey {
key: (SigningKey::from_bytes(sk).to_scalar()
* curve25519_dalek::edwards::CompressedEdwardsY(**pk)
.decompress()
.expect("pk should be guaranteed to be the y coordinate"))
.compress()
.to_bytes(),
}
}

pub fn from_sk(sk: &PrivateKey) -> Self {
SharedKey { key: **sk }
}

pub fn encrypt(&self, payload: Vec<u8>, nonce: [u8; 12]) -> Option<Vec<u8>> {
let encryption_key =
aead::LessSafeKey::new(aead::UnboundKey::new(&aead::AES_256_GCM, &self.key).ok()?);

let mut cipher_text = payload;
encryption_key
.seal_in_place_append_tag(
aead::Nonce::assume_unique_for_key(nonce),
aead::Aad::empty(),
&mut cipher_text,
)
.ok()?;

Some(cipher_text)
}

pub fn decrypt(&self, cipher_text: Vec<u8>, nonce: [u8; aead::NONCE_LEN]) -> Option<Vec<u8>> {
let decryption_key =
aead::LessSafeKey::new(aead::UnboundKey::new(&aead::AES_256_GCM, &self.key).ok()?);

let mut payload = cipher_text;
let decrypted_len = decryption_key
.open_in_place(
aead::Nonce::assume_unique_for_key(nonce),
aead::Aad::empty(),
&mut payload,
)
.ok()?
.len();

payload.truncate(decrypted_len);

Some(payload)
}
}

#[cfg(test)]
mod tests {
use eyre::OptionExt;

use super::*;

#[test]
fn test_encrypt_decrypt() -> eyre::Result<()> {
let mut csprng = rand::thread_rng();

let signer = PrivateKey::random(&mut csprng);
let verifier = PrivateKey::random(&mut csprng);

let signer_shared_key = SharedKey::new(&signer, &verifier.public_key());
let verifier_shared_key = SharedKey::new(&verifier, &signer.public_key());

let payload = b"privacy is important";
let nonce = [0u8; aead::NONCE_LEN];

let encrypted_payload = signer_shared_key
.encrypt(payload.to_vec(), nonce)
.ok_or_eyre("encryption failed")?;

let decrypted_payload = verifier_shared_key
.decrypt(encrypted_payload, nonce)
.ok_or_eyre("decryption failed")?;

assert_eq!(decrypted_payload, payload);
assert_ne!(decrypted_payload, b"privacy is not important");

Ok(())
}

#[test]
fn test_decrypt_with_invalid_key() -> eyre::Result<()> {
let mut csprng = rand::thread_rng();

let signer = PrivateKey::random(&mut csprng);
let verifier = PrivateKey::random(&mut csprng);
let invalid = PrivateKey::random(&mut csprng);

let signer_shared_key = SharedKey::new(&signer, &verifier.public_key());
let invalid_shared_key = SharedKey::new(&invalid, &invalid.public_key());

let token = b"privacy is important";
let nonce = [0u8; aead::NONCE_LEN];

let encrypted_token = signer_shared_key
.encrypt(token.to_vec(), nonce)
.ok_or_eyre("encryption failed")?;

let decrypted_data = invalid_shared_key.decrypt(encrypted_token, nonce);

assert!(decrypted_data.is_none());

Ok(())
}
}
1 change: 1 addition & 0 deletions crates/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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 @@ -75,7 +75,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!("{ind} Private Key: {}", identity.cyan());
println!("{ind} Public Key: {}", identity.public_key().cyan());
}
Expand Down
26 changes: 24 additions & 2 deletions crates/node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use calimero_blobstore::{BlobManager, FileSystem};
use calimero_context::config::ContextConfig;
use calimero_context::ContextManager;
use calimero_context_config::ProposalAction;
use calimero_crypto::SharedKey;
use calimero_network::client::NetworkClient;
use calimero_network::config::NetworkConfig;
use calimero_network::types::{NetworkEvent, PeerId};
Expand All @@ -34,7 +35,7 @@ use calimero_store::db::RocksDB;
use calimero_store::key::ContextMeta as ContextMetaKey;
use calimero_store::Store;
use camino::Utf8PathBuf;
use eyre::{bail, eyre, Result as EyreResult};
use eyre::{bail, eyre, OptionExt, Result as EyreResult};
use libp2p::gossipsub::{IdentTopic, Message, TopicHash};
use libp2p::identity::Keypair;
use rand::{thread_rng, Rng};
Expand Down Expand Up @@ -335,6 +336,16 @@ impl Node {
return Ok(());
}

let Some(sender_key) = self.ctx_manager.get_sender_key(&context_id, &author_id)? else {
return self.initiate_sync(context_id, source).await;
};

let shared_key = SharedKey::from_sk(&sender_key);

let artifact = &shared_key
.decrypt(artifact, [0; 12])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought I'd mention.. ideally, IVs shouldn't be constant as that impairs the effectiveness of GCM encryption and this can be passed in plain text, but that's an easy fix for a later PR, we can merge with this

.ok_or_eyre("failed to decrypt message")?;

let Some(outcome) = self
.execute(
&mut context,
Expand Down Expand Up @@ -368,11 +379,22 @@ impl Node {
.await
!= 0
{
let sender_key = self
.ctx_manager
.get_sender_key(&context.id, &executor_public_key)?
.ok_or_eyre("expected own identity to have sender key")?;

let shared_key = SharedKey::from_sk(&sender_key);

let artifact_encrypted = shared_key
.encrypt(outcome.artifact.clone(), [0; 12])
.ok_or_eyre("encryption failed")?;

let message = to_vec(&BroadcastMessage::StateDelta {
context_id: context.id,
author_id: executor_public_key,
root_hash: context.root_hash,
artifact: outcome.artifact.as_slice().into(),
artifact: artifact_encrypted.as_slice().into(),
})?;

let _ignored = self
Expand Down
Loading
Loading