use crate::{
crypto::{self, HashAlgorithm, SigningKey},
header::{FieldName, HeaderFields},
message_hash::{self, BodyHashError, BodyHashResults},
signer::{
self,
format::{self, UnsignedDkimSignature},
BodyLength, Expiration, HeaderSelection, OutputFormat, SignRequest, SigningError,
SigningOutput, SigningResult, Timestamp,
},
};
use std::{collections::HashSet, time::SystemTime};
use tracing::trace;
pub async fn perform_signing<T>(
request: SignRequest<T>,
headers: &HeaderFields,
bh_results: &BodyHashResults,
) -> SigningResult
where
T: AsRef<SigningKey>,
{
let domain = request.domain;
let selector = request.selector;
let algorithm = request.algorithm;
let canonicalization = request.canonicalization;
trace!(%domain, %selector, "performing signing");
let body_length = request.body_length.to_usize().expect("unsupported integer conversion");
let hash_alg = algorithm.hash_algorithm();
let key = (body_length, hash_alg, canonicalization.body);
let bh_result = bh_results.get(&key)
.expect("requested body hash result not available");
let (body_hash, final_len) = match bh_result {
Ok((h, final_len)) => (h.clone(), *final_len),
Err(BodyHashError::InsufficientInput) => {
trace!("not signing, got insufficient message body content");
return Err(SigningError::InsufficientContent);
}
Err(BodyHashError::InputTruncated) => {
panic!("unexpected canonicalization error");
}
};
let body_length = match request.body_length {
BodyLength::NoLimit => None,
BodyLength::MessageContent | BodyLength::Exact(_) => match final_len.try_into() {
Ok(n) => Some(n),
Err(_) => {
trace!("not signing, message too large");
return Err(SigningError::Overflow);
}
},
};
let signed_headers = match &request.header_selection {
HeaderSelection::Auto => select_signed_headers(headers).into_iter().cloned().collect(),
HeaderSelection::Manual(h) => h.clone(),
};
assert!(signed_headers.iter().any(|name| *name == "From"));
assert!(!signed_headers.iter().any(|name| name.as_ref().contains(';')));
let (timestamp, expiration) =
compute_t_and_x(request.timestamp, request.expiration, now_unix_secs);
let copied_headers = if request.copy_headers {
let copied_headers = prepare_copied_headers(headers, &signed_headers);
Box::from(copied_headers)
} else {
[].into()
};
let ext_tags = request.ext_tags.into_iter()
.map(|(k, v)| (k.into_boxed_str(), v.into_boxed_str()))
.collect();
let sig = UnsignedDkimSignature {
algorithm,
body_hash,
canonicalization,
domain,
signed_headers: signed_headers.into(),
identity: request.identity,
body_length,
selector,
timestamp,
expiration,
copied_headers,
ext_tags,
};
produce_signature(
sig,
request.signing_key.as_ref(),
&request.format,
headers,
).await
}
fn select_signed_headers(headers: &HeaderFields) -> Vec<&FieldName> {
let def: HashSet<_> = signer::default_signed_headers().into_iter().collect();
signer::select_headers(headers, move |name| def.contains(name)).collect()
}
fn compute_t_and_x(
timestamp: Timestamp,
expiration: Expiration,
now_secs: fn() -> u64,
) -> (Option<u64>, Option<u64>) {
let mut timestamp = match timestamp {
Timestamp::None => None,
Timestamp::Now => Some(now_secs()),
Timestamp::Exact(t) => Some(t),
};
let expiration = match expiration {
Expiration::Never => None,
Expiration::After(duration) => {
let s = duration.as_secs();
Some(timestamp.unwrap_or_else(now_secs).saturating_add(s))
}
Expiration::Exact(x) => Some(x),
};
if let (Some(t), Some(x)) = (timestamp, expiration) {
assert!(x > 0);
if t >= x {
timestamp = Some(x - 1);
}
}
(timestamp, expiration)
}
fn now_unix_secs() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |t| t.as_secs())
}
fn prepare_copied_headers(
headers: &HeaderFields,
selected_headers: &[FieldName],
) -> Vec<(FieldName, Box<[u8]>)> {
let mut result = vec![];
for (name, value) in headers.as_ref() {
if selected_headers.contains(name) {
result.push((name.clone(), value.as_ref().into()));
}
}
result
}
async fn produce_signature(
sig: UnsignedDkimSignature,
signing_key: &SigningKey,
format: &OutputFormat,
headers: &HeaderFields,
) -> SigningResult {
let b_len = estimate_b_tag_length(signing_key);
let (mut formatted_header_value, insertion_index) = sig.format_without_signature(format, b_len);
let header_name = &format.header_name;
let algorithm = sig.algorithm;
let hash_alg = algorithm.hash_algorithm();
let data_hash = message_hash::compute_data_hash(
hash_alg,
sig.canonicalization.header,
headers,
&sig.signed_headers,
header_name,
&formatted_header_value,
);
assert_eq!(signing_key.key_type(), algorithm.key_type());
let signature_data = sign_hash(signing_key, hash_alg, &data_hash)
.await?
.into_boxed_slice();
let sig = sig.into_signature(signature_data);
format::insert_signature_data(
&mut formatted_header_value,
insertion_index,
header_name,
&sig.signature_data[..],
format.line_width.into(),
&format.indentation,
);
Ok(SigningOutput {
header_name: header_name.into(),
header_value: formatted_header_value,
signature: sig,
})
}
fn estimate_b_tag_length(signing_key: &SigningKey) -> usize {
let n = signing_key.signature_length();
(n + 2) / 3 * 4
}
async fn sign_hash(
signing_key: &SigningKey,
hash_alg: HashAlgorithm,
data_hash: &[u8],
) -> Result<Vec<u8>, SigningError> {
match signing_key {
SigningKey::Rsa(k) => match crypto::sign_rsa(hash_alg, k, data_hash) {
Ok(s) => {
trace!("RSA signing successful");
Ok(s)
}
Err(e) => {
trace!("RSA signing failed: {e}");
Err(SigningError::SigningFailure)
}
},
SigningKey::Ed25519(k) => {
let s = crypto::sign_ed25519(k, data_hash);
trace!("Ed25519 signing successful");
Ok(s)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn compute_t_and_x_ok() {
use super::{Expiration as X, Timestamp as T};
let secs = Duration::from_secs;
let compute2 = |t, x| compute_t_and_x(t, x, || unreachable!());
let compute3 = compute_t_and_x;
assert_eq!(compute2(T::None, X::Never), (None, None));
assert_eq!(compute2(T::None, X::Exact(3)), (None, Some(3)));
assert_eq!(compute2(T::Exact(1), X::After(secs(3))), (Some(1), Some(4)));
assert_eq!(compute2(T::Exact(1), X::Exact(3)), (Some(1), Some(3)));
assert_eq!(compute3(T::None, X::After(secs(3)), || 1), (None, Some(4)));
assert_eq!(compute3(T::Now, X::Never, || 1), (Some(1), None));
assert_eq!(compute3(T::Now, X::After(secs(3)), || 1), (Some(1), Some(4)));
assert_eq!(compute3(T::Now, X::Exact(3), || 1), (Some(1), Some(3)));
assert_eq!(compute3(T::Now, X::Exact(3), || 3), (Some(2), Some(3)));
}
}