| | |
| |
|
| | use anyhow::{Context as _, Result}; |
| |
|
| | use super::HandshakeMessage; |
| | use super::qrinvite::QrInvite; |
| | use crate::chat::{self, ChatId, is_contact_in_chat}; |
| | use crate::chatlist_events; |
| | use crate::constants::{Blocked, Chattype}; |
| | use crate::contact::Origin; |
| | use crate::context::Context; |
| | use crate::events::EventType; |
| | use crate::key::self_fingerprint; |
| | use crate::log::LogExt; |
| | use crate::message::{Message, MsgId, Viewtype}; |
| | use crate::mimeparser::{MimeMessage, SystemMessage}; |
| | use crate::param::{Param, Params}; |
| | use crate::securejoin::{ContactId, encrypted_and_signed, verify_sender_by_fingerprint}; |
| | use crate::stock_str; |
| | use crate::sync::Sync::*; |
| | use crate::tools::{smeared_time, time}; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Result<ChatId> { |
| | |
| | |
| | |
| | let private_chat_id = private_chat_id(context, &invite).await?; |
| |
|
| | ContactId::scaleup_origin(context, &[invite.contact_id()], Origin::SecurejoinJoined).await?; |
| | context.emit_event(EventType::ContactsChanged(None)); |
| |
|
| | let has_key = context |
| | .sql |
| | .exists( |
| | "SELECT COUNT(*) FROM public_keys WHERE fingerprint=?", |
| | (invite.fingerprint().hex(),), |
| | ) |
| | .await?; |
| |
|
| | |
| | { |
| | |
| | |
| | let joining_chat_id = match invite { |
| | QrInvite::Group { ref grpid, .. } | QrInvite::Broadcast { ref grpid, .. } => { |
| | if let Some((joining_chat_id, _blocked)) = |
| | chat::get_chat_id_by_grpid(context, grpid).await? |
| | { |
| | if is_contact_in_chat(context, joining_chat_id, ContactId::SELF).await? { |
| | Some(joining_chat_id) |
| | } else { |
| | None |
| | } |
| | } else { |
| | None |
| | } |
| | } |
| | QrInvite::Contact { .. } => None, |
| | }; |
| |
|
| | if let Some(joining_chat_id) = joining_chat_id { |
| | |
| | |
| | |
| | |
| | context.emit_event(EventType::SecurejoinJoinerProgress { |
| | contact_id: invite.contact_id(), |
| | progress: JoinerProgress::Succeeded.into_u16(), |
| | }); |
| | return Ok(joining_chat_id); |
| | } else if has_key |
| | && verify_sender_by_fingerprint(context, invite.fingerprint(), invite.contact_id()) |
| | .await? |
| | { |
| | |
| | info!(context, "Taking securejoin protocol shortcut"); |
| | send_handshake_message( |
| | context, |
| | &invite, |
| | private_chat_id, |
| | BobHandshakeMsg::RequestWithAuth, |
| | ) |
| | .await?; |
| |
|
| | context.emit_event(EventType::SecurejoinJoinerProgress { |
| | contact_id: invite.contact_id(), |
| | progress: JoinerProgress::RequestWithAuthSent.into_u16(), |
| | }); |
| | } else { |
| | send_handshake_message(context, &invite, private_chat_id, BobHandshakeMsg::Request) |
| | .await?; |
| |
|
| | insert_new_db_entry(context, invite.clone(), private_chat_id).await?; |
| | } |
| | } |
| |
|
| | match invite { |
| | QrInvite::Group { .. } => { |
| | let joining_chat_id = joining_chat_id(context, &invite, private_chat_id).await?; |
| | let msg = stock_str::secure_join_started(context, invite.contact_id()).await; |
| | chat::add_info_msg(context, joining_chat_id, &msg).await?; |
| | Ok(joining_chat_id) |
| | } |
| | QrInvite::Broadcast { .. } => { |
| | let joining_chat_id = joining_chat_id(context, &invite, private_chat_id).await?; |
| | |
| | if !is_contact_in_chat(context, joining_chat_id, invite.contact_id()).await? { |
| | chat::add_to_chat_contacts_table( |
| | context, |
| | time(), |
| | joining_chat_id, |
| | &[invite.contact_id()], |
| | ) |
| | .await?; |
| | } |
| |
|
| | |
| | if !is_contact_in_chat(context, joining_chat_id, ContactId::SELF).await? { |
| | let msg = |
| | stock_str::secure_join_broadcast_started(context, invite.contact_id()).await; |
| | chat::add_info_msg(context, joining_chat_id, &msg).await?; |
| | } |
| | Ok(joining_chat_id) |
| | } |
| | QrInvite::Contact { .. } => { |
| | |
| | |
| | if !has_key { |
| | chat::add_info_msg_with_cmd( |
| | context, |
| | private_chat_id, |
| | &stock_str::securejoin_wait(context).await, |
| | SystemMessage::SecurejoinWait, |
| | None, |
| | time(), |
| | None, |
| | None, |
| | None, |
| | ) |
| | .await?; |
| | } |
| | Ok(private_chat_id) |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | async fn insert_new_db_entry(context: &Context, invite: QrInvite, chat_id: ChatId) -> Result<i64> { |
| | |
| | |
| | |
| | context |
| | .sql |
| | .insert( |
| | "INSERT INTO bobstate (invite, next_step, chat_id) VALUES (?, ?, ?);", |
| | (invite, 0, chat_id), |
| | ) |
| | .await |
| | } |
| |
|
| | async fn delete_securejoin_wait_msg(context: &Context, chat_id: ChatId) -> Result<()> { |
| | if let Some((msg_id, param)) = context |
| | .sql |
| | .query_row_optional( |
| | " |
| | SELECT id, param FROM msgs |
| | WHERE timestamp=(SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND hidden=0) |
| | AND chat_id=? AND hidden=0 |
| | LIMIT 1 |
| | ", |
| | (chat_id, chat_id), |
| | |row| { |
| | let id: MsgId = row.get(0)?; |
| | let param: String = row.get(1)?; |
| | let param: Params = param.parse().unwrap_or_default(); |
| | Ok((id, param)) |
| | }, |
| | ) |
| | .await? |
| | && param.get_cmd() == SystemMessage::SecurejoinWait |
| | { |
| | let on_server = false; |
| | msg_id.trash(context, on_server).await?; |
| | context.emit_event(EventType::MsgDeleted { chat_id, msg_id }); |
| | context.emit_msgs_changed_without_msg_id(chat_id); |
| | chatlist_events::emit_chatlist_item_changed(context, chat_id); |
| | context.emit_msgs_changed_without_ids(); |
| | chatlist_events::emit_chatlist_changed(context); |
| | } |
| | Ok(()) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | pub(super) async fn handle_auth_required( |
| | context: &Context, |
| | message: &MimeMessage, |
| | ) -> Result<HandshakeMessage> { |
| | |
| | let bob_states = context |
| | .sql |
| | .query_map_vec("SELECT id, invite FROM bobstate", (), |row| { |
| | let row_id: i64 = row.get(0)?; |
| | let invite: QrInvite = row.get(1)?; |
| | Ok((row_id, invite)) |
| | }) |
| | .await?; |
| |
|
| | info!( |
| | context, |
| | "Bob Step 4 - handling {{vc,vg}}-auth-required message." |
| | ); |
| |
|
| | let mut auth_sent = false; |
| | for (bobstate_row_id, invite) in bob_states { |
| | if !encrypted_and_signed(context, message, invite.fingerprint()) { |
| | continue; |
| | } |
| |
|
| | if !verify_sender_by_fingerprint(context, invite.fingerprint(), invite.contact_id()).await? |
| | { |
| | continue; |
| | } |
| |
|
| | info!(context, "Fingerprint verified.",); |
| | let chat_id = private_chat_id(context, &invite).await?; |
| | delete_securejoin_wait_msg(context, chat_id) |
| | .await |
| | .context("delete_securejoin_wait_msg") |
| | .log_err(context) |
| | .ok(); |
| | send_handshake_message(context, &invite, chat_id, BobHandshakeMsg::RequestWithAuth).await?; |
| | context |
| | .sql |
| | .execute("DELETE FROM bobstate WHERE id=?", (bobstate_row_id,)) |
| | .await?; |
| |
|
| | match invite { |
| | QrInvite::Contact { .. } | QrInvite::Broadcast { .. } => {} |
| | QrInvite::Group { .. } => { |
| | |
| | |
| | let contact_id = invite.contact_id(); |
| | let msg = stock_str::secure_join_replies(context, contact_id).await; |
| | let chat_id = joining_chat_id(context, &invite, chat_id).await?; |
| | chat::add_info_msg(context, chat_id, &msg).await?; |
| | } |
| | } |
| |
|
| | context.emit_event(EventType::SecurejoinJoinerProgress { |
| | contact_id: invite.contact_id(), |
| | progress: JoinerProgress::RequestWithAuthSent.into_u16(), |
| | }); |
| |
|
| | auth_sent = true; |
| | } |
| |
|
| | if auth_sent { |
| | |
| | Ok(HandshakeMessage::Done) |
| | } else { |
| | |
| | |
| | |
| | |
| | Ok(HandshakeMessage::Ignore) |
| | } |
| | } |
| |
|
| | |
| | pub(crate) async fn send_handshake_message( |
| | context: &Context, |
| | invite: &QrInvite, |
| | chat_id: ChatId, |
| | step: BobHandshakeMsg, |
| | ) -> Result<()> { |
| | let mut msg = Message { |
| | viewtype: Viewtype::Text, |
| | text: step.body_text(invite), |
| | hidden: true, |
| | ..Default::default() |
| | }; |
| | msg.param.set_cmd(SystemMessage::SecurejoinMessage); |
| |
|
| | |
| | msg.param.set(Param::Arg, step.securejoin_header(invite)); |
| |
|
| | match step { |
| | BobHandshakeMsg::Request => { |
| | |
| | msg.param.set(Param::Arg2, invite.invitenumber()); |
| | msg.force_plaintext(); |
| | } |
| | BobHandshakeMsg::RequestWithAuth => { |
| | |
| | msg.param.set(Param::Arg2, invite.authcode()); |
| | msg.param.set_int(Param::GuaranteeE2ee, 1); |
| |
|
| | |
| | let bob_fp = self_fingerprint(context).await?; |
| | msg.param.set(Param::Arg3, bob_fp); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if let QrInvite::Group { grpid, .. } = invite { |
| | msg.param.set(Param::Arg4, grpid); |
| | } |
| | } |
| | }; |
| |
|
| | chat::send_msg(context, chat_id, &mut msg).await?; |
| | Ok(()) |
| | } |
| |
|
| | |
| | pub(crate) enum BobHandshakeMsg { |
| | |
| | Request, |
| | |
| | RequestWithAuth, |
| | } |
| |
|
| | impl BobHandshakeMsg { |
| | |
| | |
| | |
| | |
| | |
| | fn body_text(&self, invite: &QrInvite) -> String { |
| | format!("Secure-Join: {}", self.securejoin_header(invite)) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | fn securejoin_header(&self, invite: &QrInvite) -> &'static str { |
| | match self { |
| | Self::Request => match invite { |
| | QrInvite::Contact { .. } => "vc-request", |
| | QrInvite::Group { .. } => "vg-request", |
| | QrInvite::Broadcast { .. } => "vg-request", |
| | }, |
| | Self::RequestWithAuth => match invite { |
| | QrInvite::Contact { .. } => "vc-request-with-auth", |
| | QrInvite::Group { .. } => "vg-request-with-auth", |
| | QrInvite::Broadcast { .. } => "vg-request-with-auth", |
| | }, |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | async fn private_chat_id(context: &Context, invite: &QrInvite) -> Result<ChatId> { |
| | let hidden = match invite { |
| | QrInvite::Contact { .. } => Blocked::Not, |
| | QrInvite::Group { .. } => Blocked::Yes, |
| | QrInvite::Broadcast { .. } => Blocked::Yes, |
| | }; |
| |
|
| | ChatId::create_for_contact_with_blocked(context, invite.contact_id(), hidden) |
| | .await |
| | .with_context(|| format!("can't create chat for contact {}", invite.contact_id())) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | async fn joining_chat_id( |
| | context: &Context, |
| | invite: &QrInvite, |
| | alice_chat_id: ChatId, |
| | ) -> Result<ChatId> { |
| | match invite { |
| | QrInvite::Contact { .. } => Ok(alice_chat_id), |
| | QrInvite::Group { grpid, name, .. } | QrInvite::Broadcast { name, grpid, .. } => { |
| | let chattype = if matches!(invite, QrInvite::Group { .. }) { |
| | Chattype::Group |
| | } else { |
| | Chattype::InBroadcast |
| | }; |
| |
|
| | let chat_id = match chat::get_chat_id_by_grpid(context, grpid).await? { |
| | Some((chat_id, _blocked)) => { |
| | chat_id.unblock_ex(context, Nosync).await?; |
| | chat_id |
| | } |
| | None => { |
| | ChatId::create_multiuser_record( |
| | context, |
| | chattype, |
| | grpid, |
| | name, |
| | Blocked::Not, |
| | None, |
| | smeared_time(context), |
| | ) |
| | .await? |
| | } |
| | }; |
| | Ok(chat_id) |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | pub(crate) enum JoinerProgress { |
| | |
| | |
| | |
| | RequestWithAuthSent, |
| | |
| | Succeeded, |
| | } |
| |
|
| | impl JoinerProgress { |
| | pub(crate) fn into_u16(self) -> u16 { |
| | match self { |
| | JoinerProgress::RequestWithAuthSent => 400, |
| | JoinerProgress::Succeeded => 1000, |
| | } |
| | } |
| | } |
| |
|