(dignifiedquire, hpk, jikstra)

- fix and test peerstate::from_fingerprint
- add and test python API for secure-join QR + setup-contact
This commit is contained in:
holger krekel
2019-09-04 19:12:50 +02:00
parent b9cfcce284
commit 2920732435
6 changed files with 111 additions and 30 deletions

View File

@@ -14,7 +14,7 @@ except ImportError:
import deltachat import deltachat
from . import const from . import const
from .capi import ffi, lib 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 from .chatting import Contact, Chat, Message
@@ -329,6 +329,26 @@ class Account(object):
raise RuntimeError("could not send out autocrypt setup message") raise RuntimeError("could not send out autocrypt setup message")
return from_dc_charpointer(res) 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): def start_threads(self):
""" start IMAP/SMTP threads (and configure account if it hasn't happened). """ start IMAP/SMTP threads (and configure account if it hasn't happened).

View File

@@ -1,5 +1,6 @@
from .capi import lib from .capi import lib
from .capi import ffi from .capi import ffi
from datetime import datetime
def as_dc_charpointer(obj): def as_dc_charpointer(obj):
@@ -17,3 +18,29 @@ def iter_array(dc_array_t, constructor):
def from_dc_charpointer(obj): def from_dc_charpointer(obj):
return ffi.string(ffi.gc(obj, lib.dc_str_unref)).decode("utf8") 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)

View File

@@ -213,6 +213,15 @@ def wait_configuration_progress(account, target):
break 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): def wait_successful_IMAP_SMTP_connection(account):
imap_ok = smtp_ok = False imap_ok = smtp_ok = False
while not imap_ok or not smtp_ok: while not imap_ok or not smtp_ok:

View File

@@ -4,7 +4,7 @@ import os
from deltachat import const, Account from deltachat import const, Account
from deltachat.message import Message from deltachat.message import Message
from datetime import datetime, timedelta 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: class TestOfflineAccountBasic:
@@ -546,3 +546,25 @@ class TestOnlineAccount:
print("*************** Setup Code: ", setup_code) print("*************** Setup Code: ", setup_code)
msg.continue_key_transfer(setup_code) msg.continue_key_transfer(setup_code)
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"] 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)

View File

@@ -12,6 +12,7 @@ use crate::dc_mimeparser::*;
use crate::dc_token::*; use crate::dc_token::*;
use crate::dc_tools::*; use crate::dc_tools::*;
use crate::e2ee::*; use crate::e2ee::*;
use crate::error::Error;
use crate::key::*; use crate::key::*;
use crate::lot::LotState; use crate::lot::LotState;
use crate::message::*; use crate::message::*;
@@ -164,7 +165,7 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) ->
bob.status = 0; bob.status = 0;
bob.qr_scan = Some(qr_scan); bob.qr_scan = Some(qr_scan);
} }
if 0 != fingerprint_equals_sender( if fingerprint_equals_sender(
context, context,
context context
.bob .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, context: &Context,
fingerprint: impl AsRef<str>, fingerprint: impl AsRef<str>,
contact_chat_id: u32, contact_chat_id: u32,
) -> libc::c_int { ) -> bool {
let mut fingerprint_equal = 0;
let contacts = chat::get_chat_contacts(context, contact_chat_id); let contacts = chat::get_chat_contacts(context, contact_chat_id);
if contacts.len() == 1 { if contacts.len() == 1 {
@@ -350,15 +350,12 @@ unsafe fn fingerprint_equals_sender(
if peerstate.public_key_fingerprint.is_some() if peerstate.public_key_fingerprint.is_some()
&& &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap() && &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap()
{ {
fingerprint_equal = 1; return true;
} }
} }
} else {
return 0;
} }
} }
false
fingerprint_equal
} }
/* library private: secure-join */ /* library private: secure-join */
@@ -508,13 +505,11 @@ pub unsafe fn dc_handle_securejoin_handshake(
); );
end_bobs_joining(context, 0i32); end_bobs_joining(context, 0i32);
ok_to_continue = false; ok_to_continue = false;
} else if 0 } else if !fingerprint_equals_sender(
== fingerprint_equals_sender( context,
context, &scanned_fingerprint_of_alice,
&scanned_fingerprint_of_alice, contact_chat_id,
contact_chat_id, ) {
)
{
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
contact_chat_id, 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, b"Auth not encrypted.\x00" as *const u8 as *const libc::c_char,
); );
ok_to_continue = false; ok_to_continue = false;
} else if 0 } else if !fingerprint_equals_sender(context, as_str(fingerprint), contact_chat_id)
== fingerprint_equals_sender(context, as_str(fingerprint), contact_chat_id)
{ {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
@@ -607,7 +601,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
b"Auth invalid.\x00" as *const u8 as *const libc::c_char, b"Auth invalid.\x00" as *const u8 as *const libc::c_char,
); );
ok_to_continue = false; 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( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
@@ -752,7 +746,9 @@ pub unsafe fn dc_handle_securejoin_handshake(
ok_to_continue = true; ok_to_continue = true;
} }
if ok_to_continue { 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( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
@@ -914,9 +910,7 @@ unsafe fn could_not_establish_secure_connection(
error!(context, 0, "{} ({})", &msg, as_str(details)); error!(context, 0, "{} ({})", &msg, as_str(details));
} }
unsafe fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> libc::c_int { fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Result<(), Error> {
let mut success = 0;
if let Some(ref mut peerstate) = if let Some(ref mut peerstate) =
Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref()) Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref())
{ {
@@ -924,11 +918,13 @@ unsafe fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>)
peerstate.prefer_encrypt = EncryptPreference::Mutual; peerstate.prefer_encrypt = EncryptPreference::Mutual;
peerstate.to_save = Some(ToSave::All); peerstate.to_save = Some(ToSave::All);
peerstate.save_to_db(&context.sql, false); peerstate.save_to_db(&context.sql, false);
success = 1; return Ok(());
} }
} }
bail!(
success "could not mark peer as verified for fingerprint {}",
fingerprint.as_ref()
);
} }
/* ****************************************************************************** /* ******************************************************************************

View File

@@ -178,8 +178,11 @@ impl<'a> Peerstate<'a> {
OR gossip_key_fingerprint=? COLLATE NOCASE \ OR gossip_key_fingerprint=? COLLATE NOCASE \
ORDER BY public_key_fingerprint=? DESC;"; ORDER BY public_key_fingerprint=? DESC;";
let fp = fingerprint.as_bytes(); Self::from_stmt(
Self::from_stmt(context, query, params![fp, fp, fp]) context,
query,
params![fingerprint, fingerprint, fingerprint],
)
} }
fn from_stmt<P>(context: &'a Context, query: &str, params: P) -> Option<Self> fn from_stmt<P>(context: &'a Context, query: &str, params: P) -> Option<Self>
@@ -523,6 +526,10 @@ mod tests {
// clear to_save, as that is not persissted // clear to_save, as that is not persissted
peerstate.to_save = None; peerstate.to_save = None;
assert_eq!(peerstate, peerstate_new); 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] #[test]