Skip to content

DKIM2#848

Open
juliebin wants to merge 56 commits into
mainfrom
1259-dkim2-prototyping
Open

DKIM2#848
juliebin wants to merge 56 commits into
mainfrom
1259-dkim2-prototyping

Conversation

@juliebin

@juliebin juliebin commented Jun 1, 2026

Copy link
Copy Markdown
Contributor
  • Give your PR a recognizable title. For example: "FE-123: Add new prop to component" or "Resolve Issue Create new-pricing-increases-costs #123: Fix bug in component"
  • Your PR title will be visible in changelogs

What Changed

  • What changes does this PR propose?
  • Provide screenshots or screen recordings for any visual changes.

How To Test or Verify

  • Describe any steps that may help reviewers verify changes.
  • Anything beyond basic unit testing, such as assistive tech usage, or special interactions.

PR Checklist

Below are some checklists to follow for the correct procedure in different circumstance. The first list ("All PRs Checklist") should be followed for ALL PRs. The next 2 are additive to this list depending on what type of PR you are using.

For example: If you are submitting a content change to one of the support documents, your checklist would include the:

  • "All PRs Checklist"
  • AND the "Content Changes Checklist

If you are submitting a feature addition, enhancement, or bug fix, your checklist would include the:

  • "All PRs Checklist"
  • AND the "Development Changes Checklist"

All PRs Checklist

  • Give your pull request a meaningful name.
  • Use lowercase filenames.
  • Apply at least one team label according to which team is the content expert (ie. team-FE or team-SAZ)
  • Pull request approval from the FE team or content experts (see label applied above) that isn't the content creator.

Content Changes Checklist

  • Check that your article looks correct in the preview here or in a Netlify deploy preview.
  • Check the links in your article.
  • Check the images in your article (if there are any)
  • Check to make sure you are using markdown appropriately as outlined in examples/article.md in the root of the project directory and on the momentum doc's preface article
  • Check to make sure the Copy and Tone Guidelines are followed.

Development Changes Checklist (some checks are automatic github actions and will not be listed here. ie. "all tests pass")

  • The appropriate tests are created in cypress/ directory in the root of the project
  • The lighthouse score is passing according to the FE Support Docs' Service Outline SLI/SLOs

Note

Low Risk
Documentation-only changes with no runtime or configuration behavior; main risk is inaccurate operator guidance if the draft spec or product behavior diverges from the text.

Overview
Adds a full DKIM2 documentation set for Momentum 4, targeting draft ietf-dkim-dkim2-spec-02, placed next to the existing DKIM1 articles.

The new /momentum/4/dkim2 overview explains DKIM2 vs DKIM1, the dkim2 {} config stanza, Lua-driven signing/verification via validate_data_spool_each_rcpt, key reuse, coexistence with DKIM1/ARC, and documented implementation gaps (lower-hop crypto, DSN, forwarder/recipe automation).

Child pages document msys.validate.dkim2.sign() (hooks, options, forwarder bridges, recipes), verify() (§10.4 per-recipient checks, result table, §9.4 SMTP guidance), ar_clauses() for combined Authentication-Results, and a debug reference (debug_level, reason codes, recipe-chain paniclog strings, dkim2_* context keys).

content/momentum/navigation.yml gains a “Using DKIM2” section with links to signing and verifying anchors under Configuring Momentum.

Reviewed by Cursor Bugbot for commit 129960a. Bugbot is set up for automated code reviews on this repo. Configure here.

@netlify

netlify Bot commented Jun 1, 2026

Copy link
Copy Markdown

Deploy Preview for support-docs ready!

Name Link
🔨 Latest commit 129960a
🔍 Latest deploy log https://app.netlify.com/projects/support-docs/deploys/6a36afb984e43f000880c6b7
😎 Deploy Preview https://deploy-preview-848--support-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment thread content/momentum/4/dkim2.md
Comment thread content/momentum/4/using-dkim2.md Outdated
Comment thread content/momentum/4/dkim2.md Outdated
Comment thread content/momentum/4/dkim2.md Outdated
Comment thread content/momentum/4/dkim2.md
Comment thread content/momentum/4/using-dkim2.md Outdated
Comment thread content/momentum/4/dkim2.md
Comment thread content/momentum/4/dkim2.md Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 86efa82. Configure here.

