NAME

Mail::DKIM2::Signer - Sign a message with a DKIM2-Signature header

SYNOPSIS

use Mail::DKIM2::Signer;

# A signature covers a Message-Instance, so a message that has none yet
# gets one first (an originating hop: m=1).
use Mail::DKIM2::MessageInstance;
use Mail::DKIM2::Common qw(fold_header);
unless ($message =~ /^Message-Instance:/mi) {
    my $mi = Mail::DKIM2::MessageInstance->calculate($message);
    $message = fold_header('Message-Instance: ' . $mi->as_string) . "\r\n" . $message;
}

my $signer = Mail::DKIM2::Signer->new(
    Domain   => 'example.com',
    Selector => 'sel1',
    KeyFile  => '/etc/dkim2/sel1.pem',
    MailFrom => '<sender@example.com>',
    RcptTo   => ['<recipient@example.net>'],
);

# One shot ...
$signer->load($message);
# ... or streaming, with CRLF line endings
$signer->PRINT($chunk) for @chunks;
$signer->CLOSE;

die $signer->result_detail unless $signer->result eq 'signed';
my $header = $signer->as_string;    # "DKIM2-Signature: i=1; ..." (folded)

# The same message to several envelope recipients: one pass, one cheap
# signature per recipient.
for my $rcpt (@rcpts) {
    my $header = $signer->sign_for_recipient($rcpt);
}

DESCRIPTION

Adds a DKIM2-Signature header for this hop. The message must already carry the Message-Instance header this hop wants to sign over (see Mail::DKIM2::MessageInstance; a message with none is a fail); the Signer reads the existing Message-Instance and DKIM2-Signature headers, chooses the next i=, builds the signing input of -09 section 10.5, and signs it. It does not alter the message: the caller prepends the header as_string returns.

Nothing it signs is in the body (section 12.8), so the body is discarded as it arrives, and a caller holding the message in a file may feed it the header block alone.

Extends Mail::DKIM2::HeaderParser, which provides PRINT, CLOSE, load and the tie interface.

This module implements draft-ietf-dkim-dkim2-spec-09; 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)

Required:

Domain

The signing domain, the d= tag.

Selector

The selector of the key, published at <Selector>._domainkey.<Domain>.

Key or KeyFile

The private key as a Crypt::PK::RSA or Crypt::PK::Ed25519 object, or the path of a PEM file holding one (loaded with "load_private_key" in Mail::DKIM2::Common).

Optional:

Algorithm

rsa-sha256 (the default) or ed25519-sha256.

MailFrom

The envelope sender of this hop, recorded in mf=. Bare addresses are bracketed; '<>' or undef records the null sender.

RcptTo

An arrayref of this hop's envelope recipients, recorded in rt=.

NextDomain

The d= of the hop that will sign next, recorded in nd= for an imaginary forwarding hop (-09 section 12.3). Excludes MailFrom and RcptTo.

Timestamp

The t= value; defaults to now. Fix it for reproducible test output.

Nonce

The n= value, at most 64 characters.

Flags

An arrayref of f= flags, such as feedback. An exploded among them is a request for the flag, as Exploded is.

Exploded

True when this hop sends the message to more than one address, as a list manager does (-09 section 12.5). The signature then carries the exploded flag, which it also gets whenever rt= holds more than one address. Never at i=1: there the flag, requested this way or in Flags, is dropped.

PolicyCallback, Resolver

The source of the Domain Policy Record (-09 section 3.8; new in 0.29), as for Mail::DKIM2::Verifier: a hop that must make From Authentic may sign with a d= below the From domain when that domain's record does not say f=strict (contract E3.4). The record is looked up only then, from PolicyCallback (($domain, $signer), returning the TXT strings or dying with TEMPERROR:) or through Resolver (default: a Net::DNS::Resolver). A strict, multiple or unavailable record refuses signing, the details naming why after the usual "not Authentic" text. $signer->fetch_domain_policy($domain) is the default lookup, for a callback to fall back to.

An option not listed here croaks.

METHODS

PRINT($bytes), CLOSE(), load($input)

Feed the message; see Mail::DKIM2::HeaderParser.

fetch_domain_policy($domain)

The default Domain Policy Record lookup (the TXT records at _domainkey.<domain> through Resolver, an arrayref; dies with TEMPERROR: on a failure), for a PolicyCallback to fall back to. New in 0.29.

result()

Undef until CLOSE; then 'signed', or 'fail' when the message cannot be signed: no Message-Instance to sign over, a chain already at the length limit, a DKIM2-Signature with a missing or malformed i= (or one that does not parse), an i= or m= that is not a chain number (see "chain_number_error($field, $tag, $n)" in Mail::DKIM2::Common), or a repeated i= or m=; or, when the message has no DKIM2-Signature yet or a Message-Instance no signature covers changed From, a From that is not Authentic for Domain (-09 sections 11 and 12.4: exactly one From field holding one mailbox whose domain is Domain or below it, or above it when the From domain's Domain Policy Record allows that), with details From header field is not Authentic for d=<Domain>, followed by : <reason> when the record decided it (From <dom> forbids subdomain signing, multiple records for <dom> policy, policy for <dom> unavailable). "details()" says which. A failure is a result, not an exception: PRINT and CLOSE return normally.

details()

The reason for a 'fail' result, or undef.

result_detail()

result and details together, e.g. "fail (PERMERROR ...)".

as_string()

The complete DKIM2-Signature: ... header, folded for insertion. The folding is part of what was signed and must not be changed.

Croaks no signature: REASON when there is no signature: before CLOSE, or when result is fail (REASON is details). Check result first. (Before 0.24 it returned the empty string, which a caller that prepended it unchecked turned into a blank line ending the message's header block.)

signature()

The Mail::DKIM2::Signature object, available after CLOSE.

sign_for_recipient($rcpt)

Re-signs for a different envelope recipient (one address or an arrayref of them) and returns the folded header. -09 section 12.8 signs only the Message-Instance and DKIM2-Signature fields, so the body and header hashes in the Message-Instance are the same for every recipient and only rt= changes; an extra recipient costs one signature over a few hundred bytes, not another pass over the message. Feed the message once, then call this per recipient. Croaks on a signature carrying nd=, which excludes rt=.

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.