159 lines
4.7 KiB
Rust
159 lines
4.7 KiB
Rust
/*
|
|
* 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/>.
|
|
*/
|
|
|
|
/*! Verification of OpenPGP signatures */
|
|
use crate::email::{
|
|
attachment_types::{ContentType, MultipartType},
|
|
attachments::Attachment,
|
|
};
|
|
use crate::{MeliError, Result};
|
|
|
|
/// Convert raw attachment to the form needed for signature verification ([rfc3156](https://tools.ietf.org/html/rfc3156))
|
|
///
|
|
/// ## rfc3156
|
|
/// ```text
|
|
/// 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>, &Attachment)> {
|
|
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
|
|
&& p.content_type != ContentType::CMSSignature
|
|
})
|
|
.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
|
|
|| s.content_type == ContentType::CMSSignature
|
|
}) {
|
|
sig
|
|
} else {
|
|
return Err(MeliError::new(
|
|
"multipart/signed attachment without a signature part".to_string(),
|
|
));
|
|
};
|
|
Ok((signed_part, signature))
|
|
}
|
|
_ => Err(MeliError::new(
|
|
"Should not give non-signed attachments to this function",
|
|
)),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct DecryptionMetadata {
|
|
pub recipients: Vec<Recipient>,
|
|
pub file_name: Option<String>,
|
|
pub session_key: Option<String>,
|
|
pub is_mime: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Recipient {
|
|
pub keyid: Option<String>,
|
|
pub status: Result<()>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct SignatureMetadata {
|
|
pub signatures: Vec<Recipient>,
|
|
pub file_name: Option<String>,
|
|
pub is_mime: bool,
|
|
}
|