Skip to content

fix: DKIM sign formatting#483

Closed
AdityaAudi wants to merge 2 commits intoKumoCorp:mainfrom
AdityaAudi:fix/dkim-sign-formatting
Closed

fix: DKIM sign formatting#483
AdityaAudi wants to merge 2 commits intoKumoCorp:mainfrom
AdityaAudi:fix/dkim-sign-formatting

Conversation

@AdityaAudi
Copy link
Copy Markdown
Contributor

Motivation

During post-migration validation of a CIDR block of customer mail, AWS SES testing endpoint reported dkim=permerror on DKIM signatures generated by KumoMTA after migration. This indicated a signature formatting issue that could impact delivery for customers using strict DKIM validators. The issue only manifested when signing many headers (especially with List-Unsubscribe headers included), suggesting a formatting bug rather than a cryptographic problem. Interestingly, more lenient validators (like Outlook) accepted the same messages, confirming the issue was specific to header formatting rather than the signing algorithm.

Problem Statement

The DKIM-Signature header generation in KumoMTA had a critical bug in RFC 5322 header folding logic. When the h= parameter (list of signed headers) exceeded the 75-character line length limit and needed to wrap across multiple lines, the folding algorithm would split header field names mid-word instead of only at proper delimiters (colons).

Example of Corrupted Output (observed in AWS SES testing):

h=from:to:subject:date:message-id:list-unsubscrib	e:		list-unsubscribe-post:...
                                              ^^^^^ SPLIT IN MIDDLE OF HEADER NAME

This malformed h= value would be inserted into the DKIM-Signature header, creating an unparseable signature that strict validators reject while lenient validators attempt to work around.

Root Cause

The serialize() function in header.rs (lines 97-170) performed two sequential wrapping operations:

  • First wrap (lines 119-151): Custom colon-aware wrapping for the h= parameter using a custom word separator that breaks only after colons (correct)
  • Second wrap (lines 162-169): Generic textwrap::fill() with AsciiSpace word separator applied to the entire output (probably incorrect)

The second wrap would re-wrap already-wrapped content, and since it only understood space-delimited words, it could break header names mid-word when they didn't fit on a single 75-character line. The h= value would be unwrapped and re-wrapped without understanding its colon-delimited structure.

Solution

Modified the serialize() function to intelligently handle wrapping:

  • Detect pre-wrapped content: Check if output contains \r\n (indicating h= or b= already on separate lines)
  • Selective wrapping: Only apply generic wrapping to content before the first line break
  • Preserve special parameters: Keep the already-wrapped h=/b= sections intact with proper RFC 5322 tab indentation
  • Safe fallback: For short output without line breaks, apply standard wrapping as before

This ensures that:

  • The colon-aware h= wrapping is preserved exactly as formatted
  • Header names are never split mid-word
  • RFC 5322 compliance is maintained (proper tab indentation for continuation lines)
  • Backward compatibility is preserved for all other use cases

@AdityaAudi AdityaAudi changed the title fix: DKIM sign formattin fix: DKIM sign formatting Feb 27, 2026
@wez wez closed this in f0e48bd Feb 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant