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