mirror of
https://github.com/chatmail/core.git
synced 2026-05-02 21:06:31 +03:00
Add verifier information (#3839)
* add verifier information * cleanup Co-authored-by: bjoern <r10s@b44t.com> * finish name change * simple improvements & new ffi * fixs Co-authored-by: bjoern <r10s@b44t.com> Co-authored-by: septias <xxsebastian.kleahnxx@gmail.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
### API-Changes
|
### API-Changes
|
||||||
- jsonrpc: add python API for webxdc updates #3872
|
- jsonrpc: add python API for webxdc updates #3872
|
||||||
|
- Add ffi functions to retrieve `verified by` information #3786
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Do not add an error if the message is encrypted but not signed #3860
|
- Do not add an error if the message is encrypted but not signed #3860
|
||||||
@@ -26,8 +27,6 @@
|
|||||||
- Only send the message about ephemeral timer change if the chat is promoted #3847
|
- Only send the message about ephemeral timer change if the chat is promoted #3847
|
||||||
- Use relative paths in `accounts.toml` #3838
|
- Use relative paths in `accounts.toml` #3838
|
||||||
|
|
||||||
### API-Changes
|
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Set read/write timeouts for IMAP over SOCKS5 #3833
|
- Set read/write timeouts for IMAP over SOCKS5 #3833
|
||||||
- Treat attached PGP keys as peer keys with mutual encryption preference #3832
|
- Treat attached PGP keys as peer keys with mutual encryption preference #3832
|
||||||
|
|||||||
@@ -4735,6 +4735,37 @@ int dc_contact_is_blocked (const dc_contact_t* contact);
|
|||||||
int dc_contact_is_verified (dc_contact_t* contact);
|
int dc_contact_is_verified (dc_contact_t* contact);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the address that verified a contact
|
||||||
|
*
|
||||||
|
* The UI may use this in addition to a checkmark showing the verification status
|
||||||
|
*
|
||||||
|
* @memberof dc_contact_t
|
||||||
|
* @param contact The contact object.
|
||||||
|
* @return
|
||||||
|
* A string containing the verifiers address. If it is the same address as the contact itself,
|
||||||
|
* we verified the contact ourself. If it is an empty string, we don't have verifier
|
||||||
|
* information or the contact is not verified.
|
||||||
|
*/
|
||||||
|
char* dc_contact_get_verifier_addr (dc_contact_t* contact);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the `ContactId` that verified a contact
|
||||||
|
*
|
||||||
|
* The UI may use this in addition to a checkmark showing the verification status
|
||||||
|
*
|
||||||
|
* @memberof dc_contact_t
|
||||||
|
* @param contact The contact object.
|
||||||
|
* @return
|
||||||
|
* The `ContactId` of the verifiers address. If it is the same address as the contact itself,
|
||||||
|
* we verified the contact ourself. If it is 0, we don't have verifier information or
|
||||||
|
* the contact is not verified.
|
||||||
|
*/
|
||||||
|
int dc_contact_get_verifier_id (dc_contact_t* contact);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class dc_provider_t
|
* @class dc_provider_t
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3963,6 +3963,40 @@ pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> l
|
|||||||
.unwrap_or_default() as libc::c_int
|
.unwrap_or_default() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_contact_get_verifier_addr(
|
||||||
|
contact: *mut dc_contact_t,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
if contact.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_contact_get_verifier_addr()");
|
||||||
|
return "".strdup();
|
||||||
|
}
|
||||||
|
let ffi_contact = &*contact;
|
||||||
|
let ctx = &*ffi_contact.context;
|
||||||
|
block_on(Contact::get_verifier_addr(
|
||||||
|
ctx,
|
||||||
|
&ffi_contact.contact.get_id(),
|
||||||
|
))
|
||||||
|
.log_err(ctx, "failed to get verifier for contact")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.strdup()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_contact_get_verifier_id(contact: *mut dc_contact_t) -> libc::c_int {
|
||||||
|
if contact.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_contact_get_verifier_id()");
|
||||||
|
return 0 as libc::c_int;
|
||||||
|
}
|
||||||
|
let ffi_contact = &*contact;
|
||||||
|
let ctx = &*ffi_contact.context;
|
||||||
|
let contact_id = block_on(Contact::get_verifier_id(ctx, &ffi_contact.contact.get_id()))
|
||||||
|
.log_err(ctx, "failed to get verifier")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
contact_id.to_u32() as libc::c_int
|
||||||
|
}
|
||||||
// dc_lot_t
|
// dc_lot_t
|
||||||
|
|
||||||
pub type dc_lot_t = lot::Lot;
|
pub type dc_lot_t = lot::Lot;
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ class Contact(object):
|
|||||||
"""Return True if the contact is verified."""
|
"""Return True if the contact is verified."""
|
||||||
return lib.dc_contact_is_verified(self._dc_contact)
|
return lib.dc_contact_is_verified(self._dc_contact)
|
||||||
|
|
||||||
|
def get_verifier(self, contact):
|
||||||
|
"""Return the address of the contact that verified the contact"""
|
||||||
|
return from_dc_charpointer(lib.dc_contact_get_verifier_addr(contact._dc_contact))
|
||||||
|
|
||||||
def get_profile_image(self) -> Optional[str]:
|
def get_profile_image(self) -> Optional[str]:
|
||||||
"""Get contact profile image.
|
"""Get contact profile image.
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ class TestGroupStressTests:
|
|||||||
|
|
||||||
def test_qr_verified_group_and_chatting(acfactory, lp):
|
def test_qr_verified_group_and_chatting(acfactory, lp):
|
||||||
ac1, ac2, ac3 = acfactory.get_online_accounts(3)
|
ac1, ac2, ac3 = acfactory.get_online_accounts(3)
|
||||||
|
ac1_addr = ac1.get_self_contact().addr
|
||||||
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||||
chat1 = ac1.create_group_chat("hello", verified=True)
|
chat1 = ac1.create_group_chat("hello", verified=True)
|
||||||
assert chat1.is_protected()
|
assert chat1.is_protected()
|
||||||
@@ -141,12 +142,17 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
|
|||||||
msg_out = chat1.send_text("hello")
|
msg_out = chat1.send_text("hello")
|
||||||
assert msg_out.is_encrypted()
|
assert msg_out.is_encrypted()
|
||||||
|
|
||||||
lp.sec("ac2: read message and check it's verified chat")
|
lp.sec("ac2: read message and check that it's a verified chat")
|
||||||
msg = ac2._evtracker.wait_next_incoming_message()
|
msg = ac2._evtracker.wait_next_incoming_message()
|
||||||
assert msg.text == "hello"
|
assert msg.text == "hello"
|
||||||
assert msg.chat.is_protected()
|
assert msg.chat.is_protected()
|
||||||
assert msg.is_encrypted()
|
assert msg.is_encrypted()
|
||||||
|
|
||||||
|
lp.sec("ac2: Check that ac2 verified ac1")
|
||||||
|
# If we verified the contact ourselves then verifier addr == contact addr
|
||||||
|
ac2_ac1_contact = ac2.get_contacts()[0]
|
||||||
|
assert ac2.get_self_contact().get_verifier(ac2_ac1_contact) == ac1_addr
|
||||||
|
|
||||||
lp.sec("ac2: send message and let ac1 read it")
|
lp.sec("ac2: send message and let ac1 read it")
|
||||||
chat2.send_text("world")
|
chat2.send_text("world")
|
||||||
msg = ac1._evtracker.wait_next_incoming_message()
|
msg = ac1._evtracker.wait_next_incoming_message()
|
||||||
@@ -168,6 +174,12 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
|
|||||||
assert msg.is_system_message()
|
assert msg.is_system_message()
|
||||||
assert not msg.error
|
assert not msg.error
|
||||||
|
|
||||||
|
lp.sec("ac2: Check that ac1 verified ac3 for ac2")
|
||||||
|
ac2_ac1_contact = ac2.get_contacts()[0]
|
||||||
|
assert ac2.get_self_contact().get_verifier(ac2_ac1_contact) == ac1_addr
|
||||||
|
ac2_ac3_contact = ac2.get_contacts()[1]
|
||||||
|
assert ac2.get_self_contact().get_verifier(ac2_ac3_contact) == ac1_addr
|
||||||
|
|
||||||
lp.sec("ac2: send message and let ac3 read it")
|
lp.sec("ac2: send message and let ac3 read it")
|
||||||
chat2.send_text("hi")
|
chat2.send_text("hi")
|
||||||
# Skip system message about added member
|
# Skip system message about added member
|
||||||
|
|||||||
@@ -3562,6 +3562,7 @@ mod tests {
|
|||||||
use crate::constants::{DC_GCL_ARCHIVED_ONLY, DC_GCL_NO_SPECIALS};
|
use crate::constants::{DC_GCL_ARCHIVED_ONLY, DC_GCL_NO_SPECIALS};
|
||||||
use crate::contact::Contact;
|
use crate::contact::Contact;
|
||||||
use crate::receive_imf::receive_imf;
|
use crate::receive_imf::receive_imf;
|
||||||
|
|
||||||
use crate::test_utils::TestContext;
|
use crate::test_utils::TestContext;
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
|||||||
@@ -1138,6 +1138,31 @@ impl Contact {
|
|||||||
Ok(VerifiedStatus::Unverified)
|
Ok(VerifiedStatus::Unverified)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the address that verified the given contact
|
||||||
|
pub async fn get_verifier_addr(
|
||||||
|
context: &Context,
|
||||||
|
contact_id: &ContactId,
|
||||||
|
) -> Result<Option<String>> {
|
||||||
|
let contact = Contact::load_from_db(context, *contact_id).await?;
|
||||||
|
|
||||||
|
Ok(Peerstate::from_addr(context, contact.get_addr())
|
||||||
|
.await?
|
||||||
|
.and_then(|peerstate| peerstate.get_verifier().map(|addr| addr.to_owned())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_verifier_id(
|
||||||
|
context: &Context,
|
||||||
|
contact_id: &ContactId,
|
||||||
|
) -> Result<Option<ContactId>> {
|
||||||
|
let verifier_addr = Contact::get_verifier_addr(context, contact_id).await?;
|
||||||
|
if let Some(addr) = verifier_addr {
|
||||||
|
Ok(Contact::lookup_id_by_addr(context, &addr, Origin::AddressBook).await?)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the ContactId that verified the given contact
|
||||||
pub async fn get_real_cnt(context: &Context) -> Result<usize> {
|
pub async fn get_real_cnt(context: &Context) -> Result<usize> {
|
||||||
if !context.sql.is_open().await {
|
if !context.sql.is_open().await {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
@@ -2300,7 +2325,6 @@ bob@example.net:
|
|||||||
CCCB 5AA9 F6E1 141C 9431
|
CCCB 5AA9 F6E1 141C 9431
|
||||||
65F1 DB18 B18C BCF7 0487"
|
65F1 DB18 B18C BCF7 0487"
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
|||||||
verified_key: Some(pub_key.clone()),
|
verified_key: Some(pub_key.clone()),
|
||||||
verified_key_fingerprint: Some(pub_key.fingerprint()),
|
verified_key_fingerprint: Some(pub_key.fingerprint()),
|
||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
|
verifier: None,
|
||||||
};
|
};
|
||||||
vec![(Some(peerstate), addr)]
|
vec![(Some(peerstate), addr)]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,7 @@
|
|||||||
clippy::bool_assert_comparison,
|
clippy::bool_assert_comparison,
|
||||||
clippy::manual_split_once,
|
clippy::manual_split_once,
|
||||||
clippy::format_push_string,
|
clippy::format_push_string,
|
||||||
clippy::bool_to_int_with_if,
|
clippy::bool_to_int_with_if
|
||||||
// This lint can be re-enabled once we don't target
|
|
||||||
// Rust 1.56 anymore:
|
|
||||||
clippy::collapsible_str_replace
|
|
||||||
)]
|
)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|||||||
@@ -100,11 +100,7 @@ impl Kml {
|
|||||||
if self.tag.contains(KmlTag::WHEN) || self.tag.contains(KmlTag::COORDINATES) {
|
if self.tag.contains(KmlTag::WHEN) || self.tag.contains(KmlTag::COORDINATES) {
|
||||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||||
|
|
||||||
let val = val
|
let val = val.replace(['\n', '\r', '\t', ' '], "");
|
||||||
.replace('\n', "")
|
|
||||||
.replace('\r', "")
|
|
||||||
.replace('\t', "")
|
|
||||||
.replace(' ', "");
|
|
||||||
|
|
||||||
if self.tag.contains(KmlTag::WHEN) && val.len() >= 19 {
|
if self.tag.contains(KmlTag::WHEN) && val.len() >= 19 {
|
||||||
// YYYY-MM-DDTHH:MM:SSZ
|
// YYYY-MM-DDTHH:MM:SSZ
|
||||||
|
|||||||
@@ -1635,6 +1635,8 @@ impl MimeMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parses `Autocrypt-Gossip` headers from the email and applies them to peerstates.
|
/// Parses `Autocrypt-Gossip` headers from the email and applies them to peerstates.
|
||||||
|
/// Params:
|
||||||
|
/// from: The address which sent the message currently beeing parsed
|
||||||
///
|
///
|
||||||
/// Returns the set of mail recipient addresses for which valid gossip headers were found.
|
/// Returns the set of mail recipient addresses for which valid gossip headers were found.
|
||||||
async fn update_gossip_peerstates(
|
async fn update_gossip_peerstates(
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ pub struct Peerstate {
|
|||||||
pub verified_key: Option<SignedPublicKey>,
|
pub verified_key: Option<SignedPublicKey>,
|
||||||
pub verified_key_fingerprint: Option<Fingerprint>,
|
pub verified_key_fingerprint: Option<Fingerprint>,
|
||||||
pub fingerprint_changed: bool,
|
pub fingerprint_changed: bool,
|
||||||
|
/// The address that verified this contact
|
||||||
|
pub verifier: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Peerstate {
|
impl PartialEq for Peerstate {
|
||||||
@@ -103,9 +105,11 @@ impl Peerstate {
|
|||||||
verified_key: None,
|
verified_key: None,
|
||||||
verified_key_fingerprint: None,
|
verified_key_fingerprint: None,
|
||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
|
verifier: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create peerstate from gossip
|
||||||
pub fn from_gossip(gossip_header: &Aheader, message_time: i64) -> Self {
|
pub fn from_gossip(gossip_header: &Aheader, message_time: i64) -> Self {
|
||||||
Peerstate {
|
Peerstate {
|
||||||
addr: gossip_header.addr.clone(),
|
addr: gossip_header.addr.clone(),
|
||||||
@@ -119,7 +123,6 @@ impl Peerstate {
|
|||||||
// learn encryption preferences of other members immediately and don't send unencrypted
|
// learn encryption preferences of other members immediately and don't send unencrypted
|
||||||
// messages to a group where everyone prefers encryption.
|
// messages to a group where everyone prefers encryption.
|
||||||
prefer_encrypt: gossip_header.prefer_encrypt,
|
prefer_encrypt: gossip_header.prefer_encrypt,
|
||||||
|
|
||||||
public_key: None,
|
public_key: None,
|
||||||
public_key_fingerprint: None,
|
public_key_fingerprint: None,
|
||||||
gossip_key: Some(gossip_header.public_key.clone()),
|
gossip_key: Some(gossip_header.public_key.clone()),
|
||||||
@@ -128,13 +131,14 @@ impl Peerstate {
|
|||||||
verified_key: None,
|
verified_key: None,
|
||||||
verified_key_fingerprint: None,
|
verified_key_fingerprint: None,
|
||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
|
verifier: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_addr(context: &Context, addr: &str) -> Result<Option<Peerstate>> {
|
pub async fn from_addr(context: &Context, addr: &str) -> Result<Option<Peerstate>> {
|
||||||
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
||||||
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
||||||
verified_key, verified_key_fingerprint \
|
verified_key, verified_key_fingerprint, verifier \
|
||||||
FROM acpeerstates \
|
FROM acpeerstates \
|
||||||
WHERE addr=? COLLATE NOCASE LIMIT 1;";
|
WHERE addr=? COLLATE NOCASE LIMIT 1;";
|
||||||
Self::from_stmt(context, query, paramsv![addr]).await
|
Self::from_stmt(context, query, paramsv![addr]).await
|
||||||
@@ -146,7 +150,7 @@ impl Peerstate {
|
|||||||
) -> Result<Option<Peerstate>> {
|
) -> Result<Option<Peerstate>> {
|
||||||
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
||||||
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
||||||
verified_key, verified_key_fingerprint \
|
verified_key, verified_key_fingerprint, verifier \
|
||||||
FROM acpeerstates \
|
FROM acpeerstates \
|
||||||
WHERE public_key_fingerprint=? \
|
WHERE public_key_fingerprint=? \
|
||||||
OR gossip_key_fingerprint=? \
|
OR gossip_key_fingerprint=? \
|
||||||
@@ -162,7 +166,7 @@ impl Peerstate {
|
|||||||
) -> Result<Option<Peerstate>> {
|
) -> Result<Option<Peerstate>> {
|
||||||
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
||||||
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
||||||
verified_key, verified_key_fingerprint \
|
verified_key, verified_key_fingerprint, verifier \
|
||||||
FROM acpeerstates \
|
FROM acpeerstates \
|
||||||
WHERE verified_key_fingerprint=? \
|
WHERE verified_key_fingerprint=? \
|
||||||
OR addr=? COLLATE NOCASE \
|
OR addr=? COLLATE NOCASE \
|
||||||
@@ -219,6 +223,7 @@ impl Peerstate {
|
|||||||
.transpose()
|
.transpose()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
|
verifier: row.get("verifier")?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@@ -358,11 +363,19 @@ impl Peerstate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set this peerstate to verified
|
||||||
|
/// Make sure to call `self.save_to_db` to save these changes
|
||||||
|
/// Params:
|
||||||
|
/// verifier:
|
||||||
|
/// The address which verifies the given contact
|
||||||
|
/// If we are verifying the contact, use that contacts address
|
||||||
|
/// Returns whether the value of the key has changed
|
||||||
pub fn set_verified(
|
pub fn set_verified(
|
||||||
&mut self,
|
&mut self,
|
||||||
which_key: PeerstateKeyType,
|
which_key: PeerstateKeyType,
|
||||||
fingerprint: &Fingerprint,
|
fingerprint: &Fingerprint,
|
||||||
verified: PeerstateVerifiedStatus,
|
verified: PeerstateVerifiedStatus,
|
||||||
|
verifier: String,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if verified == PeerstateVerifiedStatus::BidirectVerified {
|
if verified == PeerstateVerifiedStatus::BidirectVerified {
|
||||||
match which_key {
|
match which_key {
|
||||||
@@ -372,6 +385,7 @@ impl Peerstate {
|
|||||||
{
|
{
|
||||||
self.verified_key = self.public_key.clone();
|
self.verified_key = self.public_key.clone();
|
||||||
self.verified_key_fingerprint = self.public_key_fingerprint.clone();
|
self.verified_key_fingerprint = self.public_key_fingerprint.clone();
|
||||||
|
self.verifier = Some(verifier);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -383,6 +397,7 @@ impl Peerstate {
|
|||||||
{
|
{
|
||||||
self.verified_key = self.gossip_key.clone();
|
self.verified_key = self.gossip_key.clone();
|
||||||
self.verified_key_fingerprint = self.gossip_key_fingerprint.clone();
|
self.verified_key_fingerprint = self.gossip_key_fingerprint.clone();
|
||||||
|
self.verifier = Some(verifier);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -407,8 +422,9 @@ impl Peerstate {
|
|||||||
gossip_key_fingerprint,
|
gossip_key_fingerprint,
|
||||||
verified_key,
|
verified_key,
|
||||||
verified_key_fingerprint,
|
verified_key_fingerprint,
|
||||||
addr)
|
addr,
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
verifier)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
ON CONFLICT (addr)
|
ON CONFLICT (addr)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
last_seen = excluded.last_seen,
|
last_seen = excluded.last_seen,
|
||||||
@@ -420,7 +436,8 @@ impl Peerstate {
|
|||||||
public_key_fingerprint = excluded.public_key_fingerprint,
|
public_key_fingerprint = excluded.public_key_fingerprint,
|
||||||
gossip_key_fingerprint = excluded.gossip_key_fingerprint,
|
gossip_key_fingerprint = excluded.gossip_key_fingerprint,
|
||||||
verified_key = excluded.verified_key,
|
verified_key = excluded.verified_key,
|
||||||
verified_key_fingerprint = excluded.verified_key_fingerprint",
|
verified_key_fingerprint = excluded.verified_key_fingerprint,
|
||||||
|
verifier = excluded.verifier",
|
||||||
paramsv![
|
paramsv![
|
||||||
self.last_seen,
|
self.last_seen,
|
||||||
self.last_seen_autocrypt,
|
self.last_seen_autocrypt,
|
||||||
@@ -433,6 +450,7 @@ impl Peerstate {
|
|||||||
self.verified_key.as_ref().map(|k| k.to_bytes()),
|
self.verified_key.as_ref().map(|k| k.to_bytes()),
|
||||||
self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()),
|
self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()),
|
||||||
self.addr,
|
self.addr,
|
||||||
|
self.verifier,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -447,6 +465,11 @@ impl Peerstate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the address that verified the contact
|
||||||
|
pub fn get_verifier(&self) -> Option<&str> {
|
||||||
|
self.verifier.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
/// Add an info message to all the chats with this contact, informing about
|
/// Add an info message to all the chats with this contact, informing about
|
||||||
/// a [`PeerstateChange`].
|
/// a [`PeerstateChange`].
|
||||||
///
|
///
|
||||||
@@ -672,6 +695,7 @@ mod tests {
|
|||||||
verified_key: Some(pub_key.clone()),
|
verified_key: Some(pub_key.clone()),
|
||||||
verified_key_fingerprint: Some(pub_key.fingerprint()),
|
verified_key_fingerprint: Some(pub_key.fingerprint()),
|
||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
|
verifier: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@@ -711,6 +735,7 @@ mod tests {
|
|||||||
verified_key: None,
|
verified_key: None,
|
||||||
verified_key_fingerprint: None,
|
verified_key_fingerprint: None,
|
||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
|
verifier: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@@ -743,6 +768,7 @@ mod tests {
|
|||||||
verified_key: None,
|
verified_key: None,
|
||||||
verified_key_fingerprint: None,
|
verified_key_fingerprint: None,
|
||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
|
verifier: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@@ -805,6 +831,7 @@ mod tests {
|
|||||||
verified_key: None,
|
verified_key: None,
|
||||||
verified_key_fingerprint: None,
|
verified_key_fingerprint: None,
|
||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
|
verifier: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
peerstate.apply_header(&header, 100);
|
peerstate.apply_header(&header, 100);
|
||||||
|
|||||||
@@ -896,6 +896,7 @@ mod tests {
|
|||||||
verified_key: None,
|
verified_key: None,
|
||||||
verified_key_fingerprint: None,
|
verified_key_fingerprint: None,
|
||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
|
verifier: None,
|
||||||
};
|
};
|
||||||
assert!(
|
assert!(
|
||||||
peerstate.save_to_db(&ctx.ctx.sql).await.is_ok(),
|
peerstate.save_to_db(&ctx.ctx.sql).await.is_ok(),
|
||||||
|
|||||||
@@ -2138,13 +2138,14 @@ async fn check_verified_properties(
|
|||||||
|| peerstate.verified_key_fingerprint != peerstate.public_key_fingerprint
|
|| peerstate.verified_key_fingerprint != peerstate.public_key_fingerprint
|
||||||
&& peerstate.verified_key_fingerprint != peerstate.gossip_key_fingerprint
|
&& peerstate.verified_key_fingerprint != peerstate.gossip_key_fingerprint
|
||||||
{
|
{
|
||||||
info!(context, "{} has verified {}.", contact.get_addr(), to_addr,);
|
info!(context, "{} has verified {}.", contact.get_addr(), to_addr);
|
||||||
let fp = peerstate.gossip_key_fingerprint.clone();
|
let fp = peerstate.gossip_key_fingerprint.clone();
|
||||||
if let Some(fp) = fp {
|
if let Some(fp) = fp {
|
||||||
peerstate.set_verified(
|
peerstate.set_verified(
|
||||||
PeerstateKeyType::GossipKey,
|
PeerstateKeyType::GossipKey,
|
||||||
&fp,
|
&fp,
|
||||||
PeerstateVerifiedStatus::BidirectVerified,
|
PeerstateVerifiedStatus::BidirectVerified,
|
||||||
|
contact.get_addr().to_owned(),
|
||||||
);
|
);
|
||||||
peerstate.save_to_db(&context.sql).await?;
|
peerstate.save_to_db(&context.sql).await?;
|
||||||
is_verified = true;
|
is_verified = true;
|
||||||
|
|||||||
@@ -411,7 +411,14 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
.await?;
|
.await?;
|
||||||
return Ok(HandshakeMessage::Ignore);
|
return Ok(HandshakeMessage::Ignore);
|
||||||
}
|
}
|
||||||
if mark_peer_as_verified(context, &fingerprint).await.is_err() {
|
let contact_addr = Contact::load_from_db(context, contact_id)
|
||||||
|
.await?
|
||||||
|
.get_addr()
|
||||||
|
.to_owned();
|
||||||
|
if mark_peer_as_verified(context, &fingerprint, contact_addr)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
could_not_establish_secure_connection(
|
could_not_establish_secure_connection(
|
||||||
context,
|
context,
|
||||||
contact_id,
|
contact_id,
|
||||||
@@ -531,7 +538,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
///
|
///
|
||||||
/// - if we see the self-sent-message vg-member-added/vc-contact-confirm,
|
/// - if we see the self-sent-message vg-member-added/vc-contact-confirm,
|
||||||
/// we know that we're an inviter-observer.
|
/// 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
|
/// 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,
|
/// 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.
|
/// we can mark the peer as verified as well.
|
||||||
///
|
///
|
||||||
@@ -586,7 +593,17 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
|||||||
return Ok(HandshakeMessage::Ignore);
|
return Ok(HandshakeMessage::Ignore);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if mark_peer_as_verified(context, &fingerprint).await.is_err() {
|
if mark_peer_as_verified(
|
||||||
|
context,
|
||||||
|
&fingerprint,
|
||||||
|
Contact::load_from_db(context, contact_id)
|
||||||
|
.await?
|
||||||
|
.get_addr()
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
could_not_establish_secure_connection(
|
could_not_establish_secure_connection(
|
||||||
context,
|
context,
|
||||||
contact_id,
|
contact_id,
|
||||||
@@ -634,12 +651,17 @@ async fn could_not_establish_secure_connection(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn mark_peer_as_verified(context: &Context, fingerprint: &Fingerprint) -> Result<(), Error> {
|
async fn mark_peer_as_verified(
|
||||||
|
context: &Context,
|
||||||
|
fingerprint: &Fingerprint,
|
||||||
|
verifier: String,
|
||||||
|
) -> Result<(), Error> {
|
||||||
if let Some(ref mut peerstate) = Peerstate::from_fingerprint(context, fingerprint).await? {
|
if let Some(ref mut peerstate) = Peerstate::from_fingerprint(context, fingerprint).await? {
|
||||||
if peerstate.set_verified(
|
if peerstate.set_verified(
|
||||||
PeerstateKeyType::PublicKey,
|
PeerstateKeyType::PublicKey,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
PeerstateVerifiedStatus::BidirectVerified,
|
PeerstateVerifiedStatus::BidirectVerified,
|
||||||
|
verifier,
|
||||||
) {
|
) {
|
||||||
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||||
peerstate.save_to_db(&context.sql).await.unwrap_or_default();
|
peerstate.save_to_db(&context.sql).await.unwrap_or_default();
|
||||||
@@ -931,6 +953,7 @@ mod tests {
|
|||||||
verified_key: None,
|
verified_key: None,
|
||||||
verified_key_fingerprint: None,
|
verified_key_fingerprint: None,
|
||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
|
verifier: None,
|
||||||
};
|
};
|
||||||
peerstate.save_to_db(&bob.ctx.sql).await?;
|
peerstate.save_to_db(&bob.ctx.sql).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ impl BobState {
|
|||||||
///
|
///
|
||||||
/// This deviates from the protocol by also sending a confirmation message in response
|
/// 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
|
/// to the *vc-contact-confirm* message. This has no specific value to the protocol and
|
||||||
/// is only done out of symmerty with *vg-member-added* handling.
|
/// is only done out of symmetry with *vg-member-added* handling.
|
||||||
async fn step_contact_confirm(
|
async fn step_contact_confirm(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
@@ -366,7 +366,12 @@ impl BobState {
|
|||||||
"Contact confirm message not encrypted",
|
"Contact confirm message not encrypted",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
mark_peer_as_verified(context, self.invite.fingerprint()).await?;
|
mark_peer_as_verified(
|
||||||
|
context,
|
||||||
|
self.invite.fingerprint(),
|
||||||
|
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));
|
||||||
|
|||||||
@@ -664,6 +664,13 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid);
|
|||||||
95
|
95
|
||||||
).await?;
|
).await?;
|
||||||
}
|
}
|
||||||
|
if dbversion < 96 {
|
||||||
|
sql.execute_migration(
|
||||||
|
"ALTER TABLE acpeerstates ADD COLUMN verifier TEXT DEFAULT '';",
|
||||||
|
96,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
let new_version = sql
|
let new_version = sql
|
||||||
.get_raw_config_int(VERSION_CFG)
|
.get_raw_config_int(VERSION_CFG)
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ impl TestContext {
|
|||||||
///
|
///
|
||||||
/// This is a shortcut which automatically calls [`TestContext::configure_alice`] after
|
/// This is a shortcut which automatically calls [`TestContext::configure_alice`] after
|
||||||
/// creating the context.
|
/// creating the context.
|
||||||
|
/// alice-email: alice@example.org
|
||||||
pub async fn new_alice() -> Self {
|
pub async fn new_alice() -> Self {
|
||||||
Self::builder().configure_alice().build().await
|
Self::builder().configure_alice().build().await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -593,11 +593,7 @@ impl rusqlite::types::ToSql for EmailAddress {
|
|||||||
|
|
||||||
/// Makes sure that a user input that is not supposed to contain newlines does not contain newlines.
|
/// Makes sure that a user input that is not supposed to contain newlines does not contain newlines.
|
||||||
pub(crate) fn improve_single_line_input(input: &str) -> String {
|
pub(crate) fn improve_single_line_input(input: &str) -> String {
|
||||||
input
|
input.replace(['\n', '\r'], " ").trim().to_string()
|
||||||
.replace('\n', " ")
|
|
||||||
.replace('\r', " ")
|
|
||||||
.trim()
|
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait IsNoneOrEmpty<T> {
|
pub(crate) trait IsNoneOrEmpty<T> {
|
||||||
|
|||||||
Reference in New Issue
Block a user