Revision history for Mail::DKIM2
0.14 2026-10-07
- The Verifier and MessageInstance->chain_verifies walk the header
history past an instance whose body Recipe is null: the body is
unrecoverable there, but every lower instance's header hashes
are still checked down to m=1. (t/mi-null-header-history.t)
- Validate (and bin/validate.pl): a null body Recipe no longer fails
the chain; the header history below it is checked, and the lower
levels report body_hash 'not-checked'. (t/validate-null-body.t)
- dkim2-milter --allow-null-body-recipe (default off): a message
whose top Message-Instance has a null body Recipe is no longer
signed silently; it is refused (X-DKIM2-Info
not-signed=null-body-recipe) unless the option is set, and signed
with X-DKIM2-Info null-body-recipe when it is.
- bin/dkim2sign no longer signs blindly over an existing chain: like
dkim2-milter it verifies the upstream DKIM2-Signatures (an unsigned
top Message-Instance is allowed) and that the Message-Instance chain
undoes cleanly, and refuses (exit 1, nothing on stdout) otherwise.
A top null body Recipe is refused unless --allow-null-body-recipe.
New --dns-json (default $DKIM2_DNS_JSON) and --ignore-timestamps.
The decision is the new Mail::DKIM2::Gate, shared with the milter;
the Signer library itself still signs ungated. (t/sign-cli.t)
- nd= bridge: when the top DKIM2-Signature carries nd=, the Gate
(and so dkim2sign and dkim2-milter) signs only if nd= equals the
signing d= (case-insensitive) and otherwise refuses with "top
signature nd=X names another domain". Verifier gains
next_domain_ok($d) / NextDomainOK; Gate->check takes
SigningDomain. Every non-nd case behaves as before.
(t/sign-cli.t, t/milter-script.t)
- A Message-Instance whose r= has a "b" that is neither null nor an
array (e.g. 5, "x", {}) is now a PERMERROR instead of being read
as a null body Recipe; likewise an "h" that is not an object.
(t/mi-null-recipe.t)
- A body Recipe's structure (integer bounds, ascending, no overlap)
is validated even when a null body Recipe above it means it is
never applied; a malformed one below a null now fails the chain.
(t/mi-null-header-history.t)
- DKIM2_DATE is 2026-10-07: what the milter signs and how the
Verifier judges a null body Recipe changed.
0.13 2026-10-04
- The header Recipe builder treated "no instances" and "one empty
instance" of a field as the same, so removing an empty header
(a bare "Bcc:", which Sympa's egress now removes) went unrecorded
and the Message-Instance did not verify. (t/recipe-empty-header.t)
0.12 2026-10-04
- undo() rebuilt a base64 or quoted-printable body encoded twice:
Recipes work on wire lines, so the rebuilt body is already in its
transfer encoding, and Email::MIME->body_set encoded it again.
Every such message a list re-encoded (footer appended, body
re-wrapped) failed "m=1 does not match content" and the milter
refused to sign it -- 17 of 88 charset-corpus samples through
Mailman, while the Python undo rebuilt them byte for byte. The
body is now set as raw octets. (t/undo-encoded-body.t)
- DKIM2_DATE is 2026-10-04: the Message-Instance headers this
library emits changed shape in 0.11 ("b" literals, integer copy
ranges), and the X-DKIM2-Info date stamp follows emitted-header
changes.
0.11 2026-10-04
Fixes found by replaying public-archive mail in assorted charsets
(ISO-2022-JP, GB2312/GB18030, Big5, EUC-KR, Latin-1, raw 8-bit
headers) through the signers, verifiers and list managers
(interop util/charset-corpus.sh).
- Recipe literals carrying any octet >= 0x80 are emitted as a new
{"b": [base64, ...]} step instead of {"d": [...]}. A literal is
the raw octets of a header value or body line; JSON text is
UTF-8, so the old encoder wrote ISO-2022-JP, GB18030, Big5 and
Latin-1 octets into the JSON as they were, which no strict JSON
parser reads back. The decoder rejects a "b" item that is not
RFC 4648 base64 or decodes to something containing CR or LF.
Agreed extension to spec-06 §5, proposed to the WG.
(t/recipe-base64.t)
- Recipe copy ranges must ascend (spec-06 §5.1): each "c" step
starts after the one before it ends. undo() used to sort the
ranges and reject only overlap; it now rejects an out-of-order
range too, for body and header Recipes alike. The header Recipe
builder in calculate() no longer emits one: a header instance a
hop moved above one it left alone is recorded literally.
(t/recipe-order.t, t/undo-bounds.t)
- Two more Recipe schema rules the other verifiers already hold, so
every implementation gives the same verdict: a "c" bound must be
a JSON integer (a string such as "2" is malformed; told apart by
the scalar's flags, not its text), an empty "d" or "b" array is
malformed (minItems 1), and so is a "d" string containing CR or
LF (§5.1/§5.2 MUST NOT). (t/recipe-order.t, t/recipe-base64.t)
- Recipe copy ranges are emitted as JSON integers. An index used as
a hash key while de-duplicating header copies was stringified in
place, so every Sympa Message-Instance carried {"c":["2","2"]},
which the spec-06 schema forbids and strict verifiers reject.
(t/recipe-integers.t)
- A broken Content-Type (`text/plain; Windows-1252`) no longer makes
every verification print Email::MIME's "Illegal parameter" warning:
the library parses with parameter checking relaxed, for the parse
only, since DKIM2 never reads a MIME parameter.
(t/malformed-content-type.t)
- The test suite is self-contained: the test keys and dns.json ship
under t/data/ (t/data-in-sync.t keeps them equal to the interop
repository's shared copies), bin/validate.pl takes --dns-json and
defaults to that copy, and the tests that cross-check the other
implementations or the deployment templates skip outside the
repository. 0.10's tests could not run from the tarball.
0.10 2026-10-02
API cleanup ahead of a CPAN release. Incompatible changes are marked *.
- New top-level Mail::DKIM2 module documenting the conventions every
module follows; every module now carries the distribution $VERSION.
- Constructor options are CamelCase and validated: an unknown option
croaks. Verifier options (SkipTimestampCheck, AllowUnsignedMI,
MidProcess, HeadersOnly, PubkeyCallback, Resolver, IgnorePrefixes)
can be given to new() and are no longer silently discarded.
- load($input): one-shot PRINT+CLOSE taking a string, scalar ref,
filehandle or Email::MIME, normalising LF to CRLF. TIEHANDLE lets a
Signer or Verifier be tied to a filehandle.
- Verifier->signatures and ->top_signature for Authentication-Results
writers.
* Ignore prefixes are per instance: Common::ignore_header_prefixes is
gone; pass IgnorePrefixes to Verifier->new and to
MessageInstance->calculate/verify/chain_verifies instead.
should_skip takes the prefixes as a second argument.
* Key fetching moves to the Verifier: Signature->fetch_public_key is
replaced by Verifier->fetch_public_key($signature, $idx), driven by
the Resolver option. Only NXDOMAIN/NOERROR/NODATA are permanent; any
other resolver error is temperror. The pubkey callback receives the
verifier as a third argument.
* Signer no longer dies from inside PRINT on a chain it cannot
extend: result() is 'fail' and details() says why. result() is
undef (not '?') before CLOSE. details() and result_detail() added.
* Signature: mail_from(), rcpt_to() and flags() are get/set like the
other tag accessors; set_rcpt_to is removed.
* Mail::DKIM2::DSN methods take CamelCase named arguments (Message,
Signer, To, ReportingMTA, Status, Reason, PubkeyCallback,
ForwarderDomain, SkipAuthentication, SkipTimestampCheck) instead of
a hashref. Validate::report takes PubkeyCallback, DnsPath,
SkipTimestampCheck.
* Command-line tools: dkim2sign (was dkim2sign.pl) and the new
dkim2verify are installed; verify-sig.pl, calculate-dkim2.pl and
the *-mailversion tools are removed.
- POD rewritten for spec-06 (the previous text described
draft-clayton-08 tags); Net::DNS declared as a prerequisite.
- dkim2-milter and dkim2-split-lmtp are installed programs (were
bin/*.pl, run from the checkout). X-DKIM2-Info sw= says
dkim2-milter.
0.01 2026-03-08
- Initial release
- Implements draft-clayton-dkim2-spec-08
- Signer: streaming DKIM2-Signature generation with SMTP param recording
- Verifier: full chain verification (all signatures, not just outermost)
- MessageInstance: calculate, verify, and undo with header/body diff recipes
- Signature: tag-value parser with base64-encoded JSON tags
- HeaderParser: thin streaming base class replacing Mail::DKIM::Common
- Common: shared canonicalization, hashing, domain matching utilities
Keyboard Shortcuts
Global
s
Focus search bar
?
Bring up this help dialog
GitHub
gp
Go to pull requests
gi
Go to GitHub issues (only if GitHub is preferred repository)