Add support for encrypted and signed multipart emails (#444)

Co-authored-by: Tim Anderson <tim@claritynetworks.com.au>
This commit is contained in:
Paolo Barbolini
2020-08-02 21:43:24 +02:00
parent 4b693e2ae3
commit 72015da467

View File

@@ -176,7 +176,7 @@ impl EmailFormat for SinglePart {
/// The kind of multipart
///
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub enum MultiPartKind {
/// Mixed kind to combine unrelated content parts
///
@@ -192,6 +192,12 @@ pub enum MultiPartKind {
///
/// For example, you can include images into HTML content using that.
Related,
/// Encrypted kind for encrypted messages
Encrypted { protocol: String },
/// Signed kind for signed messages
Signed { protocol: String, micalg: String },
}
/// Create a random MIME boundary.
@@ -210,13 +216,21 @@ impl MultiPartKind {
use self::MultiPartKind::*;
format!(
"multipart/{}; boundary=\"{}\"",
"multipart/{}; boundary=\"{}\"{}",
match self {
Mixed => "mixed",
Alternative => "alternative",
Related => "related",
Encrypted { .. } => "encrypted",
Signed { .. } => "signed",
},
boundary
boundary,
match self {
Encrypted { protocol } => format!("; protocol=\"{}\"", protocol),
Signed { protocol, micalg } =>
format!("; protocol=\"{}\"; micalg=\"{}\"", protocol, micalg),
_ => String::new(),
}
)
.parse()
.unwrap()
@@ -228,6 +242,15 @@ impl MultiPartKind {
"mixed" => Some(Mixed),
"alternative" => Some(Alternative),
"related" => Some(Related),
"signed" => m.get_param("protocol").and_then(|p| {
m.get_param("micalg").map(|micalg| Signed {
protocol: p.as_str().to_owned(),
micalg: micalg.as_str().to_owned(),
})
}),
"encrypted" => m.get_param("protocol").map(|p| Encrypted {
protocol: p.as_str().to_owned(),
}),
_ => None,
}
}
@@ -340,6 +363,20 @@ impl MultiPart {
MultiPart::builder().kind(MultiPartKind::Related)
}
/// Creates encrypted multipart builder
///
/// Shortcut for `MultiPart::builder().kind(MultiPartKind::Encrypted{ protocol })`
pub fn encrypted(protocol: String) -> MultiPartBuilder {
MultiPart::builder().kind(MultiPartKind::Encrypted { protocol })
}
/// Creates signed multipart builder
///
/// Shortcut for `MultiPart::builder().kind(MultiPartKind::Signed{ protocol, micalg })`
pub fn signed(protocol: String, micalg: String) -> MultiPartBuilder {
MultiPart::builder().kind(MultiPartKind::Signed { protocol, micalg })
}
/// Add part to multipart
pub fn part(mut self, part: Part) -> Self {
self.parts.push(part);
@@ -524,6 +561,122 @@ mod test {
"int main() { return 0; }\r\n",
"--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK--\r\n"));
}
#[test]
fn multi_part_encrypted() {
let part = MultiPart::encrypted("application/pgp-encrypted".to_owned())
.boundary("F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK")
.part(Part::Single(
SinglePart::builder()
.header(header::ContentType(
"application/pgp-encrypted".parse().unwrap(),
))
.body(String::from("Version: 1")),
))
.singlepart(
SinglePart::builder()
.header(ContentType(
"application/octet-stream; name=\"encrypted.asc\""
.parse()
.unwrap(),
))
.header(header::ContentDisposition {
disposition: header::DispositionType::Inline,
parameters: vec![header::DispositionParam::Filename(
header::Charset::Ext("utf-8".into()),
None,
"encrypted.asc".as_bytes().into(),
)],
})
.body(String::from(concat!(
"-----BEGIN PGP MESSAGE-----\r\n",
"wV4D0dz5vDXklO8SAQdA5lGX1UU/eVQqDxNYdHa7tukoingHzqUB6wQssbMfHl8w\r\n",
"...\r\n",
"-----END PGP MESSAGE-----\r\n"
))),
);
assert_eq!(String::from_utf8(part.formatted()).unwrap(),
concat!("Content-Type: multipart/encrypted;",
" boundary=\"F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\";",
" protocol=\"application/pgp-encrypted\"\r\n",
"\r\n",
"--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
"Content-Type: application/pgp-encrypted\r\n",
"\r\n",
"Version: 1\r\n",
"--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
"Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n",
"Content-Disposition: inline; filename=\"encrypted.asc\"\r\n",
"\r\n",
"-----BEGIN PGP MESSAGE-----\r\n",
"wV4D0dz5vDXklO8SAQdA5lGX1UU/eVQqDxNYdHa7tukoingHzqUB6wQssbMfHl8w\r\n",
"...\r\n",
"-----END PGP MESSAGE-----\r\n",
"\r\n",
"--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK--\r\n"));
}
#[test]
fn multi_part_signed() {
let part = MultiPart::signed(
"application/pgp-signature".to_owned(),
"pgp-sha256".to_owned(),
)
.boundary("F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK")
.part(Part::Single(
SinglePart::builder()
.header(header::ContentType("text/plain".parse().unwrap()))
.body(String::from("Test email for signature")),
))
.singlepart(
SinglePart::builder()
.header(ContentType(
"application/pgp-signature; name=\"signature.asc\""
.parse()
.unwrap(),
))
.header(header::ContentDisposition {
disposition: header::DispositionType::Attachment,
parameters: vec![header::DispositionParam::Filename(
header::Charset::Ext("utf-8".into()),
None,
"signature.asc".as_bytes().into(),
)],
})
.body(String::from(concat!(
"-----BEGIN PGP SIGNATURE-----\r\n",
"\r\n",
"iHUEARYIAB0WIQTNsp3S/GbdE0KoiQ+IGQOscREZuQUCXyOzDAAKCRCIGQOscREZ\r\n",
"udgDAQCv3FJ3QWW5bRaGZAa0Ug6vASFdkvDMKoRwcoFnHPthjQEAiQ8skkIyE2GE\r\n",
"PoLpAXiKpT+NU8S8+8dfvwutnb4dSwM=\r\n",
"=3FYZ\r\n",
"-----END PGP SIGNATURE-----\r\n",
))),
);
assert_eq!(String::from_utf8(part.formatted()).unwrap(),
concat!("Content-Type: multipart/signed;",
" boundary=\"F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\";",
" protocol=\"application/pgp-signature\";",
" micalg=\"pgp-sha256\"\r\n",
"\r\n",
"--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
"Content-Type: text/plain\r\n",
"\r\n",
"Test email for signature\r\n",
"--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK\r\n",
"Content-Type: application/pgp-signature; name=\"signature.asc\"\r\n",
"Content-Disposition: attachment; filename=\"signature.asc\"\r\n",
"\r\n",
"-----BEGIN PGP SIGNATURE-----\r\n",
"\r\n",
"iHUEARYIAB0WIQTNsp3S/GbdE0KoiQ+IGQOscREZuQUCXyOzDAAKCRCIGQOscREZ\r\n",
"udgDAQCv3FJ3QWW5bRaGZAa0Ug6vASFdkvDMKoRwcoFnHPthjQEAiQ8skkIyE2GE\r\n",
"PoLpAXiKpT+NU8S8+8dfvwutnb4dSwM=\r\n",
"=3FYZ\r\n",
"-----END PGP SIGNATURE-----\r\n",
"\r\n",
"--F2mTKN843loAAAAA8porEdAjCKhArPxGeahYoZYSftse1GT/84tup+O0bs8eueVuAlMK--\r\n"));
}
#[test]
fn multi_part_alternative() {