NAME
Mail::DKIM2::Verifier - Verify the DKIM2-Signature chain on a message
SYNOPSIS
use Mail::DKIM2::Verifier;
my $verifier = Mail::DKIM2::Verifier->new->load($message);
print $verifier->result, "\n"; # pass, fail, none, permerror, temperror
print $verifier->result_detail, "\n"; # pass (i=1..3 verified)
# Streaming, with CRLF line endings:
my $v = Mail::DKIM2::Verifier->new(Resolver => $net_dns_resolver);
$v->PRINT($chunk) for @chunks;
$v->CLOSE;
# For Authentication-Results:
if (my $top = $v->top_signature) {
printf "dkim2=%s header.d=%s header.i=%d\n",
$v->result, $top->domain, $top->sequence;
}
DESCRIPTION
Verifies every DKIM2-Signature on a message, not only the outermost: the chain must be complete (i=1 to i=N with no gaps), each signature must verify over the headers that existed when it was made, consecutive hops must satisfy the chain-of-custody rules of spec-06 section 11.4, and the Message-Instance chain must undo cleanly back to the first instance, each one matching the content it describes. The outcome is a result and a reason, never an exception; see result below.
Extends Mail::DKIM2::HeaderParser, which provides PRINT, CLOSE, load and the tie interface. A message with no DKIM2-Signature is decided from its headers alone and its body is not kept.
This module implements draft-ietf-dkim-dkim2-spec-06; see "STATUS" in Mail::DKIM2 for what that means for the wire format and the API, and "CONVENTIONS" in Mail::DKIM2 for the option, input and error conventions every module here follows.
CONSTRUCTOR
new(%options)
All options are optional. The boolean ones and Resolver also have a snake_case method of the same name that gets or sets them after construction; PubkeyCallback has set_pubkey_callback.
- Resolver
-
A Net::DNS::Resolver (or anything with the same
queryanderrorstringmethods) for the default public-key lookup. Created on demand if not given. A milter host passes its own so its timeouts apply. - PubkeyCallback
-
A code reference that replaces the DNS lookup. Called as
($signature, $index, $verifier)for each item of each signature'ss=tag; returns a Crypt::PK::RSA or Crypt::PK::Ed25519 object, undef for "no such key", or dies with a string to report a transient failure. It can fall back to$verifier->fetch_public_key($signature, $index)for keys it does not know. - SkipTimestampCheck
-
Do not fail a signature for the age of its
t=. For test fixtures. - IgnorePrefixes
-
An arrayref of header-field-name prefixes an operator's own border adds and strips, excluded from the header hash. Anything but undef or an arrayref croaks. See "Operator-local header fields" in Mail::DKIM2.
- AllowUnsignedMI
-
Permit a Message-Instance with a higher
m=than any signature covers, which spec-06 section 11 otherwise makes a permerror. For an outbound path that verifies the message it is about to sign, where the new instance legitimately exists before its signature does. Never set it on inbound mail. - MidProcess
-
The verifier is looking at a partial view of the chain, with higher signatures stripped (as the validator does walking the chain top-down), so the highest remaining signature is not the true top and the checks that apply only to the top are suppressed. Implies
AllowUnsignedMI. - HeadersOnly
-
The message has no body, as with the returned original in a DSN's
text/rfc822-headerspart (spec-06 section 12.1.2). Signatures and the chain are checked as usual; of the Message-Instance content check, only the top instance's header hash can be, so only that is.
An option not listed here croaks.
METHODS
PRINT($bytes), CLOSE(), load($input)
Feed the message; see Mail::DKIM2::HeaderParser.
result()
One of:
pass-
Every signature verified, the chain is complete and consistent, and the Message-Instance chain undoes cleanly.
fail-
A signature did not verify, the chain has a gap or a chain-of-custody mismatch, a Message-Instance does not match the content, or a Recipe did not undo.
none-
No DKIM2-Signature headers.
permerror-
The message is malformed in a way no retry will fix: too many fields, a repeated number, a duplicate tag, an unsigned Message-Instance, a required tag missing, a Message-Instance whose hash sets do not parse, a key too short.
temperror-
A public key could not be fetched for a transient reason. Retry later.
Before CLOSE this is none.
details()
The reason, with no result word wrapped around it, e.g. "i=1..3 verified" or "missing DKIM2-Signature i=2"; undef when there is none. Use this when embedding the reason in something that already states the result, such as an Authentication-Results comment.
result_detail()
result and details together, e.g. "pass (i=1..3 verified)".
signatures()
The DKIM2-Signature headers the message carried, as Mail::DKIM2::Signature objects in ascending i= order.
top_signature()
The highest-i= signature, or undef. Its domain and sequence are header.d and header.i for Authentication-Results.
fetch_public_key($signature, $index)
The default key source: a TXT lookup of <selector>._domainkey.<d=> through the Resolver. Returns a key object, undef when the answer positively says there is no such record (NXDOMAIN, NOERROR or NODATA), and dies with a TEMPERROR: message for anything else, including a resolver error string it has never seen. The verifier maps that die to temperror: spec-06 section 10 makes a DNS failure retryable, never a fail, which would read as a forged signature.
resolver([$resolver]), set_pubkey_callback(\&cb), skip_timestamp_check([$bool]), allow_unsigned_mi([$bool]), mid_process([$bool]), headers_only([$bool])
Get or set the constructor options of the same names.
VERIFICATION PROCESS
Shape. At most 32 Message-Instance and 32 DKIM2-Signature fields, no
i=orm=twice, and no Message-Instance above the highest signedm=. Any of these is apermerrordecided from the headers alone, before any key is fetched. A tag repeated within one signature is apermerrorfound when that signature is checked.Chain completeness.
i=1toi=Nwith no gaps; likewisem=1to the highest instance.Each signature. The signing input for
i=Kis the canonicalized Message-Instance headers up to them=it names, the DKIM2-Signature headers below it, and itself with an emptys=value, in that order (spec-06 section 8.5). Every item ins=whose algorithm is known and whose key can be fetched must verify; an item whose key is absent is skipped if another item verifies.Chain of custody. For consecutive hops, the
mf=domain ofi=Kmust be the same as or below art=domain ofi=K-1; annd=hop instead names thed=of the hop that follows (spec-06 sections 9.3 and 11.4).Flags. A hop that changed the message after a
donotmodify, or exploded it after adonotexplode, is afail.Content. The top Message-Instance must match the message; its Recipe is applied and the next instance down checked against the result, back to
m=1or an instance that declares the previous state unrecoverable.
AUTHOR
Bron Gondwana <brong@fastmailteam.com>
COPYRIGHT AND LICENSE
Copyright (c) 2025-2026 Fastmail Pty Ltd. This is free software; you can redistribute it and/or modify it under the same terms as Perl itself.