Skip to content

Add option to force encryption#8220

Open
link2xt wants to merge 6 commits into
link2xt/remove-timesmearingfrom
link2xt/process-unencrypted
Open

Add option to force encryption#8220
link2xt wants to merge 6 commits into
link2xt/remove-timesmearingfrom
link2xt/process-unencrypted

Conversation

@link2xt
Copy link
Copy Markdown
Collaborator

@link2xt link2xt commented May 7, 2026

Closes #7494

There is a new force_encryption config which is enabled by default. For users with recently active unencrypted chats it is disabled in a migration. Enabling it prevents both sending and receiving unencrypted messages, so when sending to unencrypted chats we will no longer send unencrypted message to a chatmail relay for it to be rejected, but fail locally.

There are many changes to tests because we had a lot of tests using unencrypted chats, I have put them into separate commits.

The setting is not per-relay as there have been more discussion after the comments below. UIs will put the setting somewhere deeper than "Advanced" likely inside the "Relays" configuration but not in the individual relay settings.

Based on #8226 but does not really depend on it. There are small merge conflicts if #8226 is not merged first, but I can rebase this PR on main if needed.

@link2xt link2xt force-pushed the link2xt/process-unencrypted branch 8 times, most recently from 8914248 to f1652d4 Compare May 12, 2026 12:50
@r10s

This comment was marked as outdated.

@link2xt

This comment was marked as outdated.

@link2xt link2xt force-pushed the link2xt/process-unencrypted branch 4 times, most recently from bb4bde4 to b47d145 Compare May 13, 2026 20:48
@link2xt link2xt changed the title WIP: feat: add option to process unencrypted messages Add option to force encryption May 13, 2026
@link2xt link2xt marked this pull request as ready for review May 13, 2026 20:49
@link2xt link2xt marked this pull request as draft May 13, 2026 20:49
@link2xt link2xt force-pushed the link2xt/process-unencrypted branch from b47d145 to c67dc51 Compare May 13, 2026 20:49
@link2xt link2xt force-pushed the link2xt/process-unencrypted branch 3 times, most recently from 15d971f to a1137cb Compare May 14, 2026 18:41
@hpk42 hpk42 added the blocker label May 14, 2026
@link2xt link2xt force-pushed the link2xt/process-unencrypted branch 3 times, most recently from a412867 to 5836a72 Compare May 14, 2026 23:41
@link2xt link2xt changed the base branch from main to link2xt/remove-timesmearing May 15, 2026 00:17
@link2xt link2xt force-pushed the link2xt/process-unencrypted branch from 5836a72 to 098f508 Compare May 15, 2026 00:18
link2xt added 2 commits May 15, 2026 03:47
This change is a preparation for ignoring
unencrypted messages by default.

