Skip to content

Commit

Permalink
Bring your own cache
Browse files Browse the repository at this point in the history
  • Loading branch information
mdecimus committed Dec 28, 2024
1 parent db1928b commit 42a8a95
Show file tree
Hide file tree
Showing 21 changed files with 1,276 additions and 627 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
mail-auth 0.6.0
================================
- Bring your own cache (or none at all): All validation functions can now take a `Parameters` struct that allows you to provide custom caches implementing the `ResolverCache` trait. By default no cache is used.

mail-auth 0.5.1
================================
- Build `AuthenticatedMessage` from `mail-parser::Message`.
Expand Down
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "mail-auth"
description = "DKIM, ARC, SPF and DMARC library for Rust"
version = "0.5.1"
version = "0.6.0"
edition = "2021"
authors = [ "Stalwart Labs <[email protected]>"]
license = "Apache-2.0 OR MIT"
Expand All @@ -26,10 +26,8 @@ test = []
ahash = "0.8.0"
ed25519-dalek = { version = "2.0", optional = true }
flate2 = "1.0.25"
lru-cache = "0.1.2"
mail-parser = { version = "0.9", features = ["ludicrous_mode", "full_encoding"] }
mail-builder = { version = "0.3", features = ["ludicrous_mode"] }
parking_lot = "0.12.0"
quick-xml = { version = "0.37", optional = true }
ring = { version = "0.17", optional = true }
rsa = { version = "0.9.6", optional = true }
Expand All @@ -41,6 +39,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 }
quick_cache = "0.6.9"

[dev-dependencies]
tokio = { version = "1.16", features = ["net", "io-util", "time", "rt-multi-thread", "macros"] }
Expand Down
70 changes: 43 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ Features:
### DKIM Signature Verification

```rust
// Create a resolver using Cloudflare DNS
let resolver = Resolver::new_cloudflare_tls().unwrap();
// Create an authenticator using Cloudflare DNS
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();

// Parse message
let authenticated_message = AuthenticatedMessage::parse(RFC5322_MESSAGE.as_bytes()).unwrap();

// Validate signature
let result = resolver.verify_dkim(&authenticated_message).await;
let result = authenticator.verify_dkim(&authenticated_message).await;

// Make sure all signatures passed verification
assert!(result.iter().all(|s| s.result() == &DkimResult::Pass));
Expand Down Expand Up @@ -85,14 +85,14 @@ Features:
### ARC Chain Verification

```rust
// Create a resolver using Cloudflare DNS
let resolver = Resolver::new_cloudflare_tls().unwrap();
// Create an authenticator using Cloudflare DNS
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();

// Parse message
let authenticated_message = AuthenticatedMessage::parse(RFC5322_MESSAGE.as_bytes()).unwrap();

// Validate ARC chain
let result = resolver.verify_arc(&authenticated_message).await;
let result = authenticator.verify_arc(&authenticated_message).await;

// Make sure ARC passed verification
assert_eq!(result.result(), &DkimResult::Pass);
Expand All @@ -101,15 +101,15 @@ Features:
### ARC Chain Sealing

```rust
// Create a resolver using Cloudflare DNS
let resolver = Resolver::new_cloudflare_tls().unwrap();
// Create an authenticator using Cloudflare DNS
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();

// Parse message to be sealed
let authenticated_message = AuthenticatedMessage::parse(RFC5322_MESSAGE.as_bytes()).unwrap();

// Verify ARC and DKIM signatures
let arc_result = resolver.verify_arc(&authenticated_message).await;
let dkim_result = resolver.verify_dkim(&authenticated_message).await;
let arc_result = authenticator.verify_arc(&authenticated_message).await;
let dkim_result = authenticator.verify_dkim(&authenticated_message).await;

// Build Authenticated-Results header
let auth_results = AuthenticationResults::new("mx.mydomain.org")
Expand Down Expand Up @@ -137,45 +137,61 @@ Features:
### SPF Policy Evaluation

```rust
// Create a resolver using Cloudflare DNS
let resolver = Resolver::new_cloudflare_tls().unwrap();
// Create an authenticator using Cloudflare DNS
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();

