mirror of
https://github.com/chatmail/core.git
synced 2026-04-01 21:12:13 +03:00
Compare commits
2 Commits
6ab8b3c3e7
...
430cccfd32
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
430cccfd32 | ||
|
|
822a99ea9c |
12
src/e2ee.rs
12
src/e2ee.rs
@@ -40,7 +40,6 @@ impl EncryptHelper {
|
||||
keyring: Vec<SignedPublicKey>,
|
||||
mail_to_encrypt: MimePart<'static>,
|
||||
compress: bool,
|
||||
anonymous_recipients: bool,
|
||||
seipd_version: SeipdVersion,
|
||||
) -> Result<String> {
|
||||
let sign_key = load_self_secret_key(context).await?;
|
||||
@@ -49,15 +48,8 @@ impl EncryptHelper {
|
||||
let cursor = Cursor::new(&mut raw_message);
|
||||
mail_to_encrypt.clone().write_part(cursor).ok();
|
||||
|
||||
let ctext = pgp::pk_encrypt(
|
||||
raw_message,
|
||||
keyring,
|
||||
sign_key,
|
||||
compress,
|
||||
anonymous_recipients,
|
||||
seipd_version,
|
||||
)
|
||||
.await?;
|
||||
let ctext =
|
||||
pgp::pk_encrypt(raw_message, keyring, sign_key, compress, seipd_version).await?;
|
||||
|
||||
Ok(ctext)
|
||||
}
|
||||
|
||||
@@ -1934,6 +1934,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
|
||||
// We also don't send read receipts for contact requests.
|
||||
// Read receipts will not be sent even after accepting the chat.
|
||||
let to_id = if curr_blocked == Blocked::Not
|
||||
&& !curr_hidden
|
||||
&& curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
|
||||
&& curr_param.get_cmd() == SystemMessage::Unknown
|
||||
&& context.should_send_mdns().await?
|
||||
|
||||
@@ -1163,17 +1163,6 @@ impl MimeFactory {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Do not anonymize OpenPGP recipients.
|
||||
//
|
||||
// This is disabled to avoid interoperability problems
|
||||
// with old core versions <1.160.0 that do not support
|
||||
// receiving messages with wildcard Key IDs:
|
||||
// <https://github.com/chatmail/core/issues/7378>
|
||||
//
|
||||
// The option should be changed to true
|
||||
// once new core versions are sufficiently deployed.
|
||||
let anonymous_recipients = false;
|
||||
|
||||
if context.get_config_bool(Config::TestHooks).await?
|
||||
&& let Some(hook) = &*context.pre_encrypt_mime_hook.lock()
|
||||
{
|
||||
@@ -1211,7 +1200,6 @@ impl MimeFactory {
|
||||
encryption_keyring,
|
||||
message,
|
||||
compress,
|
||||
anonymous_recipients,
|
||||
seipd_version,
|
||||
)
|
||||
.await?
|
||||
|
||||
19
src/pgp.rs
19
src/pgp.rs
@@ -151,7 +151,6 @@ pub async fn pk_encrypt(
|
||||
public_keys_for_encryption: Vec<SignedPublicKey>,
|
||||
private_key_for_signing: SignedSecretKey,
|
||||
compress: bool,
|
||||
anonymous_recipients: bool,
|
||||
seipd_version: SeipdVersion,
|
||||
) -> Result<String> {
|
||||
Handle::current()
|
||||
@@ -198,11 +197,7 @@ pub async fn pk_encrypt(
|
||||
let mut msg = msg.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
|
||||
|
||||
for pkey in pkeys {
|
||||
if anonymous_recipients {
|
||||
msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
|
||||
} else {
|
||||
msg.encrypt_to_key(&mut rng, &pkey)?;
|
||||
}
|
||||
msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
|
||||
}
|
||||
|
||||
let hash_algorithm = private_key_for_signing.hash_alg();
|
||||
@@ -227,11 +222,7 @@ pub async fn pk_encrypt(
|
||||
);
|
||||
|
||||
for pkey in pkeys {
|
||||
if anonymous_recipients {
|
||||
msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
|
||||
} else {
|
||||
msg.encrypt_to_key(&mut rng, &pkey)?;
|
||||
}
|
||||
msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
|
||||
}
|
||||
|
||||
let hash_algorithm = private_key_for_signing.hash_alg();
|
||||
@@ -707,7 +698,6 @@ mod tests {
|
||||
|
||||
/// A ciphertext encrypted to Alice & Bob, signed by Alice.
|
||||
async fn ctext_signed() -> &'static String {
|
||||
let anonymous_recipients = true;
|
||||
CTEXT_SIGNED
|
||||
.get_or_init(|| async {
|
||||
let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
|
||||
@@ -718,7 +708,6 @@ mod tests {
|
||||
keyring,
|
||||
KEYS.alice_secret.clone(),
|
||||
compress,
|
||||
anonymous_recipients,
|
||||
SeipdVersion::V2,
|
||||
)
|
||||
.await
|
||||
@@ -905,12 +894,12 @@ mod tests {
|
||||
let pk_for_encryption = load_self_public_key(alice).await?;
|
||||
|
||||
// Encrypt a message, but only to self, not to Bob:
|
||||
let compress = true;
|
||||
let ctext = pk_encrypt(
|
||||
plain,
|
||||
vec![pk_for_encryption],
|
||||
KEYS.alice_secret.clone(),
|
||||
true,
|
||||
true,
|
||||
compress,
|
||||
SeipdVersion::V2,
|
||||
)
|
||||
.await?;
|
||||
|
||||
152
src/reaction.rs
152
src/reaction.rs
@@ -393,7 +393,9 @@ mod tests {
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::config::Config;
|
||||
use crate::contact::{Contact, Origin};
|
||||
use crate::message::{MessageState, Viewtype, delete_msgs};
|
||||
use crate::key::{load_self_public_key, load_self_secret_key};
|
||||
use crate::message::{MessageState, Viewtype, delete_msgs, markseen_msgs};
|
||||
use crate::pgp::{SeipdVersion, pk_encrypt};
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::sql::housekeeping;
|
||||
use crate::test_utils::E2EE_INFO_MSGS;
|
||||
@@ -956,4 +958,152 @@ Content-Disposition: reaction\n\
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that if reaction requests a read receipt,
|
||||
/// no read receipt is sent when the chat is marked as noticed.
|
||||
///
|
||||
/// Reactions create hidden messages in the chat,
|
||||
/// and when marking the chat as noticed marks
|
||||
/// such messages as seen, read receipts should never be sent
|
||||
/// to avoid the sender of reaction from learning
|
||||
/// that receiver opened the chat.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_reaction_request_mdn() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let alice_chat_id = alice.create_chat_id(bob).await;
|
||||
let alice_sent_msg = alice.send_text(alice_chat_id, "Hello!").await;
|
||||
|
||||
let bob_msg = bob.recv_msg(&alice_sent_msg).await;
|
||||
bob_msg.chat_id.accept(bob).await?;
|
||||
assert_eq!(bob_msg.state, MessageState::InFresh);
|
||||
let bob_chat_id = bob_msg.chat_id;
|
||||
bob_chat_id.accept(bob).await?;
|
||||
|
||||
markseen_msgs(bob, vec![bob_msg.id]).await?;
|
||||
assert_eq!(
|
||||
bob.sql
|
||||
.count(
|
||||
"SELECT COUNT(*) FROM smtp_mdns WHERE from_id!=?",
|
||||
(ContactId::SELF,)
|
||||
)
|
||||
.await?,
|
||||
1
|
||||
);
|
||||
bob.sql.execute("DELETE FROM smtp_mdns", ()).await?;
|
||||
|
||||
// Construct reaction with an MDN request.
|
||||
// Note the `Chat-Disposition-Notification-To` header.
|
||||
let known_id = bob_msg.rfc724_mid;
|
||||
let new_id = "e2b6e69e-4124-4e2a-b79f-e4f1be667165@localhost";
|
||||
|
||||
let plain_text = format!(
|
||||
"Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"; \r
|
||||
hp=\"cipher\"\r
|
||||
Content-Disposition: reaction\r
|
||||
From: \"Alice\" <alice@example.org>\r
|
||||
To: \"Bob\" <bob@example.net>\r
|
||||
Subject: Message from Alice\r
|
||||
Date: Sat, 14 Mar 2026 01:02:03 +0000\r
|
||||
In-Reply-To: <{known_id}>\r
|
||||
References: <{known_id}>\r
|
||||
Chat-Version: 1.0\r
|
||||
Chat-Disposition-Notification-To: alice@example.org\r
|
||||
Message-ID: <{new_id}>\r
|
||||
HP-Outer: From: <alice@example.org>\r
|
||||
HP-Outer: To: \"hidden-recipients\": ;\r
|
||||
HP-Outer: Subject: [...]\r
|
||||
HP-Outer: Date: Sat, 14 Mar 2026 01:02:03 +0000\r
|
||||
HP-Outer: Message-ID: <{new_id}>\r
|
||||
HP-Outer: In-Reply-To: <{known_id}>\r
|
||||
HP-Outer: References: <{known_id}>\r
|
||||
HP-Outer: Chat-Version: 1.0\r
|
||||
Content-Transfer-Encoding: base64\r
|
||||
\r
|
||||
8J+RgA==\r
|
||||
"
|
||||
);
|
||||
|
||||
let alice_public_key = load_self_public_key(alice).await?;
|
||||
let bob_public_key = load_self_public_key(bob).await?;
|
||||
let alice_secret_key = load_self_secret_key(alice).await?;
|
||||
let public_keys_for_encryption = vec![alice_public_key, bob_public_key];
|
||||
let compress = true;
|
||||
let encrypted_payload = pk_encrypt(
|
||||
plain_text.as_bytes().to_vec(),
|
||||
public_keys_for_encryption,
|
||||
alice_secret_key,
|
||||
compress,
|
||||
SeipdVersion::V2,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let boundary = "boundary123";
|
||||
let rcvd_mail = format!(
|
||||
"From: <alice@example.org>\r
|
||||
To: \"hidden-recipients\": ;\r
|
||||
Subject: [...]\r
|
||||
Date: Sat, 14 Mar 2026 01:02:03 +0000\r
|
||||
Message-ID: <{new_id}>\r
|
||||
In-Reply-To: <{known_id}>\r
|
||||
References: <{known_id}>\r
|
||||
Content-Type: multipart/encrypted; protocol=\"application/pgp-encrypted\";\r
|
||||
boundary=\"{boundary}\"\r
|
||||
MIME-Version: 1.0\r
|
||||
\r
|
||||
--{boundary}\r
|
||||
Content-Type: application/pgp-encrypted; charset=\"utf-8\"\r
|
||||
Content-Description: PGP/MIME version identification\r
|
||||
Content-Transfer-Encoding: 7bit\r
|
||||
\r
|
||||
Version: 1\r
|
||||
\r
|
||||
--{boundary}\r
|
||||
Content-Type: application/octet-stream; name=\"encrypted.asc\";\r
|
||||
charset=\"utf-8\"\r
|
||||
Content-Description: OpenPGP encrypted message\r
|
||||
Content-Disposition: inline; filename=\"encrypted.asc\";\r
|
||||
Content-Transfer-Encoding: 7bit\r
|
||||
\r
|
||||
{encrypted_payload}
|
||||
--{boundary}--\r
|
||||
"
|
||||
);
|
||||
|
||||
let received = receive_imf(bob, rcvd_mail.as_bytes(), false)
|
||||
.await?
|
||||
.unwrap();
|
||||
let bob_hidden_msg = Message::load_from_db(bob, *received.msg_ids.last().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(bob_hidden_msg.hidden);
|
||||
assert_eq!(bob_hidden_msg.chat_id, bob_chat_id);
|
||||
|
||||
// Bob does not see new message and cannot mark it as seen directly,
|
||||
// but can mark the chat as noticed when opening it.
|
||||
marknoticed_chat(bob, bob_chat_id).await?;
|
||||
|
||||
assert_eq!(
|
||||
bob.sql
|
||||
.count(
|
||||
"SELECT COUNT(*) FROM smtp_mdns WHERE from_id!=?",
|
||||
(ContactId::SELF,)
|
||||
)
|
||||
.await?,
|
||||
0,
|
||||
"Bob should not send MDN to Alice"
|
||||
);
|
||||
|
||||
// MDN request was ignored, but reaction was not.
|
||||
let reactions = get_msg_reactions(bob, bob_msg.id).await?;
|
||||
assert_eq!(reactions.reactions.len(), 1);
|
||||
assert_eq!(
|
||||
reactions.emoji_sorted_by_frequency(),
|
||||
vec![("👀".to_string(), 1)]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user