feat: Decrypt and answer on Alice's side (sending the message seems not to work yet)

This commit is contained in:
Hocuri
2026-01-20 17:10:34 +01:00
parent 1014bf17a4
commit c91b349f6c
7 changed files with 125 additions and 106 deletions

View File

@@ -2064,6 +2064,22 @@ pub(crate) async fn set_msg_failed(
Ok(()) 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<MsgId> {
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 /// The number of messages assigned to unblocked chats
pub async fn get_unblocked_msg_cnt(context: &Context) -> usize { pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
match context match context

View File

@@ -2367,7 +2367,7 @@ pub(crate) async fn render_symm_encrypted_securejoin_message(
// leaking information about the tokens. // leaking information about the tokens.
let compress = false; let compress = false;
let encrypted = encrypt_helper 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?; .await?;
wrap_encrypted_part(encrypted) wrap_encrypted_part(encrypted)

View File

@@ -18,7 +18,6 @@ use crate::authres::handle_authres;
use crate::blob::BlobObject; use crate::blob::BlobObject;
use crate::chat::ChatId; use crate::chat::ChatId;
use crate::config::Config; use crate::config::Config;
use crate::constants;
use crate::contact::ContactId; use crate::contact::ContactId;
use crate::context::Context; use crate::context::Context;
use crate::decrypt::{try_decrypt, validate_detached_signature}; 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, get_filemeta, parse_receive_headers, smeared_time, time, truncate_msg_text, validate_id,
}; };
use crate::{chatlist_events, location, tools}; use crate::{chatlist_events, location, tools};
use crate::{constants, token};
/// Public key extracted from `Autocrypt-Gossip` /// Public key extracted from `Autocrypt-Gossip`
/// header with associated information. /// header with associated information.
@@ -382,7 +382,7 @@ impl MimeMessage {
let mail_raw; // Memory location for a possible decrypted message. let mail_raw; // Memory location for a possible decrypted message.
let decrypted_msg; // Decrypted signed OpenPGP message. let decrypted_msg; // Decrypted signed OpenPGP message.
let secrets: Vec<String> = context let mut secrets: Vec<String> = context
.sql .sql
.query_map_vec("SELECT secret FROM broadcast_secrets", (), |row| { .query_map_vec("SELECT secret FROM broadcast_secrets", (), |row| {
let secret: String = row.get(0)?; let secret: String = row.get(0)?;
@@ -390,6 +390,8 @@ impl MimeMessage {
}) })
.await?; .await?;
secrets.extend(token::lookup_all(context, token::Namespace::Auth).await?);
let (mail, is_encrypted) = let (mail, is_encrypted) =
match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring, &secrets)) { match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring, &secrets)) {
Ok(Some(mut msg)) => { Ok(Some(mut msg)) => {

View File

@@ -177,22 +177,6 @@ pub(crate) async fn receive_imf_from_inbox(
receive_imf_inner(context, rfc724_mid, imf_raw, seen).await 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<MsgId> {
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( async fn get_to_and_past_contact_ids(
context: &Context, context: &Context,
mime_parser: &MimeMessage, mime_parser: &MimeMessage,
@@ -477,7 +461,7 @@ pub(crate) async fn receive_imf_inner(
return Ok(None); 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 { return Ok(Some(ReceivedMsg {
chat_id: DC_CHAT_ID_TRASH, chat_id: DC_CHAT_ID_TRASH,
@@ -653,7 +637,7 @@ pub(crate) async fn receive_imf_inner(
match res { match res {
securejoin::HandshakeMessage::Done | securejoin::HandshakeMessage::Ignore => { 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 { received_msg = Some(ReceivedMsg {
chat_id: DC_CHAT_ID_TRASH, chat_id: DC_CHAT_ID_TRASH,
state: MessageState::InSeen, state: MessageState::InSeen,

View File

@@ -20,14 +20,14 @@ use crate::headerdef::HeaderDef;
use crate::key::{DcKey, Fingerprint, load_self_public_key}; use crate::key::{DcKey, Fingerprint, load_self_public_key};
use crate::log::LogExt as _; use crate::log::LogExt as _;
use crate::log::warn; use crate::log::warn;
use crate::message::{Message, Viewtype}; use crate::message::{self, Message, MsgId, Viewtype};
use crate::mimeparser::{MimeMessage, SystemMessage}; use crate::mimeparser::{MimeMessage, SystemMessage};
use crate::param::Param; use crate::param::Param;
use crate::qr::check_qr; use crate::qr::check_qr;
use crate::securejoin::bob::JoinerProgress; use crate::securejoin::bob::JoinerProgress;
use crate::sync::Sync::*; use crate::sync::Sync::*;
use crate::tools::{create_id, time}; use crate::tools::{create_id, create_outgoing_rfc724_mid, time};
use crate::{SecurejoinSource, stats}; use crate::{SecurejoinSource, mimefactory, stats};
use crate::{SecurejoinUiPath, token}; use crate::{SecurejoinUiPath, token};
mod bob; mod bob;
@@ -346,12 +346,18 @@ pub(crate) enum HandshakeMessage {
/// Step of Secure-Join protocol. /// Step of Secure-Join protocol.
#[derive(Debug, Display, PartialEq, Eq)] #[derive(Debug, Display, PartialEq, Eq)]
pub(crate) enum SecureJoinStep { pub(crate) enum SecureJoinStep {
/// vc-request or vg-request /// vc-request or vg-request; only used in legacy securejoin
Request { invitenumber: String }, Request { invitenumber: String },
/// vc-auth-required or vg-auth-required /// vc-auth-required or vg-auth-required; only used in legacy securejoin
AuthRequired, 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 /// vc-request-with-auth or vg-request-with-auth
RequestWithAuth, RequestWithAuth,
@@ -381,6 +387,8 @@ pub(crate) fn get_secure_join_step(mime_message: &MimeMessage) -> Option<SecureJ
}) })
} else if let Some(step) = mime_message.get_header(HeaderDef::SecureJoin) { } else if let Some(step) = mime_message.get_header(HeaderDef::SecureJoin) {
match step { match step {
"vc-request-pubkey" => Some(SecureJoinStep::RequestPubkey),
"vc-pubkey" => Some(SecureJoinStep::Pubkey),
"vg-auth-required" | "vc-auth-required" => Some(SecureJoinStep::AuthRequired), "vg-auth-required" | "vc-auth-required" => Some(SecureJoinStep::AuthRequired),
"vg-request-with-auth" | "vc-request-with-auth" => { "vg-request-with-auth" | "vc-request-with-auth" => {
Some(SecureJoinStep::RequestWithAuth) Some(SecureJoinStep::RequestWithAuth)
@@ -438,7 +446,10 @@ pub(crate) async fn handle_securejoin_handshake(
// will improve security (completely unrelated to the securejoin protocol) // will improve security (completely unrelated to the securejoin protocol)
// and is something we want to do in the future: // and is something we want to do in the future:
// https://www.rfc-editor.org/rfc/rfc9580.html#name-surreptitious-forwarding // 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 mut self_found = false;
let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint(); let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint();
for (addr, key) in &mime_message.gossiped_keys { 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 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 => { SecureJoinStep::RequestWithAuth => {
/*========================================================== /*==========================================================
==== Alice - the inviter side ==== ==== 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. /// Observe self-sent Securejoin message.
/// ///
/// In a multi-device-setup, there may be other devices that "see" the handshake messages. /// 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 { match step {
SecureJoinStep::Request { .. } SecureJoinStep::Request { .. }
| SecureJoinStep::AuthRequired | SecureJoinStep::AuthRequired
| SecureJoinStep::RequestPubkey
| SecureJoinStep::Pubkey
| SecureJoinStep::Deprecated | SecureJoinStep::Deprecated
| SecureJoinStep::Unknown { .. } => { | SecureJoinStep::Unknown { .. } => {
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);

View File

@@ -11,10 +11,12 @@ use crate::context::Context;
use crate::events::EventType; use crate::events::EventType;
use crate::key::self_fingerprint; use crate::key::self_fingerprint;
use crate::log::LogExt; use crate::log::LogExt;
use crate::message::{Message, MsgId, Viewtype}; use crate::message::{self, Message, MsgId, Viewtype};
use crate::mimeparser::{MimeMessage, SystemMessage}; use crate::mimeparser::{MimeMessage, SystemMessage};
use crate::param::{Param, Params}; 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::stock_str;
use crate::sync::Sync::*; use crate::sync::Sync::*;
use crate::tools::{create_outgoing_rfc724_mid, smeared_time, time}; 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) { if invite.is_v3() && matches!(step, BobHandshakeMsg::Request) {
// Send a minimal symmetrically-encrypted vc-request message // 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 rfc724_mid = create_outgoing_rfc724_mid();
let contact = Contact::get_by_id(context, invite.contact_id()).await?; let contact = Contact::get_by_id(context, invite.contact_id()).await?;
let recipient = contact.get_addr(); let recipient = contact.get_addr();
let attach_self_pubkey = false; let attach_self_pubkey = false;
let rendered_message = mimefactory::render_symm_encrypted_securejoin_message( let rendered_message = mimefactory::render_symm_encrypted_securejoin_message(
context, context,
step.securejoin_header(invite), "vc-request-pubkey",
&rfc724_mid, &rfc724_mid,
attach_self_pubkey, attach_self_pubkey,
invite.authcode(), invite.authcode(),
) )
.await?; .await?;
// TODO code duplication let msg_id = message::insert_tombstone(context, &rfc724_mid).await?;
context insert_into_smtp(context, &rfc724_mid, recipient, rendered_message, msg_id).await?;
.sql
.execute(
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
VALUES (?1, ?2, ?3, ?4)",
(
&rfc724_mid,
&recipient,
&rendered_message,
0, // TODO
),
)
.await?;
context.scheduler.interrupt_smtp().await; context.scheduler.interrupt_smtp().await;
} else { } else {
let mut msg = Message { let mut msg = Message {

View File

@@ -62,6 +62,17 @@ pub async fn lookup(
.await .await
} }
pub async fn lookup_all(context: &Context, namespace: Namespace) -> Result<Vec<String>> {
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( pub async fn lookup_or_new(
context: &Context, context: &Context,
namespace: Namespace, namespace: Namespace,