70.1.8. pepsi-stage-dkim-sign¶
DKIM-sign a message in the Pepsi pipeline
- Manual section:
1
70.1.8.1.1. Name¶
pepsi-stage-dkim-sign - DKIM-signing stage of the Pepsi pipeline.
70.1.8.1.2. Synopsis¶
pepsi-stage-dkim-sign [GLOBAL-OPTIONS] worker
70.1.8.1.3. Description¶
pepsi-stage-dkim-sign is a stage program run by pepsi-dispatch(1)
as a persistent worker reading message ids on standard input. It prepends two DKIM-Signature headers (one RSA,
one Ed25519) to the message’s raw bytes and advances the row to its
NEXT_STAGE (a delivery stage). Signing only prepends header lines, so any
existing signatures lower in the message are undisturbed. It does not touch the
row’s state, so any RFC 3461 state.dsn parameters are preserved for later
stages to honour.
The signing domain (d=) is SIGNING_DOMAIN if configured, otherwise the
domain of the message’s From: header. The keys are the per-domain DKIM keys
under the shared [pepsi] KEY_DIR (provisioned by pepsi-setup(1)), using
the selectors from the [pepsi] section.
This stage exists so that message construction and DKIM signing are separate
steps. In particular pepsi-stage-bounce(1) builds an unsigned DSN and
points its NEXT_STAGE here, so a generated bounce is signed as its
postmaster’s domain before delivery.
Signing is fail-closed: if no signing domain can be determined (no
SIGNING_DOMAIN and no usable From: header), or the domain’s keys are
missing, or signing errors, the message is moved to the terminal failed state
(with the reason in state.last_error) rather than being advanced unsigned.
A stage that cannot sign is a misconfiguration — most often the message reached
dkim-sign for a domain this deployment holds no keys for — and failing it
loudly (for the operator, or pepsi-failure-bouncer(1), to see) is safer
than silently forwarding it unsigned, which defeats the reason the stage was
placed in the pipeline.
70.1.8.1.3.1. Body coverage¶
By default the signature strictly covers the whole body: any change to the body
in transit invalidates it. With COVER_BODY = no the signatures instead carry
a body-length (l=) tag fixed to the body as signed. A verifier then ignores
anything appended after that length, so a downstream hop may add a trailing
footer without breaking the signature. The signed portion of the body is still
fully covered; only appended content is tolerated.
70.1.8.1.3.2. Header coverage¶
The headers covered by the signature (the h= tag) are the SIGNED_HEADERS
list (default: the common originator/MIME headers). From and Subject are
listed twice so they are over-signed (RFC 6376 §8.15): a header named once
more than it actually occurs is signed one extra (empty) time, so an attacker who
prepends a second From: or Subject: — the headers that change a message’s
apparent origin or meaning — produces an occurrence the signature does not cover,
which breaks verification. Listing any header in SIGNED_HEADERS that is absent
from the message likewise over-signs it (it cannot later be added).
70.1.8.1.4. Configuration¶
The stage is wired and configured through its own [stage-<name>] section
(PROGRAM = pepsi-stage-dkim-sign, a required NEXT_STAGE). Its options —
COVER_BODY, HEADER_CANONICALIZATION/BODY_CANONICALIZATION (chosen
independently), SIGNATURE_EXPIRATION_DAYS, SIGNED_HEADERS and SIGNING_DOMAIN
— together with the shared [pepsi] key material (KEY_DIR,
DKIM_SELECTOR) are documented in pepsi.conf(5). The hash is always
SHA-256; both an RSA and an Ed25519 signature are emitted.
70.1.8.1.5. State¶
Inputs: none from state — the signing domain comes from
SIGNING_DOMAIN or the from_header column, and the body hash from the
loaded message.
Outputs: none added to state. The signatures are prepended to the
headers column; the whole state — including state.dsn — is preserved
unchanged. The state layout is described in pepsi.state(7).
Transitions: on success advances to NEXT_STAGE (the delivery stage),
having prepended the DKIM-Signature header(s). If the signing domain cannot be
determined or its keys are missing / signing errors, the message is moved to the
terminal failed state (fail-closed; see above). The stage never pauses,
reroutes or finishes.
70.1.8.1.6. Commands¶
Run as a worker, the program signs each message and advances it.
70.1.8.1.7. Global Options¶
- -c FILE, –config FILE
Read the configuration from FILE instead of the default search path.
- -L LOGLEVEL, –log LOGLEVEL
Set the logging verbosity (
error,warn,info,debug,trace; defaultinfo).- -h, –help
Print a usage summary and exit.
- -V, –version
Print the version and exit.
70.1.8.1.8. Exit Status¶
- 0
The message was signed and moved to its next stage, was failed (fail-closed) when it could not be signed, or the subcommand succeeded.
- 1
An error occurred (misconfiguration, the message is not in the expected state, or a database error). The reason is written to the log.
70.1.8.1.9. See Also¶
pepsi-config(1), pepsi-stage-bounce(1), pepsi-stage-relay-to-internet(1), pepsi-stage-relay-to-smarthost(1), pepsi-dispatch(1), pepsi-ingress(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)
70.1.8.1.10. Bugs¶
Report bugs to the Pepsi issue tracker.