// Verify HELO identity
let result = resolver
.verify_spf_helo("127.0.0.1".parse().unwrap(), "gmail.com", "my-local-domain.org")
let result = authenticator
.verify_spf(SpfParameters::verify_ehlo(
"127.0.0.1".parse().unwrap(),
"gmail.com",
"my-local-domain.org",
))
.await;
assert_eq!(result.result(), SpfResult::Fail);

// Verify MAIL-FROM identity
let result = resolver
.verify_spf_sender("::1".parse().unwrap(), "gmail.com", "my-local-domain.org", "[email protected]")
let result = authenticator
.verify_spf(SpfParameters::verify_mail_from(
"::1".parse().unwrap(),
"gmail.com",
"my-local-domain.org",
"[email protected]",
))
.await;
assert_eq!(result.result(), SpfResult::Fail);
```

### DMARC Policy Evaluation

```rust
// Create a resolver using Cloudflare DNS
let resolver = Resolver::new_cloudflare_tls().unwrap();
// Create an authenticator using Cloudflare DNS
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();

// Verify DKIM signatures
let authenticated_message = AuthenticatedMessage::parse(RFC5322_MESSAGE.as_bytes()).unwrap();
let dkim_result = resolver.verify_dkim(&authenticated_message).await;
let dkim_result = authenticator.verify_dkim(&authenticated_message).await;

// Verify SPF MAIL-FROM identity
let spf_result = resolver
.verify_spf_sender("::1".parse().unwrap(), "example.org", "my-local-domain.org", "[email protected]")
let spf_result = authenticator
.verify_spf(SpfParameters::verify_mail_from(
"::1".parse().unwrap(),
"example.org",
"my-host-domain.org",
"[email protected]",
))
.await;

// Verify DMARC
let dmarc_result = resolver
let dmarc_result = authenticator
.verify_dmarc(
&authenticated_message,
&dkim_result,
"example.org",
&spf_result,
|domain| psl::domain_str(domain).unwrap_or(domain),
DmarcParameters::new(
&authenticated_message,
&dkim_result,
"example.org",
&spf_result,
)
.with_domain_suffix_fn(|domain| psl::domain_str(domain).unwrap_or(domain)),
)
.await;
assert_eq!(dmarc_result.dkim_result(), &DmarcResult::Pass);
Expand Down
10 changes: 5 additions & 5 deletions examples/arc_seal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use mail_auth::{
crypto::{RsaKey, Sha256},
headers::HeaderWriter,
},
AuthenticatedMessage, AuthenticationResults, Resolver,
AuthenticatedMessage, AuthenticationResults, MessageAuthenticator,
};

const TEST_MESSAGE: &str = include_str!("../resources/arc/001.txt");
Expand All @@ -37,15 +37,15 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=

