diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index aba59fea6..6f42b474b 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -14,7 +14,7 @@ except ImportError: import deltachat from . import const from .capi import ffi, lib -from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array +from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array, DCLot from .chatting import Contact, Chat, Message @@ -329,6 +329,26 @@ class Account(object): raise RuntimeError("could not send out autocrypt setup message") return from_dc_charpointer(res) + def get_setup_contact_qr(self): + """ get/Create Setup-Contact QR Code as ascii-string """ + res = lib.dc_get_securejoin_qr(self._dc_context, 0) + return from_dc_charpointer(res) + + def check_qr(self, qr): + """ check qr code ...""" + res = ffi.gc( + lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)), + lib.dc_lot_unref + ) + return DCLot(res) + + def setup_secure_contact(self, qr): + """ """ + chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr)) + if chat_id == 0: + raise ValueError("could not setup secure contact") + return Chat(self, chat_id) + def start_threads(self): """ start IMAP/SMTP threads (and configure account if it hasn't happened). diff --git a/python/src/deltachat/cutil.py b/python/src/deltachat/cutil.py index aa739484c..2b9aa7a0f 100644 --- a/python/src/deltachat/cutil.py +++ b/python/src/deltachat/cutil.py @@ -1,5 +1,6 @@ from .capi import lib from .capi import ffi +from datetime import datetime def as_dc_charpointer(obj): @@ -17,3 +18,29 @@ def iter_array(dc_array_t, constructor): def from_dc_charpointer(obj): return ffi.string(ffi.gc(obj, lib.dc_str_unref)).decode("utf8") + + +class DCLot: + def __init__(self, dc_lot): + self._dc_lot = dc_lot + + def id(self): + return lib.dc_lot_get_id(self._dc_lot) + + def state(self): + return lib.dc_lot_get_state(self._dc_lot) + + def text1(self): + return from_dc_charpointer(lib.dc_lot_get_text1(self._dc_lot)) + + def text1_meaning(self): + return lib.dc_lot_get_text1_meaning(self._dc_lot) + + def text2(self): + return from_dc_charpointer(lib.dc_lot_get_text2(self._dc_lot)) + + def timestamp(self): + ts = lib.dc_lot_get_timestamp(self._dc_lot) + if ts == 0: + return None + return datetime.utcfromtimestamp(ts) diff --git a/python/tests/conftest.py b/python/tests/conftest.py index b97370093..1e8e6dece 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -213,6 +213,15 @@ def wait_configuration_progress(account, target): break +def wait_securejoin_inviter_progress(account, target): + while 1: + evt_name, data1, data2 = \ + account._evlogger.get_matching("DC_EVENT_SECUREJOIN_INVITER_PROGRESS") + if data2 >= target: + print("** SECUREJOINT-INVITER PROGRESS {}".format(target), account) + break + + def wait_successful_IMAP_SMTP_connection(account): imap_ok = smtp_ok = False while not imap_ok or not smtp_ok: diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 5acf972c4..f3e66d16e 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -4,7 +4,7 @@ import os from deltachat import const, Account from deltachat.message import Message from datetime import datetime, timedelta -from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection +from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection, wait_securejoin_inviter_progress class TestOfflineAccountBasic: @@ -546,3 +546,25 @@ class TestOnlineAccount: print("*************** Setup Code: ", setup_code) msg.continue_key_transfer(setup_code) assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"] + + def test_setup_contact(self, acfactory, lp): + # note that the receiving account needs to be configured and running + # before ther setup message is send. DC does not read old messages + # as of Jul2019 + ac1 = acfactory.get_online_configuring_account() + ac2 = acfactory.get_online_configuring_account() + wait_configuration_progress(ac2, 1000) + wait_configuration_progress(ac1, 1000) + lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin") + + qr = ac1.get_setup_contact_qr() + assert qr.startswith("OPENPGP4FPR:") + + res = ac2.check_qr(qr) + assert res.state() == 200 + assert res.id() == 10 + + lp.sec("ac2: start QR-code based setup contact protocol") + ch = ac2.setup_secure_contact(qr) + assert ch.id >= 10 + wait_securejoin_inviter_progress(ac1, 1000) diff --git a/src/dc_securejoin.rs b/src/dc_securejoin.rs index a1d26b9ea..37d9fc26b 100644 --- a/src/dc_securejoin.rs +++ b/src/dc_securejoin.rs @@ -12,6 +12,7 @@ use crate::dc_mimeparser::*; use crate::dc_token::*; use crate::dc_tools::*; use crate::e2ee::*; +use crate::error::Error; use crate::key::*; use crate::lot::LotState; use crate::message::*; @@ -164,7 +165,7 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> bob.status = 0; bob.qr_scan = Some(qr_scan); } - if 0 != fingerprint_equals_sender( + if fingerprint_equals_sender( context, context .bob @@ -334,12 +335,11 @@ unsafe fn chat_id_2_contact_id(context: &Context, contact_chat_id: uint32_t) -> } } -unsafe fn fingerprint_equals_sender( +fn fingerprint_equals_sender( context: &Context, fingerprint: impl AsRef, contact_chat_id: u32, -) -> libc::c_int { - let mut fingerprint_equal = 0; +) -> bool { let contacts = chat::get_chat_contacts(context, contact_chat_id); if contacts.len() == 1 { @@ -350,15 +350,12 @@ unsafe fn fingerprint_equals_sender( if peerstate.public_key_fingerprint.is_some() && &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap() { - fingerprint_equal = 1; + return true; } } - } else { - return 0; } } - - fingerprint_equal + false } /* library private: secure-join */ @@ -508,13 +505,11 @@ pub unsafe fn dc_handle_securejoin_handshake( ); end_bobs_joining(context, 0i32); ok_to_continue = false; - } else if 0 - == fingerprint_equals_sender( - context, - &scanned_fingerprint_of_alice, - contact_chat_id, - ) - { + } else if !fingerprint_equals_sender( + context, + &scanned_fingerprint_of_alice, + contact_chat_id, + ) { could_not_establish_secure_connection( context, contact_chat_id, @@ -578,8 +573,7 @@ pub unsafe fn dc_handle_securejoin_handshake( b"Auth not encrypted.\x00" as *const u8 as *const libc::c_char, ); ok_to_continue = false; - } else if 0 - == fingerprint_equals_sender(context, as_str(fingerprint), contact_chat_id) + } else if !fingerprint_equals_sender(context, as_str(fingerprint), contact_chat_id) { could_not_establish_secure_connection( context, @@ -607,7 +601,7 @@ pub unsafe fn dc_handle_securejoin_handshake( b"Auth invalid.\x00" as *const u8 as *const libc::c_char, ); ok_to_continue = false; - } else if 0 == mark_peer_as_verified(context, as_str(fingerprint)) { + } else if mark_peer_as_verified(context, as_str(fingerprint)).is_err() { could_not_establish_secure_connection( context, contact_chat_id, @@ -752,7 +746,9 @@ pub unsafe fn dc_handle_securejoin_handshake( ok_to_continue = true; } if ok_to_continue { - if 0 == mark_peer_as_verified(context, &scanned_fingerprint_of_alice) { + if mark_peer_as_verified(context, &scanned_fingerprint_of_alice) + .is_err() + { could_not_establish_secure_connection( context, contact_chat_id, @@ -914,9 +910,7 @@ unsafe fn could_not_establish_secure_connection( error!(context, 0, "{} ({})", &msg, as_str(details)); } -unsafe fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef) -> libc::c_int { - let mut success = 0; - +fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef) -> Result<(), Error> { if let Some(ref mut peerstate) = Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref()) { @@ -924,11 +918,13 @@ unsafe fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef) peerstate.prefer_encrypt = EncryptPreference::Mutual; peerstate.to_save = Some(ToSave::All); peerstate.save_to_db(&context.sql, false); - success = 1; + return Ok(()); } } - - success + bail!( + "could not mark peer as verified for fingerprint {}", + fingerprint.as_ref() + ); } /* ****************************************************************************** diff --git a/src/peerstate.rs b/src/peerstate.rs index b203904c7..932cef8d5 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -178,8 +178,11 @@ impl<'a> Peerstate<'a> { OR gossip_key_fingerprint=? COLLATE NOCASE \ ORDER BY public_key_fingerprint=? DESC;"; - let fp = fingerprint.as_bytes(); - Self::from_stmt(context, query, params![fp, fp, fp]) + Self::from_stmt( + context, + query, + params![fingerprint, fingerprint, fingerprint], + ) } fn from_stmt

(context: &'a Context, query: &str, params: P) -> Option @@ -523,6 +526,10 @@ mod tests { // clear to_save, as that is not persissted peerstate.to_save = None; assert_eq!(peerstate, peerstate_new); + let peerstate_new2 = + Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint()) + .expect("failed to load peerstate from db"); + assert_eq!(peerstate, peerstate_new2); } #[test]