mdecimus 4 months ago
132 commits
b1d4e628 main
Updated CHANGELOG
File Commit Size Mode Time
.github Deleted FUNDING NaN B d--------- 10 months ago
.gitignore Initial commit 320 B -rw-r--r-- 2 years ago
CHANGELOG.md Updated CHANGELOG 2.8 KiB -rw-r--r-- 4 months ago
Cargo.toml Use public suffix list for DMARC relaxed alignment verificat… 1.5 KiB -rw-r--r-- 4 months ago
LICENSE-APACHE v0.1.0 10.6 KiB -rw-r--r-- 2 years ago
LICENSE-MIT v0.1.0 1023 B -rw-r--r-- 2 years ago
README.md Use public suffix list for DMARC relaxed alignment verificat… 9.9 KiB -rw-r--r-- 4 months ago
examples Use public suffix list for DMARC relaxed alignment verificat… NaN B d--------- 4 months ago
fuzz Merge pull request #17 from InstantDomain/no-rsa-key-panic NaN B d--------- 1 year ago
resources DNS lookup limit being hit too early during SPF verificatio… NaN B d--------- 4 months ago
src DNS lookup limit being hit too early during SPF verificatio… NaN B d--------- 4 months ago

mail-auth

crates.io build docs.rs crates.io

mail-auth is an e-mail authentication and reporting library written in Rust that supports the DKIM, ARC, SPF and DMARC protocols. The library aims to be fast, safe and correct while supporting all major message authentication and reporting RFCs.

Features:

  • DomainKeys Identified Mail (DKIM):
    • ED25519-SHA256 (Edwards-Curve Digital Signature Algorithm), RSA-SHA256 and RSA-SHA1 signing and verification.
    • DKIM Authorized Third-Party Signatures.
    • DKIM failure reporting using the Abuse Reporting Format.
    • Key-pair generation for both RSA and Ed25519 (enabled by the generate feature).
  • Authenticated Received Chain (ARC):
    • ED25519-SHA256 (Edwards-Curve Digital Signature Algorithm), RSA-SHA256 and RSA-SHA1 chain verification.
    • ARC sealing.
  • Sender Policy Framework (SPF):
    • Policy evaluation.
    • SPF failure reporting using the Abuse Reporting Format.
  • Domain-based Message Authentication, Reporting, and Conformance (DMARC):
    • Policy evaluation.
    • DMARC aggregate report parsing and generation.
  • Abuse Reporting Format (ARF):
    • Abuse and Authentication failure reporting.
    • Feedback report parsing and generation.
  • SMTP TLS Reporting:
    • Report parsing and generation.

Usage examples

DKIM Signature Verification

// Create a resolver using Cloudflare DNS
let resolver = Resolver::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;
// Make sure all signatures passed verification
assert!(result.iter().all(|s| s.result() == &DkimResult::Pass));

DKIM Signing

// Sign an e-mail message using RSA-SHA256
let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
let signature_rsa = DkimSigner::from_key(pk_rsa)
.domain("example.com")
.selector("default")
.headers(["From", "To", "Subject"])
.sign(RFC5322_MESSAGE.as_bytes())
.unwrap();
// Sign an e-mail message using ED25519-SHA256
let pk_ed = Ed25519Key::from_bytes(
&base64_decode(ED25519_PUBLIC_KEY.as_bytes()).unwrap(),
&base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(),
)
.unwrap();
let signature_ed = DkimSigner::from_key(pk_ed)
.domain("example.com")
.selector("default-ed")
.headers(["From", "To", "Subject"])
.sign(RFC5322_MESSAGE.as_bytes())
.unwrap();
// Print the message including both signatures to stdout
println!(
"{}{}{}",
signature_rsa.to_header(),
signature_ed.to_header(),
RFC5322_MESSAGE
);

ARC Chain Verification

// Create a resolver using Cloudflare DNS
let resolver = Resolver::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;
// Make sure ARC passed verification
assert_eq!(result.result(), &DkimResult::Pass);

ARC Chain Sealing

// Create a resolver using Cloudflare DNS
let resolver = Resolver::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;
// Build Authenticated-Results header
let auth_results = AuthenticationResults::new("mx.mydomain.org")
.with_dkim_result(&dkim_result, "sender@example.org")
.with_arc_result(&arc_result, "127.0.0.1".parse().unwrap());
// Seal message
if arc_result.can_be_sealed() {
// Seal the e-mail message using RSA-SHA256
let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
let arc_set = ArcSealer::from_key(pk_rsa)
.domain("example.org")
.selector("default")
.headers(["From", "To", "Subject", "DKIM-Signature"])
.seal(&authenticated_message, &auth_results, &arc_result)
.unwrap();
// Print the sealed message to stdout
println!("{}{}", arc_set.to_header(), RFC5322_MESSAGE)
} else {
eprintln!("The message could not be sealed, probably an ARC chain with cv=fail was found.")
}

SPF Policy Evaluation

// Create a resolver using Cloudflare DNS
let resolver = Resolver::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")
.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", "sender@gmail.com")
.await;
assert_eq!(result.result(), SpfResult::Fail);

DMARC Policy Evaluation

// Create a resolver using Cloudflare DNS
let resolver = Resolver::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;
// Verify SPF MAIL-FROM identity
let spf_result = resolver
.verify_spf_sender("::1".parse().unwrap(), "example.org", "my-local-domain.org", "sender@example.org")
.await;
// Verify DMARC
let dmarc_result = resolver
.verify_dmarc(
&authenticated_message,
&dkim_result,
"example.org",
&spf_result,
|domain| psl::domain_str(domain).unwrap_or(domain),
)
.await;
assert_eq!(dmarc_result.dkim_result(), &DmarcResult::Pass);
assert_eq!(dmarc_result.spf_result(), &DmarcResult::Pass);

More examples available under the examples directory.

Testing & Fuzzing

To run the testsuite:

$ cargo test

To fuzz the library with cargo-fuzz:

$ cargo +nightly fuzz run mail_auth

Conformed RFCs

DKIM

SPF

DMARC

ARF

SMTP TLS Reporting

License

Licensed under either of

  • Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
  • MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.

Copyright (C) 2020-2023, Stalwart Labs Ltd.

Clone

HTTP

Subscribe

License

Authors

Mauro D: 51%
Dirkjan Ochtman: 22%
Titus Sanchez: 1%
MTRNord: 0%
Mies van der Lippe: 0%

Analysis