summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-09-25 23:12:38 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-09-26 13:11:08 +0300
commitf27b815aa79e234c299a4a1255c7f001ec68e1cd (patch)
treee39407cb7ede95e978f10aa6579b3926871024ca
parent9305e543cf04ee2e53c989d2f29a6ea0b57092c4 (diff)
Add verification of GPG signed messages
-rw-r--r--melib/src/email.rs1
-rw-r--r--melib/src/email/attachments.rs22
-rw-r--r--melib/src/email/signatures.rs129
-rw-r--r--ui/src/components/mail/view.rs49
4 files changed, 190 insertions, 11 deletions
diff --git a/melib/src/email.rs b/melib/src/email.rs
index 4c4dcd05..ca69f928 100644
--- a/melib/src/email.rs
+++ b/melib/src/email.rs
@@ -35,6 +35,7 @@ mod address;
pub mod parser;
use crate::parser::BytesExt;
pub use address::*;
+pub mod signatures;
use crate::backends::BackendOp;
use crate::error::{MeliError, Result};
diff --git a/melib/src/email/attachments.rs b/melib/src/email/attachments.rs
index 06cbf4f4..bbe9cc38 100644
--- a/melib/src/email/attachments.rs
+++ b/melib/src/email/attachments.rs
@@ -411,7 +411,7 @@ impl Attachment {
fn get_text_recursive(&self, text: &mut Vec<u8>) {
match self.content_type {
- ContentType::Text { .. } => {
+ ContentType::Text { .. } | ContentType::PGPSignature => {
text.extend(decode(self, None));
}
ContentType::Multipart {
@@ -536,6 +536,16 @@ impl Attachment {
_ => false,
}
}
+
+ pub fn is_signed(&self) -> bool {
+ match self.content_type {
+ ContentType::Multipart {
+ kind: MultipartType::Signed,
+ ..
+ } => true,
+ _ => false,
+ }
+ }
}
pub fn interpret_format_flowed(_t: &str) -> String {
@@ -559,7 +569,7 @@ fn decode_rec_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) ->
.unwrap_or_else(|| a.mime_type())
.to_string()
.into_bytes(),
- ContentType::PGPSignature => a.content_type.to_string().into_bytes(),
+ ContentType::PGPSignature => Vec::new(),
ContentType::MessageRfc822 => {
let temp = decode_rfc822(a.body());
decode_rec(&temp, None)
@@ -580,6 +590,14 @@ fn decode_rec_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) ->
}
decode_helper(a, filter)
}
+ MultipartType::Signed => {
+ let mut vec = Vec::new();
+ for a in parts {
+ vec.extend(decode_rec_helper(a, filter));
+ }
+ vec.extend(decode_helper(a, filter));
+ vec
+ }
_ => {
let mut vec = Vec::new();
for a in parts {
diff --git a/melib/src/email/signatures.rs b/melib/src/email/signatures.rs
new file mode 100644
index 00000000..cf6ce4ac
--- /dev/null
+++ b/melib/src/email/signatures.rs
@@ -0,0 +1,129 @@
+/*
+ * meli - email module.
+ *
+ * Copyright 2019 Manos Pitsidianakis
+ *
+ * This file is part of meli.
+ *
+ * meli is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * meli is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with meli. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use crate::email::attachments::{Attachment, ContentType, MultipartType};
+use crate::email::parser::BytesExt;
+use crate::{MeliError, Result};
+
+/// rfc3156
+/// Upon receipt of a signed message, an application MUST:
+///
+/// (1) Convert line endings to the canonical <CR><LF> sequence before
+/// the signature can be verified. This is necessary since the
+/// local MTA may have converted to a local end of line convention.
+/// (2) Pass both the signed data and its associated content headers
+/// along with the OpenPGP signature to the signature verification
+/// service.
+///
+pub fn convert_attachment_to_rfc_spec(input: &[u8]) -> Vec<u8> {
+ if input.is_empty() {
+ return Vec::new();
+ }
+ let mut ret = Vec::with_capacity(input.len());
+
+ if input[0] == b'\n' {
+ /* This is an RFC violation but test for it anyway */
+ ret.push(b'\r');
+ ret.push(b'\n');
+ } else {
+ ret.push(input[0]);
+ }
+
+ let mut ctr = 1;
+
+ while ctr < input.len() {
+ if input[ctr] == b'\r' && ctr + 1 < input.len() && input[ctr + 1] == b'\n' {
+ ret.push(b'\r');
+ ret.push(b'\n');
+ ctr += 2;
+ } else if input[ctr] == b'\n' {
+ ret.push(b'\r');
+ ret.push(b'\n');
+ ctr += 1;
+ } else {
+ ret.push(input[ctr]);
+ ctr += 1;
+ }
+ }
+ loop {
+ match ret.iter().last() {
+ None => {
+ break;
+ }
+ Some(b'\n') | Some(b'\r') => {
+ ret.pop();
+ }
+ _ => {
+ break;
+ }
+ }
+ }
+ ret.push(0x0d);
+ ret.push(0x0a);
+ ret
+}
+
+pub fn verify_signature(a: &Attachment) -> Result<(Vec<u8>, &[u8])> {
+ match a.content_type {
+ ContentType::Multipart {
+ kind: MultipartType::Signed,
+ ref parts,
+ boundary: _,
+ } => {
+ if parts.len() != 2 {
+ return Err(MeliError::new(format!(
+ "Illegal number of parts in multipart/signed. Expected 2 got {}",
+ parts.len()
+ )));
+ }
+
+ let part_boundaries = a.part_boundaries();
+
+ let signed_part: Vec<u8> = if let Some(v) = parts
+ .iter()
+ .zip(part_boundaries.iter())
+ .find(|(p, _)| p.content_type != ContentType::PGPSignature)
+ .map(|(_, s)| convert_attachment_to_rfc_spec(s.display_bytes(a.body())))
+ {
+ v
+ } else {
+ return Err(MeliError::new(
+ "multipart/signed attachment without a signed part".to_string(),
+ ));
+ };
+ let signature = if let Some(sig) = parts
+ .iter()
+ .find(|s| s.content_type == ContentType::PGPSignature)
+ .map(|a| a.body())
+ {
+ sig.trim()
+ } else {
+ return Err(MeliError::new(
+ "multipart/signed attachment without a signature part".to_string(),
+ ));
+ };
+ Ok((signed_part, signature))
+ }
+ _ => {
+ unreachable!("Should not give non-signed attachments to this function");
+ }
+ }
+}
diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs
index 5385e035..154bd01a 100644
--- a/ui/src/components/mail/view.rs
+++ b/ui/src/components/mail/view.rs
@@ -193,6 +193,45 @@ impl MailView {
return;
}
}
+ } else if a.is_signed() {
+ v.clear();
+ match melib::signatures::verify_signature(a) {
+ Ok((bytes, sig)) => {
+ let bytes_file = create_temp_file(&bytes, None, None, true);
+ let signature_file = create_temp_file(sig, None, None, true);
+ if let Ok(gpg) = Command::new("gpg2")
+ .args(&[
+ "--output",
+ "-",
+ "--verify",
+ signature_file.path.to_str().unwrap(),
+ bytes_file.path.to_str().unwrap(),
+ ])
+ .stdin(Stdio::piped())
+ .stderr(Stdio::piped())
+ .spawn()
+ {
+ v.extend(gpg.wait_with_output().unwrap().stderr);
+ } else {
+ context.replies.push_back(UIEvent::Notification(
+ Some(
+ "Failed to find an application to verify PGP signature"
+ .to_string(),
+ ),
+ String::new(),
+ Some(NotificationType::ERROR),
+ ));
+ return;
+ }
+ }
+ Err(e) => {
+ context.replies.push_back(UIEvent::Notification(
+ Some(e.to_string()),
+ String::new(),
+ Some(NotificationType::ERROR),
+ ));
+ }
+ }
}
})),
))
@@ -765,7 +804,7 @@ impl Component for MailView {
return true;
}
- ContentType::Text { .. } => {
+ ContentType::Text { .. } | ContentType::PGPSignature => {
self.mode = ViewMode::Attachment(lidx);
self.dirty = true;
}
@@ -823,14 +862,6 @@ impl Component for MailView {
));
return true;
}
- ContentType::PGPSignature => {
- context.replies.push_back(UIEvent::StatusEvent(
- StatusEvent::DisplayMessage(
- "Signatures aren't supported yet".to_string(),
- ),
- ));
- return true;
- }
}
} else {
context.replies.push_back(UIEvent::StatusEvent(