Skip to content

feat: allow scanning multiple securejoin QR codes in parallel #6534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 20 additions & 13 deletions src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ use crate::mimeparser::SystemMessage;
use crate::param::{Param, Params};
use crate::peerstate::Peerstate;
use crate::receive_imf::ReceivedMsg;
use crate::securejoin::BobState;
use crate::smtp::send_msg_to_smtp;
use crate::stock_str;
use crate::sync::{self, Sync::*, SyncData};
Expand Down Expand Up @@ -2569,19 +2568,27 @@ pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
/// Checks if there is a 1:1 chat in-progress SecureJoin for Bob and, if necessary, schedules a task
/// unblocking the chat and notifying the user accordingly.
pub(crate) async fn resume_securejoin_wait(context: &Context) -> Result<()> {
let Some(bobstate) = BobState::from_db(&context.sql).await? else {
return Ok(());
};
if !bobstate.in_progress() {
return Ok(());
}
let chat_id = bobstate.alice_chat();
let chat = Chat::load_from_db(context, chat_id).await?;
let timeout = chat
.check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
let chat_ids: Vec<ChatId> = context
.sql
.query_map(
"SELECT chat_id FROM bobstate",
(),
|row| {
let chat_id: ChatId = row.get(0)?;
Ok(chat_id)
},
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)
.await?;
if timeout > 0 {
chat_id.spawn_securejoin_wait(context, timeout);

for chat_id in chat_ids {
let chat = Chat::load_from_db(context, chat_id).await?;
let timeout = chat
.check_securejoin_wait(context, constants::SECUREJOIN_WAIT_TIMEOUT)
.await?;
if timeout > 0 {
chat_id.spawn_securejoin_wait(context, timeout);
}
}
Ok(())
}
Expand Down
6 changes: 5 additions & 1 deletion src/contact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,11 @@ pub enum Origin {
/// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling contact_is_verified() !
SecurejoinInvited = 0x0100_0000,

/// set on Bob's side for contacts scanned and verified from a QR code. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling contact_is_verified() !
/// Set on Bob's side for contacts scanned from a QR code.
/// Only means the contact has been scanned from the QR code,
/// but does not mean that securejoin succeeded
/// or the key has not changed since the last scan.
/// Getting the current key verification status requires calling contact_is_verified() !
SecurejoinJoined = 0x0200_0000,

/// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
Expand Down
2 changes: 1 addition & 1 deletion src/receive_imf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ pub(crate) async fn receive_imf_inner(
if mime_parser.get_header(HeaderDef::SecureJoin).is_some() {
let res;
if mime_parser.incoming {
res = handle_securejoin_handshake(context, &mime_parser, from_id)
res = handle_securejoin_handshake(context, &mut mime_parser, from_id)
.await
.context("error in Secure-Join message handling")?;

Expand Down
43 changes: 21 additions & 22 deletions src/securejoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ use crate::token;
use crate::tools::time;

mod bob;
mod bobstate;
mod qrinvite;

pub(crate) use bobstate::BobState;
use qrinvite::QrInvite;

use crate::token::Namespace;
Expand Down Expand Up @@ -168,8 +166,7 @@ async fn securejoin(context: &Context, qr: &str) -> Result<ChatId> {
bob::start_protocol(context, invite).await
}

/// Send handshake message from Alice's device;
/// Bob's handshake messages are sent in `BobState::send_handshake_message()`.
/// Send handshake message from Alice's device.
async fn send_alice_handshake_msg(
context: &Context,
contact_id: ContactId,
Expand Down Expand Up @@ -259,7 +256,7 @@ pub(crate) enum HandshakeMessage {
/// This leaves it on the IMAP server. It means other devices on this account can
/// receive and potentially process this message as well. This is useful for example
/// when the other device is running the protocol and has the relevant QR-code
/// information while this device does not have the joiner state ([`BobState`]).
/// information while this device does not have the joiner state.
Ignore,
/// The message should be further processed by incoming message handling.
///
Expand All @@ -281,7 +278,7 @@ pub(crate) enum HandshakeMessage {
/// database; this is done by `receive_imf()` later on as needed.
pub(crate) async fn handle_securejoin_handshake(
context: &Context,
mime_message: &MimeMessage,
mime_message: &mut MimeMessage,
contact_id: ContactId,
) -> Result<HandshakeMessage> {
if contact_id.is_special() {
Expand Down Expand Up @@ -479,15 +476,10 @@ pub(crate) async fn handle_securejoin_handshake(
==== Step 7 in "Setup verified contact" protocol ====
=======================================================*/
"vc-contact-confirm" => {
if let Some(mut bobstate) = BobState::from_db(&context.sql).await? {
if !bobstate.is_msg_expected(context, step) {
warn!(context, "Unexpected vc-contact-confirm.");
return Ok(HandshakeMessage::Ignore);
}

bobstate.step_contact_confirm(context).await?;
bobstate.emit_progress(context, JoinerProgress::Succeeded);
}
context.emit_event(EventType::SecurejoinJoinerProgress {
contact_id,
progress: JoinerProgress::Succeeded.to_usize(),
});
Ok(HandshakeMessage::Ignore)
}
"vg-member-added" => {
Expand All @@ -506,15 +498,22 @@ pub(crate) async fn handle_securejoin_handshake(
);
return Ok(HandshakeMessage::Propagate);
}
if let Some(mut bobstate) = BobState::from_db(&context.sql).await? {
if !bobstate.is_msg_expected(context, step) {
warn!(context, "Unexpected vg-member-added.");
return Ok(HandshakeMessage::Propagate);
}

bobstate.step_contact_confirm(context).await?;
bobstate.emit_progress(context, JoinerProgress::Succeeded);
// Mark peer as backward verified.
//
// This is needed for the case when we join a non-protected group
// because in this case `Chat-Verified` header that otherwise
// sets backward verification is not sent.
if let Some(peerstate) = &mut mime_message.peerstate {
peerstate.backward_verified_key_id =
Some(context.get_config_i64(Config::KeyId).await?).filter(|&id| id > 0);
peerstate.save_to_db(&context.sql).await?;
}

context.emit_event(EventType::SecurejoinJoinerProgress {
contact_id,
progress: JoinerProgress::Succeeded.to_usize(),
});
Ok(HandshakeMessage::Propagate)
}

Expand Down
Loading
Loading