Skip to content

Commit

Permalink
feat(encryption): Added support for nonces (#978)
Browse files Browse the repository at this point in the history
  • Loading branch information
petarjuki7 authored Nov 26, 2024
1 parent eade923 commit db551eb
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 60 deletions.
23 changes: 11 additions & 12 deletions crates/crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ use calimero_primitives::identity::{PrivateKey, PublicKey};
use ed25519_dalek::{SecretKey, SigningKey};
use ring::aead;

pub const NONCE_LEN: usize = 12;

pub type Nonce = [u8; NONCE_LEN];

#[derive(Copy, Clone, Debug)]
pub struct SharedKey {
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 {
Expand All @@ -29,7 +27,7 @@ impl SharedKey {
SharedKey { key: **sk }
}

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

Expand All @@ -45,7 +43,7 @@ impl SharedKey {
Some(cipher_text)
}

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

Expand All @@ -68,12 +66,13 @@ impl SharedKey {
#[cfg(test)]
mod tests {
use eyre::OptionExt;
use rand::thread_rng;

use super::*;

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

let signer = PrivateKey::random(&mut csprng);
let verifier = PrivateKey::random(&mut csprng);
Expand All @@ -82,7 +81,7 @@ mod tests {
let verifier_shared_key = SharedKey::new(&verifier, &signer.public_key());

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

let encrypted_payload = signer_shared_key
.encrypt(payload.to_vec(), nonce)
Expand All @@ -100,7 +99,7 @@ mod tests {

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

let signer = PrivateKey::random(&mut csprng);
let verifier = PrivateKey::random(&mut csprng);
Expand All @@ -110,7 +109,7 @@ mod tests {
let invalid_shared_key = SharedKey::new(&invalid, &invalid.public_key());

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

let encrypted_token = signer_shared_key
.encrypt(token.to_vec(), nonce)
Expand Down
11 changes: 8 additions & 3 deletions crates/node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use calimero_context::config::ContextConfig;
use calimero_context::ContextManager;
use calimero_context_config::repr::ReprTransmute;
use calimero_context_config::ProposalAction;
use calimero_crypto::SharedKey;
use calimero_crypto::{Nonce, SharedKey, NONCE_LEN};
use calimero_network::client::NetworkClient;
use calimero_network::config::NetworkConfig;
use calimero_network::types::{NetworkEvent, PeerId};
Expand Down Expand Up @@ -305,13 +305,15 @@ impl Node {
author_id,
root_hash,
artifact,
nonce,
} => {
self.handle_state_delta(
source,
context_id,
author_id,
root_hash,
artifact.into_owned(),
nonce,
)
.await?;
}
Expand All @@ -327,6 +329,7 @@ impl Node {
author_id: PublicKey,
root_hash: Hash,
artifact: Vec<u8>,
nonce: [u8; NONCE_LEN],
) -> EyreResult<()> {
let Some(mut context) = self.ctx_manager.get_context(&context_id)? else {
bail!("context '{}' not found", context_id);
Expand All @@ -344,7 +347,7 @@ impl Node {
let shared_key = SharedKey::from_sk(&sender_key);

let artifact = &shared_key
.decrypt(artifact, [0; 12])
.decrypt(artifact, nonce)
.ok_or_eyre("failed to decrypt message")?;

let Some(outcome) = self
Expand Down Expand Up @@ -386,16 +389,18 @@ impl Node {
.ok_or_eyre("expected own identity to have sender key")?;

let shared_key = SharedKey::from_sk(&sender_key);
let nonce = thread_rng().gen::<Nonce>();

let artifact_encrypted = shared_key
.encrypt(outcome.artifact.clone(), [0; 12])
.encrypt(outcome.artifact.clone(), nonce)
.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: artifact_encrypted.as_slice().into(),
nonce,
})?;

let _ignored = self
Expand Down
39 changes: 24 additions & 15 deletions crates/node/src/sync.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::time::Duration;

use calimero_crypto::SharedKey;
use calimero_crypto::{Nonce, SharedKey};
use calimero_network::stream::{Message, Stream};
use calimero_primitives::context::ContextId;
use eyre::{bail, Result as EyreResult};
use eyre::{bail, eyre, OptionExt, Result as EyreResult};
use futures_util::{SinkExt, StreamExt};
use libp2p::gossipsub::TopicHash;
use libp2p::PeerId;
Expand All @@ -28,15 +28,14 @@ pub struct SyncConfig {
async fn send(
stream: &mut Stream,
message: &StreamMessage<'_>,
shared_key: Option<SharedKey>,
shared_key: Option<(SharedKey, Nonce)>,
) -> EyreResult<()> {
let base_data = borsh::to_vec(message)?;

let data = match shared_key {
Some(key) => match key.encrypt(base_data, [0; 12]) {
Some(data) => data,
None => bail!("encryption failed"),
},
Some((key, nonce)) => key
.encrypt(base_data, nonce)
.ok_or_eyre("encryption failed")?,
None => base_data,
};

Expand All @@ -47,7 +46,7 @@ async fn send(
async fn recv(
stream: &mut Stream,
duration: Duration,
shared_key: Option<SharedKey>,
shared_key: Option<(SharedKey, Nonce)>,
) -> EyreResult<Option<StreamMessage<'static>>> {
let Some(message) = timeout(duration, stream.next()).await? else {
return Ok(None);
Expand All @@ -56,10 +55,17 @@ async fn recv(
let message_data = message?.data.into_owned();

let data = match shared_key {
Some(key) => match key.decrypt(message_data, [0; 12]) {
Some(data) => data,
None => bail!("decryption failed"),
},
Some((key, nonce)) => {
match key.decrypt(
message_data,
nonce
.try_into()
.map_err(|_| eyre!("nonce must be 12 bytes"))?,
) {
Some(data) => data,
None => bail!("decryption failed"),
}
}
None => message_data,
};

Expand Down Expand Up @@ -158,12 +164,14 @@ impl Node {
return Ok(None);
};

let (context_id, their_identity, payload) = match message {
let (context_id, their_identity, payload, nonce) = match message {
StreamMessage::Init {
context_id,
party_id,
payload,
} => (context_id, party_id, payload),
next_nonce,
..
} => (context_id, party_id, payload, next_nonce),
unexpected @ (StreamMessage::Message { .. } | StreamMessage::OpaqueError) => {
bail!("expected initialization handshake, got {:?}", unexpected)
}
Expand Down Expand Up @@ -201,7 +209,7 @@ impl Node {

match payload {
InitPayload::KeyShare => {
self.handle_key_share_request(&context, our_identity, their_identity, stream)
self.handle_key_share_request(&context, our_identity, their_identity, stream, nonce)
.await?
}
InitPayload::BlobShare { blob_id } => {
Expand Down Expand Up @@ -233,6 +241,7 @@ impl Node {
their_root_hash,
their_application_id,
stream,
nonce,
)
.await?
}
Expand Down
37 changes: 29 additions & 8 deletions crates/node/src/sync/blobs.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use calimero_crypto::SharedKey;
use calimero_crypto::{Nonce, SharedKey, NONCE_LEN};
use calimero_network::stream::Stream;
use calimero_primitives::blobs::BlobId;
use calimero_primitives::context::Context;
use calimero_primitives::identity::PublicKey;
use eyre::{bail, OptionExt};
use futures_util::stream::poll_fn;
use futures_util::TryStreamExt;
use rand::{thread_rng, Rng};
use tokio::sync::mpsc;
use tracing::{debug, warn};

Expand All @@ -29,12 +30,15 @@ impl Node {
"Initiating blob share",
);

let our_nonce = thread_rng().gen::<Nonce>();

send(
stream,
&StreamMessage::Init {
context_id: context.id,
party_id: our_identity,
payload: InitPayload::BlobShare { blob_id },
next_nonce: our_nonce,
},
None,
)
Expand All @@ -44,13 +48,14 @@ impl Node {
bail!("connection closed while awaiting blob share handshake");
};

let their_identity = match ack {
let (their_identity, mut their_nonce) = match ack {
StreamMessage::Init {
party_id,
payload:
InitPayload::BlobShare {
blob_id: ack_blob_id,
},
next_nonce,
..
} => {
if ack_blob_id != blob_id {
Expand All @@ -61,7 +66,7 @@ impl Node {
);
}

party_id
(party_id, next_nonce)
}
unexpected @ (StreamMessage::Init { .. }
| StreamMessage::Message { .. }
Expand All @@ -88,13 +93,20 @@ impl Node {
let read_task = async {
let mut sequencer = Sequencer::default();

while let Some(msg) = recv(stream, self.sync_config.timeout, Some(shared_key)).await? {
let (sequence_id, chunk) = match msg {
while let Some(msg) = recv(
stream,
self.sync_config.timeout,
Some((shared_key, their_nonce)),
)
.await?
{
let (sequence_id, chunk, their_new_nonce) = match msg {
StreamMessage::OpaqueError => bail!("other peer ran into an error"),
StreamMessage::Message {
sequence_id,
payload: MessagePayload::BlobShare { chunk },
} => (sequence_id, chunk),
next_nonce,
} => (sequence_id, chunk, next_nonce),
unexpected @ (StreamMessage::Init { .. } | StreamMessage::Message { .. }) => {
bail!("unexpected message: {:?}", unexpected)
}
Expand All @@ -107,6 +119,8 @@ impl Node {
}

tx.send(Ok(chunk)).await?;

their_nonce = their_new_nonce;
}

drop(tx);
Expand Down Expand Up @@ -163,13 +177,15 @@ impl Node {
.ok_or_eyre("expected own identity to have private key")?;

let shared_key = SharedKey::new(&private_key, &their_identity);
let mut our_nonce = thread_rng().gen::<Nonce>();

send(
stream,
&StreamMessage::Init {
context_id: context.id,
party_id: our_identity,
payload: InitPayload::BlobShare { blob_id },
next_nonce: our_nonce,
},
None,
)
Expand All @@ -178,26 +194,31 @@ impl Node {
let mut sequencer = Sequencer::default();

while let Some(chunk) = blob.try_next().await? {
let our_new_nonce = thread_rng().gen::<Nonce>();
send(
stream,
&StreamMessage::Message {
sequence_id: sequencer.next(),
payload: MessagePayload::BlobShare {
chunk: chunk.into_vec().into(),
},
next_nonce: our_new_nonce,
},
Some(shared_key),
Some((shared_key, our_nonce)),
)
.await?;

our_nonce = our_new_nonce;
}

send(
stream,
&StreamMessage::Message {
sequence_id: sequencer.next(),
payload: MessagePayload::BlobShare { chunk: b"".into() },
next_nonce: [0; NONCE_LEN],
},
Some(shared_key),
Some((shared_key, our_nonce)),
)
.await?;

Expand Down
Loading

0 comments on commit db551eb

Please sign in to comment.