signer: MIME headers and Message-ID signed; a long header line logged (audit M6, M8)

Security audit 2026-10-17, M6 and M8 (nits), stage 6.

M6: Content-Type, MIME-Version and Message-ID were not in h= (as in
OpenDKIM's default list): a relay could change a MIME boundary or the type
and the signature still verified. They are signed when present; Message-ID
is not over-signed, since cleanup adds one a message lacks after the
milters and an absent name in h= would refuse it.

M8: a header line over smtp_line_length_limit (998) is broken by Postfix's
smtp client after signing and the signature fails silently; only body
lines were logged. longHeaderLine names the header, once per message, in
the same words.

Tests: TestSignLinesCoverTheMIMEHeadersAndLongHeaderLinesAreNamed; the h=
expectations gain Message-ID; e2e dkim_shapes expects content-type: in h=
(and the receiver's verification over it). Docs: security.md,
architecture.md, guide; CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-17 22:37:14 +03:00
co-authored by Claude Opus 5.5
parent 820b1bb968
commit 5c5f49bad1
7 changed files with 72 additions and 13 deletions
+4 -2
View File
@@ -294,8 +294,10 @@ then OpenDKIM did, in a program of its own
- **What is signed** — `relaxed/simple`, `rsa-sha256`, with
`github.com/emersion/go-msgauth` v0.7.0 (`dkim`). `h=`: every header of
the message whose name is in RFC 6376 § 5.4.1's list as OpenDKIM's
`dkim_should_signhdrs[]` held it, and `Sender` and `List-Unsubscribe-Post`
besides (`signHeaders`), each occurrence, in message order; then, once
`dkim_should_signhdrs[]` held it, and `Sender`, `List-Unsubscribe-Post`,
`Content-Type`, `MIME-Version` and `Message-ID` besides (`signHeaders`;
`Message-ID` is not over-signed: cleanup adds one a message lacks after the
milters), each occurrence, in message order; then, once
more, each of `Subject`, `Date`, `To`, `Cc`, `Reply-To` and `Sender` the
message has (`overSign` - only those present: Postfix's cleanup adds a
missing `Date:` after the milters, out of their sight, and a name in `h=`
+4 -3
View File
@@ -1529,12 +1529,13 @@ DMARC counts that signature only when its domain aligns with the domain of
signature as another of its domains, or as the server itself.
**Lines of more than 998 bytes.** The signer signs the message as the
application handed it over. Postfix's SMTP client breaks a body line longer
than 998 bytes when it delivers the message (`smtp_line_length_limit`),
application handed it over. Postfix's SMTP client breaks a line longer
than 998 bytes, in the body or in a header, when it delivers the message (`smtp_line_length_limit`),
after the signature was made, and the receiver's check of the signature
then fails. Such a message is still sent; the container log names its
domain and login (`dkim-signer: example.com (login "shop"): a body line
longer than 998 bytes - ...`). Mail libraries keep lines short with
longer than 998 bytes - ...`, or `a line of the Subject header is longer
than 998 bytes - ...`). Mail libraries keep lines short with
quoted-printable or base64; an application that writes its own body has to
as well. A message whose header section is over 1 MiB is refused,
`552 5.3.4`.
+4 -2
View File
@@ -324,8 +324,10 @@ operator can act on it.
server's key may sign it. What cannot be signed (a login with no domain, a
name with no key, a read error) is deferred, never sent unsigned. Port 25
has no signer. `relaxed/simple`, rsa-sha256; `h=` names every header of
RFC 6376 § 5.4.1's list the message has, and `Sender` and
`List-Unsubscribe-Post`, then once more each of the shown ones present
RFC 6376 § 5.4.1's list the message has, and `Sender`,
`List-Unsubscribe-Post`, `Content-Type`, `MIME-Version` and `Message-ID`
(the last three since `2.14.4`, audit M6: unsigned, a relay could change a
MIME boundary and the signature still verified), then once more each of the shown ones present
(Subject, Date, To, Cc, Reply-To, Sender) and `From` last, so one of those
added after signing breaks the signature. Until `2.8.0` OpenDKIM signed - a
2018 beta with three 2026 CVEs open and nobody upstream to fix them