1use std::collections::BTreeSet;
4use std::io::Cursor;
5
6use anyhow::{bail, Result};
7use mail_builder::mime::MimePart;
8use num_traits::FromPrimitive;
9
10use crate::aheader::{Aheader, EncryptPreference};
11use crate::config::Config;
12use crate::context::Context;
13use crate::key::{load_self_public_key, load_self_secret_key, SignedPublicKey};
14use crate::peerstate::Peerstate;
15use crate::pgp;
16
17#[derive(Debug)]
18pub struct EncryptHelper {
19 pub prefer_encrypt: EncryptPreference,
20 pub addr: String,
21 pub public_key: SignedPublicKey,
22}
23
24impl EncryptHelper {
25 pub async fn new(context: &Context) -> Result<EncryptHelper> {
26 let prefer_encrypt =
27 EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled).await?)
28 .unwrap_or_default();
29 let addr = context.get_primary_self_addr().await?;
30 let public_key = load_self_public_key(context).await?;
31
32 Ok(EncryptHelper {
33 prefer_encrypt,
34 addr,
35 public_key,
36 })
37 }
38
39 pub fn get_aheader(&self) -> Aheader {
40 let pk = self.public_key.clone();
41 let addr = self.addr.to_string();
42 Aheader::new(addr, pk, self.prefer_encrypt)
43 }
44
45 pub(crate) async fn should_encrypt(
47 &self,
48 context: &Context,
49 peerstates: &[(Option<Peerstate>, String)],
50 ) -> Result<bool> {
51 let is_chatmail = context.is_chatmail().await?;
52 for (peerstate, _addr) in peerstates {
53 if let Some(peerstate) = peerstate {
54 if is_chatmail || peerstate.prefer_encrypt != EncryptPreference::Reset {
57 continue;
58 }
59 }
60 return Ok(false);
61 }
62 Ok(true)
63 }
64
65 pub(crate) fn encryption_keyring(
73 &self,
74 context: &Context,
75 verified: bool,
76 peerstates: &[(Option<Peerstate>, String)],
77 ) -> Result<(Vec<SignedPublicKey>, BTreeSet<String>)> {
78 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 }
86
87 let mut verifier_addresses: Vec<&str> = Vec::new();
88
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 }
102 }
103
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 }
111
112 if verified {
115 for (peerstate, _addr) in peerstates {
116 if let Some(peerstate) = peerstate {
117 if let (Some(key), Some(verifier)) = (
118 peerstate.secondary_verified_key.as_ref(),
119 peerstate.secondary_verifier.as_deref(),
120 ) {
121 if verifier_addresses.contains(&verifier) {
122 keyring.push(key.clone());
123 }
124 }
125 }
126 }
127 }
128
129 Ok((keyring, missing_key_addresses))
130 }
131
132 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> {
140 let sign_key = load_self_secret_key(context).await?;
141
142 let mut raw_message = Vec::new();
143 let cursor = Cursor::new(&mut raw_message);
144 mail_to_encrypt.clone().write_part(cursor).ok();
145
146 let ctext = pgp::pk_encrypt(&raw_message, keyring, Some(sign_key), compress).await?;
147
148 Ok(ctext)
149 }
150
151 pub async fn sign(self, context: &Context, mail: &MimePart<'static>) -> Result<String> {
154 let sign_key = load_self_secret_key(context).await?;
155 let mut buffer = Vec::new();
156 let cursor = Cursor::new(&mut buffer);
157 mail.clone().write_part(cursor).ok();
158 let signature = pgp::pk_calc_signature(&buffer, &sign_key)?;
159 Ok(signature)
160 }
161}
162
163pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
171 load_self_public_key(context).await?;
172 Ok(())
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::chat::send_text_msg;
179 use crate::config::Config;
180 use crate::key::DcKey;
181 use crate::message::{Message, Viewtype};
182 use crate::param::Param;
183 use crate::receive_imf::receive_imf;
184 use crate::test_utils::{bob_keypair, TestContext, TestContextManager};
185
186 mod ensure_secret_key_exists {
187 use super::*;
188
189 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
190 async fn test_prexisting() {
191 let t = TestContext::new_alice().await;
192 assert!(ensure_secret_key_exists(&t).await.is_ok());
193 }
194
195 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
196 async fn test_not_configured() {
197 let t = TestContext::new().await;
198 assert!(ensure_secret_key_exists(&t).await.is_err());
199 }
200 }
201
202 #[test]
203 fn test_mailmime_parse() {
204 let plain = b"Chat-Disposition-Notification-To: hello@world.de
205Chat-Group-ID: CovhGgau8M-
206Chat-Group-Name: Delta Chat Dev
207Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
208 =?utf-8?Q?all=3A?= rust core master ...
209Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
210Content-Transfer-Encoding: quoted-printable
211
212sidenote for all: things are trick atm recomm=
213end not to try to run with desktop or ios unless you are ready to hunt bugs
214
215-- =20
216Sent with my Delta Chat Messenger: https://delta.chat";
217 let mail = mailparse::parse_mail(plain).expect("failed to parse valid message");
218
219 assert_eq!(mail.headers.len(), 6);
220 assert!(
221 mail.get_body().unwrap().starts_with(
222 "sidenote for all: things are trick atm recommend not to try to run with desktop or ios unless you are ready to hunt bugs")
223 );
224 }
225
226 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
227 async fn test_encrypted_no_autocrypt() -> anyhow::Result<()> {
228 let mut tcm = TestContextManager::new();
229 let alice = tcm.alice().await;
230 let bob = tcm.bob().await;
231
232 let chat_alice = alice.create_email_chat(&bob).await.id;
233 let chat_bob = bob.create_email_chat(&alice).await.id;
234
235 let mut msg = Message::new(Viewtype::Text);
237 let sent = alice.send_msg(chat_alice, &mut msg).await;
238
239 let msg = bob.recv_msg(&sent).await;
241 assert!(!msg.get_showpadlock());
242
243 let peerstate_alice = Peerstate::from_addr(&bob.ctx, "alice@example.org")
244 .await?
245 .expect("no peerstate found in the database");
246 assert_eq!(peerstate_alice.prefer_encrypt, EncryptPreference::Mutual);
247
248 let mut msg = Message::new(Viewtype::Text);
250 let sent = bob.send_msg(chat_bob, &mut msg).await;
251
252 let msg = alice.recv_msg(&sent).await;
256 assert!(msg.get_showpadlock());
257
258 let peerstate_bob = Peerstate::from_addr(&alice.ctx, "bob@example.net")
259 .await?
260 .expect("no peerstate found in the database");
261 assert_eq!(peerstate_bob.prefer_encrypt, EncryptPreference::Mutual);
262
263 let mut msg = Message::new(Viewtype::Text);
267 msg.param.set_int(Param::SkipAutocrypt, 1);
268 let sent = alice.send_msg(chat_alice, &mut msg).await;
269
270 let msg = bob.recv_msg(&sent).await;
271 assert!(msg.get_showpadlock());
272 let peerstate_alice = Peerstate::from_addr(&bob.ctx, "alice@example.org")
273 .await?
274 .expect("no peerstate found in the database");
275 assert_eq!(peerstate_alice.prefer_encrypt, EncryptPreference::Mutual);
276
277 let mut msg = Message::new(Viewtype::Text);
279 msg.force_plaintext();
280 let sent = alice.send_msg(chat_alice, &mut msg).await;
281
282 let msg = bob.recv_msg(&sent).await;
283 assert!(!msg.get_showpadlock());
284 let peerstate_alice = Peerstate::from_addr(&bob.ctx, "alice@example.org")
285 .await?
286 .expect("no peerstate found in the database");
287 assert_eq!(peerstate_alice.prefer_encrypt, EncryptPreference::Mutual);
288
289 let mut msg = Message::new(Viewtype::Text);
291 msg.force_plaintext();
292 msg.param.set_int(Param::SkipAutocrypt, 1);
293 let sent = alice.send_msg(chat_alice, &mut msg).await;
294
295 let msg = bob.recv_msg(&sent).await;
296 assert!(!msg.get_showpadlock());
297 let peerstate_alice = Peerstate::from_addr(&bob.ctx, "alice@example.org")
298 .await?
299 .expect("no peerstate found in the database");
300 assert_eq!(peerstate_alice.prefer_encrypt, EncryptPreference::Reset);
301
302 Ok(())
303 }
304
305 fn new_peerstates(prefer_encrypt: EncryptPreference) -> Vec<(Option<Peerstate>, String)> {
306 let addr = "bob@foo.bar";
307 let pub_key = bob_keypair().public;
308 let peerstate = Peerstate {
309 addr: addr.into(),
310 last_seen: 13,
311 last_seen_autocrypt: 14,
312 prefer_encrypt,
313 public_key: Some(pub_key.clone()),
314 public_key_fingerprint: Some(pub_key.dc_fingerprint()),
315 gossip_key: Some(pub_key.clone()),
316 gossip_timestamp: 15,
317 gossip_key_fingerprint: Some(pub_key.dc_fingerprint()),
318 verified_key: Some(pub_key.clone()),
319 verified_key_fingerprint: Some(pub_key.dc_fingerprint()),
320 verifier: None,
321 secondary_verified_key: None,
322 secondary_verified_key_fingerprint: None,
323 secondary_verifier: None,
324 backward_verified_key_id: None,
325 fingerprint_changed: false,
326 };
327 vec![(Some(peerstate), addr.to_string())]
328 }
329
330 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
331 async fn test_should_encrypt() -> Result<()> {
332 let t = TestContext::new_alice().await;
333 let encrypt_helper = EncryptHelper::new(&t).await.unwrap();
334
335 let ps = new_peerstates(EncryptPreference::NoPreference);
336 assert!(encrypt_helper.should_encrypt(&t, &ps).await?);
337
338 let ps = new_peerstates(EncryptPreference::Reset);
339 assert!(!encrypt_helper.should_encrypt(&t, &ps).await?);
340
341 let ps = new_peerstates(EncryptPreference::Mutual);
342 assert!(encrypt_helper.should_encrypt(&t, &ps).await?);
343
344 let ps = vec![(None, "bob@foo.bar".to_string())];
346 assert!(!encrypt_helper.should_encrypt(&t, &ps).await?);
347 Ok(())
348 }
349
350 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
351 async fn test_chatmail_can_send_unencrypted() -> Result<()> {
352 let mut tcm = TestContextManager::new();
353 let bob = &tcm.bob().await;
354 bob.set_config_bool(Config::IsChatmail, true).await?;
355 let bob_chat_id = receive_imf(
356 bob,
357 b"From: alice@example.org\n\
358 To: bob@example.net\n\
359 Message-ID: <2222@example.org>\n\
360 Date: Sun, 22 Mar 3000 22:37:58 +0000\n\
361 \n\
362 Hello\n",
363 false,
364 )
365 .await?
366 .unwrap()
367 .chat_id;
368 bob_chat_id.accept(bob).await?;
369 send_text_msg(bob, bob_chat_id, "hi".to_string()).await?;
370 let sent_msg = bob.pop_sent_msg().await;
371 let msg = Message::load_from_db(bob, sent_msg.sender_msg_id).await?;
372 assert!(!msg.get_showpadlock());
373 Ok(())
374 }
375}