Skip to content

Commit ae4c3f3

Browse files
committed
feat: add option to force encryption
1 parent 93ac040 commit ae4c3f3

15 files changed

Lines changed: 144 additions & 19 deletions

File tree

deltachat-ffi/deltachat.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ char* dc_get_blobdir (const dc_context_t* context);
487487
* 0 = Everybody (except explicitly blocked contacts),
488488
* 1 = Contacts (default, does not include contact requests),
489489
* 2 = Nobody (calls never result in a notification).
490+
* - `force_encryption` = 1 (default) to force encryption, 0 to allow unencrypted messages.
490491
*
491492
* Also, there are configs that are only needed
492493
* if you want to use the deprecated dc_configure() API, such as:

deltachat-rpc-client/tests/test_chatlist_events.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def test_delivery_status_failed(acfactory: ACFactory) -> None:
8787
Test change status on chatlistitem when status changes failed
8888
"""
8989
(alice,) = acfactory.get_online_accounts(1)
90+
alice.set_config("force_encryption", "0")
9091

9192
invalid_contact = alice.create_contact("example@example.com", "invalid address")
9293
invalid_chat = alice.get_chat_by_id(alice._rpc.create_chat_by_contact_id(alice.id, invalid_contact.id))

python/tests/test_3_offline.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ def test_get_contacts_and_delete(self, acfactory):
153153

154154
def test_delete_referenced_contact_hides_contact(self, acfactory):
155155
ac1 = acfactory.get_pseudo_configured_account()
156-
contact1 = ac1.create_contact("some1@example.com", name="some1")
156+
ac2 = acfactory.get_pseudo_configured_account()
157+
contact1 = ac1.create_contact(ac2)
157158
msg = contact1.create_chat().send_text("one message")
158159
assert ac1.delete_contact(contact1)
159160
assert not msg.filemime
@@ -185,8 +186,9 @@ def ac1(self, acfactory):
185186
return acfactory.get_pseudo_configured_account()
186187

187188
@pytest.fixture()
188-
def chat1(self, ac1):
189-
return ac1.create_contact("some1@example.org", name="some1").create_chat()
189+
def chat1(self, ac1, acfactory):
190+
ac2 = acfactory.get_pseudo_configured_account()
191+
return ac1.create_contact(ac2).create_chat()
190192

191193
def test_display(self, chat1):
192194
str(chat1)
@@ -404,7 +406,7 @@ def test_create_contact(self, acfactory):
404406
contact2 = ac1.create_contact("display1 <x@example.org>", "real")
405407
assert contact2.name == "real"
406408

407-
def test_send_lots_of_offline_msgs(self, acfactory):
409+
def test_send_lots_of_offline_msgs(self, acfactory, chat1):
408410
ac1 = acfactory.get_pseudo_configured_account()
409411
ac1.set_config("configured_mail_server", "example.org")
410412
ac1.set_config("configured_mail_user", "example.org")
@@ -413,13 +415,13 @@ def test_send_lots_of_offline_msgs(self, acfactory):
413415
ac1.set_config("configured_send_user", "example.org")
414416
ac1.set_config("configured_send_pw", "example.org")
415417
ac1.start_io()
416-
chat = ac1.create_contact("some1@example.org", name="some1").create_chat()
417418
for i in range(50):
418-
chat.send_text("hello")
419+
chat1.send_text("hello")
419420

420421
def test_create_chat_simple(self, acfactory):
421422
ac1 = acfactory.get_pseudo_configured_account()
422-
contact1 = ac1.create_contact("some1@example.org", name="some1")
423+
ac2 = acfactory.get_pseudo_configured_account()
424+
contact1 = ac1.create_contact(ac2)
423425
contact1.create_chat().send_text("hello")
424426

425427
def test_chat_message_distinctions(self, ac1, chat1):

python/tests/test_4_lowlevel.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ def test_sig():
152152

153153
def test_markseen_invalid_message_ids(acfactory):
154154
ac1 = acfactory.get_pseudo_configured_account()
155-
contact1 = ac1.create_contact("some1@example.com", name="some1")
155+
ac2 = acfactory.get_pseudo_configured_account()
156+
contact1 = ac1.create_contact(ac2)
156157
chat = contact1.create_chat()
157158
chat.send_text("one message")
158159
ac1._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")

src/chat.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2823,7 +2823,12 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
28232823
.await?;
28242824
}
28252825

2826-
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
2826+
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default()
2827+
|| (!msg
2828+
.param
2829+
.get_bool(Param::ForcePlaintext)
2830+
.unwrap_or_default()
2831+
&& context.get_config_bool(Config::ForceEncryption).await?);
28272832
let mimefactory = match MimeFactory::from_msg(context, msg.clone()).await {
28282833
Ok(mf) => mf,
28292834
Err(err) => {

src/config.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,13 @@ pub enum Config {
486486
/// Experimental option denoting that the current profile is shared between multiple team members.
487487
/// For now, the only effect of this option is that seen flags are not synchronized.
488488
TeamProfile,
489+
490+
/// Force encryption.
491+
///
492+
/// When enabled, unencrypted messages cannot be sent
493+
/// and incoming unencrypted messages are not fetched and not processed.
494+
#[strum(props(default = "1"))]
495+
ForceEncryption,
489496
}
490497

491498
impl Config {
@@ -501,7 +508,11 @@ impl Config {
501508
pub(crate) fn is_synced(&self) -> bool {
502509
matches!(
503510
self,
504-
Self::Displayname | Self::MdnsEnabled | Self::Selfavatar | Self::Selfstatus,
511+
Self::Displayname
512+
| Self::MdnsEnabled
513+
| Self::Selfavatar
514+
| Self::Selfstatus
515+
| Self::ForceEncryption,
505516
)
506517
}
507518

src/context.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,12 @@ impl Context {
10531053
"team_profile",
10541054
self.get_config_bool(Config::TeamProfile).await?.to_string(),
10551055
);
1056+
res.insert(
1057+
"force_encryption",
1058+
self.get_config_bool(Config::ForceEncryption)
1059+
.await?
1060+
.to_string(),
1061+
);
10561062

10571063
let elapsed = time_elapsed(&self.creation_time);
10581064
res.insert("uptime", duration_to_str(elapsed));

src/e2ee.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
109109
#[cfg(test)]
110110
mod tests {
111111
use super::*;
112+
use crate::chat;
112113
use crate::chat::send_text_msg;
113114
use crate::config::Config;
114115
use crate::message::Message;
@@ -155,6 +156,19 @@ Sent with my Delta Chat Messenger: https://delta.chat";
155156
);
156157
}
157158

159+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
160+
async fn test_cannot_send_unencrypted_by_default() -> Result<()> {
161+
let mut tcm = TestContextManager::new();
162+
let alice = &tcm.alice().await;
163+
let bob = &tcm.bob().await;
164+
let chat = alice.create_email_chat(bob).await;
165+
166+
let mut msg = Message::new_text("Hello!".to_string());
167+
assert!(chat::send_msg(alice, chat.id, &mut msg).await.is_err());
168+
169+
Ok(())
170+
}
171+
158172
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
159173
async fn test_chatmail_can_send_unencrypted() -> Result<()> {
160174
let mut tcm = TestContextManager::new();

src/imap.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1994,12 +1994,21 @@ pub(crate) async fn prefetch_should_download(
19941994
// prevent_rename=true as this might be a mailing list message and in this case it would be bad if we rename the contact.
19951995
// (prevent_rename is the last argument of from_field_to_contact_id())
19961996

1997+
let is_encrypted = if let Some(content_type) = headers.get_header_value(HeaderDef::ContentType)
1998+
{
1999+
mailparse::parse_content_type(&content_type).mimetype == "multipart/encrypted"
2000+
} else {
2001+
false
2002+
};
2003+
19972004
if flags.any(|f| f == Flag::Draft) {
19982005
info!(context, "Ignoring draft message");
19992006
return Ok(false);
20002007
}
20012008

2002-
let should_download = !blocked_contact || maybe_ndn;
2009+
let should_download = maybe_ndn
2010+
|| (!blocked_contact
2011+
&& (is_encrypted || !context.get_config_bool(Config::ForceEncryption).await?));
20032012
Ok(should_download)
20042013
}
20052014

src/imap/session.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const PREFETCH_FLAGS: &str = "(UID RFC822.SIZE BODY.PEEK[HEADER.FIELDS (\
2121
DATE \
2222
X-MICROSOFT-ORIGINAL-MESSAGE-ID \
2323
FROM \
24+
CONTENT-TYPE \
2425
CHAT-VERSION \
2526
CHAT-IS-POST-MESSAGE \
2627
AUTOCRYPT-SETUP-MESSAGE\

0 commit comments

Comments
 (0)