Skip to content

Commit 7624a50

Browse files
committed
fix: do not fail to send the message if some keys are missing
1 parent 568c044 commit 7624a50

File tree

5 files changed

+153
-67
lines changed

5 files changed

+153
-67
lines changed

src/e2ee.rs

Lines changed: 64 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! End-to-end encryption support.
22
3+
use std::collections::BTreeSet;
34
use std::io::Cursor;
45

5-
use anyhow::{format_err, Context as _, Result};
6+
use anyhow::{bail, Result};
67
use mail_builder::mime::MimePart;
78
use num_traits::FromPrimitive;
89

@@ -42,70 +43,76 @@ impl EncryptHelper {
4243
}
4344

4445
/// Determines if we can and should encrypt.
45-
///
46-
/// `e2ee_guaranteed` should be set to true for replies to encrypted messages (as required by
47-
/// Autocrypt Level 1, version 1.1) and for messages sent in protected groups.
48-
///
49-
/// Returns an error if `e2ee_guaranteed` is true, but one or more keys are missing.
5046
pub(crate) async fn should_encrypt(
5147
&self,
5248
context: &Context,
53-
e2ee_guaranteed: bool,
5449
peerstates: &[(Option<Peerstate>, String)],
5550
) -> Result<bool> {
5651
let is_chatmail = context.is_chatmail().await?;
57-
let missing_peerstate_addr = peerstates.iter().find_map(|(peerstate, addr)| {
52+
for (peerstate, _addr) in peerstates {
5853
if let Some(peerstate) = peerstate {
59-
if is_chatmail
60-
|| e2ee_guaranteed
61-
|| peerstate.prefer_encrypt != EncryptPreference::Reset
62-
{
63-
return None;
54+
// For chatmail we ignore the encryption preference,
55+
// because we can either send encrypted or not at all.
56+
if is_chatmail || peerstate.prefer_encrypt != EncryptPreference::Reset {
57+
continue;
6458
}
6559
}
66-
Some(addr)
67-
});
68-
if let Some(addr) = missing_peerstate_addr {
69-
if e2ee_guaranteed {
70-
return Err(format_err!(
71-
"Peerstate for {addr:?} missing, cannot encrypt"
72-
));
73-
}
60+
return Ok(false);
7461
}
75-
Ok(missing_peerstate_addr.is_none())
62+
Ok(true)
7663
}
7764

78-
/// Tries to encrypt the passed in `mail`.
79-
pub async fn encrypt(
80-
self,
65+
/// Constructs a vector of public keys for given peerstates.
66+
///
67+
/// In addition returns the set of recipient addresses
68+
/// for which there is no key available.
69+
///
70+
/// Returns an error if there are recipients
71+
/// other than self, but no recipient keys are available.
72+
pub(crate) fn encryption_keyring(
73+
&self,
8174
context: &Context,
8275
verified: bool,
83-
mail_to_encrypt: MimePart<'static>,
84-
peerstates: Vec<(Option<Peerstate>, String)>,
85-
compress: bool,
86-
) -> Result<String> {
87-
let mut keyring: Vec<SignedPublicKey> = Vec::new();
76+
peerstates: &[(Option<Peerstate>, String)],
77+
) -> Result<(Vec<SignedPublicKey>, BTreeSet<String>)> {
78+
// Encrypt to self unconditionally,
79+
// even for a single-device setup.
80+
let mut keyring = vec![self.public_key.clone()];
81+
let mut missing_key_addresses = BTreeSet::new();
82+
83+
if peerstates.is_empty() {
84+
return Ok((keyring, missing_key_addresses));
85+
}
8886

8987
let mut verifier_addresses: Vec<&str> = Vec::new();
9088

91-
for (peerstate, addr) in peerstates
92-
.iter()
93-
.filter_map(|(state, addr)| state.clone().map(|s| (s, addr)))
94-
{
95-
let key = peerstate
96-
.take_key(verified)
97-
.with_context(|| format!("proper enc-key for {addr} missing, cannot encrypt"))?;
98-
keyring.push(key);
99-
verifier_addresses.push(addr);
89+
for (peerstate, addr) in peerstates {
90+
if let Some(peerstate) = peerstate {
91+
if let Some(key) = peerstate.clone().take_key(verified) {
92+
keyring.push(key);
93+
verifier_addresses.push(addr);
94+
} else {
95+
warn!(context, "Encryption key for {addr} is missing.");
96+
missing_key_addresses.insert(addr.clone());
97+
}
98+
} else {
99+
warn!(context, "Peerstate for {addr} is missing.");
100+
missing_key_addresses.insert(addr.clone());
101+
}
100102
}
101103

102-
// Encrypt to self.
103-
keyring.push(self.public_key.clone());
104+
debug_assert!(
105+
!keyring.is_empty(),
106+
"At least our own key is in the keyring"
107+
);
108+
if keyring.len() <= 1 {
109+
bail!("No recipient keys are available, cannot encrypt");
110+
}
104111

105112
// Encrypt to secondary verified keys
106113
// if we also encrypt to the introducer ("verifier") of the key.
107114
if verified {
108-
for (peerstate, _addr) in &peerstates {
115+
for (peerstate, _addr) in peerstates {
109116
if let Some(peerstate) = peerstate {
110117
if let (Some(key), Some(verifier)) = (
111118
peerstate.secondary_verified_key.as_ref(),
@@ -119,6 +126,17 @@ impl EncryptHelper {
119126
}
120127
}
121128

129+
Ok((keyring, missing_key_addresses))
130+
}
131+
132+
/// Tries to encrypt the passed in `mail`.
133+
pub async fn encrypt(
134+
self,
135+
context: &Context,
136+
keyring: Vec<SignedPublicKey>,
137+
mail_to_encrypt: MimePart<'static>,
138+
compress: bool,
139+
) -> Result<String> {
122140
let sign_key = load_self_secret_key(context).await?;
123141

124142
let mut raw_message = Vec::new();
@@ -315,22 +333,17 @@ Sent with my Delta Chat Messenger: https://delta.chat";
315333
let encrypt_helper = EncryptHelper::new(&t).await.unwrap();
316334

317335
let ps = new_peerstates(EncryptPreference::NoPreference);
318-
assert!(encrypt_helper.should_encrypt(&t, true, &ps).await?);
319-
// Own preference is `Mutual` and we have the peer's key.
320-
assert!(encrypt_helper.should_encrypt(&t, false, &ps).await?);
336+
assert!(encrypt_helper.should_encrypt(&t, &ps).await?);
321337

322338
let ps = new_peerstates(EncryptPreference::Reset);
323-
assert!(encrypt_helper.should_encrypt(&t, true, &ps).await?);
324-
assert!(!encrypt_helper.should_encrypt(&t, false, &ps).await?);
339+
assert!(!encrypt_helper.should_encrypt(&t, &ps).await?);
325340

326341
let ps = new_peerstates(EncryptPreference::Mutual);
327-
assert!(encrypt_helper.should_encrypt(&t, true, &ps).await?);
328-
assert!(encrypt_helper.should_encrypt(&t, false, &ps).await?);
342+
assert!(encrypt_helper.should_encrypt(&t, &ps).await?);
329343

330344
// test with missing peerstate
331345
let ps = vec![(None, "bob@foo.bar".to_string())];
332-
assert!(encrypt_helper.should_encrypt(&t, true, &ps).await.is_err());
333-
assert!(!encrypt_helper.should_encrypt(&t, false, &ps).await?);
346+
assert!(!encrypt_helper.should_encrypt(&t, &ps).await?);
334347
Ok(())
335348
}
336349

src/message.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,6 +1732,10 @@ pub async fn delete_msgs_ex(
17321732
!delete_for_all || msg.from_id == ContactId::SELF,
17331733
"Can delete only own messages for others"
17341734
);
1735+
ensure!(
1736+
!delete_for_all || msg.get_showpadlock(),
1737+
"Cannot request deletion of unencrypted message for others"
1738+
);
17351739

17361740
modified_chat_ids.insert(msg.chat_id);
17371741
deleted_rfc724_mid.push(msg.rfc724_mid.clone());

src/mimefactory.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ pub struct MimeFactory {
8686
/// Vector of pairs of recipient name and address that goes into the `To` field.
8787
///
8888
/// The list of actual message recipient addresses may be different,
89-
/// e.g. if members are hidden for broadcast lists.
89+
/// e.g. if members are hidden for broadcast lists
90+
/// or if the keys for some recipients are missing
91+
/// and encrypted message cannot be sent to them.
9092
to: Vec<(String, String)>,
9193

9294
/// Vector of pairs of past group member names and addresses.
@@ -784,9 +786,7 @@ impl MimeFactory {
784786

785787
let peerstates = self.peerstates_for_recipients(context).await?;
786788
let is_encrypted = !self.should_force_plaintext()
787-
&& encrypt_helper
788-
.should_encrypt(context, e2ee_guaranteed, &peerstates)
789-
.await?;
789+
&& (e2ee_guaranteed || encrypt_helper.should_encrypt(context, &peerstates).await?);
790790
let is_securejoin_message = if let Loaded::Message { msg, .. } = &self.loaded {
791791
msg.param.get_cmd() == SystemMessage::SecurejoinMessage
792792
} else {
@@ -982,14 +982,23 @@ impl MimeFactory {
982982
Loaded::Mdn { .. } => true,
983983
};
984984

985+
let (encryption_keyring, missing_key_addresses) =
986+
encrypt_helper.encryption_keyring(context, verified, &peerstates)?;
987+
985988
// XXX: additional newline is needed
986989
// to pass filtermail at
987990
// <https://github.com/deltachat/chatmail/blob/4d915f9800435bf13057d41af8d708abd34dbfa8/chatmaild/src/chatmaild/filtermail.py#L84-L86>
988991
let encrypted = encrypt_helper
989-
.encrypt(context, verified, message, peerstates, compress)
992+
.encrypt(context, encryption_keyring, message, compress)
990993
.await?
991994
+ "\n";
992995

996+
// Remove recipients for which the key is missing.
997+
if !missing_key_addresses.is_empty() {
998+
self.recipients
999+
.retain(|addr| !missing_key_addresses.contains(addr));
1000+
}
1001+
9931002
// Set the appropriate Content-Type for the outer message
9941003
MimePart::new(
9951004
"multipart/encrypted; protocol=\"application/pgp-encrypted\"",

src/receive_imf/receive_imf_tests.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4924,11 +4924,10 @@ async fn test_protected_group_add_remove_member_missing_key() -> Result<()> {
49244924
let fiona_addr = fiona.get_config(Config::Addr).await?.unwrap();
49254925
mark_as_verified(alice, fiona).await;
49264926
let alice_fiona_id = alice.add_or_lookup_contact(fiona).await.id;
4927-
assert!(add_contact_to_chat(alice, group_id, alice_fiona_id)
4928-
.await
4929-
.is_err());
4930-
// Sending the message failed,
4931-
// but member is added to the chat locally already.
4927+
add_contact_to_chat(alice, group_id, alice_fiona_id).await?;
4928+
4929+
// The message is not sent to Bob,
4930+
// but member is added to the chat locally anyway.
49324931
assert!(is_contact_in_chat(alice, group_id, alice_fiona_id).await?);
49334932
let msg = alice.get_last_msg_in(group_id).await;
49344933
assert!(msg.is_info());
@@ -4937,10 +4936,6 @@ async fn test_protected_group_add_remove_member_missing_key() -> Result<()> {
49374936
stock_str::msg_add_member_local(alice, &fiona_addr, ContactId::SELF).await
49384937
);
49394938

4940-
// Now the chat has a message "You added member fiona@example.net. [INFO] !!" (with error) that
4941-
// may be confusing, but if the error is displayed in UIs, it's more or less ok. This is not a
4942-
// normal scenario anyway.
4943-
49444939
remove_contact_from_chat(alice, group_id, alice_bob_id).await?;
49454940
assert!(!is_contact_in_chat(alice, group_id, alice_bob_id).await?);
49464941
let msg = alice.get_last_msg_in(group_id).await;
@@ -4949,7 +4944,6 @@ async fn test_protected_group_add_remove_member_missing_key() -> Result<()> {
49494944
msg.get_text(),
49504945
stock_str::msg_del_member_local(alice, &bob_addr, ContactId::SELF,).await
49514946
);
4952-
assert!(msg.error().is_some());
49534947
Ok(())
49544948
}
49554949

src/tests/verified_chats.rs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use anyhow::Result;
22
use pretty_assertions::assert_eq;
33

4-
use crate::chat::{self, add_contact_to_chat, Chat, ProtectionStatus};
4+
use crate::chat::{
5+
self, add_contact_to_chat, remove_contact_from_chat, send_msg, Chat, ProtectionStatus,
6+
};
57
use crate::chatlist::Chatlist;
68
use crate::config::Config;
79
use crate::constants::{Chattype, DC_GCL_FOR_FORWARDING};
@@ -953,6 +955,70 @@ async fn test_no_unencrypted_name_if_encrypted() -> Result<()> {
953955
Ok(())
954956
}
955957

958+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
959+
async fn test_verified_lost_member_added() -> Result<()> {
960+
let mut tcm = TestContextManager::new();
961+
let alice = &tcm.alice().await;
962+
let bob = &tcm.bob().await;
963+
let fiona = &tcm.fiona().await;
964+
965+
tcm.execute_securejoin(bob, alice).await;
966+
tcm.execute_securejoin(fiona, alice).await;
967+
968+
let alice_chat_id = alice
969+
.create_group_with_members(ProtectionStatus::Protected, "Group", &[bob])
970+
.await;
971+
let alice_sent = alice.send_text(alice_chat_id, "Hi!").await;
972+
let bob_chat_id = bob.recv_msg(&alice_sent).await.chat_id;
973+
assert_eq!(chat::get_chat_contacts(bob, bob_chat_id).await?.len(), 2);
974+
975+
// Attempt to add member, but message is lost.
976+
let fiona_id = alice.add_or_lookup_contact(fiona).await.id;
977+
add_contact_to_chat(alice, alice_chat_id, fiona_id).await?;
978+
alice.pop_sent_msg().await;
979+
980+
let alice_sent = alice.send_text(alice_chat_id, "Hi again!").await;
981+
bob.recv_msg(&alice_sent).await;
982+
assert_eq!(chat::get_chat_contacts(bob, bob_chat_id).await?.len(), 3);
983+
984+
bob_chat_id.accept(bob).await?;
985+
let sent = bob.send_text(bob_chat_id, "Hello!").await;
986+
let sent_msg = Message::load_from_db(bob, sent.sender_msg_id).await?;
987+
assert_eq!(sent_msg.get_showpadlock(), true);
988+
989+
// The message will not be sent to Fiona.
990+
// Test that Fiona will not be able to decrypt it.
991+
let fiona_rcvd = fiona.recv_msg(&sent).await;
992+
assert_eq!(fiona_rcvd.get_showpadlock(), false);
993+
assert_eq!(
994+
fiona_rcvd.get_text(),
995+
"[...] – [This message was encrypted for another setup.]"
996+
);
997+
998+
// Advance the time so Alice does not leave at the same second
999+
// as the group was created.
1000+
SystemTime::shift(std::time::Duration::from_secs(100));
1001+
1002+
// Alice leaves the chat.
1003+
remove_contact_from_chat(alice, alice_chat_id, ContactId::SELF).await?;
1004+
assert_eq!(
1005+
chat::get_chat_contacts(alice, alice_chat_id).await?.len(),
1006+
2
1007+
);
1008+
bob.recv_msg(&alice.pop_sent_msg().await).await;
1009+
1010+
// Now only Bob and Fiona are in the chat.
1011+
assert_eq!(chat::get_chat_contacts(bob, bob_chat_id).await?.len(), 2);
1012+
1013+
// Bob cannot send messages anymore because there are no recipients
1014+
// other than self for which Bob has the key.
1015+
let mut msg = Message::new_text("No key for Fiona".to_string());
1016+
let result = send_msg(bob, bob_chat_id, &mut msg).await;
1017+
assert!(result.is_err());
1018+
1019+
Ok(())
1020+
}
1021+
9561022
// ============== Helper Functions ==============
9571023

9581024
async fn assert_verified(this: &TestContext, other: &TestContext, protected: ProtectionStatus) {

0 commit comments

Comments
 (0)