#[tokio::main]
async fn main() {
// Create a resolver using Cloudflare DNS
let resolver = Resolver::new_cloudflare_tls().unwrap();
// Create an authenticator using Cloudflare DNS
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();

// Parse message to be sealed
let authenticated_message = AuthenticatedMessage::parse(TEST_MESSAGE.as_bytes()).unwrap();

// Verify ARC and DKIM signatures
let arc_result = resolver.verify_arc(&authenticated_message).await;
let dkim_result = resolver.verify_dkim(&authenticated_message).await;
let arc_result = authenticator.verify_arc(&authenticated_message).await;
let dkim_result = authenticator.verify_dkim(&authenticated_message).await;

// Build Authenticated-Results header
let auth_results = AuthenticationResults::new("mx.mydomain.org")
Expand Down
8 changes: 4 additions & 4 deletions examples/arc_verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@
* except according to those terms.
*/

use mail_auth::{AuthenticatedMessage, DkimResult, Resolver};
use mail_auth::{AuthenticatedMessage, DkimResult, MessageAuthenticator};

const TEST_MESSAGE: &str = include_str!("../resources/arc/001.txt");

#[tokio::main]
async fn main() {
// Create a resolver using Cloudflare DNS
let resolver = Resolver::new_cloudflare_tls().unwrap();
// Create an authenticator using Cloudflare DNS
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();

// Parse message
let authenticated_message = AuthenticatedMessage::parse(TEST_MESSAGE.as_bytes()).unwrap();

// Validate ARC chain
let result = resolver.verify_arc(&authenticated_message).await;
let result = authenticator.verify_arc(&authenticated_message).await;

// Make sure ARC passed verification
assert_eq!(result.result(), &DkimResult::Pass);
Expand Down
8 changes: 4 additions & 4 deletions examples/dkim_verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* except according to those terms.
*/

use mail_auth::{AuthenticatedMessage, DkimResult, Resolver};
use mail_auth::{AuthenticatedMessage, DkimResult, MessageAuthenticator};

const TEST_MESSAGE: &str = r#"DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed;
d=football.example.com; [email protected];
Expand Down Expand Up @@ -39,14 +39,14 @@ Joe."#;

#[tokio::main]
async fn main() {
// Create a resolver using Cloudflare DNS
let resolver = Resolver::new_cloudflare_tls().unwrap();
// Create an authenticator using Cloudflare DNS
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();

// Parse message
let authenticated_message = AuthenticatedMessage::parse(TEST_MESSAGE.as_bytes()).unwrap();

// Validate signature
let result = resolver.verify_dkim(&authenticated_message).await;
let result = authenticator.verify_dkim(&authenticated_message).await;

// Make sure all signatures passed verification
assert!(result.iter().all(|s| s.result() == &DkimResult::Pass));
Expand Down
31 changes: 18 additions & 13 deletions examples/dmarc_verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
* except according to those terms.
*/

use mail_auth::{AuthenticatedMessage, DmarcResult, Resolver};
use mail_auth::{
dmarc::verify::DmarcParameters, spf::verify::SpfParameters, AuthenticatedMessage, DmarcResult,
MessageAuthenticator,
};

const TEST_MESSAGE: &str = r#"DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed;
d=football.example.com; [email protected];
Expand Down Expand Up @@ -39,31 +42,33 @@ Joe."#;

#[tokio::main]
async fn main() {
// Create a resolver using Cloudflare DNS
let resolver = Resolver::new_cloudflare_tls().unwrap();
// Create an authenticator using Cloudflare DNS
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();

// Verify DKIM signatures
let authenticated_message = AuthenticatedMessage::parse(TEST_MESSAGE.as_bytes()).unwrap();
let dkim_result = resolver.verify_dkim(&authenticated_message).await;
let dkim_result = authenticator.verify_dkim(&authenticated_message).await;

// Verify SPF MAIL-FROM identity
let spf_result = resolver
.verify_spf_sender(
let spf_result = authenticator
.verify_spf(SpfParameters::verify_mail_from(
"::1".parse().unwrap(),
"example.org",
"my-host-domain.org",
"[email protected]",
)
))
.await;

// Verify DMARC
let dmarc_result = resolver
let dmarc_result = authenticator
.verify_dmarc(
&authenticated_message,
&dkim_result,
"example.org",
&spf_result,
|domain| psl::domain_str(domain).unwrap_or(domain),
DmarcParameters::new(
&authenticated_message,
&dkim_result,
"example.org",
&spf_result,
)
.with_domain_suffix_fn(|domain| psl::domain_str(domain).unwrap_or(domain)),
)
.await;
assert_eq!(dmarc_result.dkim_result(), &DmarcResult::Pass);
Expand Down
22 changes: 11 additions & 11 deletions examples/spf_verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,31 @@
* except according to those terms.
*/

use mail_auth::{Resolver, SpfResult};
use mail_auth::{spf::verify::SpfParameters, MessageAuthenticator, SpfResult};

#[tokio::main]
async fn main() {
// Create a resolver using Cloudflare DNS
let resolver = Resolver::new_cloudflare_tls().unwrap();
// Create an authenticator using Cloudflare DNS
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();

// Verify HELO identity
let result = resolver
.verify_spf_helo(
let result = authenticator
.verify_spf(SpfParameters::verify_ehlo(
"127.0.0.1".parse().unwrap(),
"gmail.com",
"my-host-domain.org",
)
"my-local-domain.org",
))
.await;
assert_eq!(result.result(), SpfResult::Fail);

// Verify MAIL-FROM identity
let result = resolver
.verify_spf_sender(
let result = authenticator
.verify_spf(SpfParameters::verify_mail_from(
"::1".parse().unwrap(),
"gmail.com",
"my-host-domain.org",
"my-local-domain.org",
"[email protected]",
)
))
.await;
assert_eq!(result.result(), SpfResult::Fail);
}
Loading

0 comments on commit 42a8a95

Please sign in to comment.