New test_utils::encrypt_raw_message and
test_utils::receive_encrypted_imf are
used to encrypt the messages before
"receiving" them with receive_imf.
@link2xt link2xt force-pushed the link2xt/process-unencrypted branch from 098f508 to cb8ffb6 Compare May 15, 2026 01:50
@link2xt link2xt force-pushed the link2xt/remove-timesmearing branch from 2b97913 to 5e61bba Compare May 15, 2026 01:50
@link2xt link2xt force-pushed the link2xt/process-unencrypted branch 5 times, most recently from e1886a0 to ec863ca Compare May 15, 2026 14:42
@link2xt link2xt force-pushed the link2xt/process-unencrypted branch from ec863ca to ae4c3f3 Compare May 15, 2026 15:10
@link2xt link2xt marked this pull request as ready for review May 15, 2026 15:29
Comment thread src/sql/migrations.rs
//
// Corner case of ad hoc groups with only self as a member is ignored.
let max_unencrypted_timestamp: i64 = transaction.query_row(
"SELECT IFNULL(MAX(msgs.timestamp), 0)
Copy link
Copy Markdown
Collaborator Author

@link2xt link2xt May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQLite "explain query plan" output:

QUERY PLAN
|--SEARCH contacts USING COVERING INDEX contacts_fingerprint_index (fingerprint=? AND rowid>?)
|--SEARCH chats_contacts USING INDEX chats_contacts_index2 (contact_id=?)
`--SEARCH msgs USING INDEX msgs_index2 (chat_id=?)

Comment thread src/imap.rs Outdated
Comment on lines +1997 to +2011
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) {
info!(context, "Ignoring draft message");
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?));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this drops SecureJoin V2 messages from being downloaded. Probably no test is catching that?

Copy link
Copy Markdown
Collaborator Author

@link2xt link2xt May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is no online test. There are Rust tests, but will need to copy manipulate_qr to Python tests.

Copy link
Copy Markdown
Collaborator Author

@link2xt link2xt May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually there are cross-core SecureJoin tests, testing that 2.24.0 Bob can scan Alice's QR on a new core, and new core somehow downloads the message. SecureJoin v3 was added in 2.45.0

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alice_and_remote_bob fixture sends Alice as vCard to Bob first, so the test is not testing requesting the key because Bob already has Alice's key from a vCard. Will need to fix this, so full securejoin is tested.

Copy link
Copy Markdown
Collaborator Author

@link2xt link2xt May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this and expanded cross-core test to catch the problem (in separate commits for now, will squash the fixup later)

Comment thread src/e2ee.rs
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());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, if the profile is e2ee only, create_email_chat is never called from UIs.

but i guess a user that toggles "force encryption" and then tries to send a message in an email chat would get an error. However, the error here is pretty internal, and there is no stock string for "can not send unencrypted message".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is InvalidUnencryptedMail stock string, we can try to make sure it is used.

Comment thread src/test_utils.rs
Subject: [...]\r
\r
\r
--{boundary}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing "\r"

Comment thread src/test_utils.rs
Comment on lines +1283 to +1284
\r
{encrypted_payload}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing "\r" ? (for some gh UI reason i could not just comment on the {encrypted_payload} line.

Copy link
Copy Markdown
Contributor

@hpk42 hpk42 May 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess mailparse is lenient enough about just "\n" but i can not claim i fully understand all line-ending business at play.

Comment thread src/imap.rs
let is_encrypted = headers
.get_header_value(HeaderDef::ContentType)
.is_some_and(|content_type| {
mailparse::parse_content_type(&content_type).mimetype == "multipart/encrypted"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do i understand correctly that we don't want to support "multipart/mixed" encrypted email? An example is test-data/message/google-workspace-mixed-up.eml

Comment thread src/calls/calls_tests.rs
let encrypted_message = test_utils::encrypt_raw_message(
bob,
&[alice],
b"From: bob@example.net\r\n\
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe replace "\n" with "\r\n" in encrypt_raw_message() before encryption, or do we sometimes need just "\n"?

Comment thread src/sql/migrations.rs

let now = tools::time();
let max_unencrypted_timestamp = std::cmp::max(max_unencrypted_timestamp, max_mailing_list_timestamp);
if max_unencrypted_timestamp.saturating_add(3600 * 24 * 90) > now {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete_device_after affects this if it's set to <= 5 weeks. It may even be disabled, but set right before, we can't know that. Just disabling force_encryption for existing users is probably not what we want however. Maybe it makes sense to also check if there are any messages older than 90 days at all, not sure.

Comment thread src/e2ee.rs
assert!(chat::send_msg(alice, chat.id, &mut msg).await.is_err());
assert_eq!(
msg.error().unwrap(),
"\u{26a0}\u{fe0f} Your email provider example.org requires end-to-end encryption which is not setup yet."
Copy link
Copy Markdown
Collaborator

@iequidoo iequidoo May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Your server" or "Your relay" may be better these days, may be reworded in another PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add option to process unencrypted messages

5 participants