Skip to content

Commit

Permalink
Update message encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronFeickert committed Apr 17, 2023
1 parent aa7f8a3 commit ba5a9b0
Show file tree
Hide file tree
Showing 9 changed files with 335 additions and 562 deletions.
306 changes: 110 additions & 196 deletions comms/dht/src/crypt.rs

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions comms/dht/src/dht.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@ mod test {
let mut service = dht.inbound_middleware_layer().layer(SinkService::new(out_tx));

let msg = wrap_in_envelope_body!(b"secret".to_vec());
// Don't encrypt
let dht_envelope = make_dht_envelope(
&node_identity,
&msg,
Expand Down Expand Up @@ -540,10 +541,11 @@ mod test {
peer_manager.add_peer(node_identity.to_peer()).await.unwrap();

// Dummy out channel, we are not testing outbound here.
let (out_tx, _out_rx) = mpsc::channel(10);
let (out_tx, _) = mpsc::channel(10);

let shutdown = Shutdown::new();
let dht = Dht::builder()
.local_test()
.with_outbound_sender(out_tx)
.build(
Arc::clone(&node_identity),
Expand Down Expand Up @@ -619,7 +621,7 @@ mod test {
let ecdh_key = CommsDHKE::new(node_identity2.secret_key(), node_identity2.public_key());
let key_message = crypt::generate_key_message(&ecdh_key);
let mut encrypted_bytes = msg.encode_into_bytes_mut();
crypt::encrypt_message(&key_message, &mut encrypted_bytes).unwrap();
crypt::encrypt_message(&key_message, &mut encrypted_bytes, b"test associated data").unwrap();
let dht_envelope = make_dht_envelope(
&node_identity2,
&encrypted_bytes.to_vec(),
Expand Down
2 changes: 0 additions & 2 deletions comms/dht/src/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ impl DhtMessageType {
pub struct DhtMessageHeader {
pub version: DhtProtocolVersion,
pub destination: NodeDestination,
/// Encoded MessageSignature. Depending on message flags, this may be encrypted. This can refer to the same peer
/// that sent the message or another peer if the message is being propagated.
pub message_signature: Vec<u8>,
pub ephemeral_public_key: Option<CommsPublicKey>,
pub message_type: DhtMessageType,
Expand Down
477 changes: 164 additions & 313 deletions comms/dht/src/inbound/decryption.rs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions comms/dht/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ pub fn comms_dht_hash_domain_key_message() -> DomainSeparatedHasher<CommsChallen
DomainSeparatedHasher::<CommsChallenge, DHTCommsHashDomain>::new_with_label("key_message")
}

pub fn comms_dht_hash_domain_key_signature() -> DomainSeparatedHasher<CommsChallenge, DHTCommsHashDomain> {
DomainSeparatedHasher::<CommsChallenge, DHTCommsHashDomain>::new_with_label("key_signature")
pub fn comms_dht_hash_domain_key_mask() -> DomainSeparatedHasher<CommsChallenge, DHTCommsHashDomain> {
DomainSeparatedHasher::<CommsChallenge, DHTCommsHashDomain>::new_with_label("key_mask")
}

pub fn comms_dht_hash_domain_message_signature() -> DomainSeparatedHasher<CommsChallenge, DHTCommsHashDomain> {
Expand Down
59 changes: 29 additions & 30 deletions comms/dht/src/outbound/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,51 +489,52 @@ where S: Service<DhtOutboundMessage, Response = (), Error = PipelineError>
mut body: BytesMut,
) -> Result<FinalMessageParts, DhtOutboundError> {
match encryption {
OutboundEncryption::EncryptFor(public_key) => {
trace!(target: LOG_TARGET, "Encrypting message for {}", public_key);
// Generate ephemeral public/private key pair and ECDH shared secret
let (e_secret_key, e_public_key) = CommsPublicKey::random_keypair(&mut OsRng);
let shared_ephemeral_secret = CommsDHKE::new(&e_secret_key, &**public_key);
// Encrypt the message, protecting the sender identity
OutboundEncryption::EncryptFor(recipient_public_key) => {
trace!(target: LOG_TARGET, "Encrypting message for {}", recipient_public_key);

// Generate key message for encryption of message
// Perform an ephemeral ECDH exchange against the recipient public key
let (ephemeral_secret_key, ephemeral_public_key) = CommsPublicKey::random_keypair(&mut OsRng);
let shared_ephemeral_secret = CommsDHKE::new(&ephemeral_secret_key, &**recipient_public_key);

// Produce a masked sender public key using an offset mask derived from the ECDH exchange
let mask = crypt::generate_key_mask(&shared_ephemeral_secret)
.map_err(|e| DhtOutboundError::CipherError(e.to_string()))?;
let masked_sender_public_key = &mask * self.node_identity.public_key();

// Pad and encrypt the message using the masked sender public key
let key_message = crypt::generate_key_message(&shared_ephemeral_secret);
// Encrypt the message with the body with key message above
crypt::encrypt_message(&key_message, &mut body)?;
crypt::encrypt_message(&key_message, &mut body, masked_sender_public_key.as_bytes())?;
let encrypted_body = body.freeze();

// Produce domain separated signature signature
let mac_signature = crypt::create_message_domain_separated_hash_parts(
// Produce a hash that binds the message and metadata
let binding_hash = crypt::create_message_domain_separated_hash_parts(
self.protocol_version,
destination,
message_type,
flags,
expires,
Some(&e_public_key),
Some(&ephemeral_public_key),
&encrypted_body,
);

// Generate key signature for encryption of signature
let key_signature = crypt::generate_key_signature(&shared_ephemeral_secret);

// Sign the encrypted message
let signature =
MessageSignature::new_signed(self.node_identity.secret_key().clone(), &mac_signature).to_proto();

// Perform authenticated encryption with ChaCha20-Poly1305 and set the origin field
let encrypted_message_signature =
crypt::encrypt_signature(&key_signature, &signature.to_encoded_bytes())?;
// Sign the encrypted message using the masked sender key
let masked_sender_secret_key = mask * self.node_identity.secret_key();
let signature = MessageSignature::new_signed(masked_sender_secret_key, &binding_hash).to_proto();

Ok((
Some(Arc::new(e_public_key)),
Some(encrypted_message_signature.into()),
Some(Arc::new(ephemeral_public_key)),
Some(signature.to_encoded_bytes().into()), // this includes the masked signer public key
encrypted_body,
))
},
// Keep the message unencrypted
OutboundEncryption::ClearText => {
trace!(target: LOG_TARGET, "Encryption not requested for message");

// We may or may not sign it
if include_origin {
let binding_message_representation = crypt::create_message_domain_separated_hash_parts(
let binding_hash = crypt::create_message_domain_separated_hash_parts(
self.protocol_version,
destination,
message_type,
Expand All @@ -542,12 +543,10 @@ where S: Service<DhtOutboundMessage, Response = (), Error = PipelineError>
None,
&body,
);
let signature = MessageSignature::new_signed(
self.node_identity.secret_key().clone(),
&binding_message_representation,
)
.to_proto();
Ok((None, Some(signature.to_encoded_bytes().into()), body.freeze()))
let signature =
MessageSignature::new_signed(self.node_identity.secret_key().clone(), &binding_hash).to_proto();
Ok((None, Some(signature.to_encoded_bytes().into()), body.freeze())) // this includes the signer
// public key
} else {
Ok((None, None, body.freeze()))
}
Expand Down
5 changes: 2 additions & 3 deletions comms/dht/src/outbound/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ use std::{
time::{Duration, Instant},
};

use chacha20::Nonce;
use log::*;
use tari_comms::{
message::{MessageTag, MessagingReplyTx},
Expand Down Expand Up @@ -136,7 +135,7 @@ impl OutboundServiceMockState {
.map(|(p, mut b)| {
if p.encryption.is_encrypt() {
// Remove prefix data
(p, b.split_off(mem::size_of::<u32>() + mem::size_of::<Nonce>()))
(p, b.split_off(mem::size_of::<u32>()))
} else {
(p, b)
}
Expand All @@ -148,7 +147,7 @@ impl OutboundServiceMockState {
self.calls.lock().await.pop().map(|(p, mut b)| {
if p.encryption.is_encrypt() {
// Remove prefix data
(p, b.split_off(mem::size_of::<u32>() + mem::size_of::<Nonce>()))
(p, b.split_off(mem::size_of::<u32>()))
} else {
(p, b)
}
Expand Down
16 changes: 9 additions & 7 deletions comms/dht/src/store_forward/saf_handler/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,26 +557,28 @@ where S: Service<DecryptedDhtMessage, Response = (), Error = PipelineError>
"Attempting to decrypt message signature ({} byte(s))",
header.message_signature.len()
);
let shared_secret = CommsDHKE::new(node_identity.secret_key(), ephemeral_public_key);
let key_signature = crypt::generate_key_signature(&shared_secret);
let decrypted = crypt::decrypt_signature(&key_signature, &header.message_signature)?;
let authenticated_pk = Self::authenticate_message(&decrypted, header, body)?;
let masked_sender_public_key = Self::authenticate_message(&header.message_signature, header, body)?;

trace!(
target: LOG_TARGET,
"Attempting to decrypt message body ({} byte(s))",
body.len()
);

let key_message = crypt::generate_key_message(&shared_secret);
let shared_ephemeral_secret = CommsDHKE::new(node_identity.secret_key(), ephemeral_public_key);
let key_message = crypt::generate_key_message(&shared_ephemeral_secret);
let mut decrypted_bytes = BytesMut::from(body);
crypt::decrypt_message(&key_message, &mut decrypted_bytes)?;
crypt::decrypt_message(&key_message, &mut decrypted_bytes, masked_sender_public_key.as_bytes())?;
let envelope_body =
EnvelopeBody::decode(decrypted_bytes.freeze()).map_err(|_| StoreAndForwardError::DecryptionFailed)?;
if envelope_body.is_empty() {
return Err(StoreAndForwardError::InvalidEnvelopeBody);
}
Ok((Some(authenticated_pk), envelope_body))

// Unmask the sender public key
let mask = crypt::generate_key_mask(&shared_ephemeral_secret)?;
let mask_inverse = mask.invert().ok_or(StoreAndForwardError::DecryptionFailed)?;
Ok((Some(mask_inverse * masked_sender_public_key), envelope_body))
} else {
let authenticated_pk = if header.message_signature.is_empty() {
None
Expand Down
22 changes: 15 additions & 7 deletions comms/dht/src/test_utils/makers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use tari_comms::{
use tari_crypto::keys::PublicKey;
use tari_storage::lmdb_store::{LMDBBuilder, LMDBConfig};
use tari_test_utils::{paths::create_temporary_data_path, random};
use tari_utilities::ByteArray;

use crate::{
crypt,
Expand Down Expand Up @@ -78,6 +79,7 @@ pub fn make_dht_header(
trace: MessageTag,
include_destination: bool,
) -> Result<DhtMessageHeader, DhtOutboundError> {
// For testing purposes, the destination is the same node as the sender (or empty)
let destination = if include_destination {
NodeDestination::PublicKey(Box::new(node_identity.public_key().clone()))
} else {
Expand All @@ -95,11 +97,15 @@ pub fn make_dht_header(
Some(e_public_key),
message,
);
let signature = make_valid_message_signature(node_identity, &binding_message_representation);
if flags.is_encrypted() {
let shared_secret = CommsDHKE::new(e_secret_key, node_identity.public_key());
let key_signature = crypt::generate_key_signature(&shared_secret);
message_signature = crypt::encrypt_signature(&key_signature, &signature)?;
// We need to offset the sender key by an ECDH-derived mask
let shared_ephemeral_secret = CommsDHKE::new(e_secret_key, node_identity.public_key());
let mask = crypt::generate_key_mask(&shared_ephemeral_secret).unwrap();
message_signature =
make_valid_message_signature(&(mask * node_identity.secret_key()), &binding_message_representation);
} else {
message_signature =
make_valid_message_signature(node_identity.secret_key(), &binding_message_representation);
}
}
Ok(DhtMessageHeader {
Expand All @@ -118,8 +124,8 @@ pub fn make_dht_header(
})
}

pub fn make_valid_message_signature(node_identity: &NodeIdentity, message: &[u8]) -> Vec<u8> {
MessageSignature::new_signed(node_identity.secret_key().clone(), message)
pub fn make_valid_message_signature(secret_key: &CommsSecretKey, message: &[u8]) -> Vec<u8> {
MessageSignature::new_signed(secret_key.clone(), message)
.to_proto()
.to_encoded_bytes()
}
Expand Down Expand Up @@ -202,8 +208,10 @@ pub fn make_dht_envelope<T: prost::Message>(
let message = if flags.is_encrypted() {
let shared_secret = CommsDHKE::new(&e_secret_key, node_identity.public_key());
let key_message = crypt::generate_key_message(&shared_secret);
let mask = crypt::generate_key_mask(&shared_secret).unwrap();
let masked_public_key = mask * node_identity.public_key();
let mut message = prepare_message(true, message);
crypt::encrypt_message(&key_message, &mut message).unwrap();
crypt::encrypt_message(&key_message, &mut message, masked_public_key.as_bytes()).unwrap();
message.freeze()
} else {
prepare_message(false, message).freeze()
Expand Down

0 comments on commit ba5a9b0

Please sign in to comment.