1use std::collections::HashSet;
4
5use anyhow::Result;
6use deltachat_contact_tools::addr_cmp;
7use mailparse::ParsedMail;
8
9use crate::aheader::Aheader;
10use crate::context::Context;
11use crate::key::{DcKey, Fingerprint, SignedPublicKey, SignedSecretKey};
12use crate::peerstate::Peerstate;
13use crate::pgp;
14
15pub fn try_decrypt(
19 mail: &ParsedMail<'_>,
20 private_keyring: &[SignedSecretKey],
21) -> Result<Option<::pgp::composed::Message>> {
22 let Some(encrypted_data_part) = get_encrypted_mime(mail) else {
23 return Ok(None);
24 };
25
26 let data = encrypted_data_part.get_body_raw()?;
27 let msg = pgp::pk_decrypt(data, private_keyring)?;
28
29 Ok(Some(msg))
30}
31
32pub(crate) fn get_encrypted_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMail<'b>> {
34 get_autocrypt_mime(mail)
35 .or_else(|| get_mixed_up_mime(mail))
36 .or_else(|| get_attachment_mime(mail))
37}
38
39fn get_mixed_up_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMail<'b>> {
57 if mail.ctype.mimetype != "multipart/mixed" {
58 return None;
59 }
60 if let [first_part, second_part, third_part] = &mail.subparts[..] {
61 if first_part.ctype.mimetype == "text/plain"
62 && second_part.ctype.mimetype == "application/pgp-encrypted"
63 && third_part.ctype.mimetype == "application/octet-stream"
64 {
65 Some(third_part)
66 } else {
67 None
68 }
69 } else {
70 None
71 }
72}
73
74fn get_attachment_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMail<'b>> {
82 if mail.ctype.mimetype != "multipart/mixed" {
83 return None;
84 }
85 if let [first_part, second_part] = &mail.subparts[..] {
86 if first_part.ctype.mimetype == "text/plain"
87 && second_part.ctype.mimetype == "multipart/encrypted"
88 {
89 get_autocrypt_mime(second_part)
90 } else {
91 None
92 }
93 } else {
94 None
95 }
96}
97
98fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMail<'b>> {
102 if mail.ctype.mimetype != "multipart/encrypted" {
103 return None;
104 }
105 if let [first_part, second_part] = &mail.subparts[..] {
106 if first_part.ctype.mimetype == "application/pgp-encrypted"
107 && second_part.ctype.mimetype == "application/octet-stream"
108 {
109 Some(second_part)
110 } else {
111 None
112 }
113 } else {
114 None
115 }
116}
117
118pub(crate) fn validate_detached_signature<'a, 'b>(
125 mail: &'a ParsedMail<'b>,
126 public_keyring_for_validate: &[SignedPublicKey],
127) -> Option<(&'a ParsedMail<'b>, HashSet<Fingerprint>)> {
128 if mail.ctype.mimetype != "multipart/signed" {
129 return None;
130 }
131
132 if let [first_part, second_part] = &mail.subparts[..] {
133 let content = first_part.raw_bytes;
135 let ret_valid_signatures = match second_part.get_body_raw() {
136 Ok(signature) => pgp::pk_validate(content, &signature, public_keyring_for_validate)
137 .unwrap_or_default(),
138 Err(_) => Default::default(),
139 };
140 Some((first_part, ret_valid_signatures))
141 } else {
142 None
143 }
144}
145
146pub(crate) fn keyring_from_peerstate(peerstate: Option<&Peerstate>) -> Vec<SignedPublicKey> {
148 let mut public_keyring_for_validate = Vec::new();
149 if let Some(peerstate) = peerstate {
150 if let Some(key) = &peerstate.public_key {
151 public_keyring_for_validate.push(key.clone());
152 } else if let Some(key) = &peerstate.gossip_key {
153 public_keyring_for_validate.push(key.clone());
154 }
155 }
156 public_keyring_for_validate
157}
158
159pub(crate) async fn get_autocrypt_peerstate(
166 context: &Context,
167 from: &str,
168 autocrypt_header: Option<&Aheader>,
169 message_time: i64,
170 allow_aeap: bool,
171) -> Result<Option<Peerstate>> {
172 let allow_change = !context.is_self_addr(from).await?;
173 let mut peerstate;
174
175 if let Some(header) = autocrypt_header {
177 if allow_aeap {
178 peerstate = Peerstate::from_verified_fingerprint_or_addr(
184 context,
185 &header.public_key.dc_fingerprint(),
186 from,
187 )
188 .await?;
189 } else {
190 peerstate = Peerstate::from_addr(context, from).await?;
191 }
192
193 if let Some(ref mut peerstate) = peerstate {
194 if addr_cmp(&peerstate.addr, from) {
195 if allow_change {
196 peerstate.apply_header(context, header, message_time);
197 peerstate.save_to_db(&context.sql).await?;
198 } else {
199 info!(
200 context,
201 "Refusing to update existing peerstate of {}", &peerstate.addr
202 );
203 }
204 }
205 } else {
212 let p = Peerstate::from_header(header, message_time);
213 p.save_to_db(&context.sql).await?;
214 peerstate = Some(p);
215 }
216 } else {
217 peerstate = Peerstate::from_addr(context, from).await?;
218 }
219
220 Ok(peerstate)
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::receive_imf::receive_imf;
227 use crate::test_utils::TestContext;
228
229 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
230 async fn test_mixed_up_mime() -> Result<()> {
231 let mixed_up_mime = include_bytes!("../test-data/message/protonmail-mixed-up.eml");
235 let mail = mailparse::parse_mail(mixed_up_mime)?;
236 assert!(get_autocrypt_mime(&mail).is_none());
237 assert!(get_mixed_up_mime(&mail).is_some());
238 assert!(get_attachment_mime(&mail).is_none());
239
240 let repaired_mime = include_bytes!("../test-data/message/protonmail-repaired.eml");
246 let mail = mailparse::parse_mail(repaired_mime)?;
247 assert!(get_autocrypt_mime(&mail).is_some());
248 assert!(get_mixed_up_mime(&mail).is_none());
249 assert!(get_attachment_mime(&mail).is_none());
250
251 let attachment_mime = include_bytes!("../test-data/message/google-workspace-mixed-up.eml");
254 let mail = mailparse::parse_mail(attachment_mime)?;
255 assert!(get_autocrypt_mime(&mail).is_none());
256 assert!(get_mixed_up_mime(&mail).is_none());
257 assert!(get_attachment_mime(&mail).is_some());
258
259 let bob = TestContext::new_bob().await;
260 receive_imf(&bob, attachment_mime, false).await?;
261 let msg = bob.get_last_msg().await;
262 assert_eq!(msg.text, "Hello from Thunderbird!");
263
264 Ok(())
265 }
266
267 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
268 async fn test_mixed_up_mime_long() -> Result<()> {
269 let mixed_up_mime = include_bytes!("../test-data/message/mixed-up-long.eml");
272 let bob = TestContext::new_bob().await;
273 receive_imf(&bob, mixed_up_mime, false).await?;
274 let msg = bob.get_last_msg().await;
275 assert!(!msg.get_text().is_empty());
276 assert!(msg.has_html());
277 assert!(msg.id.get_html(&bob).await?.unwrap().len() > 40000);
278 Ok(())
279 }
280}