Add verification of GPG signed messages
parent
9305e543cf
commit
f27b815aa7
|
@ -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};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue