mirror of
https://github.com/chatmail/core.git
synced 2026-05-05 22:36:30 +03:00
(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:
@@ -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).
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ******************************************************************************
|
/* ******************************************************************************
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user