dachs / mail-auth
Framework-neutral PHP mail-auth package for DKIM, ARC, SPF, and DMARC evidence.
Requires
- php: >=8.2
- ext-openssl: *
- ext-sodium: *
- jeremykendall/php-domain-parser: ^6.4
- mlocati/spf-lib: ^3.3
Requires (Dev)
- phpunit/phpunit: ^11.0
README
Framework-neutral PHP mail-auth package for DKIM, ARC, SPF, and DMARC evidence.
This package is maintained by Dachs Consulting for standards-based inbound email evidence. It operates on raw RFC 5322 messages and explicit protocol inputs and can be used as a standalone mail-auth library.
Scope
This package verifies and creates DKIM signatures, validates and seals ARC chains, verifies SPF, and evaluates DMARC organizational-domain alignment.
Implemented and regression-tested coverage:
- DKIM-Signature parsing, including folded signature headers
- mandatory
Fromheader signing enforcement from RFC 6376 rsa-sha256verification/signing via OpenSSLed25519-sha256verification/signing via Sodium/RFC 8463 key recordssimpleandrelaxedheader/body canonicalization- DKIM body hash validation (
bh=) - optional body length tag (
l=) - signature timestamp/expiry policy (
t=/x=) - signing identity domain validation (
i=) - DNS TXT public-key lookup through injectable resolvers
- multi-string DNS TXT record concatenation
- DKIM key-record policy checks for version, key type, hash algorithms, service type, and granularity (
v=,k=,h=,s=,g=) - optional relaxed domain alignment against the visible sender domain
- multiple signatures, accepting the first verified aligned signature
- DKIM signing with RSA and Ed25519
- SPF verification via a maintained SPF engine behind package-owned DNS/result boundaries
- DMARC organizational-domain resolution using Public Suffix List rules
- ARC structural and cryptographic chain validation (
none|pass|fail) for set continuity, instance rules,cv=, AMS verification, AS verification, and ARC-Seal-only tag constraints - ARC sealing for application-owned outbound/forwarded evidence flows
Security posture:
rsa-sha256anded25519-sha256are accepted.- weaker algorithms such as
rsa-sha1are intentionally rejected for production use. - ARC cryptographic chain validation/sealing is implemented for the package boundary; third-party and self-generated ARC fixtures are cross-checked against dkimpy.
Architecture
DkimVerifier: orchestrates DKIM parsing, canonicalization, DNS key lookup, crypto verification, and alignment evidence.DkimSigner: creates DKIM signatures without mutating MIME content.DkimMessageParser: splits raw RFC 5322 headers/body without MIME mutation.DkimSignatureHeaderParser: parses foldedDKIM-Signaturefields into tags.DkimCanonicalizer: implementssimple/relaxedheader and body canonicalization.DkimDnsResolver: injectable DNS TXT resolver boundary.MailAuthDnsResolver: TXT/A/AAAA/MX/PTR/reverse-DNS boundary for SPF and other mail-auth protocols.DkimPublicKeyParser: parses and validates DKIM DNS key records.DkimCryptoVerifier: performs OpenSSL/Sodium signature verification and signing.DkimAlignmentChecker: evaluates relaxed sender-domain alignment.SpfVerifier: SPF checker wrapper returning protocol evidence values.DmarcOrganizationalDomainResolver: Public-Suffix-List based organizational domain/alignment helper.DmarcEvaluator: evaluates SPF/DKIM identifier alignment and DMARC disposition.ArcChainValidator: ARC structural and cryptographic chain validator.ArcSealer: creates ARC sets for application-owned sealing flows.
Installation
composer require dachs/mail-auth
Usage
DKIM
use Dachs\MailAuth\DkimVerifier;
$dkim = new DkimVerifier();
$dkimResult = $dkim->verifyAlignedFromHeader($rawRfc822Message);
if (! $dkimResult->passed) {
throw new RuntimeException($dkimResult->error?->value ?? 'DKIM verification failed');
}
$evidence = [
'dkim' => 'pass',
'alignment' => 'relaxed',
'dkim_domain' => $dkimResult->domain,
'selector' => $dkimResult->selector,
'algorithm' => $dkimResult->algorithm,
];
SPF
SPF verification is evaluated from the SMTP transaction inputs, not from the message body.
use Dachs\MailAuth\SpfResult;
use Dachs\MailAuth\SpfVerifier;
$spf = new SpfVerifier();
$spfResult = $spf->verify(
ip: '203.0.113.10',
heloDomain: 'mx.sender.example',
mailFrom: 'bounce@sender.example',
receiverDomain: 'receiver.example',
);
if ($spfResult->result === SpfResult::Pass) {
$evidence['spf'] = 'pass';
$evidence['spf_domain'] = 'sender.example';
}
DMARC
DMARC combines identifier alignment from DKIM and SPF with the visible From domain policy.
use Dachs\MailAuth\DmarcEvaluationInput;
use Dachs\MailAuth\DmarcEvaluator;
use Dachs\MailAuth\DmarcPolicy;
$dmarc = new DmarcEvaluator();
$dmarcResult = $dmarc->evaluate(new DmarcEvaluationInput(
fromDomain: 'sender.example',
dkimPassed: $dkimResult->passed,
dkimDomain: $dkimResult->domain,
spfPassed: $spfResult->result === SpfResult::Pass,
spfDomain: 'sender.example',
policy: DmarcPolicy::Reject,
));
$evidence['dmarc'] = $dmarcResult->disposition->value;
$evidence['dmarc_dkim_aligned'] = $dmarcResult->dkimAligned;
$evidence['dmarc_spf_aligned'] = $dmarcResult->spfAligned;
ARC
ARC can preserve authentication results across forwarding hops by validating or sealing an ARC chain.
use Dachs\MailAuth\ArcChainValidator;
use Dachs\MailAuth\ArcValidationStatus;
$arc = new ArcChainValidator();
$arcResult = $arc->validateStructure($rawRfc822Message);
if ($arcResult->status === ArcValidationStatus::Pass) {
$evidence['arc'] = 'pass';
$evidence['arc_sets'] = $arcResult->sets;
}
For tests or custom DNS infrastructure, inject DkimDnsResolver / MailAuthDnsResolver.
Public status and error values are PHP backed enums, not free-form strings:
DkimVerificationErrorArcValidationStatusArcValidationErrorSpfResultDmarcDispositionDmarcPolicy
Use ->value when serializing evidence payloads.
use Dachs\MailAuth\ArrayDkimDnsResolver;
use Dachs\MailAuth\ArrayMailAuthDnsResolver;
use Dachs\MailAuth\DkimVerifier;
use Dachs\MailAuth\SpfVerifier;
$dkim = new DkimVerifier(new ArrayDkimDnsResolver([
'test._domainkey.example.com' => 'v=DKIM1; k=rsa; p=...',
]));
$spf = new SpfVerifier(new ArrayMailAuthDnsResolver(txt: [
'example.com' => 'v=spf1 ip4:203.0.113.10 -all',
]));
Development
composer install
composer validate --strict
composer test
Interop fixtures are committed as static PHPUnit fixtures. Regenerate them with dkimpy/authres when changing DKIM or ARC signing semantics:
uv run --with dkimpy --with authres python tests/fixtures/interop/generate_fixtures.py
composer test