diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 23e849f86..ec22f5c8d 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -287,6 +287,8 @@ char* dc_get_blobdir (const dc_context_t* context); * To save traffic, however, the avatar is attached only as needed * and also recoded to a reasonable size. * - `e2ee_enabled` = 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default) + * - `e2ee_force` = 1=ignore encryption preferences of others, + * 0=use majority vote when deciding whether to encrypt (default). * - `mdns_enabled` = 0=do not send or request read receipts, * 1=send and request read receipts (default) * - `bcc_self` = 0=do not send a copy of outgoing messages to self (default), diff --git a/src/config.rs b/src/config.rs index a0cd9d76b..802905a7d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -64,6 +64,13 @@ pub enum Config { #[strum(props(default = "1"))] E2eeEnabled, + /// Ignore Autocrypt recommendation for message encryption if possible. + /// + /// The only expection is when recommendation is "disable", i.e. encryption is not possible + /// because some recipient has no OpenPGP key. + #[strum(props(default = "0"))] + E2eeForce, + #[strum(props(default = "1"))] MdnsEnabled, diff --git a/src/context.rs b/src/context.rs index ce3ddec38..7d596f900 100644 --- a/src/context.rs +++ b/src/context.rs @@ -307,6 +307,7 @@ impl Context { .await? .unwrap_or_else(|| "unknown".to_string()); let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await?; + let e2ee_force = self.get_config_int(Config::E2eeForce).await?; let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?; let bcc_self = self.get_config_int(Config::BccSelf).await?; let send_sync_msgs = self.get_config_int(Config::SendSyncMsgs).await?; @@ -394,6 +395,7 @@ impl Context { res.insert("configured_mvbox_folder", configured_mvbox_folder); res.insert("mdns_enabled", mdns_enabled.to_string()); res.insert("e2ee_enabled", e2ee_enabled.to_string()); + res.insert("e2ee_force", e2ee_force.to_string()); res.insert( "key_gen_type", self.get_config_int(Config::KeyGenType).await?.to_string(), diff --git a/src/e2ee.rs b/src/e2ee.rs index a162fa8d6..5dc25fbdf 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -19,6 +19,7 @@ use crate::pgp; #[derive(Debug)] pub struct EncryptHelper { pub prefer_encrypt: EncryptPreference, + force_preference: bool, pub addr: String, pub public_key: SignedPublicKey, } @@ -28,6 +29,7 @@ impl EncryptHelper { let prefer_encrypt = EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled).await?) .unwrap_or_default(); + let force_preference = context.get_config_bool(Config::E2eeForce).await?; let addr = match context.get_config(Config::ConfiguredAddr).await? { None => { bail!("addr not configured!"); @@ -39,6 +41,7 @@ impl EncryptHelper { Ok(EncryptHelper { prefer_encrypt, + force_preference, addr, public_key, }) @@ -100,11 +103,17 @@ impl EncryptHelper { } } - // Count number of recipients, including self. - // This does not depend on whether we send a copy to self or not. - let recipients_count = peerstates.len() + 1; + let want_encrypt = if self.force_preference { + // Ignore preferences of others. + self.prefer_encrypt == EncryptPreference::Mutual + } else { + // Count number of recipients, including self. + // This does not depend on whether we send a copy to self or not. + let recipients_count = peerstates.len() + 1; + 2 * prefer_encrypt_count > recipients_count + }; - Ok(e2ee_guaranteed || 2 * prefer_encrypt_count > recipients_count) + Ok(e2ee_guaranteed || want_encrypt) } /// Tries to encrypt the passed in `mail`. @@ -381,6 +390,7 @@ mod tests { use crate::chat; use crate::constants::Viewtype; + use crate::dc_receive_imf::dc_receive_imf; use crate::message::Message; use crate::param::Param; use crate::peerstate::ToSave; @@ -602,4 +612,68 @@ Sent with my Delta Chat Messenger: https://delta.chat"; Ok(()) } + + #[async_std::test] + async fn test_e2ee_force() -> Result<()> { + let alice = TestContext::new_alice().await; + let bob = TestContext::new_bob().await; + + let alice_chat = alice.create_chat(&bob).await; + let bob_chat = bob.create_chat(&alice).await; + + alice.set_config(Config::ShowEmails, Some("2")).await?; + bob.set_config(Config::ShowEmails, Some("2")).await?; + + // Alice does not prefer encryption. + alice.set_config(Config::E2eeEnabled, Some("0")).await?; + bob.set_config(Config::E2eeEnabled, Some("1")).await?; + + // Alice sends her key to Bob. + let sent_msg = alice.send_text(alice_chat.id, "Hi Bob").await; + bob.recv_msg(&sent_msg).await; + let received_msg = bob.get_last_msg().await; + assert!(!received_msg.get_showpadlock()); + + // Bob should not encrypt, because Alice does not prefer encryption. + let sent_msg = bob + .send_text(bob_chat.id, "This should not be encrypted") + .await; + alice.recv_msg(&sent_msg).await; + let received_msg = alice.get_last_msg().await; + assert!(!received_msg.get_showpadlock()); + + // Bob ignores Alice's preference for no encryption. + bob.set_config(Config::E2eeForce, Some("1")).await?; + let sent_msg = bob.send_text(bob_chat.id, "This should be encrypted").await; + alice.recv_msg(&sent_msg).await; + let received_msg = alice.get_last_msg().await; + assert!(received_msg.get_showpadlock()); + + // Alice switches to MUA without Autocrypt support. + dc_receive_imf( + &bob, + br#"Subject: Hello from MUA +Message-ID: foobar@example.com +To: Bob +From: Alice +Content-Type: text/plain; charset=utf-8 +Date: Sun, 14 Mar 2500 00:00:00 +0000 + +Hello from MUA."#, + "INBOX", + 100, + false, + ) + .await?; + + // Bob can't encrypt now because Alice has no key. + let sent_msg = bob + .send_text(bob_chat.id, "This should not be encrypted again") + .await; + alice.recv_msg(&sent_msg).await; + let received_msg = alice.get_last_msg().await; + assert!(!received_msg.get_showpadlock()); + + Ok(()) + } }