From c91b349f6c4fdc87220540355f148c7a9c43e0a1 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Tue, 20 Jan 2026 17:10:34 +0100 Subject: [PATCH] feat: Decrypt and answer on Alice's side (sending the message seems not to work yet) --- src/message.rs | 16 ++++++++ src/mimefactory.rs | 2 +- src/mimeparser.rs | 6 ++- src/receive_imf.rs | 20 +--------- src/securejoin.rs | 90 ++++++++++++++++++++++++++++++++++++++++--- src/securejoin/bob.rs | 86 ++++------------------------------------- src/token.rs | 11 ++++++ 7 files changed, 125 insertions(+), 106 deletions(-) diff --git a/src/message.rs b/src/message.rs index 82ca488b4..13e1e1e03 100644 --- a/src/message.rs +++ b/src/message.rs @@ -2064,6 +2064,22 @@ pub(crate) async fn set_msg_failed( Ok(()) } +/// Inserts a tombstone into `msgs` table +/// to prevent downloading the same message in the future. +/// +/// Returns tombstone database row ID. +pub(crate) async fn insert_tombstone(context: &Context, rfc724_mid: &str) -> Result { + let row_id = context + .sql + .insert( + "INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)", + (rfc724_mid, DC_CHAT_ID_TRASH), + ) + .await?; + let msg_id = MsgId::new(u32::try_from(row_id)?); + Ok(msg_id) +} + /// The number of messages assigned to unblocked chats pub async fn get_unblocked_msg_cnt(context: &Context) -> usize { match context diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 9b818b25e..c82cd416b 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -2367,7 +2367,7 @@ pub(crate) async fn render_symm_encrypted_securejoin_message( // leaking information about the tokens. let compress = false; let encrypted = encrypt_helper - .encrypt_symmetrically(context, auth, message, compress) + .encrypt_symmetrically(context, auth, message, compress) // TODO this also signs the message. vc-request-pubkey shouldn't be signed. .await?; wrap_encrypted_part(encrypted) diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 33a02830c..8982416dd 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -18,7 +18,6 @@ use crate::authres::handle_authres; use crate::blob::BlobObject; use crate::chat::ChatId; use crate::config::Config; -use crate::constants; use crate::contact::ContactId; use crate::context::Context; use crate::decrypt::{try_decrypt, validate_detached_signature}; @@ -36,6 +35,7 @@ use crate::tools::{ get_filemeta, parse_receive_headers, smeared_time, time, truncate_msg_text, validate_id, }; use crate::{chatlist_events, location, tools}; +use crate::{constants, token}; /// Public key extracted from `Autocrypt-Gossip` /// header with associated information. @@ -382,7 +382,7 @@ impl MimeMessage { let mail_raw; // Memory location for a possible decrypted message. let decrypted_msg; // Decrypted signed OpenPGP message. - let secrets: Vec = context + let mut secrets: Vec = context .sql .query_map_vec("SELECT secret FROM broadcast_secrets", (), |row| { let secret: String = row.get(0)?; @@ -390,6 +390,8 @@ impl MimeMessage { }) .await?; + secrets.extend(token::lookup_all(context, token::Namespace::Auth).await?); + let (mail, is_encrypted) = match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring, &secrets)) { Ok(Some(mut msg)) => { diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 8a58fd6de..37aea4266 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -177,22 +177,6 @@ pub(crate) async fn receive_imf_from_inbox( receive_imf_inner(context, rfc724_mid, imf_raw, seen).await } -/// Inserts a tombstone into `msgs` table -/// to prevent downloading the same message in the future. -/// -/// Returns tombstone database row ID. -async fn insert_tombstone(context: &Context, rfc724_mid: &str) -> Result { - let row_id = context - .sql - .insert( - "INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)", - (rfc724_mid, DC_CHAT_ID_TRASH), - ) - .await?; - let msg_id = MsgId::new(u32::try_from(row_id)?); - Ok(msg_id) -} - async fn get_to_and_past_contact_ids( context: &Context, mime_parser: &MimeMessage, @@ -477,7 +461,7 @@ pub(crate) async fn receive_imf_inner( return Ok(None); } - let msg_ids = vec![insert_tombstone(context, rfc724_mid).await?]; + let msg_ids = vec![message::insert_tombstone(context, rfc724_mid).await?]; return Ok(Some(ReceivedMsg { chat_id: DC_CHAT_ID_TRASH, @@ -653,7 +637,7 @@ pub(crate) async fn receive_imf_inner( match res { securejoin::HandshakeMessage::Done | securejoin::HandshakeMessage::Ignore => { - let msg_id = insert_tombstone(context, rfc724_mid).await?; + let msg_id = message::insert_tombstone(context, rfc724_mid).await?; received_msg = Some(ReceivedMsg { chat_id: DC_CHAT_ID_TRASH, state: MessageState::InSeen, diff --git a/src/securejoin.rs b/src/securejoin.rs index 5286b2516..219242a71 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -20,14 +20,14 @@ use crate::headerdef::HeaderDef; use crate::key::{DcKey, Fingerprint, load_self_public_key}; use crate::log::LogExt as _; use crate::log::warn; -use crate::message::{Message, Viewtype}; +use crate::message::{self, Message, MsgId, Viewtype}; use crate::mimeparser::{MimeMessage, SystemMessage}; use crate::param::Param; use crate::qr::check_qr; use crate::securejoin::bob::JoinerProgress; use crate::sync::Sync::*; -use crate::tools::{create_id, time}; -use crate::{SecurejoinSource, stats}; +use crate::tools::{create_id, create_outgoing_rfc724_mid, time}; +use crate::{SecurejoinSource, mimefactory, stats}; use crate::{SecurejoinUiPath, token}; mod bob; @@ -346,12 +346,18 @@ pub(crate) enum HandshakeMessage { /// Step of Secure-Join protocol. #[derive(Debug, Display, PartialEq, Eq)] pub(crate) enum SecureJoinStep { - /// vc-request or vg-request + /// vc-request or vg-request; only used in legacy securejoin Request { invitenumber: String }, - /// vc-auth-required or vg-auth-required + /// vc-auth-required or vg-auth-required; only used in legacy securejoin AuthRequired, + /// vc-request-pubkey; only used in securejoin v3 + RequestPubkey, + + /// vc-pubkey; only used in securejoin v3 + Pubkey, + /// vc-request-with-auth or vg-request-with-auth RequestWithAuth, @@ -381,6 +387,8 @@ pub(crate) fn get_secure_join_step(mime_message: &MimeMessage) -> Option Some(SecureJoinStep::RequestPubkey), + "vc-pubkey" => Some(SecureJoinStep::Pubkey), "vg-auth-required" | "vc-auth-required" => Some(SecureJoinStep::AuthRequired), "vg-request-with-auth" | "vc-request-with-auth" => { Some(SecureJoinStep::RequestWithAuth) @@ -438,7 +446,10 @@ pub(crate) async fn handle_securejoin_handshake( // will improve security (completely unrelated to the securejoin protocol) // and is something we want to do in the future: // https://www.rfc-editor.org/rfc/rfc9580.html#name-surreptitious-forwarding - if !matches!(step, SecureJoinStep::Request { .. }) { + if !matches!( + step, + SecureJoinStep::Request { .. } | SecureJoinStep::RequestPubkey + ) { let mut self_found = false; let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint(); for (addr, key) in &mime_message.gossiped_keys { @@ -506,6 +517,53 @@ pub(crate) async fn handle_securejoin_handshake( ========================================================*/ bob::handle_auth_required(context, mime_message).await } + SecureJoinStep::RequestPubkey => { + /*======================================================== + ==== Alice - the inviter's side ===== + ==== Bob requests our public key (Securejoin v3) ===== + ========================================================*/ + + if !mime_message.was_encrypted() { + warn!(context, "Ignoring unencrypted RequestPubkey"); + return Ok(HandshakeMessage::Ignore); + } + let Some(auth) = mime_message.get_header(HeaderDef::SecureJoinAuth) else { + warn!( + context, + "Ignoring {step} message because of missing auth code." + ); + return Ok(HandshakeMessage::Ignore); + }; + if !token::exists(context, token::Namespace::Auth, auth).await? { + warn!(context, "Secure-join denied (bad auth)."); + return Ok(HandshakeMessage::Ignore); + } + + let rfc724_mid = create_outgoing_rfc724_mid(); + let addr = ContactAddress::new(&mime_message.from.addr)?; + let attach_self_pubkey = true; + let rendered_message = mimefactory::render_symm_encrypted_securejoin_message( + context, + "vc-pubkey", + &rfc724_mid, + attach_self_pubkey, + auth, + ) + .await?; + + let msg_id = message::insert_tombstone(context, &rfc724_mid).await?; + insert_into_smtp(context, &rfc724_mid, &addr, rendered_message, msg_id).await?; + context.scheduler.interrupt_smtp().await; + + Ok(HandshakeMessage::Done) + } + SecureJoinStep::Pubkey => { + /*======================================================== + ==== Bob - the joiner's side ===== + ==== Alice sent us her pubkey (Securejoin v3) ===== + ========================================================*/ + todo!() + } SecureJoinStep::RequestWithAuth => { /*========================================================== ==== Alice - the inviter side ==== @@ -665,6 +723,24 @@ pub(crate) async fn handle_securejoin_handshake( } } +async fn insert_into_smtp( + context: &Context, + rfc724_mid: &str, + recipient: &str, + rendered_message: String, + msg_id: MsgId, +) -> Result<(), Error> { + context + .sql + .execute( + "INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) + VALUES (?1, ?2, ?3, ?4)", + (&rfc724_mid, &recipient, &rendered_message, msg_id), + ) + .await?; + Ok(()) +} + /// Observe self-sent Securejoin message. /// /// In a multi-device-setup, there may be other devices that "see" the handshake messages. @@ -696,6 +772,8 @@ pub(crate) async fn observe_securejoin_on_other_device( match step { SecureJoinStep::Request { .. } | SecureJoinStep::AuthRequired + | SecureJoinStep::RequestPubkey + | SecureJoinStep::Pubkey | SecureJoinStep::Deprecated | SecureJoinStep::Unknown { .. } => { return Ok(HandshakeMessage::Ignore); diff --git a/src/securejoin/bob.rs b/src/securejoin/bob.rs index 533c8c855..6b18b3d17 100644 --- a/src/securejoin/bob.rs +++ b/src/securejoin/bob.rs @@ -11,10 +11,12 @@ 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::message::{self, 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::securejoin::{ + ContactId, encrypted_and_signed, insert_into_smtp, verify_sender_by_fingerprint, +}; use crate::stock_str; use crate::sync::Sync::*; use crate::tools::{create_outgoing_rfc724_mid, smeared_time, time}; @@ -302,95 +304,21 @@ pub(crate) async fn send_handshake_message( if invite.is_v3() && matches!(step, BobHandshakeMsg::Request) { // Send a minimal symmetrically-encrypted vc-request message - // TODO: Either add a message to the database, or make sure that smtp.rs gets along with a 0 or NULL msg_id - /* - msg.state = MessageState::OutPending; - msg.timestamp_sort = create_smeared_timestamp(context); - msg.rfc724_mid = create_outgoing_rfc724_mid(); - let is_bot = context.get_config_bool(Config::Bot).await?; - msg.param - .set_optional(Param::Bot, Some("1").filter(|_| is_bot)); - - let raw_id = context - .sql - .insert( - "INSERT INTO msgs ( - rfc724_mid, - chat_id, - from_id, - to_id, - timestamp, - type, - state, - txt, - txt_normalized, - subject, - param, - hidden, - mime_in_reply_to, - mime_references, - mime_modified, - mime_headers, - mime_compressed, - location_id, - ephemeral_timer, - ephemeral_timestamp) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);", - params_slice![ - msg.rfc724_mid, - msg.chat_id, - msg.from_id, - to_id, - msg.timestamp_sort, - msg.viewtype, - msg.state, - msg_text, - normalize_text(&msg_text), - &msg.subject, - msg.param.to_string(), - msg.hidden, - msg.in_reply_to.as_deref().unwrap_or_default(), - new_references, - new_mime_headers.is_some(), - new_mime_headers.unwrap_or_default(), - location_id as i32, - ephemeral_timer, - ephemeral_timestamp - ], - ) - .await?; - context.new_msgs_notify.notify_one(); - msg.id = MsgId::new(u32::try_from(raw_id)?); - */ - let rfc724_mid = create_outgoing_rfc724_mid(); let contact = Contact::get_by_id(context, invite.contact_id()).await?; let recipient = contact.get_addr(); let attach_self_pubkey = false; let rendered_message = mimefactory::render_symm_encrypted_securejoin_message( context, - step.securejoin_header(invite), + "vc-request-pubkey", &rfc724_mid, attach_self_pubkey, invite.authcode(), ) .await?; - // TODO code duplication - context - .sql - .execute( - "INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) - VALUES (?1, ?2, ?3, ?4)", - ( - &rfc724_mid, - &recipient, - &rendered_message, - 0, // TODO - ), - ) - .await?; - + let msg_id = message::insert_tombstone(context, &rfc724_mid).await?; + insert_into_smtp(context, &rfc724_mid, recipient, rendered_message, msg_id).await?; context.scheduler.interrupt_smtp().await; } else { let mut msg = Message { diff --git a/src/token.rs b/src/token.rs index 1a189a151..d144b71ab 100644 --- a/src/token.rs +++ b/src/token.rs @@ -62,6 +62,17 @@ pub async fn lookup( .await } +pub async fn lookup_all(context: &Context, namespace: Namespace) -> Result> { + context + .sql + .query_map_vec( + "SELECT token FROM tokens WHERE namespc=? ORDER BY timestamp DESC LIMIT 1", + (namespace,), + |row| Ok(row.get(0)?), + ) + .await +} + pub async fn lookup_or_new( context: &Context, namespace: Namespace,