From fab39fdb973f1c5dff28a39521641889b5637261 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Fri, 11 Jul 2025 16:55:34 +0200 Subject: [PATCH] WIP, untested: Sending side of transferring the secret in member-added message --- src/chat.rs | 14 +++++++++----- src/headerdef.rs | 4 ++++ src/mimefactory.rs | 20 +++++++++++++++++++- src/param.rs | 4 +++- src/tools.rs | 19 +++++++++++++++++++ 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index bee6ec022..4da1e5ecd 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -42,9 +42,9 @@ use crate::smtp::send_msg_to_smtp; use crate::stock_str; use crate::sync::{self, Sync::*, SyncData}; use crate::tools::{ - IsNoneOrEmpty, SystemTime, buf_compress, create_id, create_outgoing_rfc724_mid, - create_smeared_timestamp, create_smeared_timestamps, get_abs_path, gm2local_offset, - smeared_time, time, truncate_msg_text, + IsNoneOrEmpty, SystemTime, buf_compress, create_broadcast_shared_secret, create_id, + create_outgoing_rfc724_mid, create_smeared_timestamp, create_smeared_timestamps, get_abs_path, + gm2local_offset, smeared_time, time, truncate_msg_text, }; use crate::webxdc::StatusUpdateSerial; use crate::{chatlist_events, imap}; @@ -3710,14 +3710,18 @@ pub(crate) async fn create_broadcast_ex( }, )?); } + let mut param = Params::new(); + // param.set(Param::Unpromoted, 1); // TODO broadcasts will just never be unpromoted for now + param.set(Param::SymmetricKey, create_broadcast_shared_secret()); t.execute( "INSERT INTO chats \ (type, name, grpid, param, created_timestamp) \ - VALUES(?, ?, ?, \'U=1\', ?);", + VALUES(?, ?, ?, ?, ?);", ( Chattype::OutBroadcast, &chat_name, &grpid, + param.to_string(), create_smeared_timestamp(context), ), )?; @@ -3918,7 +3922,7 @@ pub(crate) async fn add_contact_to_chat_ex( } add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?; } - if chat.typ == Chattype::Group && chat.is_promoted() { + if chat.is_promoted() { msg.viewtype = Viewtype::Text; let contact_addr = contact.get_addr().to_lowercase(); diff --git a/src/headerdef.rs b/src/headerdef.rs index 330a4d9ba..fd385c376 100644 --- a/src/headerdef.rs +++ b/src/headerdef.rs @@ -93,6 +93,10 @@ pub enum HeaderDef { /// This message obsoletes the text of the message defined here by rfc724_mid. ChatEdit, + /// The secret shared amongst all recipients of this broadcast channel. + /// This secret. + ChatBroadcastSecret, + /// [Autocrypt](https://autocrypt.org/) header. Autocrypt, AutocryptGossip, diff --git a/src/mimefactory.rs b/src/mimefactory.rs index db449343f..b58efc9a2 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -789,7 +789,7 @@ impl MimeFactory { } } - if let Loaded::Message { chat, .. } = &self.loaded { + if let Loaded::Message { msg, chat } = &self.loaded { if chat.typ == Chattype::OutBroadcast || chat.typ == Chattype::InBroadcast { headers.push(( "List-ID", @@ -799,6 +799,15 @@ impl MimeFactory { )) .into(), )); + + if msg.param.get_cmd() == SystemMessage::MemberAddedToGroup { + if let Some(secret) = chat.param.get(Param::SymmetricKey) { + headers.push(( + "Chat-Broadcast-Secret", + mail_builder::headers::text::Text::new(secret.to_string()).into(), + )); + } + } } } @@ -979,6 +988,15 @@ impl MimeFactory { } else { unprotected_headers.push(header.clone()); } + } else if header_name == "chat-broadcast-secret" { + if is_encrypted { + protected_headers.push(header.clone()); + } else { + warn!( + context, + "Message is unnecrypted, not including broadcast secret" + ); + } } else if is_encrypted { protected_headers.push(header.clone()); diff --git a/src/param.rs b/src/param.rs index 098c41243..b9da5d97a 100644 --- a/src/param.rs +++ b/src/param.rs @@ -169,7 +169,9 @@ pub enum Param { /// post something to the mailing list. ListPost = b'p', - /// For Chats of type [`Chattype::OutBroadcast`] and [`Chattype::InBroadcast`]: // TODO (or just OutBroadcast) + /// For Chats and Messages: + /// For chats of type [`Chattype::OutBroadcast`] and [`Chattype::InBroadcast`] // TODO (or just OutBroadcast) + /// and for messages adding members to such a chat. /// The symmetric key shared among all chat participants, /// used to encrypt and decrypt messages. SymmetricKey = b'z', diff --git a/src/tools.rs b/src/tools.rs index cb37bdeee..1c0e456ff 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -300,6 +300,25 @@ pub(crate) fn create_id() -> String { base64::engine::general_purpose::URL_SAFE.encode(arr) } +/// Generate a shared secret for a broadcast channel, consisting of 64 characters.. +/// +/// The string generated by this function has 384 bits of entropy +/// and is returned as 64 Base64 characters, each containing 6 bits of entropy. +/// 384 is chosen because it is sufficiently secure +/// (larger than AES-128 keys used for message encryption) +/// and divides both by 8 (byte size) and 6 (number of bits in a single Base64 character). +// TODO ask someone what a good size would be here +pub(crate) fn create_broadcast_shared_secret() -> String { + // ThreadRng implements CryptoRng trait and is supposed to be cryptographically secure. + let mut rng = thread_rng(); + + // Generate 384 random bits. + let mut arr = [0u8; 48]; + rng.fill(&mut arr[..]); + + base64::engine::general_purpose::URL_SAFE.encode(arr) +} + /// Returns true if given string is a valid ID. /// /// All IDs generated with `create_id()` should be considered valid.