NAME
Mail::DKIM2::Common - Canonicalization, hashing, folding and key handling for DKIM2
SYNOPSIS
use Mail::DKIM2::Common qw(
should_skip dkim2_canonicalize_header fold_header
load_private_key parse_dkim_pubkey
extract_domain relaxed_domain_match
DKIM2_DRAFT MAX_CHAIN_LENGTH
);
my $key = load_private_key('/etc/dkim2/sel1.pem');
my $pub = parse_dkim_pubkey('v=DKIM1; k=rsa; p=MIIB...');
my $folded = fold_header("Message-Instance: " . $mi->as_string);
DESCRIPTION
The functions the other modules share. Nothing is exported by default. Those under "CANONICALIZATION" and "SIGNING INPUT" define bytes the spec defines and are of interest to anyone checking this implementation against another; the rest are conveniences.
This module implements draft-ietf-dkim-dkim2-spec-07; 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.
CONSTANTS
DKIM2_DRAFT, DKIM2_REPO, DKIM2_DATE
The spec revision implemented (ietf-dkim-dkim2-spec-07), where the code lives, and the date this implementation's DKIM2 behaviour last changed. Emitted in X-DKIM2-Info debug headers (draft-gondwana-dkim2-debug-header).
MAX_CHAIN_LENGTH
32: a message carrying more Message-Instance or DKIM2-Signature fields than this, or an i= or m= naming a larger number, is a PERMERROR, found before any key is fetched. Local policy, not spec.
MAX_CHAIN_NUMBER
100: the largest number an i= or m= may be written as (at most three digits). See "chain_number_error($field, $tag, $n)".
CANONICALIZATION
should_skip($header_name, [\@prefixes])
True if the field is excluded from the header hash: the -07 section 4 list (Received, Return-Path, Message-Instance, DKIM2-Signature, DKIM-Signature, Authentication-Results, the ARC fields and others), any X-* or Received-* field, or a field whose name starts with one of the caller's prefixes, case-insensitively. The prefixes are an operator's local policy; pass them as IgnorePrefixes to Mail::DKIM2::Verifier and the Mail::DKIM2::MessageInstance class methods.
check_ignore_prefixes($value)
Croaks unless $value is undef or an array reference; returns it. Every entry point that takes IgnorePrefixes calls this.
dkim2_canonicalize_header($line)
Header-hash canonicalization (section 5.2) of one complete header line including its CRLF: unfold, lowercase the name, collapse whitespace runs to one space, trim whitespace around the colon and at the end. Returns name:value\r\n.
dkim2_canonicalize_sig_header($line)
Signing-input canonicalization (section 8.5): as above, but all whitespace in the value is removed rather than collapsed.
extract_mi_version($header_value)
The m= number of a Message-Instance value, as a number (so 01 is 1), or undef when there is no m= or it is not all ASCII digits. Accepts a string, a scalar ref, or an arrayref (first element).
mi_version_tag($header_value)
The raw m= value of a Message-Instance value with whitespace removed, or undef when there is none. Pass it to "chain_number_error($field, $tag, $n)" before trusting it.
strip_mi_versions($message, @m)
Removes the Message-Instance fields with the given m= numbers from a CRLF message string and returns the result.
SIGNING INPUT
build_signing_input(%args)
The bytes a DKIM2-Signature signs (section 8.5): the canonicalized Message-Instance headers in ascending m= order, the DKIM2-Signature headers below the one being signed in ascending i= order, and that one with empty s= values. Arguments: mi_headers, an arrayref of { v => N, raw => $line } sorted by v; dk2_headers, an arrayref of { i => N, raw => $line, sig => $signature } sorted by i; signing_i, the i= being signed or verified; signature, its Mail::DKIM2::Signature; and optionally signing_header, the exact folded text to use for it, which the Signer passes so that the folds it chose are signed.
chain_length_error($msg_or_counts)
The PERMERROR string for a message over "MAX_CHAIN_LENGTH", or undef. Takes an Email::MIME or a hashref of field counts keyed by lowercased name.
valid_sequence($i)
True when $i (a DKIM2-Signature i= value) is a positive integer written in ASCII digits. A signature whose i= is not is one no verifier can place in the chain or key: Mail::DKIM2::Verifier reports it as "UNKEYABLE_SIGNATURE_ERROR".
UNKEYABLE_SIGNATURE_ERROR
The PERMERROR string for a DKIM2-Signature with no i= tag, or one that does not parse: "PERMERROR: DKIM2-Signature i= tag=i missing".
MI_MISSING_M_ERROR
The PERMERROR string for a Message-Instance with no m= tag: "PERMERROR: Message-Instance m= tag=m missing". New in 0.25.
unkeyable_signature_error($value)
The PERMERROR string for a DKIM2-Signature header value whose i= is missing ("UNKEYABLE_SIGNATURE_ERROR") or not a positive integer ("PERMERROR: DKIM2-Signature i=<as written> syntax error"). New in 0.23.
chain_number_error($field, $tag, $n, $ident)
The PERMERROR string for an i= or m= value that is not a chain number, or undef (also for an undefined $n, which is left to the caller). The field is named by its own number as written, $ident (default $n): a DKIM2-Signature by i=, a Message-Instance by m=. A value that is not 1*DIGIT in ASCII, or is zero, is "PERMERROR: $field i=$ident syntax error" (m= for a Message-Instance; -07 section 14.2). More than three digits or above "MAX_CHAIN_NUMBER" is "PERMERROR: DKIM2-Signature i=$ident exceeds the maximum chain number of 100", with " m=$n" after $ident for a signature's m=. Above "MAX_CHAIN_LENGTH", likewise "... exceeds the maximum chain length of 32": every i= and m= names one hop, so none can be larger than the chain. 01 and 001 are 1.
duplicate_number_error($field, $tag, @numbers)
The PERMERROR string for the first number that appears twice, or undef.
ADDRESSES
extract_domain($address)
The domain of user@domain or <user@domain>, or undef.
to_rfc5321_path($address)
Wraps an address in angle brackets if it has none; undef or empty becomes <>. The form mf= and rt= carry.
domain_of($address)
The domain of an address or RFC 5321 path, as ($domain, $is_literal): the text between the last @ and the closing >, lowercased, with one trailing dot removed (<A@Example.COM.> gives example.com). $is_literal is true for a domain literal such as [192.0.2.1]. The empty list when there is no domain, as for <>. New in 0.23.
to_alabel($domain)
The domain with each non-ASCII label lowercased and written as its A-label (xn-- and the RFC 3492 Punycode), as -07 section 18 has mf=, rt= and From domains compared: b\x{fc}cher.example, as UTF-8 octets, gives xn--bcher-kva.example. ASCII labels are unchanged; a label that is not valid UTF-8 is left as it is. No UTS 46 mapping. New in 0.23.
from_domain(@values)
The domain a From header field is Authentic for (-07 section 11), given the values of every From field in one version of the header: the lowercased A-label domain of its one mailbox, or undef when there is no From field, more than one, a field holding other than exactly one mailbox (a list, a group), or a domain literal. Display names (quoted or RFC 2047) and comments are skipped; Name <addr> and a bare addr both work. New in 0.23.
from_authentic($d, @values)
True if from_domain(@values) gives a domain and it relaxed-matches the signing domain $d. New in 0.23.
from_refusal_note($d, @values)
For the log of a signer that refused a From not Authentic for $d: a line naming the From domain (as "from_domain" finds it, or what is wrong with the field) and $d, and saying that a DKIM2 list's posting address must be in the domain the milter signs with. Both milters log it. New in 0.23.
relaxed_domain_match($p1, $p2)
The relaxed domain match of -07 section 7.2: true if $p1 is $p2 or a subdomain of it. Labels come off the left of $p1 only. Both are compared as A-labels (to_alabel), without regard to case and without a trailing dot. A domain literal on either side never matches (new in 0.23).
FOLDING
Only for a header this code is creating. A header read from anywhere else is never refolded: a fold where there was no whitespace changes its canonical form and breaks every signature over it.
parse_mime($raw)
Parse $raw into an Email::MIME for the library's own use (raw header fields, raw body), with Email::MIME::ContentType's parameter check relaxed for the duration of the parse. DKIM2 never reads a MIME parameter, so a sender's broken Content-Type must not stop a message from being verified or make every verification warn. The package variable is restored afterwards.
fold_header($line, [$margin], %opts)
Folds a complete header line at $margin characters (default 72) with CRLF-tab continuations, breaking at ; first, then at a space, then where a DKIM2 field's grammar allows folding whitespace: inside a base64 value (mf=, rt=, r=, an h= digest, an s= signature value), beside the :s of an s= or h= item, or after a list comma other than in s=. It never splits a domain, selector, algorithm or hash name: such a token that will not fit stays whole on a longer line, and a line that would exceed RFC 5322's 998 characters croaks. With delimiters_only => 1 it breaks only after ; or ,, never inside a token, leaving a value that fits nowhere whole on an over-long line (the rule for X-DKIM2-Info).
fold_value($string, [$margin])
Folds a string at arbitrary positions, $margin content characters per line (default 64).
KEYS
load_private_key($pem_file)
A Crypt::PK::RSA or Crypt::PK::Ed25519 from a PEM file, whichever it holds. Dies if the file is neither.
load_private_key_data($data)
The same from key material in memory: PEM, or bare base64 DER as some key stores keep it. Returns undef rather than dying, so a signer of live mail can log and carry on.
parse_dkim_key_record($txt_record)
Validates a whole key record (draft-ietf-dkim-dkim2-dns sections 3.2 and 3.4.1) and returns the public key object: a Crypt::PK::RSA for k=rsa (the default) or a Crypt::PK::Ed25519 for k=ed25519, accepted as the raw 32 bytes of RFC 8463 or as DER. Otherwise returns (undef, $why), where $why completes "public key <selector> ...":
has a syntax error-
Not a tag-list; a tag given twice;
v=present but not the first tag or not exactlyDKIM1; nop=;p=not base64, or not a key of its type. has been revoked-
An empty
p=. has an unsupported key type-
A
k=other thanrsaored25519.
Unknown tags and the retired h=, n=, s= and t= are ignored. Tag names are case sensitive, as in DKIM1.
parse_dkim_pubkey($txt_record)
The key object from parse_dkim_key_record, or undef for a record that gives no usable key.
TAG ENCODING
encode_tag_json($data), decode_tag_json($base64)
Canonical JSON in base64, the encoding of the r= Recipe tag. decode_tag_json dies on invalid JSON, including JSON text that is not UTF-8 and an object that gives the same key twice, compared without regard to case (parsers disagree on which value wins). Every string it returns, object keys included, is UTF-8 octets.
digest64($digest_object)
The base64 of a CryptX digest object's result.
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.