mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
feat: ignore encryption preferences
Encryption preference is sent in Autocrypt header, but otherwise ignored. Delta Chat always prefers encryption if it is available.
This commit is contained in:
@@ -423,8 +423,6 @@ class ACFactory:
|
||||
where we can make valid SMTP and IMAP connections with.
|
||||
"""
|
||||
configdict = next(self._liveconfig_producer).copy()
|
||||
if "e2ee_enabled" not in configdict:
|
||||
configdict["e2ee_enabled"] = "1"
|
||||
|
||||
if self.pytestconfig.getoption("--strict-tls"):
|
||||
# Enable strict certificate checks for online accounts
|
||||
|
||||
@@ -919,64 +919,6 @@ def test_gossip_optimization(acfactory, lp):
|
||||
assert gossiped_timestamp == int(msg.time_sent.timestamp())
|
||||
|
||||
|
||||
def test_gossip_encryption_preference(acfactory, lp):
|
||||
"""Test that encryption preference of group members is gossiped to new members.
|
||||
This is a Delta Chat extension to Autocrypt 1.1.0, which Autocrypt-Gossip headers
|
||||
SHOULD NOT contain encryption preference.
|
||||
"""
|
||||
ac1, ac2, ac3 = acfactory.get_online_accounts(3)
|
||||
|
||||
lp.sec("ac1 learns that ac2 prefers encryption")
|
||||
ac1.create_chat(ac2)
|
||||
msg = ac2.create_chat(ac1).send_text("first message")
|
||||
msg = ac1._evtracker.wait_next_incoming_message()
|
||||
assert msg.text == "first message"
|
||||
assert not msg.is_encrypted()
|
||||
res = "End-to-end encryption preferred:\n{}".format(ac2.get_config("addr"))
|
||||
assert msg.chat.get_encryption_info() == res
|
||||
lp.sec("ac2 learns that ac3 prefers encryption")
|
||||
ac2.create_chat(ac3)
|
||||
msg = ac3.create_chat(ac2).send_text("I prefer encryption")
|
||||
msg = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg.text == "I prefer encryption"
|
||||
assert not msg.is_encrypted()
|
||||
|
||||
lp.sec("ac3 does not know that ac1 prefers encryption")
|
||||
ac1.create_chat(ac3)
|
||||
chat = ac3.create_chat(ac1)
|
||||
res = "No encryption:\n{}".format(ac1.get_config("addr"))
|
||||
assert chat.get_encryption_info() == res
|
||||
msg = chat.send_text("not encrypted")
|
||||
msg = ac1._evtracker.wait_next_incoming_message()
|
||||
assert msg.text == "not encrypted"
|
||||
assert not msg.is_encrypted()
|
||||
|
||||
lp.sec("ac1 creates a group chat with ac2")
|
||||
group_chat = ac1.create_group_chat("hello")
|
||||
group_chat.add_contact(ac2)
|
||||
encryption_info = group_chat.get_encryption_info()
|
||||
res = "End-to-end encryption preferred:\n{}".format(ac2.get_config("addr"))
|
||||
assert encryption_info == res
|
||||
msg = group_chat.send_text("hi")
|
||||
|
||||
msg = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg.is_encrypted()
|
||||
assert msg.text == "hi"
|
||||
|
||||
lp.sec("ac2 adds ac3 to the group")
|
||||
msg.chat.add_contact(ac3)
|
||||
assert msg.is_encrypted()
|
||||
|
||||
lp.sec("ac3 learns that ac1 prefers encryption")
|
||||
msg = ac3._evtracker.wait_next_incoming_message()
|
||||
encryption_info = msg.chat.get_encryption_info().splitlines()
|
||||
assert encryption_info[0] == "End-to-end encryption preferred:"
|
||||
assert ac1.get_config("addr") in encryption_info[1:]
|
||||
assert ac2.get_config("addr") in encryption_info[1:]
|
||||
msg = chat.send_text("encrypted")
|
||||
assert msg.is_encrypted()
|
||||
|
||||
|
||||
def test_send_first_message_as_long_unicode_with_cr(acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
|
||||
@@ -1167,61 +1109,6 @@ def test_dont_show_emails(acfactory, lp):
|
||||
assert len(msg.chat.get_messages()) == 3
|
||||
|
||||
|
||||
def test_prefer_encrypt(acfactory, lp):
|
||||
"""Test quorum rule for encryption preference in 1:1 and group chat."""
|
||||
ac1 = acfactory.new_online_configuring_account(fix_is_chatmail=True)
|
||||
ac2 = acfactory.new_online_configuring_account(fix_is_chatmail=True)
|
||||
ac3 = acfactory.new_online_configuring_account(fix_is_chatmail=True)
|
||||
acfactory.bring_accounts_online()
|
||||
ac1.set_config("e2ee_enabled", "0")
|
||||
ac2.set_config("e2ee_enabled", "1")
|
||||
ac3.set_config("e2ee_enabled", "0")
|
||||
|
||||
# Make sure we do not send a copy to ourselves. This is to
|
||||
# test that we count own preference even when we are not in
|
||||
# the recipient list.
|
||||
ac1.set_config("bcc_self", "0")
|
||||
ac2.set_config("bcc_self", "0")
|
||||
ac3.set_config("bcc_self", "0")
|
||||
|
||||
acfactory.introduce_each_other([ac1, ac2, ac3])
|
||||
|
||||
lp.sec("ac1: sending message to ac2")
|
||||
chat1 = ac1.create_chat(ac2)
|
||||
msg1 = chat1.send_text("message1")
|
||||
assert not msg1.is_encrypted()
|
||||
ac2._evtracker.wait_next_incoming_message()
|
||||
|
||||
lp.sec("ac2: sending message to ac1")
|
||||
chat2 = ac2.create_chat(ac1)
|
||||
msg2 = chat2.send_text("message2")
|
||||
# Own preference is `Mutual` and we have the peer's key.
|
||||
assert msg2.is_encrypted()
|
||||
ac1._evtracker.wait_next_incoming_message()
|
||||
|
||||
lp.sec("ac1: sending message to group chat with ac2 and ac3")
|
||||
group = ac1.create_group_chat("hello")
|
||||
group.add_contact(ac2)
|
||||
group.add_contact(ac3)
|
||||
msg3 = group.send_text("message3")
|
||||
assert not msg3.is_encrypted()
|
||||
ac2._evtracker.wait_next_incoming_message()
|
||||
ac3._evtracker.wait_next_incoming_message()
|
||||
|
||||
lp.sec("ac3: start preferring encryption and inform ac1")
|
||||
ac3.set_config("e2ee_enabled", "1")
|
||||
chat3 = ac3.create_chat(ac1)
|
||||
msg4 = chat3.send_text("message4")
|
||||
# Own preference is `Mutual` and we have the peer's key.
|
||||
assert msg4.is_encrypted()
|
||||
ac1._evtracker.wait_next_incoming_message()
|
||||
|
||||
lp.sec("ac1: sending another message to group chat with ac2 and ac3")
|
||||
msg5 = group.send_text("message5")
|
||||
# Majority prefers encryption now
|
||||
assert msg5.is_encrypted()
|
||||
|
||||
|
||||
def test_bot(acfactory, lp):
|
||||
"""Test that bot messages can be identified as such"""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
@@ -1250,59 +1137,6 @@ def test_bot(acfactory, lp):
|
||||
assert msg_in.is_bot()
|
||||
|
||||
|
||||
def test_quote_encrypted(acfactory, lp):
|
||||
"""Test that replies to encrypted messages with quotes are encrypted."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat = ac1.create_chat(ac2)
|
||||
|
||||
lp.sec("sending text message from ac1 to ac2")
|
||||
msg1 = chat.send_text("message1")
|
||||
assert not msg1.is_encrypted()
|
||||
|
||||
lp.sec("wait for ac2 to receive message")
|
||||
msg2 = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg2.text == "message1"
|
||||
assert not msg2.is_encrypted()
|
||||
|
||||
lp.sec("create new chat with contact and send back (encrypted) message")
|
||||
msg2.create_chat().send_text("message-back")
|
||||
|
||||
lp.sec("wait for ac1 to receive message")
|
||||
msg3 = ac1._evtracker.wait_next_incoming_message()
|
||||
assert msg3.text == "message-back"
|
||||
assert msg3.is_encrypted()
|
||||
|
||||
lp.sec("ac1: e2ee_enabled=0 and see if reply is encrypted")
|
||||
print("ac1: e2ee_enabled={}".format(ac1.get_config("e2ee_enabled")))
|
||||
print("ac2: e2ee_enabled={}".format(ac2.get_config("e2ee_enabled")))
|
||||
ac1.set_config("e2ee_enabled", "0")
|
||||
|
||||
for quoted_msg in msg1, msg3:
|
||||
# Save the draft with a quote.
|
||||
msg_draft = Message.new_empty(ac1, "text")
|
||||
msg_draft.set_text("message reply")
|
||||
msg_draft.quote = quoted_msg
|
||||
chat.set_draft(msg_draft)
|
||||
|
||||
# Get the draft and send it.
|
||||
msg_draft = chat.get_draft()
|
||||
chat.send_msg(msg_draft)
|
||||
|
||||
chat.set_draft(None)
|
||||
assert chat.get_draft() is None
|
||||
|
||||
# Quote should be replaced with "..." if quoted message is encrypted.
|
||||
msg_in = ac2._evtracker.wait_next_incoming_message()
|
||||
assert msg_in.text == "message reply"
|
||||
assert not msg_in.is_encrypted()
|
||||
if quoted_msg.is_encrypted():
|
||||
assert msg_in.quoted_text == "..."
|
||||
else:
|
||||
assert msg_in.quoted_text == quoted_msg.text
|
||||
|
||||
|
||||
def test_quote_attachment(tmp_path, acfactory, lp):
|
||||
"""Test that replies with an attachment and a quote are received correctly."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
|
||||
21
src/chat.rs
21
src/chat.rs
@@ -1313,8 +1313,7 @@ impl ChatId {
|
||||
///
|
||||
/// To get more verbose summary for a contact, including its key fingerprint, use [`Contact::get_encrinfo`].
|
||||
pub async fn get_encryption_info(self, context: &Context) -> Result<String> {
|
||||
let mut ret_mutual = String::new();
|
||||
let mut ret_nopreference = String::new();
|
||||
let mut ret_available = String::new();
|
||||
let mut ret_reset = String::new();
|
||||
|
||||
for contact_id in get_chat_contacts(context, self)
|
||||
@@ -1330,8 +1329,9 @@ impl ChatId {
|
||||
.filter(|peerstate| peerstate.peek_key(false).is_some())
|
||||
.map(|peerstate| peerstate.prefer_encrypt)
|
||||
{
|
||||
Some(EncryptPreference::Mutual) => ret_mutual += &format!("{addr}\n"),
|
||||
Some(EncryptPreference::NoPreference) => ret_nopreference += &format!("{addr}\n"),
|
||||
Some(EncryptPreference::Mutual) | Some(EncryptPreference::NoPreference) => {
|
||||
ret_available += &format!("{addr}\n")
|
||||
}
|
||||
Some(EncryptPreference::Reset) | None => ret_reset += &format!("{addr}\n"),
|
||||
};
|
||||
}
|
||||
@@ -1343,23 +1343,14 @@ impl ChatId {
|
||||
ret.push('\n');
|
||||
ret += &ret_reset;
|
||||
}
|
||||
if !ret_nopreference.is_empty() {
|
||||
if !ret_available.is_empty() {
|
||||
if !ret.is_empty() {
|
||||
ret.push('\n');
|
||||
}
|
||||
ret += &stock_str::e2e_available(context).await;
|
||||
ret.push(':');
|
||||
ret.push('\n');
|
||||
ret += &ret_nopreference;
|
||||
}
|
||||
if !ret_mutual.is_empty() {
|
||||
if !ret.is_empty() {
|
||||
ret.push('\n');
|
||||
}
|
||||
ret += &stock_str::e2e_preferred(context).await;
|
||||
ret.push(':');
|
||||
ret.push('\n');
|
||||
ret += &ret_mutual;
|
||||
ret += &ret_available;
|
||||
}
|
||||
|
||||
Ok(ret.trim().to_string())
|
||||
|
||||
@@ -299,10 +299,6 @@ async fn test_member_add_remove() -> Result<()> {
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
// Disable encryption so we can inspect raw message contents.
|
||||
alice.set_config(Config::E2eeEnabled, Some("0")).await?;
|
||||
bob.set_config(Config::E2eeEnabled, Some("0")).await?;
|
||||
|
||||
// Create contact for Bob on the Alice side with name "robert".
|
||||
let alice_bob_contact_id = Contact::create(&alice, "robert", "bob@example.net").await?;
|
||||
|
||||
@@ -373,9 +369,6 @@ async fn test_parallel_member_remove() -> Result<()> {
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
alice.set_config(Config::E2eeEnabled, Some("0")).await?;
|
||||
bob.set_config(Config::E2eeEnabled, Some("0")).await?;
|
||||
|
||||
let alice_bob_contact_id = Contact::create(&alice, "Bob", "bob@example.net").await?;
|
||||
let alice_fiona_contact_id = Contact::create(&alice, "Fiona", "fiona@example.net").await?;
|
||||
let alice_claire_contact_id = Contact::create(&alice, "Claire", "claire@example.net").await?;
|
||||
@@ -2677,11 +2670,10 @@ async fn test_chat_get_encryption_info() -> Result<()> {
|
||||
"No encryption:\n\
|
||||
fiona@example.net\n\
|
||||
\n\
|
||||
End-to-end encryption preferred:\n\
|
||||
End-to-end encryption available:\n\
|
||||
bob@example.net"
|
||||
);
|
||||
|
||||
bob.set_config(Config::E2eeEnabled, Some("0")).await?;
|
||||
send_text_msg(&bob, direct_chat.id, "Hello!".to_string()).await?;
|
||||
alice.recv_msg(&bob.pop_sent_msg().await).await;
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ async fn test_set_config_bool() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
// We need some config that defaults to true
|
||||
let c = Config::E2eeEnabled;
|
||||
let c = Config::MdnsEnabled;
|
||||
assert_eq!(t.get_config_bool(c).await?, true);
|
||||
t.set_config_bool(c, false).await?;
|
||||
assert_eq!(t.get_config_bool(c).await?, false);
|
||||
|
||||
@@ -442,7 +442,6 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Configure
|
||||
ctx.set_config(Config::MvboxMove, Some("0")).await?;
|
||||
ctx.set_config(Config::OnlyFetchMvbox, None).await?;
|
||||
ctx.set_config(Config::ShowEmails, None).await?;
|
||||
ctx.set_config(Config::E2eeEnabled, Some("1")).await?;
|
||||
}
|
||||
|
||||
let create_mvbox = !is_chatmail;
|
||||
|
||||
72
src/e2ee.rs
72
src/e2ee.rs
@@ -54,22 +54,13 @@ impl EncryptHelper {
|
||||
peerstates: &[(Option<Peerstate>, String)],
|
||||
) -> Result<bool> {
|
||||
let is_chatmail = context.is_chatmail().await?;
|
||||
let mut prefer_encrypt_count = if self.prefer_encrypt == EncryptPreference::Mutual {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let mut prefer_encrypt_count = 1;
|
||||
for (peerstate, addr) in peerstates {
|
||||
match peerstate {
|
||||
Some(peerstate) => {
|
||||
let prefer_encrypt = peerstate.prefer_encrypt;
|
||||
info!(context, "Peerstate for {addr:?} is {prefer_encrypt}.");
|
||||
if match peerstate.prefer_encrypt {
|
||||
EncryptPreference::NoPreference | EncryptPreference::Reset => {
|
||||
(peerstate.prefer_encrypt != EncryptPreference::Reset || is_chatmail)
|
||||
&& self.prefer_encrypt == EncryptPreference::Mutual
|
||||
}
|
||||
EncryptPreference::Mutual => true,
|
||||
EncryptPreference::Reset => is_chatmail,
|
||||
EncryptPreference::NoPreference | EncryptPreference::Mutual => true,
|
||||
} {
|
||||
prefer_encrypt_count += 1;
|
||||
}
|
||||
@@ -176,6 +167,7 @@ pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::chat::send_text_msg;
|
||||
use crate::config::Config;
|
||||
use crate::key::DcKey;
|
||||
use crate::message::{Message, Viewtype};
|
||||
use crate::param::Param;
|
||||
@@ -329,7 +321,6 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_should_encrypt() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
assert!(t.get_config_bool(Config::E2eeEnabled).await?);
|
||||
let encrypt_helper = EncryptHelper::new(&t).await.unwrap();
|
||||
|
||||
let ps = new_peerstates(EncryptPreference::NoPreference);
|
||||
@@ -352,61 +343,6 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_should_encrypt_e2ee_disabled() -> Result<()> {
|
||||
let t = &TestContext::new_alice().await;
|
||||
t.set_config_bool(Config::E2eeEnabled, false).await?;
|
||||
let encrypt_helper = EncryptHelper::new(t).await.unwrap();
|
||||
|
||||
let ps = new_peerstates(EncryptPreference::NoPreference);
|
||||
assert!(!encrypt_helper.should_encrypt(t, false, &ps).await?);
|
||||
|
||||
let ps = new_peerstates(EncryptPreference::Reset);
|
||||
assert!(encrypt_helper.should_encrypt(t, true, &ps).await?);
|
||||
|
||||
let mut ps = new_peerstates(EncryptPreference::Mutual);
|
||||
// Own preference is `NoPreference` and there's no majority with `Mutual`.
|
||||
assert!(!encrypt_helper.should_encrypt(t, false, &ps).await?);
|
||||
// Now the majority wants to encrypt. Let's encrypt, anyway there are other cases when we
|
||||
// can't send unencrypted, e.g. protected groups.
|
||||
ps.push(ps[0].clone());
|
||||
assert!(encrypt_helper.should_encrypt(t, false, &ps).await?);
|
||||
|
||||
// Test with missing peerstate.
|
||||
let ps = vec![(None, "bob@foo.bar".to_string())];
|
||||
assert!(encrypt_helper.should_encrypt(t, true, &ps).await.is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chatmail_prefers_to_encrypt() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
bob.set_config_bool(Config::IsChatmail, true).await?;
|
||||
|
||||
let bob_chat_id = tcm
|
||||
.send_recv_accept(alice, bob, "Hello from DC")
|
||||
.await
|
||||
.chat_id;
|
||||
receive_imf(
|
||||
bob,
|
||||
b"From: alice@example.org\n\
|
||||
To: bob@example.net\n\
|
||||
Message-ID: <2222@example.org>\n\
|
||||
Date: Sun, 22 Mar 3000 22:37:58 +0000\n\
|
||||
\n\
|
||||
Hello from another MUA\n",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
send_text_msg(bob, bob_chat_id, "hi".to_string()).await?;
|
||||
let sent_msg = bob.pop_sent_msg().await;
|
||||
let msg = Message::load_from_db(bob, sent_msg.sender_msg_id).await?;
|
||||
assert!(msg.get_showpadlock());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_chatmail_can_send_unencrypted() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
|
||||
@@ -1648,29 +1648,26 @@ async fn test_webxdc_no_internet_access() -> Result<()> {
|
||||
let group_id = create_group_chat(&t, ProtectionStatus::Unprotected, "chat").await?;
|
||||
let broadcast_id = create_broadcast_list(&t).await?;
|
||||
|
||||
for e2ee in ["1", "0"] {
|
||||
t.set_config(Config::E2eeEnabled, Some(e2ee)).await?;
|
||||
for chat_id in [self_id, single_id, group_id, broadcast_id] {
|
||||
for internet_xdc in [true, false] {
|
||||
let mut instance = create_webxdc_instance(
|
||||
&t,
|
||||
"foo.xdc",
|
||||
if internet_xdc {
|
||||
include_bytes!("../../test-data/webxdc/request-internet-access.xdc")
|
||||
} else {
|
||||
include_bytes!("../../test-data/webxdc/minimal.xdc")
|
||||
},
|
||||
)?;
|
||||
let instance_id = send_msg(&t, chat_id, &mut instance).await?;
|
||||
t.send_webxdc_status_update(
|
||||
instance_id,
|
||||
r#"{"summary":"real summary", "payload": 42}"#,
|
||||
)
|
||||
.await?;
|
||||
let instance = Message::load_from_db(&t, instance_id).await?;
|
||||
let info = instance.get_webxdc_info(&t).await?;
|
||||
assert_eq!(info.internet_access, false);
|
||||
}
|
||||
for chat_id in [self_id, single_id, group_id, broadcast_id] {
|
||||
for internet_xdc in [true, false] {
|
||||
let mut instance = create_webxdc_instance(
|
||||
&t,
|
||||
"foo.xdc",
|
||||
if internet_xdc {
|
||||
include_bytes!("../../test-data/webxdc/request-internet-access.xdc")
|
||||
} else {
|
||||
include_bytes!("../../test-data/webxdc/minimal.xdc")
|
||||
},
|
||||
)?;
|
||||
let instance_id = send_msg(&t, chat_id, &mut instance).await?;
|
||||
t.send_webxdc_status_update(
|
||||
instance_id,
|
||||
r#"{"summary":"real summary", "payload": 42}"#,
|
||||
)
|
||||
.await?;
|
||||
let instance = Message::load_from_db(&t, instance_id).await?;
|
||||
let info = instance.get_webxdc_info(&t).await?;
|
||||
assert_eq!(info.internet_access, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user