Compare commits

...

1 Commits

Author SHA1 Message Date
link2xt
9c681ad8a1 mimeparser: do not try to decrypt partially downloaded messages 2022-06-02 11:15:19 +00:00
2 changed files with 81 additions and 68 deletions

View File

@@ -131,6 +131,54 @@ impl EncryptHelper {
}
}
/// Applies Autocrypt header to Autocrypt peer state.
///
/// Returns updated peer state.
pub(crate) async fn get_autocrypt_peerstate(
context: &Context,
mail: &ParsedMail<'_>,
message_time: i64,
) -> Result<Option<Peerstate>> {
let from = mail
.headers
.get_header(HeaderDef::From_)
.and_then(|from_addr| mailparse::addrparse_header(from_addr).ok())
.and_then(|from| from.extract_single_info())
.map(|from| from.addr)
.unwrap_or_default();
let aheader = match Aheader::from_headers(&from, &mail.headers) {
Ok(header) => header,
Err(err) => {
warn!(context, "Failed to parse Autocrypt header: {}", err);
None
}
};
let peerstate = Peerstate::from_addr(context, &from).await?;
let peerstate = if let Some(aheader) = aheader {
if let Some(mut peerstate) = peerstate {
peerstate.apply_header(&aheader, message_time);
peerstate.save_to_db(&context.sql, false).await?;
Some(peerstate)
} else {
let p = Peerstate::from_header(&aheader, message_time);
p.save_to_db(&context.sql, true).await?;
Some(p)
}
} else {
peerstate
};
if let Some(peerstate) = peerstate.as_ref() {
peerstate
.handle_fingerprint_change(context, message_time)
.await?;
}
Ok(peerstate)
}
/// Tries to decrypt a message, but only if it is structured as an
/// Autocrypt message.
///
@@ -142,65 +190,15 @@ impl EncryptHelper {
pub async fn try_decrypt(
context: &Context,
mail: &ParsedMail<'_>,
message_time: i64,
peerstate: &Option<Peerstate>,
) -> Result<(Option<Vec<u8>>, HashSet<Fingerprint>)> {
let from = mail
.headers
.get_header(HeaderDef::From_)
.and_then(|from_addr| mailparse::addrparse_header(from_addr).ok())
.and_then(|from| from.extract_single_info())
.map(|from| from.addr)
.unwrap_or_default();
let mut peerstate = Peerstate::from_addr(context, &from).await?;
// Apply Autocrypt header
match Aheader::from_headers(&from, &mail.headers) {
Ok(Some(ref header)) => {
if let Some(ref mut peerstate) = peerstate {
peerstate.apply_header(header, message_time);
peerstate.save_to_db(&context.sql, false).await?;
} else {
let p = Peerstate::from_header(header, message_time);
p.save_to_db(&context.sql, true).await?;
peerstate = Some(p);
}
}
Ok(None) => {}
Err(err) => warn!(context, "Failed to parse Autocrypt header: {}", err),
}
// Possibly perform decryption
let mut public_keyring_for_validate: Keyring<SignedPublicKey> = Keyring::new();
if let Some(ref mut peerstate) = peerstate {
peerstate
.handle_fingerprint_change(context, message_time)
.await?;
if let Some(key) = &peerstate.public_key {
public_keyring_for_validate.add(key.clone());
} else if let Some(key) = &peerstate.gossip_key {
public_keyring_for_validate.add(key.clone());
}
}
let public_keyring_for_validate = keyring_from_peerstate(&peerstate);
let (out_mail, signatures) =
match decrypt_if_autocrypt_message(context, mail, public_keyring_for_validate).await? {
Some((out_mail, signatures)) => (Some(out_mail), signatures),
None => (None, Default::default()),
};
if let Some(mut peerstate) = peerstate {
// If message is not encrypted and it is not a read receipt, degrade encryption.
if out_mail.is_none()
&& message_time > peerstate.last_seen_autocrypt
&& !contains_report(mail)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false).await?;
}
}
Ok((out_mail, signatures))
}
@@ -283,6 +281,18 @@ fn get_attachment_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMai
}
}
fn keyring_from_peerstate(peerstate: &Option<Peerstate>) -> Keyring<SignedPublicKey> {
let mut public_keyring_for_validate: Keyring<SignedPublicKey> = Keyring::new();
if let Some(peerstate) = peerstate.as_ref() {
if let Some(key) = &peerstate.public_key {
public_keyring_for_validate.add(key.clone());
} else if let Some(key) = &peerstate.gossip_key {
public_keyring_for_validate.add(key.clone());
}
}
public_keyring_for_validate
}
async fn decrypt_if_autocrypt_message(
context: &Context,
mail: &ParsedMail<'_>,
@@ -380,18 +390,6 @@ fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
false
}
/// Checks if a MIME structure contains a multipart/report part.
///
/// As reports are often unencrypted, we do not reset the Autocrypt header in
/// this case.
///
/// However, Delta Chat itself has no problem with encrypted multipart/report
/// parts and MUAs should be encouraged to encrpyt multipart/reports as well so
/// that we could use the normal Autocrypt processing.
fn contains_report(mail: &ParsedMail<'_>) -> bool {
mail.ctype.mimetype == "multipart/report"
}
/// Ensures a private key exists for the configured user.
///
/// Normally the private key is generated when the first message is

View File

@@ -214,8 +214,9 @@ impl MimeMessage {
let mut mail_raw = Vec::new();
let mut gossiped_addr = Default::default();
let (mail, signatures, warn_empty_signature) =
match e2ee::try_decrypt(context, &mail, message_time).await {
let peerstate = e2ee::get_autocrypt_peerstate(context, &mail, message_time).await?;
let (mail, signatures, warn_empty_signature) = if partial.is_none() {
match e2ee::try_decrypt(context, &mail, &peerstate).await {
Ok((raw, signatures)) => {
if let Some(raw) = raw {
// Encrypted, but maybe unsigned message. Only if
@@ -267,7 +268,17 @@ impl MimeMessage {
(Ok(decrypted_mail), signatures, true)
} else {
// Message was not encrypted
// Message was not encrypted.
// If it is not a read receipt, degrade encryption.
if let Some(mut peerstate) = peerstate {
if message_time > peerstate.last_seen_autocrypt
&& mail.ctype.mimetype != "multipart/report"
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false).await?;
}
}
(Ok(mail), signatures, false)
}
}
@@ -275,7 +286,11 @@ impl MimeMessage {
warn!(context, "decryption failed: {}", err);
(Err(err), Default::default(), true)
}
};
}
} else {
// Partial message, do not try to decrypt.
(Ok(mail), Default::default(), false)
};
let mut parser = MimeMessage {
parts: Vec::new(),