WIP, untested: Sending side of transferring the secret in member-added message

This commit is contained in:
Hocuri
2025-07-11 16:55:34 +02:00
parent 63664396dd
commit fab39fdb97
5 changed files with 54 additions and 7 deletions

View File

@@ -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();

View File

@@ -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,

View File

@@ -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());

View File

@@ -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',

View File

@@ -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.