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:
link2xt
2021-03-29 22:24:46 +03:00
committed by link2xt
parent 4d2ac5a3a2
commit 1cd53aafff
4 changed files with 144 additions and 25 deletions

View File

@@ -209,29 +209,58 @@ pub async fn try_decrypt(
Ok((out_mail, signatures))
}
/// Returns a reference to the encrypted payload and validates the autocrypt structure.
fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail<'b>> {
ensure!(
mail.ctype.mimetype == "multipart/encrypted",
"Not a multipart/encrypted message: {}",
mail.ctype.mimetype
);
/// Returns a reference to the encrypted payload of a valid PGP/MIME message.
///
/// Returns `None` if the message is not a valid PGP/MIME message.
fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMail<'b>> {
if mail.ctype.mimetype != "multipart/encrypted" {
return None;
}
if let [first_part, second_part] = &mail.subparts[..] {
ensure!(
first_part.ctype.mimetype == "application/pgp-encrypted",
"Invalid Autocrypt Level 1 version part: {:?}",
first_part.ctype,
);
ensure!(
second_part.ctype.mimetype == "application/octet-stream",
"Invalid Autocrypt Level 1 encrypted part: {:?}",
second_part.ctype
);
Ok(second_part)
if first_part.ctype.mimetype == "application/pgp-encrypted"
&& second_part.ctype.mimetype == "application/octet-stream"
{
Some(second_part)
} else {
None
}
} 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>,
ret_valid_signatures: &mut HashSet<Fingerprint>,
) -> Result<Option<Vec<u8>>> {
let encrypted_data_part = match get_autocrypt_mime(mail) {
Err(_) => {
let encrypted_data_part = match get_autocrypt_mime(mail).or_else(|| get_mixed_up_mime(mail)) {
None => {
// not an autocrypt mime message, abort and ignore
return Ok(None);
}
Ok(res) => res,
Some(res) => res,
};
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, 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(())
}
}

View File

@@ -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))
Push | IMAP IDLE ([RFC 2177](https://tools.ietf.org/html/rfc2177))
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)
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))

View 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--

View 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--