Skip to content

Commit 098f508

Browse files
committed
feat: add option to force encryption
1 parent fa9a4af commit 098f508

12 files changed

Lines changed: 82 additions & 10 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))

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: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,12 @@ 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+
/// Process unencrypted messages.
491+
///
492+
/// Unencrypted messages are fetched and processed only if this setting is explicitly enabled.
493+
#[strum(props(default = "1"))]
494+
ForceEncryption,
489495
}
490496

491497
impl Config {
@@ -501,7 +507,11 @@ impl Config {
501507
pub(crate) fn is_synced(&self) -> bool {
502508
matches!(
503509
self,
504-
Self::Displayname | Self::MdnsEnabled | Self::Selfavatar | Self::Selfstatus,
510+
Self::Displayname
511+
| Self::MdnsEnabled
512+
| Self::Selfavatar
513+
| Self::Selfstatus
514+
| Self::ForceEncryption,
505515
)
506516
}
507517

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\

src/receive_imf.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,14 @@ pub(crate) async fn receive_imf_inner(
505505
Ok(mime_parser) => mime_parser,
506506
};
507507

508+
if !mime_parser.was_encrypted()
509+
&& mime_parser.get_header(HeaderDef::SecureJoin).is_none()
510+
&& context.get_config_bool(Config::ForceEncryption).await?
511+
{
512+
warn!(context, "Fetched unencrypted message, ignoring");
513+
return trash().await;
514+
}
515+
508516
let rfc724_mid_orig = &mime_parser
509517
.get_rfc724_mid()
510518
.unwrap_or(rfc724_mid.to_string());

src/receive_imf/receive_imf_tests.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4498,12 +4498,12 @@ async fn test_outgoing_msg_forgery() -> Result<()> {
44984498
imex(alice, ImexMode::ExportSelfKeys, export_dir.path(), None).await?;
44994499
// We need Bob only to encrypt the forged message to Alice's key, actually Bob doesn't
45004500
// participate in the scenario.
4501-
let bob = &TestContext::new().await;
4501+
let bob = &tcm.unconfigured().await;
45024502
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 0);
45034503
bob.configure_addr("bob@example.net").await;
45044504
imex(bob, ImexMode::ImportSelfKeys, export_dir.path(), None).await?;
45054505
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 1);
4506-
let malice = &TestContext::new().await;
4506+
let malice = &tcm.unconfigured().await;
45074507
malice.configure_addr(alice_addr).await;
45084508

45094509
let malice_chat_id = tcm
@@ -4513,9 +4513,8 @@ async fn test_outgoing_msg_forgery() -> Result<()> {
45134513
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 1);
45144514

45154515
let sent_msg = malice.send_text(malice_chat_id, "hi from malice").await;
4516-
let msg = alice.recv_msg(&sent_msg).await;
4517-
assert_eq!(msg.state, MessageState::OutDelivered);
4518-
assert!(!msg.get_showpadlock());
4516+
let msg = alice.recv_msg_opt(&sent_msg).await;
4517+
assert!(msg.is_none());
45194518

45204519
Ok(())
45214520
}

0 commit comments

Comments
 (0)