dkim: remove another extraneous allocation, tidy up canon interface

This commit is contained in:
Wez Furlong
2023-06-15 13:13:28 -07:00
parent 28f0786e42
commit cf11540f84
5 changed files with 67 additions and 126 deletions
+4 -3
View File
@@ -1,5 +1,5 @@
use cfdkim::canonicalization::Type;
use cfdkim::{DkimPrivateKey, SignerBuilder};
use cfdkim::{DkimPrivateKey, ParsedEmail, SignerBuilder};
use chrono::TimeZone;
use criterion::{black_box, criterion_group, criterion_main, Criterion, SamplingMode, Throughput};
use rsa::pkcs1::DecodeRsaPrivateKey;
@@ -48,10 +48,11 @@ suscipit mattis risus non placerat. Sed imperdiet, nisi et laoreet imperdiet, ur
ante, non ultricies enim lorem cursus ante. Mauris euismod turpis eu tristique lobortis. Mauris
aliquet eu tortor nec hendrerit. Aliquam in arcu ac erat venenatis pretium at sit amet magna. Lorem
ipsum dolor sit a.
"#;
"#
.replace("\n", "\r\n");
for canon in [Type::Simple, Type::Relaxed] {
let email = mailparse::parse_mail(email_text.as_bytes()).unwrap();
let email = ParsedEmail::parse_bytes(email_text.as_bytes()).unwrap();
let private_key =
rsa::RsaPrivateKey::read_pkcs1_pem_file(Path::new("./test/keys/2022.private")).unwrap();
-62
View File
@@ -1,62 +0,0 @@
///! Various utility functions to operate on bytes
pub(crate) use memchr::memmem::find;
pub(crate) fn replace(bytes: &mut [u8], from: u8, to: u8) {
let mut previous = 0;
while let Some(idx) = memchr::memchr(from, &bytes[previous..]) {
bytes[previous + idx] = to;
previous = idx + 1;
}
}
pub(crate) fn replace_within_vec(result: &mut Vec<u8>, from: &[u8], to: &[u8]) {
let from_len = from.len();
let to_len = to.len();
let mut i = 0;
while let Some(idx) = find(&result[i..], from) {
result.splice(idx + i..idx + i + from_len, to.iter().cloned());
i += idx + to_len;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn replace_slice(source: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {
let mut result = source.to_vec();
replace_within_vec(&mut result, from, to);
result
}
#[test]
fn it_find() {
assert_eq!(find(&[97, 98, 99], &[1]), None);
assert_eq!(find(&[97, 98, 99], &[97]), Some(0));
assert_eq!(find(&[97, 98, 99], &[97, 98]), Some(0));
}
#[test]
fn test_replace() {
let mut data = b"abbcb".to_vec();
replace(&mut data, b'b', b'_');
assert_eq!(data, b"a__c_");
}
#[test]
fn it_replace_slice() {
let source = "aba".as_bytes();
assert_eq!(replace_slice(source, &[97], &[99]), "cbc".as_bytes());
assert_eq!(replace_slice(source, &[97, 98], &[]), "a".as_bytes());
let source = "hello\r\nthere\r\n".as_bytes();
assert_eq!(replace_slice(source, b"\r\n", b""), "hellothere".as_bytes());
let source = "hello there".as_bytes();
assert_eq!(
replace_slice(source, b"\r\n", b""),
"hello there".as_bytes()
);
}
}
+59 -41
View File
@@ -1,5 +1,4 @@
//! Inspired from https://docs.rs/dkim/latest/src/dkim/canonicalization.rs.html
use crate::bytes;
use crate::hash::LimitHasher;
use memchr::memmem::Finder;
use once_cell::sync::Lazy;
@@ -17,6 +16,20 @@ impl Type {
Self::Relaxed => "relaxed",
}
}
pub(crate) fn canon_body(&self, body: &[u8], hasher: &mut LimitHasher) {
match self {
Self::Simple => body_simple(body, hasher),
Self::Relaxed => body_relaxed(body, hasher),
}
}
pub(crate) fn canon_header_into(&self, key: &str, value: &[u8], out: &mut Vec<u8>) {
match self {
Self::Simple => canonicalize_header_simple(key, value, out),
Self::Relaxed => canonicalize_header_relaxed(key, value, out),
}
}
}
fn do_body_simple<'a>(mut body: &'a [u8]) -> &'a [u8] {
@@ -32,7 +45,7 @@ fn do_body_simple<'a>(mut body: &'a [u8]) -> &'a [u8] {
}
/// Canonicalize body using the simple canonicalization algorithm.
pub(crate) fn canonicalize_body_simple(body: &[u8], hasher: &mut LimitHasher) {
fn body_simple(body: &[u8], hasher: &mut LimitHasher) {
let body = do_body_simple(body);
hasher.hash(body);
}
@@ -84,7 +97,7 @@ fn iter_lines(haystack: &[u8]) -> IterLines {
/// https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.3
/// Canonicalize body using the relaxed canonicalization algorithm.
pub(crate) fn apply_body_relaxed(mut body: &[u8], hasher: &mut LimitHasher) {
fn body_relaxed(mut body: &[u8], hasher: &mut LimitHasher) {
if body.is_empty() {
return;
}
@@ -96,14 +109,7 @@ pub(crate) fn apply_body_relaxed(mut body: &[u8], hasher: &mut LimitHasher) {
for mut line in iter_lines(body) {
// Ignore all whitespace at the end of the line
while let Some(c) = line.last() {
match c {
b' ' | b'\t' | b'\r' | b'\n' => {
line = &line[0..line.len() - 1];
}
_ => break,
}
}
line = trim_ws_end(line);
let mut prior = 0;
// Reduce all sequences of WSP within a line to a single SP character.
@@ -131,7 +137,7 @@ pub(crate) fn apply_body_relaxed(mut body: &[u8], hasher: &mut LimitHasher) {
}
// https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.1
pub(crate) fn canonicalize_header_simple(key: &str, value: &[u8], out: &mut Vec<u8>) {
fn canonicalize_header_simple(key: &str, value: &[u8], out: &mut Vec<u8>) {
out.extend_from_slice(key.as_bytes());
out.extend_from_slice(b": ");
out.extend_from_slice(value);
@@ -139,43 +145,55 @@ pub(crate) fn canonicalize_header_simple(key: &str, value: &[u8], out: &mut Vec<
}
// https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.2
pub(crate) fn canonicalize_header_relaxed(key: &str, value: &[u8], out: &mut Vec<u8>) {
fn canonicalize_header_relaxed(key: &str, value: &[u8], out: &mut Vec<u8>) {
let key = key.to_lowercase();
let key = key.trim_end();
out.extend_from_slice(key.as_bytes());
out.extend_from_slice(b":");
out.extend_from_slice(&canonicalize_header_value_relaxed(value));
let value = trim_ws_start(trim_ws_end(value));
let mut space_run = false;
for &c in value {
match c {
b'\r' | b'\n' => {}
b' ' | b'\t' => {
if space_run {
continue;
}
space_run = true;
out.push(b' ');
}
_ => {
space_run = false;
out.push(c);
}
}
}
out.extend_from_slice(b"\r\n");
}
fn canonicalize_header_value_relaxed(value: &[u8]) -> Vec<u8> {
let mut value = value.to_vec();
bytes::replace(&mut value, b'\t', b' ');
bytes::replace_within_vec(&mut value, b"\r\n", b"");
while value.ends_with(b" ") {
value.remove(value.len() - 1);
}
while value.starts_with(b" ") {
value.remove(0);
}
let mut previous = false;
value.retain(|c| {
if *c == b' ' {
if previous {
false
} else {
previous = true;
true
}
} else {
previous = false;
true
fn trim_ws_start(mut line: &[u8]) -> &[u8] {
while let Some(c) = line.first() {
match c {
b' ' | b'\t' | b'\r' | b'\n' => line = &line[1..],
_ => break,
}
});
}
line
}
value
fn trim_ws_end(mut line: &[u8]) -> &[u8] {
while let Some(c) = line.last() {
match c {
b' ' | b'\t' | b'\r' | b'\n' => {
line = &line[0..line.len() - 1];
}
_ => break,
}
}
line
}
#[cfg(test)]
@@ -207,7 +225,7 @@ mod tests {
limit: usize::MAX,
hashed: 0,
};
apply_body_relaxed(data, &mut hasher);
super::body_relaxed(data, &mut hasher);
hasher.finalize_bytes()
}
@@ -217,7 +235,7 @@ mod tests {
limit: usize::MAX,
hashed: 0,
};
canonicalize_body_simple(data, &mut hasher);
super::body_simple(data, &mut hasher);
hasher.finalize_bytes()
}
+4 -19
View File
@@ -1,9 +1,5 @@
use crate::canonicalization::{
self, apply_body_relaxed, canonicalize_body_simple, canonicalize_header_relaxed,
canonicalize_header_simple,
};
use crate::header::HEADER;
use crate::{DKIMError, DKIMHeader, ParsedEmail};
use crate::{canonicalization, DKIMError, DKIMHeader, ParsedEmail};
use base64::engine::general_purpose;
use base64::Engine;
use sha1::{Digest as _, Sha1};
@@ -123,10 +119,7 @@ pub(crate) fn compute_body_hash<'a>(
hashed: 0,
};
match canonicalization_type {
canonicalization::Type::Simple => canonicalize_body_simple(body, &mut hasher),
_ => apply_body_relaxed(body, &mut hasher),
};
canonicalization_type.canon_body(body, &mut hasher);
Ok(hasher.finalize())
}
@@ -174,11 +167,7 @@ pub(crate) fn compute_headers_hash<'a, 'b>(
// Add the headers defined in `h=` in the hash
for (key, value) in select_headers(headers, email)? {
if canonicalization_type == canonicalization::Type::Simple {
canonicalize_header_simple(&key, value, &mut input);
} else {
canonicalize_header_relaxed(&key, value, &mut input);
}
canonicalization_type.canon_header_into(&key, value, &mut input);
}
// Add the DKIM-Signature header in the hash. Remove the value of the
@@ -187,11 +176,7 @@ pub(crate) fn compute_headers_hash<'a, 'b>(
let sign = dkim_header.get_required_raw_tag("b");
let value = dkim_header.raw_bytes.replace(&sign, "");
let mut canonicalized_value = vec![];
if canonicalization_type == canonicalization::Type::Simple {
canonicalize_header_simple(HEADER, value.as_bytes(), &mut canonicalized_value);
} else {
canonicalize_header_relaxed(HEADER, value.as_bytes(), &mut canonicalized_value);
};
canonicalization_type.canon_header_into(HEADER, value.as_bytes(), &mut canonicalized_value);
// remove trailing "\r\n"
canonicalized_value.truncate(canonicalized_value.len() - 2);
-1
View File
@@ -13,7 +13,6 @@ use mailparse::MailHeaderMap;
#[macro_use]
extern crate quick_error;
mod bytes;
pub mod canonicalization;
pub mod dns;
mod errors;