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 withcv=pass, two sets withcv=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 ;
let raw_message: & = 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 = extract.unwrap.unwrap;
assert_eq!;
assert_eq!;
Full crypto verify (async, requires DNS)
use ;
let chain = extract?.unwrap;
match verify_chain_with_crypto.await?
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.