diff --git a/deltachat-jsonrpc/src/api/types/qr.rs b/deltachat-jsonrpc/src/api/types/qr.rs index b7c177b09..7c4207e1f 100644 --- a/deltachat-jsonrpc/src/api/types/qr.rs +++ b/deltachat-jsonrpc/src/api/types/qr.rs @@ -45,6 +45,8 @@ pub enum QrObject { /// Fingerprint of the contact key as scanned from the QR code. fingerprint: String, + authcode: String, + /// The secret shared between all members, /// used to symmetrically encrypt&decrypt messages. shared_secret: String, @@ -227,6 +229,7 @@ impl From for QrObject { grpid, contact_id, fingerprint, + authcode, shared_secret, } => { let contact_id = contact_id.to_u32(); @@ -236,6 +239,7 @@ impl From for QrObject { grpid, contact_id, fingerprint, + authcode, shared_secret, } } diff --git a/src/chat.rs b/src/chat.rs index 2de5de15e..fe95fd967 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -43,9 +43,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_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, + 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, }; use crate::webxdc::StatusUpdateSerial; use crate::{chatlist_events, imap}; @@ -1646,6 +1646,18 @@ impl Chat { self.typ == Chattype::Mailinglist } + /// Returns true if chat is an outgoing broadcast channel. + pub fn is_out_broadcast(&self) -> bool { + self.typ == Chattype::OutBroadcast + } + + /// Returns true if the chat is a broadcast channel, + /// regardless of whether self is on the sending + /// or receiving side. + pub fn is_any_broadcast(&self) -> bool { + matches!(self.typ, Chattype::OutBroadcast | Chattype::InBroadcast) + } + /// Returns None if user can send messages to this chat. /// /// Otherwise returns a reason useful for logging. @@ -1726,7 +1738,7 @@ impl Chat { match self.typ { Chattype::Single | Chattype::OutBroadcast | Chattype::Mailinglist => Ok(true), Chattype::Group => is_contact_in_chat(context, self.id, ContactId::SELF).await, - Chattype::InBroadcast => Ok(false), + Chattype::InBroadcast => Ok(true), } } @@ -2916,13 +2928,18 @@ async fn prepare_send_msg( CantSendReason::ContactRequest => { // Allow securejoin messages, they are supposed to repair the verification. // If the chat is a contact request, let the user accept it later. + msg.param.get_cmd() == SystemMessage::SecurejoinMessage } // Allow to send "Member removed" messages so we can leave the group/broadcast. // Necessary checks should be made anyway before removing contact // from the chat. - CantSendReason::NotAMember | CantSendReason::InBroadcast => { - msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup + CantSendReason::NotAMember => msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup, + CantSendReason::InBroadcast => { + matches!( + msg.param.get_cmd(), + SystemMessage::MemberRemovedFromGroup | SystemMessage::SecurejoinMessage + ) } CantSendReason::MissingKey => msg .param @@ -3777,7 +3794,7 @@ pub async fn create_group_ex( /// Returns the created chat's id. pub async fn create_broadcast(context: &Context, chat_name: String) -> Result { let grpid = create_id(); - let secret = create_broadcast_shared_secret(); + let secret = create_id(); create_broadcast_ex(context, Sync, grpid, chat_name, secret).await } @@ -3840,6 +3857,35 @@ pub(crate) async fn create_broadcast_ex( Ok(chat_id) } +pub(crate) async fn load_broadcast_shared_secret( + context: &Context, + chat_id: ChatId, +) -> Result> { + Ok(context + .sql + .query_get_value( + "SELECT secret FROM broadcasts_shared_secrets WHERE chat_id=?", + (chat_id,), + ) + .await?) +} + +pub(crate) async fn save_broadcast_shared_secret( + context: &Context, + chat_id: ChatId, + shared_secret: &str, +) -> Result<()> { + context + .sql + .execute( + "INSERT INTO broadcasts_shared_secrets (chat_id, secret) VALUES (?, ?) + ON CONFLICT(chat_id) DO UPDATE SET secret=excluded.chat_id", + (chat_id, shared_secret), + ) + .await?; + Ok(()) +} + /// Set chat contacts in the `chats_contacts` table. pub(crate) async fn update_chat_contacts_table( context: &Context, diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index ba8ee9dd8..c30b7bc72 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -7,6 +7,7 @@ use crate::imex::{ImexMode, has_backup, imex}; use crate::message::{MessengerMessage, delete_msgs}; use crate::mimeparser::{self, MimeMessage}; use crate::receive_imf::receive_imf; +use crate::securejoin::get_securejoin_qr; use crate::test_utils::{ AVATAR_64x64_BYTES, AVATAR_64x64_DEDUPLICATED, E2EE_INFO_MSGS, TestContext, TestContextManager, TimeShiftFalsePositiveNote, sync, @@ -2929,11 +2930,13 @@ async fn test_broadcast_channel_protected_listid() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob = &tcm.bob().await; - let alice_bob_contact_id = alice.add_or_lookup_contact_id(bob).await; tcm.section("Create a broadcast channel with Bob, and send a message"); let alice_chat_id = create_broadcast(alice, "My Channel".to_string()).await?; - add_contact_to_chat(alice, alice_chat_id, alice_bob_contact_id).await?; + + let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await.unwrap(); + tcm.exec_securejoin_qr(bob, alice, &qr).await; + let mut sent = alice.send_text(alice_chat_id, "Hi somebody").await; assert!(!sent.payload.contains("List-ID")); diff --git a/src/mimefactory.rs b/src/mimefactory.rs index c3e2e0295..f5b7437b6 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -15,7 +15,7 @@ use tokio::fs; use crate::aheader::{Aheader, EncryptPreference}; use crate::blob::BlobObject; -use crate::chat::{self, Chat}; +use crate::chat::{self, Chat, load_broadcast_shared_secret}; use crate::config::Config; use crate::constants::ASM_SUBJECT; use crate::constants::{Chattype, DC_FROM_HANDSHAKE}; @@ -231,6 +231,9 @@ impl MimeFactory { // Do not encrypt messages to mailing lists. encryption_keys = None; + } else if chat.is_out_broadcast() { + // Encrypt, but only symmetrically, not with the public keys. + encryption_keys = Some(Vec::new()); } else { let email_to_remove = if msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup { msg.param.get(Param::Arg) @@ -563,8 +566,10 @@ impl MimeFactory { // messages are auto-sent unlike usual unencrypted messages. step == "vg-request-with-auth" || step == "vc-request-with-auth" + || step == "vb-request-with-auth" || step == "vg-member-added" || step == "vc-contact-confirm" + // TODO possibly add vb-member-added here } } @@ -1144,7 +1149,7 @@ impl MimeFactory { }; let symmetric_key: Option = match &self.loaded { - Loaded::Message { chat, .. } if chat.typ == Chattype::OutBroadcast => { + Loaded::Message { chat, .. } if chat.is_any_broadcast() => { // If there is no symmetric key yet // (because this is an old broadcast channel, // created before we had symmetric encryption), @@ -1152,13 +1157,7 @@ impl MimeFactory { // Symmetric encryption exists since 2025-08; // some time after that, we can think about requiring everyone // to switch to symmetrically-encrypted broadcast lists. - context - .sql - .query_get_value( - "SELECT secret FROM broadcasts_shared_secrets WHERE chat_id=?", - (chat.id,), - ) - .await? + load_broadcast_shared_secret(context, chat.id).await? } _ => None, }; @@ -1515,7 +1514,10 @@ impl MimeFactory { let param2 = msg.param.get(Param::Arg2).unwrap_or_default(); if !param2.is_empty() { headers.push(( - if step == "vg-request-with-auth" || step == "vc-request-with-auth" { + if step == "vg-request-with-auth" + || step == "vc-request-with-auth" + || step == "vb-request-with-auth" + { "Secure-Join-Auth" } else { "Secure-Join-Invitenumber" diff --git a/src/qr.rs b/src/qr.rs index 2cdd8de0b..c767503f3 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -96,6 +96,8 @@ pub enum Qr { fingerprint: Fingerprint, + authcode: String, + shared_secret: String, }, @@ -396,7 +398,7 @@ pub fn format_backup(qr: &Qr) -> Result { /// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH` /// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH` -/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=BROADCAST_NAME&x=BROADCAST_ID&b=BROADCAST_SHARED_SECRET` +/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=BROADCAST_NAME&x=BROADCAST_ID&s=AUTH&b=BROADCAST_SHARED_SECRET` /// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR` async fn decode_openpgp(context: &Context, qr: &str) -> Result { let payload = qr @@ -474,7 +476,9 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result { None }; - if let (Some(addr), Some(invitenumber), Some(authcode)) = (&addr, invitenumber, authcode) { + if let (Some(addr), Some(invitenumber), Some(authcode)) = + (&addr, invitenumber, authcode.clone()) + { let addr = ContactAddress::new(addr)?; let (contact_id, _) = Contact::add_or_lookup_ex( context, @@ -545,8 +549,13 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result { authcode, }) } - } else if let (Some(addr), Some(broadcast_name), Some(grpid), Some(shared_secret)) = - (&addr, grpname, grpid, broadcast_shared_secret) + } else if let ( + Some(addr), + Some(broadcast_name), + Some(grpid), + Some(authcode), + Some(shared_secret), + ) = (&addr, grpname, grpid, authcode, broadcast_shared_secret) { // This is a broadcast channel invite link. // TODO code duplication with the previous block @@ -567,6 +576,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result { grpid, contact_id, fingerprint, + authcode, shared_secret, }) } else if let Some(addr) = addr { diff --git a/src/receive_imf.rs b/src/receive_imf.rs index aa1980332..fafaaec4f 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -44,7 +44,7 @@ use crate::securejoin::{self, handle_securejoin_handshake, observe_securejoin_on use crate::simplify; use crate::stock_str; use crate::sync::Sync::*; -use crate::tools::{self, buf_compress, create_broadcast_shared_secret, remove_subject_prefix}; +use crate::tools::{self, buf_compress, create_id, remove_subject_prefix}; use crate::{chatlist_events, ensure_and_debug_assert, ensure_and_debug_assert_eq, location}; use crate::{contact, imap}; @@ -1566,7 +1566,7 @@ async fn do_chat_assignment( } else { let name = compute_mailinglist_name(mailinglist_header, &listid, mime_parser); - let secret = create_broadcast_shared_secret(); + let secret = create_id(); chat::create_broadcast_ex(context, Nosync, listid, name, secret).await? }, ); diff --git a/src/securejoin.rs b/src/securejoin.rs index 7813c9de5..040ab88ec 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -4,7 +4,10 @@ use anyhow::{Context as _, Error, Result, bail, ensure}; use deltachat_contact_tools::ContactAddress; use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode}; -use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus, get_chat_id_by_grpid}; +use crate::chat::{ + self, Chat, ChatId, ChatIdBlocked, ProtectionStatus, get_chat_id_by_grpid, + load_broadcast_shared_secret, +}; use crate::chatlist_events; use crate::config::Config; use crate::constants::{Blocked, Chattype, NON_ALPHANUMERIC_WITHOUT_DOT}; @@ -46,9 +49,9 @@ fn inviter_progress(context: &Context, contact_id: ContactId, progress: usize) { /// Generates a Secure Join QR code. /// -/// With `group` set to `None` this generates a setup-contact QR code, with `group` set to a -/// [`ChatId`] generates a join-group QR code for the given chat. -pub async fn get_securejoin_qr(context: &Context, group: Option) -> Result { +/// With `chat` set to `None` this generates a setup-contact QR code, with `chat` set to a +/// [`ChatId`] generates a join-group/join-broadcast-channel QR code for the given chat. +pub async fn get_securejoin_qr(context: &Context, chat: Option) -> Result { /*======================================================= ==== Alice - the inviter side ==== ==== Step 1 in "Setup verified contact" protocol ==== @@ -56,12 +59,13 @@ pub async fn get_securejoin_qr(context: &Context, group: Option) -> Resu ensure_secret_key_exists(context).await.ok(); - let chat = match group { + let chat = match chat { Some(id) => { let chat = Chat::load_from_db(context, id).await?; ensure!( - chat.typ == Chattype::Group, - "Can't generate SecureJoin QR code for 1:1 chat {id}" + chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast, + "Can't generate SecureJoin QR code for chat {id} of type {}", + chat.typ ); if chat.grpid.is_empty() { let err = format!("Can't generate QR code, chat {id} is a email thread"); @@ -94,24 +98,44 @@ pub async fn get_securejoin_qr(context: &Context, group: Option) -> Resu utf8_percent_encode(&self_name, NON_ALPHANUMERIC_WITHOUT_DOT).to_string(); let qr = if let Some(chat) = chat { - // parameters used: a=g=x=i=s= - let group_name = chat.get_name(); - let group_name_urlencoded = utf8_percent_encode(group_name, NON_ALPHANUMERIC).to_string(); - if sync_token { - context - .sync_qr_code_tokens(Some(chat.grpid.as_str())) - .await?; - context.scheduler.interrupt_inbox().await; + if chat.typ == Chattype::OutBroadcast { + let broadcast_name = chat.get_name(); + let broadcast_name_urlencoded = + utf8_percent_encode(broadcast_name, NON_ALPHANUMERIC).to_string(); + let broadcast_secret = load_broadcast_shared_secret(context, chat.id) + .await? + .context("Could not find broadcast secret")?; + + format!( + "https://i.delta.chat/#{}&a={}&g={}&x={}&s={}&b={}", + fingerprint.hex(), + self_addr_urlencoded, + &broadcast_name_urlencoded, + &chat.grpid, + &auth, + broadcast_secret + ) + } else { + // parameters used: a=g=x=i=s= + let group_name = chat.get_name(); + let group_name_urlencoded = + utf8_percent_encode(group_name, NON_ALPHANUMERIC).to_string(); + if sync_token { + context + .sync_qr_code_tokens(Some(chat.grpid.as_str())) + .await?; + context.scheduler.interrupt_inbox().await; + } + format!( + "https://i.delta.chat/#{}&a={}&g={}&x={}&i={}&s={}", + fingerprint.hex(), + self_addr_urlencoded, + &group_name_urlencoded, + &chat.grpid, + &invitenumber, + &auth, + ) } - format!( - "https://i.delta.chat/#{}&a={}&g={}&x={}&i={}&s={}", - fingerprint.hex(), - self_addr_urlencoded, - &group_name_urlencoded, - &chat.grpid, - &invitenumber, - &auth, - ) } else { // parameters used: a=n=i=s= if sync_token { @@ -266,9 +290,9 @@ pub(crate) async fn handle_securejoin_handshake( info!(context, "Received secure-join message {step:?}."); - let join_vg = step.starts_with("vg-"); - - if !matches!(step, "vg-request" | "vc-request") { + // TODO talk with link2xt about whether we need to protect against this identity-misbinding attack, + // and if so, how + if !matches!(step, "vg-request" | "vc-request" | "vb-request-with-auth") { let mut self_found = false; let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint(); for (addr, key) in &mime_message.gossiped_keys { @@ -338,7 +362,7 @@ pub(crate) async fn handle_securejoin_handshake( ========================================================*/ bob::handle_auth_required(context, mime_message).await } - "vg-request-with-auth" | "vc-request-with-auth" => { + "vg-request-with-auth" | "vc-request-with-auth" | "vb-request-with-auth" => { /*========================================================== ==== Alice - the inviter side ==== ==== Steps 5+6 in "Setup verified contact" protocol ==== @@ -399,7 +423,7 @@ pub(crate) async fn handle_securejoin_handshake( ContactId::scaleup_origin(context, &[contact_id], Origin::SecurejoinInvited).await?; // for setup-contact, make Alice's one-to-one chat with Bob visible // (secure-join-information are shown in the group chat) - if !join_vg { + if step.starts_with("vc-") { ChatId::create_for_contact(context, contact_id).await?; } context.emit_event(EventType::ContactsChanged(Some(contact_id))); @@ -500,6 +524,7 @@ pub(crate) async fn handle_securejoin_handshake( /// we know that we are Alice (inviter-observer) /// that just marked peer (Bob) as verified /// in response to correct vc-request-with-auth message. +// TODO here I may be able to fix some multi-device things pub(crate) async fn observe_securejoin_on_other_device( context: &Context, mime_message: &MimeMessage, diff --git a/src/securejoin/bob.rs b/src/securejoin/bob.rs index 61f31e287..9f18f3997 100644 --- a/src/securejoin/bob.rs +++ b/src/securejoin/bob.rs @@ -4,7 +4,9 @@ use anyhow::{Context as _, Result}; use super::HandshakeMessage; use super::qrinvite::QrInvite; -use crate::chat::{self, ChatId, ProtectionStatus, is_contact_in_chat}; +use crate::chat::{ + self, ChatId, ProtectionStatus, is_contact_in_chat, save_broadcast_shared_secret, +}; use crate::constants::{Blocked, Chattype}; use crate::contact::Origin; use crate::context::Context; @@ -56,8 +58,43 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul ContactId::scaleup_origin(context, &[invite.contact_id()], Origin::SecurejoinJoined).await?; context.emit_event(EventType::ContactsChanged(None)); - // Now start the protocol and initialise the state. - { + if let QrInvite::Broadcast { shared_secret, .. } = &invite { + // TODO this causes some performance penalty because joining_chat_id is used again below, + // but maybe it's fine + let broadcast_chat_id = joining_chat_id(context, &invite, chat_id).await?; + // TODO save the secret to the second device + save_broadcast_shared_secret(context, broadcast_chat_id, shared_secret).await?; + + if verify_sender_by_fingerprint(context, invite.fingerprint(), invite.contact_id()).await? { + info!(context, "Using fast securejoin with symmetric encryption"); + + // The message has to be sent into the broadcast chat, rather than the 1:1 chat, + // so that it will be symmetrically encrypted + send_handshake_message( + context, + &invite, + broadcast_chat_id, + BobHandshakeMsg::RequestWithAuth, + ) + .await?; + + // Mark 1:1 chat as verified already. + chat_id + .set_protection( + context, + ProtectionStatus::Protected, + time(), + Some(invite.contact_id()), + ) + .await?; + + context.emit_event(EventType::SecurejoinJoinerProgress { + contact_id: invite.contact_id(), + progress: JoinerProgress::RequestWithAuthSent.to_usize(), + }); + } + } else { + // Start the original (non-broadcast) protocol and initialise the state. let has_key = context .sql .exists( @@ -115,22 +152,22 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul Ok(group_chat_id) } QrInvite::Broadcast { .. } => { - // For a secure-join we need to create the group and add the contact. The group will - // only become usable once the protocol is finished. - let group_chat_id = joining_chat_id(context, &invite, chat_id).await?; - if !is_contact_in_chat(context, group_chat_id, invite.contact_id()).await? { + // TODO code duplication with previous block + let broadcast_chat_id = joining_chat_id(context, &invite, chat_id).await?; + if !is_contact_in_chat(context, broadcast_chat_id, invite.contact_id()).await? { chat::add_to_chat_contacts_table( context, time(), - group_chat_id, + broadcast_chat_id, &[invite.contact_id()], ) .await?; } + // TODO this message should be translatable: let msg = "You were invited to join this channel. Waiting for the channel owner's device to reply…"; - chat::add_info_msg(context, group_chat_id, msg, time()).await?; - Ok(group_chat_id) + chat::add_info_msg(context, broadcast_chat_id, msg, time()).await?; + Ok(broadcast_chat_id) } QrInvite::Contact { .. } => { // For setup-contact the BobState already ensured the 1:1 chat exists because it @@ -318,7 +355,7 @@ pub(crate) async fn send_handshake_message( pub(crate) enum BobHandshakeMsg { /// vc-request or vg-request Request, - /// vc-request-with-auth or vg-request-with-auth + /// vc-request-with-auth, vg-request-with-auth, or vb-request-with-auth RequestWithAuth, } @@ -342,14 +379,14 @@ impl BobHandshakeMsg { Self::Request => match invite { QrInvite::Contact { .. } => "vc-request", QrInvite::Group { .. } => "vg-request", - QrInvite::Broadcast { .. } => "broadcast-request", + QrInvite::Broadcast { .. } => { + panic!("There is no request-with-auth for broadcasts") + } // TODO remove panic }, Self::RequestWithAuth => match invite { QrInvite::Contact { .. } => "vc-request-with-auth", QrInvite::Group { .. } => "vg-request-with-auth", - QrInvite::Broadcast { .. } => { - panic!("There is no request-with-auth for broadcasts") - } // TODO remove panic + QrInvite::Broadcast { .. } => "vb-request-with-auth", }, } } diff --git a/src/securejoin/qrinvite.rs b/src/securejoin/qrinvite.rs index bcf6e90ce..5413b1fbd 100644 --- a/src/securejoin/qrinvite.rs +++ b/src/securejoin/qrinvite.rs @@ -30,10 +30,11 @@ pub enum QrInvite { authcode: String, }, Broadcast { - broadcast_name: String, - grpid: String, contact_id: ContactId, fingerprint: Fingerprint, + broadcast_name: String, + grpid: String, + authcode: String, shared_secret: String, }, } @@ -71,8 +72,9 @@ impl QrInvite { /// The `AUTH` code of the setup-contact/secure-join protocol. pub fn authcode(&self) -> &str { match self { - Self::Contact { authcode, .. } | Self::Group { authcode, .. } => authcode, - Self::Broadcast { .. } => panic!("broadcast invite has no authcode"), // TODO panic + Self::Contact { authcode, .. } + | Self::Group { authcode, .. } + | Self::Broadcast { authcode, .. } => authcode, } } } @@ -113,15 +115,17 @@ impl TryFrom for QrInvite { grpid, contact_id, fingerprint, + authcode, shared_secret, } => Ok(QrInvite::Broadcast { broadcast_name, grpid, contact_id, fingerprint, + authcode, shared_secret, }), - _ => bail!("Unsupported QR type"), + _ => bail!("Unsupported QR type: {qr:?}"), } } } diff --git a/src/test_utils.rs b/src/test_utils.rs index 0534a66fc..c6c772cf9 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -229,15 +229,15 @@ impl TestContextManager { pub async fn exec_securejoin_qr( &self, scanner: &TestContext, - scanned: &TestContext, + inviter: &TestContext, qr: &str, ) -> ChatId { let chat_id = join_securejoin(&scanner.ctx, qr).await.unwrap(); loop { if let Some(sent) = scanner.pop_sent_msg_opt(Duration::ZERO).await { - scanned.recv_msg_opt(&sent).await; - } else if let Some(sent) = scanned.pop_sent_msg_opt(Duration::ZERO).await { + inviter.recv_msg_opt(&sent).await; + } else if let Some(sent) = inviter.pop_sent_msg_opt(Duration::ZERO).await { scanner.recv_msg_opt(&sent).await; } else { break; diff --git a/src/tools.rs b/src/tools.rs index fe462266a..59cca8d15 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -300,25 +300,6 @@ 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 - also, not sure whether the AES-128 thing is true -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.