Comment thread content/momentum/4/dkim2.md Outdated
Comment thread content/momentum/4/dkim2.md
Comment thread content/momentum/4/dkim2.md Outdated
Comment thread content/momentum/4/dkim2.md Outdated
Comment thread content/momentum/4/dkim2.md Outdated
Comment thread content/momentum/4/dkim2.md
Comment thread content/momentum/4/dkim2.md Outdated
@juliebin juliebin requested a review from dkoerichbird June 16, 2026 12:50
@juliebin

Copy link
Copy Markdown
Contributor Author

I'll restructure the page into sub-pages.


The following are known gaps or operational considerations to be aware of:

* **§10.5/§10.6 Lower-hop signatures not cryptographically verified**:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested rewrite:

  Lower-hop signatures not cryptographically verified (§9.1 / §9.2 / §10.5–10.6): Momentum runs the full cryptographic procedure — key fetch (§10.5) and EVP signature verification (§10.6) — only on the highest-i
  signature, which §9.1 makes a SHOULD. Earlier hops (i < max_i) get no key lookup and no crypto (status="chain_verified"); their integrity rests on the §8.3/§10.4 chain-of-custody check and the §9.2/§10 recipe
  reconstruction, which reverse-applies each hop's recipe to rebuild the original message and confirms the reconstructed instance-1 hashes match MI[1]'s h=. This proves end-to-end content integrity but does not
  authenticate each lower hop's signing key, so earlier-hop signer identities should not be used for Reviser reputation. The spec does not yet clearly mandate per-hop crypto (§9.1 is SHOULD; §9.3 implies it for
  reputation purposes but defers to a TBA doc, and §15 is TBA). Full per-hop verification — reverse-applying subsequent recipes to rebuild each hop's state and EVP-verifying each signature — would close this and
  is deferred to a future release.

significant architectural change that can be planned in a future
release if desirable.

* **§9.1 / §11 DSN**: The spec requires that when DKIM2 verification

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested rewrite:

- §9.1 / §11 DSN: Per §9.1, after a failed DKIM2 verification the
MTA MUST NOT generate a DSN; the spec recommends rejecting with a 5xx
during the SMTP conversation as the best alternative. This is not
automatically enforced — verify() only reports a verdict, so policy
must explicitly reject rather than bounce on verify failure. On the
generation side (§11), Momentum does not yet address a DSN to the
mf= of the highest-numbered DKIM2-Signature of the original message,
nor suppress DSN generation when that highest-numbered mf= is <>
(null sender). Inbound DSN authentication (§11.1.2, a SHOULD) is also
not implemented: the reject/propagate decision is scriptable via the
inbound hooks, but verifying the embedded returned message's
signatures — and checking signing-domain alignment against its
highest-i= rt= — has no exposed API, since verify() operates
only on the live message.

