mirror of
https://github.com/chatmail/core.git
synced 2026-09-22 13:01:21 +03:00
fix: Rerun the full securejoin protocol if the address was outdated (#8358)
Alternative to https://github.com/chatmail/core/pull/8355/, fixes https://github.com/chatmail/core/issues/8329 This fixes the following bug: Scanning QR for existing contact doesn't update relay list Two chat partners suddenly lost connectivity to all of their relays. They added another relay, which is accessible, and set it as main. Of course, they still couldn't communicate, because there was no way to announce a new relay list to each other (clients know only about old relays so they try to send updates there). That's why the friends used a different channel (e.g. met IRL) to send their QR codes once again. Expected behavior: They would expect that clients update their relay lists according to the contact info inside the QR codes, and they can continue talking in Delta. Actual behavior: But Delta Chat client doesn't update a list of announced relays on scanning a QR in case a contact already exists. It simply opens the existing chat without altering any contact info. --------- Co-authored-by: holger krekel <holger@merlinux.eu>
This commit is contained in:
@@ -39,7 +39,7 @@ mod vcard;
|
|||||||
pub use vcard::{make_vcard, parse_vcard, VcardContact};
|
pub use vcard::{make_vcard, parse_vcard, VcardContact};
|
||||||
|
|
||||||
/// Valid contact address.
|
/// Valid contact address.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ContactAddress(String);
|
pub struct ContactAddress(String);
|
||||||
|
|
||||||
impl Deref for ContactAddress {
|
impl Deref for ContactAddress {
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ impl From<Qr> for QrObject {
|
|||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let contact_id = contact_id.to_u32();
|
let contact_id = contact_id.to_u32();
|
||||||
let fingerprint = fingerprint.human_readable();
|
let fingerprint = fingerprint.human_readable();
|
||||||
@@ -255,6 +256,7 @@ impl From<Qr> for QrObject {
|
|||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let contact_id = contact_id.to_u32();
|
let contact_id = contact_id.to_u32();
|
||||||
let fingerprint = fingerprint.human_readable();
|
let fingerprint = fingerprint.human_readable();
|
||||||
@@ -276,6 +278,7 @@ impl From<Qr> for QrObject {
|
|||||||
authcode,
|
authcode,
|
||||||
invitenumber,
|
invitenumber,
|
||||||
is_v3,
|
is_v3,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let contact_id = contact_id.to_u32();
|
let contact_id = contact_id.to_u32();
|
||||||
let fingerprint = fingerprint.human_readable();
|
let fingerprint = fingerprint.human_readable();
|
||||||
|
|||||||
@@ -704,3 +704,25 @@ def test_withdraw_securejoin_qr(acfactory):
|
|||||||
and "Ignoring RequestWithAuth message because of invalid auth code." in event.msg
|
and "Ignoring RequestWithAuth message because of invalid auth code." in event.msg
|
||||||
):
|
):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def test_qr_scan_updates_new_relay_address(acfactory):
|
||||||
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
|
bob_alice_chat = bob.secure_join(alice.get_qr_code())
|
||||||
|
alice.wait_for_securejoin_inviter_success()
|
||||||
|
bob.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
|
for ac in [alice, bob]:
|
||||||
|
old_addr = ac.get_config("configured_addr")
|
||||||
|
ac.add_transport_from_qr(acfactory.get_account_qr())
|
||||||
|
ac.set_config("configured_addr", ac.list_transports()[1]["addr"])
|
||||||
|
ac.delete_transport(old_addr)
|
||||||
|
|
||||||
|
bob.secure_join(alice.get_qr_code())
|
||||||
|
alice.wait_for_securejoin_inviter_success()
|
||||||
|
bob.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
|
bob_alice_chat.send_text("hi")
|
||||||
|
snapshot = alice.wait_for_incoming_msg().get_snapshot()
|
||||||
|
assert snapshot.text == "hi"
|
||||||
|
|||||||
12
src/qr.rs
12
src/qr.rs
@@ -56,6 +56,9 @@ pub enum Qr {
|
|||||||
/// Fingerprint of the contact key as scanned from the QR code.
|
/// Fingerprint of the contact key as scanned from the QR code.
|
||||||
fingerprint: Fingerprint,
|
fingerprint: Fingerprint,
|
||||||
|
|
||||||
|
/// The inviter's addresses.
|
||||||
|
addrs: Vec<String>,
|
||||||
|
|
||||||
/// Invite number.
|
/// Invite number.
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
|
|
||||||
@@ -80,6 +83,9 @@ pub enum Qr {
|
|||||||
/// Fingerprint of the contact key as scanned from the QR code.
|
/// Fingerprint of the contact key as scanned from the QR code.
|
||||||
fingerprint: Fingerprint,
|
fingerprint: Fingerprint,
|
||||||
|
|
||||||
|
/// The inviter's addresses.
|
||||||
|
addrs: Vec<String>,
|
||||||
|
|
||||||
/// Invite number.
|
/// Invite number.
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
|
|
||||||
@@ -108,6 +114,9 @@ pub enum Qr {
|
|||||||
/// Fingerprint of the contact's key as scanned from the QR code.
|
/// Fingerprint of the contact's key as scanned from the QR code.
|
||||||
fingerprint: Fingerprint,
|
fingerprint: Fingerprint,
|
||||||
|
|
||||||
|
/// The inviter's addresses.
|
||||||
|
addrs: Vec<String>,
|
||||||
|
|
||||||
/// Invite number.
|
/// Invite number.
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
/// Authentication code.
|
/// Authentication code.
|
||||||
@@ -563,6 +572,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
grpid,
|
grpid,
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
|
addrs: vec![addr.to_string()],
|
||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
@@ -599,6 +609,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
grpid,
|
grpid,
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
|
addrs: vec![addr.to_string()],
|
||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
@@ -624,6 +635,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
Ok(Qr::AskVerifyContact {
|
Ok(Qr::AskVerifyContact {
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
|
addrs: vec![addr.to_string()],
|
||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
|
|||||||
@@ -741,7 +741,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
async fn insert_into_smtp(
|
async fn insert_into_smtp(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
rfc724_mid: &str,
|
rfc724_mid: &str,
|
||||||
recipient: &str,
|
recipients: &str,
|
||||||
rendered_message: String,
|
rendered_message: String,
|
||||||
msg_id: MsgId,
|
msg_id: MsgId,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@@ -750,7 +750,7 @@ async fn insert_into_smtp(
|
|||||||
.execute(
|
.execute(
|
||||||
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
|
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
|
||||||
VALUES (?1, ?2, ?3, ?4)",
|
VALUES (?1, ?2, ?3, ?4)",
|
||||||
(&rfc724_mid, &recipient, &rendered_message, msg_id),
|
(&rfc724_mid, &recipients, &rendered_message, msg_id),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
//! Bob's side of SecureJoin handling, the joiner-side.
|
//! Bob's side of SecureJoin handling, the joiner-side.
|
||||||
|
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
|
use pgp::composed::SignedPublicKey;
|
||||||
|
|
||||||
use super::HandshakeMessage;
|
use super::HandshakeMessage;
|
||||||
use super::qrinvite::QrInvite;
|
use super::qrinvite::QrInvite;
|
||||||
use crate::chat::{self, ChatId, is_contact_in_chat};
|
use crate::chat::{self, ChatId, is_contact_in_chat};
|
||||||
use crate::constants::{Blocked, Chattype};
|
use crate::constants::{Blocked, Chattype};
|
||||||
use crate::contact::{Contact, Origin};
|
use crate::contact::Origin;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::key::self_fingerprint;
|
use crate::key::{DcKey as _, self_fingerprint};
|
||||||
use crate::log::LogExt;
|
use crate::log::LogExt;
|
||||||
use crate::message::{self, 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::pgp::addresses_from_public_key;
|
||||||
use crate::securejoin::{
|
use crate::securejoin::{
|
||||||
ContactId, encrypted_and_signed, insert_into_smtp, verify_sender_by_fingerprint,
|
ContactId, encrypted_and_signed, insert_into_smtp, verify_sender_by_fingerprint,
|
||||||
};
|
};
|
||||||
@@ -58,14 +60,28 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
|
|||||||
QrInvite::Broadcast { .. } => {}
|
QrInvite::Broadcast { .. } => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let has_key = context
|
let public_key_bytes: Option<Vec<u8>> = context
|
||||||
.sql
|
.sql
|
||||||
.exists(
|
.query_get_value(
|
||||||
"SELECT COUNT(*) FROM public_keys WHERE fingerprint=?",
|
"SELECT public_key FROM public_keys WHERE fingerprint=?",
|
||||||
(invite.fingerprint().hex(),),
|
(invite.fingerprint().hex(),),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let key_contains_all_invite_addrs = if let Some(public_key_bytes) = public_key_bytes {
|
||||||
|
let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
|
||||||
|
if let Some(addrs_in_key) = addresses_from_public_key(&public_key) {
|
||||||
|
invite.addrs().iter().all(|a| addrs_in_key.contains(a))
|
||||||
|
} else {
|
||||||
|
// This can happen if the inviter is using an old version of Delta Chat
|
||||||
|
// that doesn't put the relay list into the key.
|
||||||
|
// In this case, we never take the securejoin protocol shortcut, which is fine.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
// Now start the protocol and initialise the state.
|
// Now start the protocol and initialise the state.
|
||||||
{
|
{
|
||||||
// `joining_chat_id` is `Some` if group chat
|
// `joining_chat_id` is `Some` if group chat
|
||||||
@@ -97,7 +113,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
|
|||||||
progress: JoinerProgress::Succeeded.into_u16(),
|
progress: JoinerProgress::Succeeded.into_u16(),
|
||||||
});
|
});
|
||||||
return Ok(joining_chat_id);
|
return Ok(joining_chat_id);
|
||||||
} else if has_key
|
} else if key_contains_all_invite_addrs
|
||||||
&& verify_sender_by_fingerprint(context, invite.fingerprint(), invite.contact_id())
|
&& verify_sender_by_fingerprint(context, invite.fingerprint(), invite.contact_id())
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
@@ -154,7 +170,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
|
|||||||
QrInvite::Contact { .. } => {
|
QrInvite::Contact { .. } => {
|
||||||
// For setup-contact the BobState already ensured the 1:1 chat exists because it is
|
// For setup-contact the BobState already ensured the 1:1 chat exists because it is
|
||||||
// used to send the handshake messages.
|
// used to send the handshake messages.
|
||||||
if !has_key {
|
if !key_contains_all_invite_addrs {
|
||||||
chat::add_info_msg_with_cmd(
|
chat::add_info_msg_with_cmd(
|
||||||
context,
|
context,
|
||||||
private_chat_id,
|
private_chat_id,
|
||||||
@@ -310,8 +326,7 @@ 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-pubkey message
|
// Send a minimal symmetrically-encrypted vc-request-pubkey message
|
||||||
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 recipients = invite.addrs().join(" ");
|
||||||
let recipient = contact.get_addr();
|
|
||||||
let alice_fp = invite.fingerprint().hex();
|
let alice_fp = invite.fingerprint().hex();
|
||||||
let auth = invite.authcode();
|
let auth = invite.authcode();
|
||||||
let shared_secret = format!("securejoin/{alice_fp}/{auth}");
|
let shared_secret = format!("securejoin/{alice_fp}/{auth}");
|
||||||
@@ -327,7 +342,7 @@ pub(crate) async fn send_handshake_message(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let msg_id = message::insert_tombstone(context, &rfc724_mid).await?;
|
let msg_id = message::insert_tombstone(context, &rfc724_mid).await?;
|
||||||
insert_into_smtp(context, &rfc724_mid, recipient, rendered_message, msg_id).await?;
|
insert_into_smtp(context, &rfc724_mid, &recipients, rendered_message, msg_id).await?;
|
||||||
context.scheduler.interrupt_smtp().await;
|
context.scheduler.interrupt_smtp().await;
|
||||||
} else {
|
} else {
|
||||||
let mut msg = Message {
|
let mut msg = Message {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ pub enum QrInvite {
|
|||||||
Contact {
|
Contact {
|
||||||
contact_id: ContactId,
|
contact_id: ContactId,
|
||||||
fingerprint: Fingerprint,
|
fingerprint: Fingerprint,
|
||||||
|
#[serde(default)]
|
||||||
|
addrs: Vec<String>,
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
authcode: String,
|
authcode: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -26,6 +28,8 @@ pub enum QrInvite {
|
|||||||
Group {
|
Group {
|
||||||
contact_id: ContactId,
|
contact_id: ContactId,
|
||||||
fingerprint: Fingerprint,
|
fingerprint: Fingerprint,
|
||||||
|
#[serde(default)]
|
||||||
|
addrs: Vec<String>,
|
||||||
name: String,
|
name: String,
|
||||||
grpid: String,
|
grpid: String,
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
@@ -36,6 +40,8 @@ pub enum QrInvite {
|
|||||||
Broadcast {
|
Broadcast {
|
||||||
contact_id: ContactId,
|
contact_id: ContactId,
|
||||||
fingerprint: Fingerprint,
|
fingerprint: Fingerprint,
|
||||||
|
#[serde(default)]
|
||||||
|
addrs: Vec<String>,
|
||||||
name: String,
|
name: String,
|
||||||
grpid: String,
|
grpid: String,
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
@@ -92,6 +98,14 @@ impl QrInvite {
|
|||||||
QrInvite::Broadcast { is_v3, .. } => is_v3,
|
QrInvite::Broadcast { is_v3, .. } => is_v3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn addrs(&self) -> &Vec<String> {
|
||||||
|
match self {
|
||||||
|
QrInvite::Contact { addrs, .. } => addrs,
|
||||||
|
QrInvite::Group { addrs, .. } => addrs,
|
||||||
|
QrInvite::Broadcast { addrs, .. } => addrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Qr> for QrInvite {
|
impl TryFrom<Qr> for QrInvite {
|
||||||
@@ -102,12 +116,14 @@ impl TryFrom<Qr> for QrInvite {
|
|||||||
Qr::AskVerifyContact {
|
Qr::AskVerifyContact {
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
|
addrs,
|
||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
} => Ok(QrInvite::Contact {
|
} => Ok(QrInvite::Contact {
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
|
addrs,
|
||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
@@ -117,12 +133,14 @@ impl TryFrom<Qr> for QrInvite {
|
|||||||
grpid,
|
grpid,
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
|
addrs,
|
||||||
invitenumber,
|
invitenumber,
|
||||||
authcode,
|
authcode,
|
||||||
is_v3,
|
is_v3,
|
||||||
} => Ok(QrInvite::Group {
|
} => Ok(QrInvite::Group {
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
|
addrs,
|
||||||
name: grpname,
|
name: grpname,
|
||||||
grpid,
|
grpid,
|
||||||
invitenumber,
|
invitenumber,
|
||||||
@@ -134,6 +152,7 @@ impl TryFrom<Qr> for QrInvite {
|
|||||||
grpid,
|
grpid,
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
|
addrs,
|
||||||
authcode,
|
authcode,
|
||||||
invitenumber,
|
invitenumber,
|
||||||
is_v3,
|
is_v3,
|
||||||
@@ -142,6 +161,7 @@ impl TryFrom<Qr> for QrInvite {
|
|||||||
grpid,
|
grpid,
|
||||||
contact_id,
|
contact_id,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
|
addrs,
|
||||||
authcode,
|
authcode,
|
||||||
invitenumber,
|
invitenumber,
|
||||||
is_v3,
|
is_v3,
|
||||||
|
|||||||
Reference in New Issue
Block a user