mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 05:22:14 +03:00
feat: add backward_verified_key_id column to acpeerstates
This commit is contained in:
@@ -1268,13 +1268,30 @@ impl Contact {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? {
|
||||
if peerstate.is_using_verified_key() {
|
||||
return Ok(true);
|
||||
}
|
||||
let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let forward_verified = peerstate.is_using_verified_key();
|
||||
let backward_verified = peerstate.is_backward_verified(context).await?;
|
||||
Ok(forward_verified && backward_verified)
|
||||
}
|
||||
|
||||
/// Returns true if we have a verified key for the contact
|
||||
/// and it is the same as Autocrypt key.
|
||||
/// This is enough to send messages to the contact in verified chat
|
||||
/// and verify received messages, but not enough to display green checkmark
|
||||
/// or add the contact to verified groups.
|
||||
pub async fn is_forward_verified(&self, context: &Context) -> Result<bool> {
|
||||
if self.id == ContactId::SELF {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
let Some(peerstate) = Peerstate::from_addr(context, &self.addr).await? else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
Ok(peerstate.is_using_verified_key())
|
||||
}
|
||||
|
||||
/// Returns the `ContactId` that verified the contact.
|
||||
|
||||
@@ -312,6 +312,7 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
secondary_verified_key: None,
|
||||
secondary_verified_key_fingerprint: None,
|
||||
secondary_verifier: None,
|
||||
backward_verified_key_id: None,
|
||||
fingerprint_changed: false,
|
||||
};
|
||||
vec![(Some(peerstate), addr)]
|
||||
|
||||
@@ -6,6 +6,7 @@ use num_traits::FromPrimitive;
|
||||
use crate::aheader::{Aheader, EncryptPreference};
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::config::Config;
|
||||
use crate::constants::Chattype;
|
||||
use crate::contact::{addr_cmp, Contact, ContactAddress, Origin};
|
||||
use crate::context::Context;
|
||||
@@ -83,6 +84,10 @@ pub struct Peerstate {
|
||||
/// The address that introduced secondary verified key.
|
||||
pub secondary_verifier: Option<String>,
|
||||
|
||||
/// Row ID of the key in the `keypairs` table
|
||||
/// that we think the peer knows as verified.
|
||||
pub backward_verified_key_id: Option<i64>,
|
||||
|
||||
/// True if it was detected
|
||||
/// that the fingerprint of the key used in chats with
|
||||
/// opportunistic encryption was changed after Peerstate creation.
|
||||
@@ -108,6 +113,7 @@ impl Peerstate {
|
||||
secondary_verified_key: None,
|
||||
secondary_verified_key_fingerprint: None,
|
||||
secondary_verifier: None,
|
||||
backward_verified_key_id: None,
|
||||
fingerprint_changed: false,
|
||||
}
|
||||
}
|
||||
@@ -137,6 +143,7 @@ impl Peerstate {
|
||||
secondary_verified_key: None,
|
||||
secondary_verified_key_fingerprint: None,
|
||||
secondary_verifier: None,
|
||||
backward_verified_key_id: None,
|
||||
fingerprint_changed: false,
|
||||
}
|
||||
}
|
||||
@@ -148,7 +155,8 @@ impl Peerstate {
|
||||
verified_key, verified_key_fingerprint, \
|
||||
verifier, \
|
||||
secondary_verified_key, secondary_verified_key_fingerprint, \
|
||||
secondary_verifier \
|
||||
secondary_verifier, \
|
||||
backward_verified_key_id \
|
||||
FROM acpeerstates \
|
||||
WHERE addr=? COLLATE NOCASE LIMIT 1;";
|
||||
Self::from_stmt(context, query, (addr,)).await
|
||||
@@ -164,7 +172,8 @@ impl Peerstate {
|
||||
verified_key, verified_key_fingerprint, \
|
||||
verifier, \
|
||||
secondary_verified_key, secondary_verified_key_fingerprint, \
|
||||
secondary_verifier \
|
||||
secondary_verifier, \
|
||||
backward_verified_key_id \
|
||||
FROM acpeerstates \
|
||||
WHERE public_key_fingerprint=? \
|
||||
OR gossip_key_fingerprint=? \
|
||||
@@ -187,7 +196,8 @@ impl Peerstate {
|
||||
verified_key, verified_key_fingerprint, \
|
||||
verifier, \
|
||||
secondary_verified_key, secondary_verified_key_fingerprint, \
|
||||
secondary_verifier \
|
||||
secondary_verifier, \
|
||||
backward_verified_key_id \
|
||||
FROM acpeerstates \
|
||||
WHERE verified_key_fingerprint=? \
|
||||
OR addr=? COLLATE NOCASE \
|
||||
@@ -255,6 +265,7 @@ impl Peerstate {
|
||||
let secondary_verifier: Option<String> = row.get("secondary_verifier")?;
|
||||
secondary_verifier.filter(|s| !s.is_empty())
|
||||
},
|
||||
backward_verified_key_id: row.get("backward_verified_key_id")?,
|
||||
fingerprint_changed: false,
|
||||
};
|
||||
|
||||
@@ -435,6 +446,17 @@ impl Peerstate {
|
||||
verified.is_some() && verified == self.peek_key_fingerprint(false)
|
||||
}
|
||||
|
||||
pub(crate) async fn is_backward_verified(&self, context: &Context) -> Result<bool> {
|
||||
let Some(backward_verified_key_id) = self.backward_verified_key_id else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let self_key_id = context.get_config_i64(Config::KeyId).await?;
|
||||
|
||||
let backward_verified = backward_verified_key_id == self_key_id;
|
||||
Ok(backward_verified)
|
||||
}
|
||||
|
||||
/// Set this peerstate to verified
|
||||
/// Make sure to call `self.save_to_db` to save these changes
|
||||
/// Params:
|
||||
@@ -510,8 +532,9 @@ impl Peerstate {
|
||||
secondary_verified_key,
|
||||
secondary_verified_key_fingerprint,
|
||||
secondary_verifier,
|
||||
backward_verified_key_id,
|
||||
addr)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
ON CONFLICT (addr)
|
||||
DO UPDATE SET
|
||||
last_seen = excluded.last_seen,
|
||||
@@ -527,7 +550,8 @@ impl Peerstate {
|
||||
verifier = excluded.verifier,
|
||||
secondary_verified_key = excluded.secondary_verified_key,
|
||||
secondary_verified_key_fingerprint = excluded.secondary_verified_key_fingerprint,
|
||||
secondary_verifier = excluded.secondary_verifier",
|
||||
secondary_verifier = excluded.secondary_verifier,
|
||||
backward_verified_key_id = excluded.backward_verified_key_id",
|
||||
(
|
||||
self.last_seen,
|
||||
self.last_seen_autocrypt,
|
||||
@@ -545,6 +569,7 @@ impl Peerstate {
|
||||
.as_ref()
|
||||
.map(|fp| fp.hex()),
|
||||
self.secondary_verifier.as_deref().unwrap_or(""),
|
||||
self.backward_verified_key_id,
|
||||
&self.addr,
|
||||
),
|
||||
)
|
||||
@@ -806,6 +831,7 @@ mod tests {
|
||||
secondary_verified_key: None,
|
||||
secondary_verified_key_fingerprint: None,
|
||||
secondary_verifier: None,
|
||||
backward_verified_key_id: None,
|
||||
fingerprint_changed: false,
|
||||
};
|
||||
|
||||
@@ -849,6 +875,7 @@ mod tests {
|
||||
secondary_verified_key: None,
|
||||
secondary_verified_key_fingerprint: None,
|
||||
secondary_verifier: None,
|
||||
backward_verified_key_id: None,
|
||||
fingerprint_changed: false,
|
||||
};
|
||||
|
||||
@@ -885,6 +912,7 @@ mod tests {
|
||||
secondary_verified_key: None,
|
||||
secondary_verified_key_fingerprint: None,
|
||||
secondary_verifier: None,
|
||||
backward_verified_key_id: None,
|
||||
fingerprint_changed: false,
|
||||
};
|
||||
|
||||
@@ -951,6 +979,7 @@ mod tests {
|
||||
secondary_verified_key: None,
|
||||
secondary_verified_key_fingerprint: None,
|
||||
secondary_verifier: None,
|
||||
backward_verified_key_id: None,
|
||||
fingerprint_changed: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -1057,6 +1057,7 @@ mod tests {
|
||||
secondary_verified_key: None,
|
||||
secondary_verified_key_fingerprint: None,
|
||||
secondary_verifier: None,
|
||||
backward_verified_key_id: None,
|
||||
fingerprint_changed: false,
|
||||
};
|
||||
assert!(
|
||||
|
||||
@@ -346,6 +346,16 @@ pub(crate) async fn receive_imf_inner(
|
||||
let verified_encryption =
|
||||
has_verified_encryption(context, &mime_parser, from_id, &to_ids).await?;
|
||||
|
||||
if verified_encryption == VerifiedEncryption::Verified
|
||||
&& mime_parser.get_header(HeaderDef::ChatVerified).is_some()
|
||||
{
|
||||
if let Some(peerstate) = &mut mime_parser.decryption_info.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?;
|
||||
}
|
||||
}
|
||||
|
||||
let received_msg = if let Some(received_msg) = received_msg {
|
||||
received_msg
|
||||
} else {
|
||||
@@ -2527,6 +2537,8 @@ async fn mark_recipients_as_verified(
|
||||
info!(context, "{verifier_addr} has verified {to_addr}.");
|
||||
if let Some(fp) = peerstate.gossip_key_fingerprint.clone() {
|
||||
peerstate.set_verified(PeerstateKeyType::GossipKey, fp, verifier_addr)?;
|
||||
peerstate.backward_verified_key_id =
|
||||
Some(context.get_config_i64(Config::KeyId).await?).filter(|&id| id > 0);
|
||||
peerstate.save_to_db(&context.sql).await?;
|
||||
|
||||
let (to_contact_id, _) = Contact::add_or_lookup(
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::mimeparser::{MimeMessage, SystemMessage};
|
||||
use crate::param::Param;
|
||||
use crate::peerstate::{Peerstate, PeerstateKeyType};
|
||||
use crate::qr::check_qr;
|
||||
use crate::securejoin::bob::JoinerProgress;
|
||||
use crate::stock_str;
|
||||
use crate::sync::Sync::*;
|
||||
use crate::token;
|
||||
@@ -204,6 +205,8 @@ async fn info_chat_id(context: &Context, contact_id: ContactId) -> Result<ChatId
|
||||
Ok(chat_id_blocked.id)
|
||||
}
|
||||
|
||||
/// Checks fingerprint and marks the contact as forward verified
|
||||
/// if fingerprint matches.
|
||||
async fn fingerprint_equals_sender(
|
||||
context: &Context,
|
||||
fingerprint: &Fingerprint,
|
||||
@@ -223,13 +226,17 @@ async fn fingerprint_equals_sender(
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(peerstate) = peerstate {
|
||||
if let Some(mut peerstate) = peerstate {
|
||||
if peerstate
|
||||
.public_key_fingerprint
|
||||
.as_ref()
|
||||
.filter(|&fp| fp == fingerprint)
|
||||
.is_some()
|
||||
{
|
||||
let verifier = contact.get_addr().to_owned();
|
||||
peerstate.set_verified(PeerstateKeyType::PublicKey, fingerprint.clone(), verifier)?;
|
||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||
peerstate.save_to_db(&context.sql).await?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
@@ -408,8 +415,14 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
.await?
|
||||
.get_addr()
|
||||
.to_owned();
|
||||
let fingerprint_found =
|
||||
mark_peer_as_verified(context, fingerprint.clone(), contact_addr).await?;
|
||||
let backward_verified = true;
|
||||
let fingerprint_found = mark_peer_as_verified(
|
||||
context,
|
||||
fingerprint.clone(),
|
||||
contact_addr,
|
||||
backward_verified,
|
||||
)
|
||||
.await?;
|
||||
if !fingerprint_found {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
@@ -484,11 +497,21 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
==== Bob - the joiner's side ====
|
||||
==== Step 7 in "Setup verified contact" protocol ====
|
||||
=======================================================*/
|
||||
"vc-contact-confirm" => match BobState::from_db(&context.sql).await? {
|
||||
Some(bobstate) => bob::handle_contact_confirm(context, bobstate, mime_message).await,
|
||||
None => Ok(HandshakeMessage::Ignore),
|
||||
},
|
||||
"vc-contact-confirm" => {
|
||||
if let Some(mut bobstate) = BobState::from_db(&context.sql).await? {
|
||||
if !bobstate.is_msg_expected(context, step.as_str()) {
|
||||
warn!(context, "Unexpected vc-contact-confirm.");
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
|
||||
bobstate.step_contact_confirm(context).await?;
|
||||
bobstate
|
||||
.notify_peer_verified(context, mime_message.timestamp_sent)
|
||||
.await?;
|
||||
bobstate.emit_progress(context, JoinerProgress::Succeeded);
|
||||
}
|
||||
Ok(HandshakeMessage::Ignore)
|
||||
}
|
||||
"vg-member-added" => {
|
||||
let Some(member_added) = mime_message
|
||||
.get_header(HeaderDef::ChatGroupMemberAdded)
|
||||
@@ -496,23 +519,30 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
else {
|
||||
warn!(
|
||||
context,
|
||||
"vg-member-added without Chat-Group-Member-Added header"
|
||||
"vg-member-added without Chat-Group-Member-Added header."
|
||||
);
|
||||
return Ok(HandshakeMessage::Propagate);
|
||||
};
|
||||
if !context.is_self_addr(member_added).await? {
|
||||
info!(
|
||||
context,
|
||||
"Member {member_added} added by unrelated SecureJoin process"
|
||||
"Member {member_added} added by unrelated SecureJoin process."
|
||||
);
|
||||
return Ok(HandshakeMessage::Propagate);
|
||||
}
|
||||
match BobState::from_db(&context.sql).await? {
|
||||
Some(bobstate) => {
|
||||
bob::handle_contact_confirm(context, bobstate, mime_message).await
|
||||
if let Some(mut bobstate) = BobState::from_db(&context.sql).await? {
|
||||
if !bobstate.is_msg_expected(context, step.as_str()) {
|
||||
warn!(context, "Unexpected vg-member-added.");
|
||||
return Ok(HandshakeMessage::Propagate);
|
||||
}
|
||||
None => Ok(HandshakeMessage::Propagate),
|
||||
|
||||
bobstate.step_contact_confirm(context).await?;
|
||||
bobstate
|
||||
.notify_peer_verified(context, mime_message.timestamp_sent)
|
||||
.await?;
|
||||
bobstate.emit_progress(context, JoinerProgress::Succeeded);
|
||||
}
|
||||
Ok(HandshakeMessage::Propagate)
|
||||
}
|
||||
|
||||
"vg-member-added-received" | "vc-contact-confirm-received" => {
|
||||
@@ -526,23 +556,25 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
}
|
||||
}
|
||||
|
||||
/// observe_securejoin_on_other_device() must be called when a self-sent securejoin message is seen.
|
||||
/// Observe self-sent Securejoin message.
|
||||
///
|
||||
/// in a multi-device-setup, there may be other devices that "see" the handshake messages.
|
||||
/// if the seen messages seen are self-sent messages encrypted+signed correctly with our key,
|
||||
/// we can make some conclusions of it:
|
||||
/// In a multi-device-setup, there may be other devices that "see" the handshake messages.
|
||||
/// If we see self-sent messages encrypted+signed correctly with our key,
|
||||
/// we can make some conclusions of it.
|
||||
///
|
||||
/// - if we see the self-sent-message vg-member-added/vc-contact-confirm,
|
||||
/// we know that we're an inviter-observer.
|
||||
/// The inviting device has marked a peer as verified on vg-request-with-auth/vc-request-with-auth
|
||||
/// before sending vg-member-added/vc-contact-confirm - so, if we observe vg-member-added/vc-contact-confirm,
|
||||
/// we can mark the peer as verified as well.
|
||||
/// If we see self-sent {vc,vg}-request-with-auth,
|
||||
/// we know that we are Bob (joiner-observer)
|
||||
/// that just marked peer (Alice) as forward-verified
|
||||
/// either after receiving {vc,vg}-auth-required
|
||||
/// or immediately after scanning the QR-code
|
||||
/// if the key was already known.
|
||||
///
|
||||
/// - if we see the self-sent-message vg-request-with-auth/vc-request-with-auth
|
||||
/// we know that we're an joiner-observer.
|
||||
/// the joining device has marked the peer as verified
|
||||
/// before sending vg-request-with-auth/vc-request-with-auth - so, if we observe vg-member-added-received,
|
||||
/// we can mark the peer as verified as well.
|
||||
/// If we see self-sent vc-contact-confirm or vg-member-added message,
|
||||
/// we know that we are Alice (inviter-observer)
|
||||
/// that just marked peer (Bob) as forward (and backward)-verified
|
||||
/// in response to correct vc-request-with-auth message.
|
||||
///
|
||||
/// In both cases we can mark the peer as forward-verified.
|
||||
pub(crate) async fn observe_securejoin_on_other_device(
|
||||
context: &Context,
|
||||
mime_message: &MimeMessage,
|
||||
@@ -556,127 +588,96 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
||||
.context("Not a Secure-Join message")?;
|
||||
info!(context, "Observing secure-join message {step:?}.");
|
||||
|
||||
match step.as_str() {
|
||||
"vg-request-with-auth"
|
||||
| "vc-request-with-auth"
|
||||
| "vg-member-added"
|
||||
| "vc-contact-confirm" => {
|
||||
if !encrypted_and_signed(
|
||||
context,
|
||||
mime_message,
|
||||
get_self_fingerprint(context).await.as_ref(),
|
||||
) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_id,
|
||||
info_chat_id(context, contact_id).await?,
|
||||
"Message not encrypted correctly.",
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
let addr = Contact::get_by_id(context, contact_id)
|
||||
.await?
|
||||
.get_addr()
|
||||
.to_lowercase();
|
||||
if mime_message.gossiped_addr.contains(&addr) {
|
||||
let mut peerstate = match Peerstate::from_addr(context, &addr).await? {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_id,
|
||||
info_chat_id(context, contact_id).await?,
|
||||
&format!("No peerstate in db for '{}' at step {}", &addr, step),
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
let fingerprint = match peerstate.gossip_key_fingerprint.clone() {
|
||||
Some(fp) => fp,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_id,
|
||||
info_chat_id(context, contact_id).await?,
|
||||
&format!(
|
||||
"No gossip key fingerprint in db for '{}' at step {}",
|
||||
&addr, step,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
peerstate.set_verified(PeerstateKeyType::GossipKey, fingerprint, addr)?;
|
||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||
peerstate.save_to_db(&context.sql).await?;
|
||||
if !matches!(
|
||||
step.as_str(),
|
||||
"vg-request-with-auth" | "vc-request-with-auth" | "vg-member-added" | "vc-contact-confirm"
|
||||
) {
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
};
|
||||
|
||||
ChatId::set_protection_for_contact(
|
||||
context,
|
||||
contact_id,
|
||||
mime_message.timestamp_sent,
|
||||
)
|
||||
.await?;
|
||||
} else if let Some(fingerprint) =
|
||||
mime_message.get_header(HeaderDef::SecureJoinFingerprint)
|
||||
{
|
||||
// FIXME: Old versions of DC send this header instead of gossips. Remove this
|
||||
// eventually.
|
||||
let fingerprint = fingerprint.parse()?;
|
||||
let fingerprint_found = mark_peer_as_verified(
|
||||
context,
|
||||
fingerprint,
|
||||
Contact::get_by_id(context, contact_id)
|
||||
.await?
|
||||
.get_addr()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
if !fingerprint_found {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_id,
|
||||
info_chat_id(context, contact_id).await?,
|
||||
format!("Fingerprint mismatch on observing {step}.").as_ref(),
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
} else {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_id,
|
||||
info_chat_id(context, contact_id).await?,
|
||||
&format!(
|
||||
"No gossip header for '{}' at step {}, please update Delta Chat on all \
|
||||
if !encrypted_and_signed(
|
||||
context,
|
||||
mime_message,
|
||||
get_self_fingerprint(context).await.as_ref(),
|
||||
) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_id,
|
||||
info_chat_id(context, contact_id).await?,
|
||||
"Message not encrypted correctly.",
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
|
||||
let addr = Contact::get_by_id(context, contact_id)
|
||||
.await?
|
||||
.get_addr()
|
||||
.to_lowercase();
|
||||
|
||||
if !mime_message.gossiped_addr.contains(&addr) {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_id,
|
||||
info_chat_id(context, contact_id).await?,
|
||||
&format!(
|
||||
"No gossip header for '{}' at step {}, please update Delta Chat on all \
|
||||
your devices.",
|
||||
&addr, step,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
if step.as_str() == "vg-member-added" {
|
||||
inviter_progress(context, contact_id, 800);
|
||||
}
|
||||
if step.as_str() == "vg-member-added" || step.as_str() == "vc-contact-confirm" {
|
||||
inviter_progress(context, contact_id, 1000);
|
||||
}
|
||||
if step.as_str() == "vg-request-with-auth" || step.as_str() == "vc-request-with-auth" {
|
||||
// This actually reflects what happens on the first device (which does the secure
|
||||
// join) and causes a subsequent "vg-member-added" message to create an unblocked
|
||||
// verified group.
|
||||
ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await?;
|
||||
}
|
||||
Ok(if step.as_str() == "vg-member-added" {
|
||||
HandshakeMessage::Propagate
|
||||
} else {
|
||||
HandshakeMessage::Ignore
|
||||
})
|
||||
}
|
||||
_ => Ok(HandshakeMessage::Ignore),
|
||||
&addr, step,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
|
||||
let Some(mut peerstate) = Peerstate::from_addr(context, &addr).await? else {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_id,
|
||||
info_chat_id(context, contact_id).await?,
|
||||
&format!("No peerstate in db for '{}' at step {}", &addr, step),
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
};
|
||||
|
||||
let Some(fingerprint) = peerstate.gossip_key_fingerprint.clone() else {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_id,
|
||||
info_chat_id(context, contact_id).await?,
|
||||
&format!(
|
||||
"No gossip key fingerprint in db for '{}' at step {}",
|
||||
&addr, step,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
};
|
||||
peerstate.set_verified(PeerstateKeyType::GossipKey, fingerprint, addr)?;
|
||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||
peerstate.save_to_db(&context.sql).await?;
|
||||
|
||||
ChatId::set_protection_for_contact(context, contact_id, mime_message.timestamp_sent).await?;
|
||||
|
||||
if step.as_str() == "vg-member-added" {
|
||||
inviter_progress(context, contact_id, 800);
|
||||
}
|
||||
if step.as_str() == "vg-member-added" || step.as_str() == "vc-contact-confirm" {
|
||||
inviter_progress(context, contact_id, 1000);
|
||||
}
|
||||
|
||||
if step.as_str() == "vg-request-with-auth" || step.as_str() == "vc-request-with-auth" {
|
||||
// This actually reflects what happens on the first device (which does the secure
|
||||
// join) and causes a subsequent "vg-member-added" message to create an unblocked
|
||||
// verified group.
|
||||
ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await?;
|
||||
}
|
||||
|
||||
if step.as_str() == "vg-member-added" {
|
||||
Ok(HandshakeMessage::Propagate)
|
||||
} else {
|
||||
Ok(HandshakeMessage::Ignore)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,12 +725,17 @@ async fn mark_peer_as_verified(
|
||||
context: &Context,
|
||||
fingerprint: Fingerprint,
|
||||
verifier: String,
|
||||
backward_verified: bool,
|
||||
) -> Result<bool> {
|
||||
let Some(ref mut peerstate) = Peerstate::from_fingerprint(context, &fingerprint).await? else {
|
||||
return Ok(false);
|
||||
};
|
||||
peerstate.set_verified(PeerstateKeyType::PublicKey, fingerprint, verifier)?;
|
||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||
if backward_verified {
|
||||
peerstate.backward_verified_key_id =
|
||||
Some(context.get_config_i64(Config::KeyId).await?).filter(|&id| id > 0);
|
||||
}
|
||||
peerstate.save_to_db(&context.sql).await?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -980,6 +986,7 @@ mod tests {
|
||||
secondary_verified_key: None,
|
||||
secondary_verified_key_fingerprint: None,
|
||||
secondary_verifier: None,
|
||||
backward_verified_key_id: None,
|
||||
fingerprint_changed: false,
|
||||
};
|
||||
peerstate.save_to_db(&bob.ctx.sql).await?;
|
||||
|
||||
@@ -86,7 +86,7 @@ pub(super) async fn handle_auth_required(
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
};
|
||||
|
||||
match bobstate.handle_message(context, message).await? {
|
||||
match bobstate.handle_auth_required(context, message).await? {
|
||||
Some(BobHandshakeStage::Terminated(why)) => {
|
||||
bobstate.notify_aborted(context, why).await?;
|
||||
Ok(HandshakeMessage::Done)
|
||||
@@ -107,46 +107,6 @@ pub(super) async fn handle_auth_required(
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles `vc-contact-confirm` and `vg-member-added` handshake messages.
|
||||
///
|
||||
/// # Bob - the joiner's side
|
||||
/// ## Step 7 in the "Setup Contact protocol"
|
||||
pub(super) async fn handle_contact_confirm(
|
||||
context: &Context,
|
||||
mut bobstate: BobState,
|
||||
message: &MimeMessage,
|
||||
) -> Result<HandshakeMessage> {
|
||||
let retval = if bobstate.is_join_group() {
|
||||
HandshakeMessage::Propagate
|
||||
} else {
|
||||
HandshakeMessage::Ignore
|
||||
};
|
||||
match bobstate.handle_message(context, message).await? {
|
||||
Some(BobHandshakeStage::Terminated(why)) => {
|
||||
bobstate.notify_aborted(context, why).await?;
|
||||
Ok(HandshakeMessage::Done)
|
||||
}
|
||||
Some(BobHandshakeStage::Completed) => {
|
||||
// Note this goes to the 1:1 chat, as when joining a group we implicitly also
|
||||
// verify both contacts (this could be a bug/security issue, see
|
||||
// e.g. https://github.com/deltachat/deltachat-core-rust/issues/1177).
|
||||
bobstate
|
||||
.notify_peer_verified(context, message.timestamp_sent)
|
||||
.await?;
|
||||
bobstate.emit_progress(context, JoinerProgress::Succeeded);
|
||||
Ok(retval)
|
||||
}
|
||||
Some(_) => {
|
||||
warn!(
|
||||
context,
|
||||
"Impossible state returned from handling handshake message"
|
||||
);
|
||||
Ok(retval)
|
||||
}
|
||||
None => Ok(retval),
|
||||
}
|
||||
}
|
||||
|
||||
/// Private implementations for user interactions about this [`BobState`].
|
||||
impl BobState {
|
||||
fn is_join_group(&self) -> bool {
|
||||
@@ -156,7 +116,7 @@ impl BobState {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_progress(&self, context: &Context, progress: JoinerProgress) {
|
||||
pub(crate) fn emit_progress(&self, context: &Context, progress: JoinerProgress) {
|
||||
let contact_id = self.invite().contact_id();
|
||||
context.emit_event(EventType::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
@@ -222,7 +182,11 @@ impl BobState {
|
||||
/// Notifies the user that the SecureJoin peer is verified.
|
||||
///
|
||||
/// This creates an info message in the chat being joined.
|
||||
async fn notify_peer_verified(&self, context: &Context, timestamp: i64) -> Result<()> {
|
||||
pub(crate) async fn notify_peer_verified(
|
||||
&self,
|
||||
context: &Context,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
let contact = Contact::get_by_id(context, self.invite().contact_id()).await?;
|
||||
let chat_id = self.joining_chat_id(context).await?;
|
||||
self.alice_chat()
|
||||
@@ -242,7 +206,7 @@ impl BobState {
|
||||
///
|
||||
/// This has an `From<JoinerProgress> for usize` impl yielding numbers between 0 and a 1000
|
||||
/// which can be shown as a progress bar.
|
||||
enum JoinerProgress {
|
||||
pub(crate) enum JoinerProgress {
|
||||
/// An error occurred.
|
||||
Error,
|
||||
/// vg-vc-request-with-auth sent.
|
||||
|
||||
@@ -11,8 +11,9 @@ use anyhow::Result;
|
||||
use rusqlite::Connection;
|
||||
|
||||
use super::qrinvite::QrInvite;
|
||||
use super::{encrypted_and_signed, fingerprint_equals_sender, mark_peer_as_verified};
|
||||
use super::{encrypted_and_signed, fingerprint_equals_sender};
|
||||
use crate::chat::{self, ChatId};
|
||||
use crate::config::Config;
|
||||
use crate::contact::{Contact, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::events::EventType;
|
||||
@@ -21,6 +22,7 @@ use crate::key::{load_self_public_key, DcKey};
|
||||
use crate::message::{Message, Viewtype};
|
||||
use crate::mimeparser::{MimeMessage, SystemMessage};
|
||||
use crate::param::Param;
|
||||
use crate::securejoin::Peerstate;
|
||||
use crate::sql::Sql;
|
||||
|
||||
/// The stage of the [`BobState`] securejoin handshake protocol state machine.
|
||||
@@ -30,14 +32,9 @@ use crate::sql::Sql;
|
||||
#[derive(Clone, Copy, Debug, Display)]
|
||||
pub enum BobHandshakeStage {
|
||||
/// Step 2 completed: (vc|vg)-request message sent.
|
||||
///
|
||||
/// Note that this is only ever returned by [`BobState::start_protocol`] and never by
|
||||
/// [`BobState::handle_message`].
|
||||
RequestSent,
|
||||
/// Step 4 completed: (vc|vg)-request-with-auth message sent.
|
||||
RequestWithAuthSent,
|
||||
/// The protocol completed successfully.
|
||||
Completed,
|
||||
/// The protocol prematurely terminated with given reason.
|
||||
Terminated(&'static str),
|
||||
}
|
||||
@@ -230,13 +227,13 @@ impl BobState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handles the given message for the securejoin handshake for Bob.
|
||||
/// Handles {vc,vg}-auth-required message of the securejoin handshake for Bob.
|
||||
///
|
||||
/// If the message was not used for this handshake `None` is returned, otherwise the new
|
||||
/// stage is returned. Once [`BobHandshakeStage::Completed`] or
|
||||
/// [`BobHandshakeStage::Terminated`] are reached this [`BobState`] should be destroyed,
|
||||
/// stage is returned. Once [`BobHandshakeStage::Terminated`] is reached this
|
||||
/// [`BobState`] should be destroyed,
|
||||
/// further calling it will just result in the messages being unused by this handshake.
|
||||
pub(crate) async fn handle_message(
|
||||
pub(crate) async fn handle_auth_required(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
mime_message: &MimeMessage,
|
||||
@@ -256,39 +253,7 @@ impl BobState {
|
||||
info!(context, "{} message out of sync for BobState", step);
|
||||
return Ok(None);
|
||||
}
|
||||
match step.as_str() {
|
||||
"vg-auth-required" | "vc-auth-required" => {
|
||||
self.step_auth_required(context, mime_message).await
|
||||
}
|
||||
"vg-member-added" | "vc-contact-confirm" => {
|
||||
self.step_contact_confirm(context, mime_message).await
|
||||
}
|
||||
_ => {
|
||||
warn!(context, "Invalid step for BobState: {}", step);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the message is expected according to the protocol.
|
||||
fn is_msg_expected(&self, context: &Context, step: &str) -> bool {
|
||||
let variant_matches = match self.invite {
|
||||
QrInvite::Contact { .. } => step.starts_with("vc-"),
|
||||
QrInvite::Group { .. } => step.starts_with("vg-"),
|
||||
};
|
||||
let step_matches = self.next.matches(context, step);
|
||||
variant_matches && step_matches
|
||||
}
|
||||
|
||||
/// Handles a *vc-auth-required* or *vg-auth-required* message.
|
||||
///
|
||||
/// # Bob - the joiner's side
|
||||
/// ## Step 4 in the "Setup Contact protocol", section 2.1 of countermitm 0.10.0
|
||||
async fn step_auth_required(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
mime_message: &MimeMessage,
|
||||
) -> Result<Option<BobHandshakeStage>> {
|
||||
info!(
|
||||
context,
|
||||
"Bob Step 4 - handling {{vc,vg}}-auth-required message."
|
||||
@@ -311,6 +276,7 @@ impl BobState {
|
||||
return Ok(Some(BobHandshakeStage::Terminated("Fingerprint mismatch")));
|
||||
}
|
||||
info!(context, "Fingerprint verified.",);
|
||||
|
||||
self.update_next(&context.sql, SecureJoinStep::ContactConfirm)
|
||||
.await?;
|
||||
self.send_handshake_message(context, BobHandshakeMsg::RequestWithAuth)
|
||||
@@ -318,36 +284,39 @@ impl BobState {
|
||||
Ok(Some(BobHandshakeStage::RequestWithAuthSent))
|
||||
}
|
||||
|
||||
/// Returns `true` if the message is expected according to the protocol.
|
||||
pub(crate) fn is_msg_expected(&self, context: &Context, step: &str) -> bool {
|
||||
let variant_matches = match self.invite {
|
||||
QrInvite::Contact { .. } => step.starts_with("vc-"),
|
||||
QrInvite::Group { .. } => step.starts_with("vg-"),
|
||||
};
|
||||
let step_matches = self.next.matches(context, step);
|
||||
variant_matches && step_matches
|
||||
}
|
||||
|
||||
/// Handles a *vc-contact-confirm* or *vg-member-added* message.
|
||||
///
|
||||
/// # Bob - the joiner's side
|
||||
/// ## Step 7 in the "Setup Contact protocol", section 2.1 of countermitm 0.10.0
|
||||
///
|
||||
/// This deviates from the protocol by also sending a confirmation message in response
|
||||
/// to the *vc-contact-confirm* message. This has no specific value to the protocol and
|
||||
/// is only done out of symmetry with *vg-member-added* handling.
|
||||
async fn step_contact_confirm(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
mime_message: &MimeMessage,
|
||||
) -> Result<Option<BobHandshakeStage>> {
|
||||
info!(
|
||||
context,
|
||||
"Bob Step 7 - handling vc-contact-confirm/vg-member-added message."
|
||||
);
|
||||
mark_peer_as_verified(
|
||||
context,
|
||||
self.invite.fingerprint().clone(),
|
||||
mime_message.from.addr.to_string(),
|
||||
)
|
||||
.await?;
|
||||
pub(crate) async fn step_contact_confirm(&mut self, context: &Context) -> Result<()> {
|
||||
let fingerprint = self.invite.fingerprint();
|
||||
let Some(ref mut peerstate) = Peerstate::from_fingerprint(context, fingerprint).await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Mark peer as backward verified.
|
||||
peerstate.backward_verified_key_id =
|
||||
Some(context.get_config_i64(Config::KeyId).await?).filter(|&id| id > 0);
|
||||
peerstate.save_to_db(&context.sql).await?;
|
||||
|
||||
Contact::scaleup_origin_by_id(context, self.invite.contact_id(), Origin::SecurejoinJoined)
|
||||
.await?;
|
||||
context.emit_event(EventType::ContactsChanged(None));
|
||||
|
||||
self.update_next(&context.sql, SecureJoinStep::Completed)
|
||||
.await?;
|
||||
Ok(Some(BobHandshakeStage::Completed))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends the requested handshake message to Alice.
|
||||
|
||||
@@ -886,6 +886,20 @@ CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
|
||||
sql.set_db_version_in_cache(version).await?;
|
||||
}
|
||||
|
||||
if dbversion < 109 {
|
||||
sql.execute_migration(
|
||||
r#"ALTER TABLE acpeerstates
|
||||
ADD COLUMN backward_verified_key_id -- What we think the contact has as our verified key
|
||||
INTEGER;
|
||||
UPDATE acpeerstates
|
||||
SET backward_verified_key_id=(SELECT value FROM config WHERE keyname='key_id')
|
||||
WHERE verified_key IS NOT NULL
|
||||
"#,
|
||||
109,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
.await?
|
||||
|
||||
@@ -1047,7 +1047,8 @@ fn print_logevent(logevent: &LogEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Saves the other account's public key as verified.
|
||||
/// Saves the other account's public key as verified
|
||||
/// and peerstate as backwards verified.
|
||||
pub(crate) async fn mark_as_verified(this: &TestContext, other: &TestContext) {
|
||||
let mut peerstate = Peerstate::from_header(
|
||||
&EncryptHelper::new(other).await.unwrap().get_aheader(),
|
||||
@@ -1063,6 +1064,7 @@ pub(crate) async fn mark_as_verified(this: &TestContext, other: &TestContext) {
|
||||
|
||||
peerstate.verified_key = peerstate.public_key.clone();
|
||||
peerstate.verified_key_fingerprint = peerstate.public_key_fingerprint.clone();
|
||||
peerstate.backward_verified_key_id = Some(this.get_config_i64(Config::KeyId).await.unwrap());
|
||||
|
||||
peerstate.save_to_db(&this.sql).await.unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user