correctly. See the [Forwarder and modifier signing](/momentum/4/dkim2/sign#forwarder-and-modifier-signing) section for how to
do this.

* **Content modifier recipe composition**: When a Momentum pipeline

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested rewrite:

- Content modifier recipe composition: When an upstream-signed
message passes through a Momentum stage that modifies it — the
engagement tracker rewriting URLs, a footer filter, a list processor
changing headers — sign() automatically detects the change (its
freshly computed header/body hashes no longer match the prior
Message-Instance) and requires a recipe= describing how to reverse
the hop; without one the sign call fails. When the full diff isn't
available, the workaround is a null recipe declaring the change
irreversible: recipe='{"b":null}' for a body change,
recipe='{"h":null}' for header changes (a precise recipe when the
diff is available). This lets signing succeed and this hop's
signature verifies downstream — but earlier signatures' content can
no longer be reconstructed past this hop, so the inner chain is
broken for that field and acceptance depends on the verifier's
policy toward a broken chain. (Originated mail needs no recipe —
there's no prior instance to diff against.) Automatic change-recording
by pipeline stages is not yet built; a planned Recipe Accumulator API
would let sign() assemble the recipe without operator involvement.

If you already publish DKIM1 keys at a selector, you can reuse the same
selector for DKIM2 without any DNS change. To generate fresh keys for
DKIM2 specifically, follow the standard openssl recipe in
[Generating DKIM Keys](/momentum/4/using-dkim#using_dkim.generating).

@dkoerichbird dkoerichbird Jun 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link is wrong; it should be: /momentum/4/using-dkim#generating-dkim-keys

| `keybuf` | yes (single) | PEM-encoded private key as a string in memory. Alternative to `keyfile` for cases where the key is held in a secrets manager or generated at runtime. When `sig_sets` is used, set per entry inside `sig_sets` instead. |
| `algorithm` | no | `"rsa-sha256"` (default) or `"ed25519-sha256"`. When `sig_sets` is used, set per entry inside `sig_sets` instead. |
| `sig_sets` | no | Array of `{selector, keyfile, keybuf, algorithm}` tables for multi-algorithm signing (§7.8). When `sig_sets` is used, `selector`/`keyfile`/`keybuf`/`algorithm` are set per entry inside `sig_sets` — do not set them at the top level. All other options (`domain`, `mailfrom`, `rcptto`, `flags`, `recipe`, etc.) remain at the top level and apply to all entries. |
| `mailfrom` | no | **Normally omitted** — Momentum reads the live envelope MAIL FROM automatically. Two production exceptions: (1) null-sender DSN/bounce messages where `mailfrom=""` is required since the envelope API returns nil for `MAIL FROM:<>`; (2) testing/simulation of specific envelope conditions without real SMTP transit. |

@dkoerichbird dkoerichbird Jun 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Lack of consistency) In the corresponding table in verify.md, the "test/simulation" is not considered a production exception.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iiuc, you're suggesting to move the second bullet out of "production exceptions", which sounds good to me.

promoted to `dkim2=temperror` or `dkim2=permerror` for specific reason codes
(noted in the table below); `status="chain_verified"` and `status="none"` are excluded from AR output.

The full set. Unless otherwise noted, each reason code below pairs with `status="fail"` in `result.signatures[i].status`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get it: "the full set".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like that can be removed.

| `info` | Adds one DNS resolution line per verified signature plus verification failures with their cause (`bh_mismatch` with expected vs. actual hash; `sig_invalid` with selector, algorithm, signed-input length, and OpenSSL detail). |
| `debug` | Adds raw TXT-record bytes from the resolver, a per-crypto-check trace line, and the raw signed-input bytes on failure. Too noisy for steady-state production; useful when chasing a specific sign/verify mismatch. |

### Per-signature reason codes

@dkoerichbird dkoerichbird Jun 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this whole section ("Per-signature reason codes") should reside in the verify.md page. It is not just debugging; it is about what can be found in the validation results.

Same for "recipe_chain detail strings" below.

| `signature_expired` | The `t=` timestamp is older than `max_sig_age_days` (default 14). §10.3 says verifiers SHOULD reject such signatures; Momentum's implementation choice is to treat this as PERMERROR (permanently unverifiable — no cryptographic verification is attempted). Maps to `dkim2=permerror` in AR output. |
| `signature_future` | The `t=` timestamp is more than `max_sig_future_secs` (default 300 s) in the future. Treated as a soft policy failure (`dkim2=fail`): the timestamp was evaluated and rejected, but it is not a permanent infrastructure error — the spec (§7.4 MAY) does not define a verdict for this case. |
| `nonce_too_long` | The `n=` nonce exceeded the 64-character ceiling (§7.3 SHOULD). Treated as `dkim2=fail` — the constraint is a SHOULD, not a structural permanent error. |
| `mailfrom_mismatch` | The signed `mf=` doesn't match the actual envelope MAIL FROM — replay-to-different-sender. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "replay-from-different-sender"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find replay-to-different-sender. and the replay-to-different-recipient in next bullet confusing. Maybe just remove those phrases, since rest of the description is clear enough?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants