Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract Resolve trait, call async function in sync context #2

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "mail-auth"
description = "DKIM, ARC, SPF and DMARC library for Rust"
version = "0.5.0"
edition = "2021"
authors = [ "Stalwart Labs <[email protected]>"]
authors = ["Stalwart Labs <[email protected]>"]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/stalwartlabs/mail-auth"
homepage = "https://github.com/stalwartlabs/mail-auth"
Expand Down Expand Up @@ -40,7 +40,7 @@ sha2 = { version = "0.10.6", features = ["oid"], optional = true }
hickory-resolver = { version = "0.24", features = ["dns-over-rustls", "dnssec-ring"] }
zip = "2.1.1"
rand = { version = "0.8.5", optional = true }

futures = { version = "0.3.30", features = ["executor"] }
[dev-dependencies]
tokio = { version = "1.16", features = ["net", "io-util", "time", "rt-multi-thread", "macros"] }
rustls-pemfile = "2"
Expand Down
2 changes: 1 addition & 1 deletion src/arc/seal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ mod test {
pk: impl SigningKey<Hasher=Sha256>,
) -> String {
let message = AuthenticatedMessage::parse(raw_message.as_bytes()).unwrap();
let dkim_result = DkimVerifier::verify_dkim(&resolver, &message).await;
let dkim_result = DkimVerifier::verify_dkim(resolver, &message).await;
let arc_result = resolver.verify_arc(&message).await;
assert!(
matches!(arc_result.result(), DkimResult::Pass | DkimResult::None),
Expand Down
2 changes: 1 addition & 1 deletion src/arc/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
dkim::{verify::Verifier, Canonicalization},
ArcOutput, AuthenticatedMessage, DkimResult, Error, Resolver,
};

use crate::common::resolve::Resolve;
use super::{ChainValidation, Set};

impl Resolver {
Expand Down
1 change: 1 addition & 0 deletions src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub mod message;
pub mod parse;
pub mod resolver;
pub mod verify;
pub mod resolve;

impl From<Error> for IprevResult {
fn from(err: Error) -> Self {
Expand Down
65 changes: 65 additions & 0 deletions src/common/resolve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use std::sync::Arc;
use hickory_resolver::Name;
use std::borrow::Cow;
use crate::common::parse::TxtRecordParser;
use crate::{Error, Resolver, Txt};
use crate::common::lru::DnsCache;
#[cfg(any(test, feature = "test"))]
use crate::common::resolver;
use crate::common::resolver::{IntoFqdn, UnwrapTxtRecord};

impl Resolve for Resolver {
async fn txt_lookup<'x, T: TxtRecordParser + Into<Txt> + UnwrapTxtRecord>(
&self,
key: impl IntoFqdn<'x>,
) -> crate::Result<Arc<T>> {
let key = key.into_fqdn();
if let Some(value) = self.cache_txt.get(key.as_ref()) {
return T::unwrap_txt(value);
}

#[cfg(any(test, feature = "test"))]
if true {
return resolver::mock_resolve(key.as_ref());
}

let txt_lookup = self
.resolver
.txt_lookup(Name::from_str_relaxed(key.as_ref())?)
.await?;
let mut result = Err(Error::InvalidRecordType);
let records = txt_lookup.as_lookup().record_iter().filter_map(|r| {
let txt_data = r.data()?.as_txt()?.txt_data();
match txt_data.len() {
1 => Cow::from(txt_data[0].as_ref()).into(),
0 => None,
_ => {
let mut entry = Vec::with_capacity(255 * txt_data.len());
for data in txt_data {
entry.extend_from_slice(data);
}
Cow::from(entry).into()
}
}
});

for record in records {
result = T::parse(record.as_ref());
if result.is_ok() {
break;
}
}
T::unwrap_txt(self.cache_txt.insert(
key.into_owned(),
result.into(),
txt_lookup.valid_until(),
))
}
}

pub trait Resolve {
async fn txt_lookup<'x, T: TxtRecordParser + Into<Txt> + UnwrapTxtRecord>(
&self,
key: impl IntoFqdn<'x>,
) -> crate::Result<Arc<T>>;
}
48 changes: 0 additions & 48 deletions src/common/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ use crate::{

use super::{
lru::{DnsCache, LruCache},
parse::TxtRecordParser,
verify::DomainKey,
};

Expand Down Expand Up @@ -119,53 +118,6 @@ impl Resolver {
Ok(result)
}

pub async fn txt_lookup<'x, T: TxtRecordParser + Into<Txt> + UnwrapTxtRecord>(
&self,
key: impl IntoFqdn<'x>,
) -> crate::Result<Arc<T>> {
let key = key.into_fqdn();
if let Some(value) = self.cache_txt.get(key.as_ref()) {
return T::unwrap_txt(value);
}

#[cfg(any(test, feature = "test"))]
if true {
return mock_resolve(key.as_ref());
}

let txt_lookup = self
.resolver
.txt_lookup(Name::from_str_relaxed(key.as_ref())?)
.await?;
let mut result = Err(Error::InvalidRecordType);
let records = txt_lookup.as_lookup().record_iter().filter_map(|r| {
let txt_data = r.data()?.as_txt()?.txt_data();
match txt_data.len() {
1 => Cow::from(txt_data[0].as_ref()).into(),
0 => None,
_ => {
let mut entry = Vec::with_capacity(255 * txt_data.len());
for data in txt_data {
entry.extend_from_slice(data);
}
Cow::from(entry).into()
}
}
});

for record in records {
result = T::parse(record.as_ref());
if result.is_ok() {
break;
}
}
T::unwrap_txt(self.cache_txt.insert(
key.into_owned(),
result.into(),
txt_lookup.valid_until(),
))
}

pub async fn mx_lookup<'x>(&self, key: impl IntoFqdn<'x>) -> crate::Result<Arc<Vec<MX>>> {
let key = key.into_fqdn();
if let Some(value) = self.cache_mx.get(key.as_ref()) {
Expand Down
2 changes: 1 addition & 1 deletion src/dkim/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ pub mod test {
message.extend_from_slice(message_.as_bytes());

let message = AuthenticatedMessage::parse_with_opts(&message, strict).unwrap();
let dkim = DkimVerifier::verify_dkim(&resolver, &message).await;
let dkim = DkimVerifier::verify_dkim(resolver, &message).await;

match (dkim.last().unwrap().result(), &expect) {
(DkimResult::Pass, Ok(_)) => (),
Expand Down
67 changes: 53 additions & 14 deletions src/dkim/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,15 @@
* except according to those terms.
*/

use std::time::SystemTime;

use crate::{
common::{
base32::Base32Writer,
headers::Writer,
verify::{DomainKey, VerifySignature},
},
is_within_pct, AuthenticatedMessage, DkimOutput, DkimResult, Error, Resolver,
is_within_pct, AuthenticatedMessage, DkimOutput, DkimResult, Error,
};

use crate::common::resolve::Resolve;
use super::{
Atps, DomainKeyReport, Flag, HashAlgorithm, Signature, RR_DNS, RR_EXPIRATION, RR_OTHER,
RR_SIGNATURE, RR_VERIFICATION,
Expand All @@ -27,14 +25,12 @@ use super::{
pub struct DkimVerifier {}

impl DkimVerifier {
// TODO replace with argument
fn current_timestamp() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
1667843664
}

pub async fn verify_dkim<'x>(resolver: &Resolver, message: &'x AuthenticatedMessage<'x>) -> Vec<DkimOutput<'x>> {
pub async fn verify_dkim<'x, R: Resolve>(resolver: &R, message: &'x AuthenticatedMessage<'x>) -> Vec<DkimOutput<'x>> {
let now = Self::current_timestamp();

let mut output = Vec::with_capacity(message.dkim_headers.len());
Expand Down Expand Up @@ -383,11 +379,7 @@ mod test {
time::{Duration, Instant},
};

use crate::{
common::{parse::TxtRecordParser, verify::DomainKey},
dkim::verify::Verifier,
AuthenticatedMessage, DkimResult, Resolver,
};
use crate::{common::{parse::TxtRecordParser, verify::DomainKey}, dkim::verify::Verifier, AuthenticatedMessage, DkimResult, Resolver, Txt};
use crate::dkim::verify::DkimVerifier;

#[ignore]
Expand Down Expand Up @@ -416,6 +408,53 @@ mod test {
}
}

use std::future::{ready, Ready};
use std::sync::Arc;
use crate::common::resolve::Resolve;
use crate::common::resolver::{IntoFqdn, UnwrapTxtRecord};

struct MockResolver {
pub dns_records: String,
}

impl Resolve for MockResolver {
async fn txt_lookup<'x, T: TxtRecordParser + Into<Txt> + UnwrapTxtRecord>(
&self,
_key: impl IntoFqdn<'x>,
) -> Result<Arc<T>, super::Error> {
Ok(Arc::new(T::parse(self.dns_records.as_bytes())?))
}
}

#[test]
fn dkim_verify_sync() {
let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
test_dir.push("resources");
test_dir.push("dkim");

for file_name in fs::read_dir(&test_dir).unwrap() {
let file_name = file_name.unwrap().path();
/*if !file_name.to_str().unwrap().contains("002") {
continue;
}*/
println!("DKIM verifying {}", file_name.to_str().unwrap());

let test = String::from_utf8(fs::read(&file_name).unwrap()).unwrap();
let (dns_records, raw_message) = test.split_once("\n\n").unwrap();
let resolver = MockResolver {
dns_records: dns_records.to_string(),
};
let raw_message = raw_message.replace('\n', "\r\n");
let message = AuthenticatedMessage::parse(raw_message.as_bytes()).unwrap();

let future = DkimVerifier::verify_dkim(&resolver, &message);
let dkim = futures::executor::block_on(future);


assert_eq!(dkim.last().unwrap().result(), &DkimResult::Pass);
}
}

#[test]
fn dkim_strip_signature() {
for (value, stripped_value) in [
Expand Down
Loading