mailrs-arc 1.2.0

RFC 8617 Authenticated Received Chain (ARC) — header parsing, chain extraction, and signature verification. ARC extends DKIM/SPF/DMARC across forwarders by chaining per-hop authentication results inside cryptographically-sealed header sets. Pairs with mailrs-dkim for canonicalization + RSA verify, mailrs-spf / mailrs-dmarc for the auth-results that ARC carries forward.
Documentation

mailrs-arc

RFC 8617 — Authenticated Received Chain (ARC) header parsing, chain extraction, and structural verification.

Part of the mailrs "stones" family. Pairs with mailrs-dkim for canonicalization + RSA verify and with mailrs-spf / mailrs-dmarc for the authentication results that ARC carries forward.

What ARC is

ARC fixes the longstanding "forwarders break DMARC" problem. Every forwarding hop adds a triplet of headers indexed by i=N:

ARC-Authentication-Results: i=N; <authres-body>
ARC-Message-Signature:      i=N; <dkim-like-signature>
ARC-Seal:                   i=N; cv={none|pass|fail}; <signature-over-chain>

A downstream verifier walks the chain from i=1 upward, validates each set, and produces a verdict (pass / fail) that DMARC can use to override forwarder breakage.

What this crate covers (1.0)

Layer Status
Parse ARC-Authentication-Results / ARC-Message-Signature / ARC-Seal
Extract chain from raw message + group by instance
Validate chain contiguity (no gaps from i=1)
Validate cv= integrity (first = none, rest = pass/fail)
Cryptographic AMS + AS verify (RSA-SHA256 / Ed25519-SHA256) ✅ (since 1.1)
ARC sealing (add a new set on outbound forward) planned 1.2

The structural layer alone is enough to:

  • Detect malformed / sparse / over-long chains (rejecting before any DNS work).
  • Detect cv= inconsistencies that prove the chain was tampered with (first set with cv=pass, two sets with cv=none, etc.).
  • Carry the chain forward to the cryptographic layer.

Since 1.1, the cryptographic layer ([verify_chain_with_crypto]) walks the chain from highest instance down and, for each set, verifies (a) ARC-Message-Signature (body hash + signed-header block, RFC 8617 §5.1.1 — same shape as DKIM §3.7) and (b) ARC-Seal (chain-prefix block, RFC 8617 §5.1.2, always relaxed/relaxed canon). A single failure short-circuits to ChainOutcome::Fail { reason } with the offending instance + header type.

The crypto routes through mailrs_dkim::crypto::verify_signature + mailrs_dkim::canon::* — RFC 8617 §5 specifies the same algorithms and canonicalization as DKIM-Signature, so we share one verified implementation instead of duplicating ~400 LOC of canon + ~100 LOC of RSA/Ed25519 verify.

Example

Structural only (synchronous, no DNS)

use mailrs_arc::{ArcChain, verify_chain, ChainOutcome};

let raw_message: &[u8] = b"\
ARC-Authentication-Results: i=1; spf=pass smtp.mailfrom=alice@example.com\r\n\
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=mail; h=From:To:Subject; bh=BH1; b=SIG1\r\n\
ARC-Seal: i=1; a=rsa-sha256; cv=none; d=example.com; s=mail; b=SEAL1\r\n\
From: alice@example.com\r\n\
Subject: hi\r\n\
\r\n\
body";

let chain = ArcChain::extract(raw_message).unwrap().unwrap();
assert_eq!(chain.sets.len(), 1);
assert_eq!(verify_chain(&chain), ChainOutcome::Pass);

Full crypto verify (async, requires DNS)

use mailrs_arc::{ArcChain, ChainOutcome, verify_chain_with_crypto};

let chain = ArcChain::extract(raw_message)?.unwrap();
match verify_chain_with_crypto(&chain, &my_resolver, raw_message).await? {
    ChainOutcome::Pass => { /* trust chain's accumulated authres */ }
    ChainOutcome::Fail { reason } => { /* "ams i=2: …" or "as i=1: …" */ }
    _ => unreachable!(),
}

my_resolver is any mailrs_dkim::DkimResolver impl — wire your existing DKIM resolver here, ARC reuses the same DNS surface.

Performance

Measured (criterion, M-series Mac, release):

Operation Median
ArcAuthResults::parse (1 set) 21 ns
ArcMessageSignature::parse (realistic) 479 ns
ArcSeal::parse (realistic) 295 ns
ArcChain::extract (2-hop chain) 3.65 µs

Reproduce: cargo bench -p mailrs-arc --bench arc.

Why this crate exists

Before mailrs-arc 1.0, the only Rust ARC implementation was the mail-auth umbrella, which bundles SPF/DKIM/DMARC/ARC into a single ~5K-LOC crate. Picking it up for "just ARC" pulls in everything else.

mailrs-arc 1.0 ships ARC as a standalone primitive. Use it with mailrs-spf / mailrs-dkim / mailrs-dmarc (the rest of the email-auth stack) or stand-alone with whatever auth stack you already have.

For mailrs's own server, mailrs-arc 1.1 closes DEPS_AUDIT candidate #1 — the server's inbound stage can swap mail_authenticator.verify_arc for verify_chain_with_crypto and drop mail-auth from runtime dependencies entirely.

License

Apache-2.0 OR MIT.