mirror of
https://github.com/chatmail/core.git
synced 2026-05-18 22:36:29 +03:00
feat: add option to force encryption
This commit is contained in:
@@ -487,6 +487,7 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* 0 = Everybody (except explicitly blocked contacts),
|
* 0 = Everybody (except explicitly blocked contacts),
|
||||||
* 1 = Contacts (default, does not include contact requests),
|
* 1 = Contacts (default, does not include contact requests),
|
||||||
* 2 = Nobody (calls never result in a notification).
|
* 2 = Nobody (calls never result in a notification).
|
||||||
|
* - `force_encryption` = 1 (default) to force encryption, 0 to allow unencrypted messages.
|
||||||
*
|
*
|
||||||
* Also, there are configs that are only needed
|
* Also, there are configs that are only needed
|
||||||
* if you want to use the deprecated dc_configure() API, such as:
|
* if you want to use the deprecated dc_configure() API, such as:
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ def test_delivery_status_failed(acfactory: ACFactory) -> None:
|
|||||||
Test change status on chatlistitem when status changes failed
|
Test change status on chatlistitem when status changes failed
|
||||||
"""
|
"""
|
||||||
(alice,) = acfactory.get_online_accounts(1)
|
(alice,) = acfactory.get_online_accounts(1)
|
||||||
|
alice.set_config("force_encryption", "0")
|
||||||
|
|
||||||
invalid_contact = alice.create_contact("example@example.com", "invalid address")
|
invalid_contact = alice.create_contact("example@example.com", "invalid address")
|
||||||
invalid_chat = alice.get_chat_by_id(alice._rpc.create_chat_by_contact_id(alice.id, invalid_contact.id))
|
invalid_chat = alice.get_chat_by_id(alice._rpc.create_chat_by_contact_id(alice.id, invalid_contact.id))
|
||||||
|
|||||||
@@ -417,11 +417,6 @@ class TestOfflineChat:
|
|||||||
for i in range(50):
|
for i in range(50):
|
||||||
chat.send_text("hello")
|
chat.send_text("hello")
|
||||||
|
|
||||||
def test_create_chat_simple(self, acfactory):
|
|
||||||
ac1 = acfactory.get_pseudo_configured_account()
|
|
||||||
contact1 = ac1.create_contact("some1@example.org", name="some1")
|
|
||||||
contact1.create_chat().send_text("hello")
|
|
||||||
|
|
||||||
def test_chat_message_distinctions(self, ac1, chat1):
|
def test_chat_message_distinctions(self, ac1, chat1):
|
||||||
past1s = datetime.now(timezone.utc) - timedelta(seconds=1)
|
past1s = datetime.now(timezone.utc) - timedelta(seconds=1)
|
||||||
msg = chat1.send_text("msg1")
|
msg = chat1.send_text("msg1")
|
||||||
|
|||||||
@@ -2823,7 +2823,12 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
|
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default()
|
||||||
|
|| (!msg
|
||||||
|
.param
|
||||||
|
.get_bool(Param::ForcePlaintext)
|
||||||
|
.unwrap_or_default()
|
||||||
|
&& context.get_config_bool(Config::ForceEncryption).await?);
|
||||||
let mimefactory = match MimeFactory::from_msg(context, msg.clone()).await {
|
let mimefactory = match MimeFactory::from_msg(context, msg.clone()).await {
|
||||||
Ok(mf) => mf,
|
Ok(mf) => mf,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
@@ -486,6 +486,12 @@ pub enum Config {
|
|||||||
/// Experimental option denoting that the current profile is shared between multiple team members.
|
/// Experimental option denoting that the current profile is shared between multiple team members.
|
||||||
/// For now, the only effect of this option is that seen flags are not synchronized.
|
/// For now, the only effect of this option is that seen flags are not synchronized.
|
||||||
TeamProfile,
|
TeamProfile,
|
||||||
|
|
||||||
|
/// Process unencrypted messages.
|
||||||
|
///
|
||||||
|
/// Unencrypted messages are fetched and processed only if this setting is explicitly enabled.
|
||||||
|
#[strum(props(default = "1"))]
|
||||||
|
ForceEncryption,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -501,7 +507,11 @@ impl Config {
|
|||||||
pub(crate) fn is_synced(&self) -> bool {
|
pub(crate) fn is_synced(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
Self::Displayname | Self::MdnsEnabled | Self::Selfavatar | Self::Selfstatus,
|
Self::Displayname
|
||||||
|
| Self::MdnsEnabled
|
||||||
|
| Self::Selfavatar
|
||||||
|
| Self::Selfstatus
|
||||||
|
| Self::ForceEncryption,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1053,6 +1053,12 @@ impl Context {
|
|||||||
"team_profile",
|
"team_profile",
|
||||||
self.get_config_bool(Config::TeamProfile).await?.to_string(),
|
self.get_config_bool(Config::TeamProfile).await?.to_string(),
|
||||||
);
|
);
|
||||||
|
res.insert(
|
||||||
|
"force_encryption",
|
||||||
|
self.get_config_bool(Config::ForceEncryption)
|
||||||
|
.await?
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
let elapsed = time_elapsed(&self.creation_time);
|
let elapsed = time_elapsed(&self.creation_time);
|
||||||
res.insert("uptime", duration_to_str(elapsed));
|
res.insert("uptime", duration_to_str(elapsed));
|
||||||
|
|||||||
14
src/e2ee.rs
14
src/e2ee.rs
@@ -109,6 +109,7 @@ pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::chat;
|
||||||
use crate::chat::send_text_msg;
|
use crate::chat::send_text_msg;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::message::Message;
|
use crate::message::Message;
|
||||||
@@ -155,6 +156,19 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_cannot_send_unencrypted_by_default() -> Result<()> {
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let alice = &tcm.alice().await;
|
||||||
|
let bob = &tcm.bob().await;
|
||||||
|
let chat = alice.create_email_chat(bob).await;
|
||||||
|
|
||||||
|
let mut msg = Message::new_text("Hello!".to_string());
|
||||||
|
assert!(chat::send_msg(alice, chat.id, &mut msg).await.is_err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_chatmail_can_send_unencrypted() -> Result<()> {
|
async fn test_chatmail_can_send_unencrypted() -> Result<()> {
|
||||||
let mut tcm = TestContextManager::new();
|
let mut tcm = TestContextManager::new();
|
||||||
|
|||||||
11
src/imap.rs
11
src/imap.rs
@@ -1994,12 +1994,21 @@ pub(crate) async fn prefetch_should_download(
|
|||||||
// prevent_rename=true as this might be a mailing list message and in this case it would be bad if we rename the contact.
|
// prevent_rename=true as this might be a mailing list message and in this case it would be bad if we rename the contact.
|
||||||
// (prevent_rename is the last argument of from_field_to_contact_id())
|
// (prevent_rename is the last argument of from_field_to_contact_id())
|
||||||
|
|
||||||
|
let is_encrypted = if let Some(content_type) = headers.get_header_value(HeaderDef::ContentType)
|
||||||
|
{
|
||||||
|
mailparse::parse_content_type(&content_type).mimetype == "multipart/encrypted"
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
if flags.any(|f| f == Flag::Draft) {
|
if flags.any(|f| f == Flag::Draft) {
|
||||||
info!(context, "Ignoring draft message");
|
info!(context, "Ignoring draft message");
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let should_download = !blocked_contact || maybe_ndn;
|
let should_download = maybe_ndn
|
||||||
|
|| (!blocked_contact
|
||||||
|
&& (is_encrypted || !context.get_config_bool(Config::ForceEncryption).await?));
|
||||||
Ok(should_download)
|
Ok(should_download)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const PREFETCH_FLAGS: &str = "(UID RFC822.SIZE BODY.PEEK[HEADER.FIELDS (\
|
|||||||
DATE \
|
DATE \
|
||||||
X-MICROSOFT-ORIGINAL-MESSAGE-ID \
|
X-MICROSOFT-ORIGINAL-MESSAGE-ID \
|
||||||
FROM \
|
FROM \
|
||||||
|
CONTENT-TYPE \
|
||||||
CHAT-VERSION \
|
CHAT-VERSION \
|
||||||
CHAT-IS-POST-MESSAGE \
|
CHAT-IS-POST-MESSAGE \
|
||||||
AUTOCRYPT-SETUP-MESSAGE\
|
AUTOCRYPT-SETUP-MESSAGE\
|
||||||
|
|||||||
@@ -505,6 +505,14 @@ pub(crate) async fn receive_imf_inner(
|
|||||||
Ok(mime_parser) => mime_parser,
|
Ok(mime_parser) => mime_parser,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !mime_parser.was_encrypted()
|
||||||
|
&& mime_parser.get_header(HeaderDef::SecureJoin).is_none()
|
||||||
|
&& context.get_config_bool(Config::ForceEncryption).await?
|
||||||
|
{
|
||||||
|
warn!(context, "Fetched unencrypted message, ignoring");
|
||||||
|
return trash().await;
|
||||||
|
}
|
||||||
|
|
||||||
let rfc724_mid_orig = &mime_parser
|
let rfc724_mid_orig = &mime_parser
|
||||||
.get_rfc724_mid()
|
.get_rfc724_mid()
|
||||||
.unwrap_or(rfc724_mid.to_string());
|
.unwrap_or(rfc724_mid.to_string());
|
||||||
|
|||||||
@@ -4498,12 +4498,12 @@ async fn test_outgoing_msg_forgery() -> Result<()> {
|
|||||||
imex(alice, ImexMode::ExportSelfKeys, export_dir.path(), None).await?;
|
imex(alice, ImexMode::ExportSelfKeys, export_dir.path(), None).await?;
|
||||||
// We need Bob only to encrypt the forged message to Alice's key, actually Bob doesn't
|
// We need Bob only to encrypt the forged message to Alice's key, actually Bob doesn't
|
||||||
// participate in the scenario.
|
// participate in the scenario.
|
||||||
let bob = &TestContext::new().await;
|
let bob = &tcm.unconfigured().await;
|
||||||
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 0);
|
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 0);
|
||||||
bob.configure_addr("bob@example.net").await;
|
bob.configure_addr("bob@example.net").await;
|
||||||
imex(bob, ImexMode::ImportSelfKeys, export_dir.path(), None).await?;
|
imex(bob, ImexMode::ImportSelfKeys, export_dir.path(), None).await?;
|
||||||
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 1);
|
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 1);
|
||||||
let malice = &TestContext::new().await;
|
let malice = &tcm.unconfigured().await;
|
||||||
malice.configure_addr(alice_addr).await;
|
malice.configure_addr(alice_addr).await;
|
||||||
|
|
||||||
let malice_chat_id = tcm
|
let malice_chat_id = tcm
|
||||||
@@ -4513,9 +4513,8 @@ async fn test_outgoing_msg_forgery() -> Result<()> {
|
|||||||
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 1);
|
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 1);
|
||||||
|
|
||||||
let sent_msg = malice.send_text(malice_chat_id, "hi from malice").await;
|
let sent_msg = malice.send_text(malice_chat_id, "hi from malice").await;
|
||||||
let msg = alice.recv_msg(&sent_msg).await;
|
let msg = alice.recv_msg_opt(&sent_msg).await;
|
||||||
assert_eq!(msg.state, MessageState::OutDelivered);
|
assert!(msg.is_none());
|
||||||
assert!(!msg.get_showpadlock());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/sync.rs
19
src/sync.rs
@@ -810,4 +810,23 @@ mod tests {
|
|||||||
assert_eq!(msg.text, "Member Me added by alice@example.org.");
|
assert_eq!(msg.text, "Member Me added by alice@example.org.");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests that "force encryption" setting is synced.
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_sync_force_encryption() -> Result<()> {
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let alice = &tcm.alice().await;
|
||||||
|
let alice2 = &tcm.alice().await;
|
||||||
|
alice.set_config_bool(Config::SyncMsgs, true).await?;
|
||||||
|
alice2.set_config_bool(Config::SyncMsgs, true).await?;
|
||||||
|
|
||||||
|
assert_eq!(alice.get_config_bool(Config::ForceEncryption).await?, true);
|
||||||
|
alice2
|
||||||
|
.set_config_bool(Config::ForceEncryption, false)
|
||||||
|
.await?;
|
||||||
|
test_utils::sync(alice2, alice).await;
|
||||||
|
assert_eq!(alice.get_config_bool(Config::ForceEncryption).await?, false);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1231,9 +1231,8 @@ ORDER BY id"
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Allow reception of unencrypted messages.
|
/// Allow reception of unencrypted messages.
|
||||||
#[expect(clippy::unused_async)]
|
|
||||||
pub async fn allow_unencrypted(&self) -> Result<()> {
|
pub async fn allow_unencrypted(&self) -> Result<()> {
|
||||||
// Does nothing for now.
|
self.set_config_bool(Config::ForceEncryption, false).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user