mirror of
https://github.com/chatmail/core.git
synced 2026-05-02 21:06:31 +03:00
Add support for "Mixed Up" MIME format
This is an PGP/MIME format produced by Microsoft Exchange and ProtonMail IMAP/SMTP Bridge, described in detail in https://tools.ietf.org/id/draft-dkg-openpgp-pgpmime-message-mangling-00.html This patch adds seamless support for "Mixed Up" Encryption, repairing mangled Autocrypt messages without notifying the user.
This commit is contained in:
100
src/e2ee.rs
100
src/e2ee.rs
@@ -209,29 +209,58 @@ pub async fn try_decrypt(
|
|||||||
Ok((out_mail, signatures))
|
Ok((out_mail, signatures))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the encrypted payload and validates the autocrypt structure.
|
/// Returns a reference to the encrypted payload of a valid PGP/MIME message.
|
||||||
fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail<'b>> {
|
///
|
||||||
ensure!(
|
/// Returns `None` if the message is not a valid PGP/MIME message.
|
||||||
mail.ctype.mimetype == "multipart/encrypted",
|
fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMail<'b>> {
|
||||||
"Not a multipart/encrypted message: {}",
|
if mail.ctype.mimetype != "multipart/encrypted" {
|
||||||
mail.ctype.mimetype
|
return None;
|
||||||
);
|
}
|
||||||
if let [first_part, second_part] = &mail.subparts[..] {
|
if let [first_part, second_part] = &mail.subparts[..] {
|
||||||
ensure!(
|
if first_part.ctype.mimetype == "application/pgp-encrypted"
|
||||||
first_part.ctype.mimetype == "application/pgp-encrypted",
|
&& second_part.ctype.mimetype == "application/octet-stream"
|
||||||
"Invalid Autocrypt Level 1 version part: {:?}",
|
{
|
||||||
first_part.ctype,
|
Some(second_part)
|
||||||
);
|
} else {
|
||||||
|
None
|
||||||
ensure!(
|
}
|
||||||
second_part.ctype.mimetype == "application/octet-stream",
|
|
||||||
"Invalid Autocrypt Level 1 encrypted part: {:?}",
|
|
||||||
second_part.ctype
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(second_part)
|
|
||||||
} else {
|
} else {
|
||||||
bail!("Invalid Autocrypt Level 1 Mime Parts")
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the encrypted payload of a ["Mixed
|
||||||
|
/// Up"][pgpmime-message-mangling] message.
|
||||||
|
///
|
||||||
|
/// According to [RFC 3156] encrypted messages should have
|
||||||
|
/// `multipart/encrypted` MIME type and two parts, but Microsoft
|
||||||
|
/// Exchange and ProtonMail IMAP/SMTP Bridge are known to mangle this
|
||||||
|
/// structure by changing the type to `multipart/mixed` and prepending
|
||||||
|
/// an empty part at the start.
|
||||||
|
///
|
||||||
|
/// ProtonMail IMAP/SMTP Bridge prepends a part literally saying
|
||||||
|
/// "Empty Message", so we don't check its contents at all, checking
|
||||||
|
/// only for `text/plain` type.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the message is not a "Mixed Up" message.
|
||||||
|
///
|
||||||
|
/// [RFC 3156]: https://www.rfc-editor.org/info/rfc3156
|
||||||
|
/// [pgpmime-message-mangling]: https://tools.ietf.org/id/draft-dkg-openpgp-pgpmime-message-mangling-00.html
|
||||||
|
fn get_mixed_up_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMail<'b>> {
|
||||||
|
if mail.ctype.mimetype != "multipart/mixed" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let [first_part, second_part, third_part] = &mail.subparts[..] {
|
||||||
|
if first_part.ctype.mimetype == "text/plain"
|
||||||
|
&& second_part.ctype.mimetype == "application/pgp-encrypted"
|
||||||
|
&& third_part.ctype.mimetype == "application/octet-stream"
|
||||||
|
{
|
||||||
|
Some(third_part)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,12 +271,12 @@ async fn decrypt_if_autocrypt_message(
|
|||||||
public_keyring_for_validate: Keyring<SignedPublicKey>,
|
public_keyring_for_validate: Keyring<SignedPublicKey>,
|
||||||
ret_valid_signatures: &mut HashSet<Fingerprint>,
|
ret_valid_signatures: &mut HashSet<Fingerprint>,
|
||||||
) -> Result<Option<Vec<u8>>> {
|
) -> Result<Option<Vec<u8>>> {
|
||||||
let encrypted_data_part = match get_autocrypt_mime(mail) {
|
let encrypted_data_part = match get_autocrypt_mime(mail).or_else(|| get_mixed_up_mime(mail)) {
|
||||||
Err(_) => {
|
None => {
|
||||||
// not an autocrypt mime message, abort and ignore
|
// not an autocrypt mime message, abort and ignore
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
Ok(res) => res,
|
Some(res) => res,
|
||||||
};
|
};
|
||||||
info!(context, "Detected Autocrypt-mime message");
|
info!(context, "Detected Autocrypt-mime message");
|
||||||
|
|
||||||
@@ -547,4 +576,27 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
|||||||
assert!(encrypt_helper.should_encrypt(&t, true, &ps).is_err());
|
assert!(encrypt_helper.should_encrypt(&t, true, &ps).is_err());
|
||||||
assert!(!encrypt_helper.should_encrypt(&t, false, &ps).unwrap());
|
assert!(!encrypt_helper.should_encrypt(&t, false, &ps).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mixed_up_mime() -> Result<()> {
|
||||||
|
// "Mixed Up" mail as received when sending an encrypted
|
||||||
|
// message using Delta Chat Desktop via ProtonMail IMAP/SMTP
|
||||||
|
// Bridge.
|
||||||
|
let mixed_up_mime = include_bytes!("../test-data/message/protonmail-mixed-up.eml");
|
||||||
|
let mail = mailparse::parse_mail(mixed_up_mime)?;
|
||||||
|
assert!(get_autocrypt_mime(&mail).is_none());
|
||||||
|
assert!(get_mixed_up_mime(&mail).is_some());
|
||||||
|
|
||||||
|
// Same "Mixed Up" mail repaired by Thunderbird 78.9.0.
|
||||||
|
//
|
||||||
|
// It added `X-Enigmail-Info: Fixed broken PGP/MIME message`
|
||||||
|
// header although the repairing is done by the built-in
|
||||||
|
// OpenPGP support, not Enigmail.
|
||||||
|
let repaired_mime = include_bytes!("../test-data/message/protonmail-repaired.eml");
|
||||||
|
let mail = mailparse::parse_mail(repaired_mime)?;
|
||||||
|
assert!(get_autocrypt_mime(&mail).is_some());
|
||||||
|
assert!(get_mixed_up_mime(&mail).is_none());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Filename encoding | Encoded Words ([RFC 2047](https://tools.ietf.
|
|||||||
Identify server folders | IMAP LIST Extension ([RFC 6154](https://tools.ietf.org/html/rfc6154))
|
Identify server folders | IMAP LIST Extension ([RFC 6154](https://tools.ietf.org/html/rfc6154))
|
||||||
Push | IMAP IDLE ([RFC 2177](https://tools.ietf.org/html/rfc2177))
|
Push | IMAP IDLE ([RFC 2177](https://tools.ietf.org/html/rfc2177))
|
||||||
Authorization | OAuth2 ([RFC 6749](https://tools.ietf.org/html/rfc6749))
|
Authorization | OAuth2 ([RFC 6749](https://tools.ietf.org/html/rfc6749))
|
||||||
End-to-end encryption | [Autocrypt Level 1](https://autocrypt.org/level1.html), OpenPGP ([RFC 4880](https://tools.ietf.org/html/rfc4880)) and Security Multiparts for MIME ([RFC 1847](https://tools.ietf.org/html/rfc1847))
|
End-to-end encryption | [Autocrypt Level 1](https://autocrypt.org/level1.html), OpenPGP ([RFC 4880](https://tools.ietf.org/html/rfc4880)), Security Multiparts for MIME ([RFC 1847](https://tools.ietf.org/html/rfc1847)) and [“Mixed Up” Encryption repairing](https://tools.ietf.org/id/draft-dkg-openpgp-pgpmime-message-mangling-00.html)
|
||||||
Configuration assistance | [Autoconfigure](https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration) and [Autodiscover](https://technet.microsoft.com/library/bb124251(v=exchg.150).aspx)
|
Configuration assistance | [Autoconfigure](https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration) and [Autodiscover](https://technet.microsoft.com/library/bb124251(v=exchg.150).aspx)
|
||||||
Messenger functions | [Chat-over-Email](https://github.com/deltachat/deltachat-core-rust/blob/master/spec.md#chat-over-email-specification)
|
Messenger functions | [Chat-over-Email](https://github.com/deltachat/deltachat-core-rust/blob/master/spec.md#chat-over-email-specification)
|
||||||
Detect mailing list | List-Id ([RFC 2919](https://tools.ietf.org/html/rfc2919)) and Precedence ([RFC 3834](https://tools.ietf.org/html/rfc3834))
|
Detect mailing list | List-Id ([RFC 2919](https://tools.ietf.org/html/rfc2919)) and Precedence ([RFC 3834](https://tools.ietf.org/html/rfc3834))
|
||||||
|
|||||||
36
test-data/message/protonmail-mixed-up.eml
Normal file
36
test-data/message/protonmail-mixed-up.eml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
Return-Path: <alice@example.com>
|
||||||
|
Delivered-To: bob@example.org
|
||||||
|
Date: Mon, 29 Mar 2021 11:30:57 +0000
|
||||||
|
To: Bob <bob@example.org>
|
||||||
|
From: Alice <alice@example.com>
|
||||||
|
Reply-To: Alice <alice@example.com>
|
||||||
|
Subject: ...
|
||||||
|
Message-ID: <Mr.AkmaxDNOYj0.oNPtoFR8EHC@example.com>
|
||||||
|
In-Reply-To: <Mr.Y1EWG9-FLhN.KUZ4cu74MYR@example.org>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary="b1_01FB8kHjERilpSep0FbmgBMNYR3TvWQ30jPthW5L0"
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
|
||||||
|
--b1_01FB8kHjERilpSep0FbmgBMNYR3TvWQ30jPthW5L0
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
Empty Message
|
||||||
|
--b1_01FB8kHjERilpSep0FbmgBMNYR3TvWQ30jPthW5L0
|
||||||
|
Content-Type: application/pgp-encrypted; name=attachment.pgp
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Disposition: attachment; filename=attachment.pgp
|
||||||
|
|
||||||
|
VmVyc2lvbjogMQ0KDQo=
|
||||||
|
|
||||||
|
--b1_01FB8kHjERilpSep0FbmgBMNYR3TvWQ30jPthW5L0
|
||||||
|
Content-Type: application/octet-stream; name=encrypted.asc
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Disposition: attachment; filename=encrypted.asc
|
||||||
|
|
||||||
|
UEdQIFBBWUxPQUQgV0FTIEhFUkUK
|
||||||
|
|
||||||
|
--b1_01FB8kHjERilpSep0FbmgBMNYR3TvWQ30jPthW5L0--
|
||||||
|
|
||||||
31
test-data/message/protonmail-repaired.eml
Normal file
31
test-data/message/protonmail-repaired.eml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
Return-Path: <alice@example.com>
|
||||||
|
Delivered-To: bob@example.org
|
||||||
|
Date: Mon, 29 Mar 2021 11:30:57 +0000
|
||||||
|
To: Bob <bob@example.org>
|
||||||
|
From: Alice <alice@example.com>
|
||||||
|
Reply-To: Alice <alice@example.com>
|
||||||
|
Subject: ...
|
||||||
|
Message-ID: <Mr.AkmaxDNOYj0.oNPtoFR8EHC@example.com>
|
||||||
|
In-Reply-To: <Mr.Y1EWG9-FLhN.KUZ4cu74MYR@example.org>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/encrypted;
|
||||||
|
protocol="application/pgp-encrypted";
|
||||||
|
boundary="b1_01FB8kHjERilpSep0FbmgBMNYR3TvWQ30jPthW5L0"
|
||||||
|
X-Enigmail-Info: Fixed broken PGP/MIME message
|
||||||
|
|
||||||
|
--b1_01FB8kHjERilpSep0FbmgBMNYR3TvWQ30jPthW5L0
|
||||||
|
Content-Type: application/pgp-encrypted; name=attachment.pgp
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Disposition: attachment; filename=attachment.pgp
|
||||||
|
|
||||||
|
VmVyc2lvbjogMQ0KDQo=
|
||||||
|
|
||||||
|
--b1_01FB8kHjERilpSep0FbmgBMNYR3TvWQ30jPthW5L0
|
||||||
|
Content-Type: application/octet-stream; name=encrypted.asc
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Disposition: attachment; filename=encrypted.asc
|
||||||
|
|
||||||
|
UEdQIFBBWUxPQUQgV0FTIEhFUkUK
|
||||||
|
|
||||||
|
--b1_01FB8kHjERilpSep0FbmgBMNYR3TvWQ30jPthW5L0--
|
||||||
|
|
||||||
Reference in New Issue
Block a user