Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions content/momentum/4/dkim2/sign.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
lastUpdated: "07/07/2026"
lastUpdated: "07/27/2026"
title: "DKIM2 Signing — sign()"
description: "Reference for the msys.validate.dkim2.sign() Lua API: hook selection, sign options, forwarder and modifier signing."
---
Expand Down Expand Up @@ -136,12 +136,14 @@ msys.validate.dkim2.sign(msg, vctx, {
```

When `sig_sets` is present, all entries sign the same canonical
signed-input and are combined into a single `s=sel1:alg1:sig1,sel2:alg2:sig2`
value on one `DKIM2-Signature` header. Per §11.6 the verifier checks
every sig-set; overall passes if any one validates, so a receiver that
only supports RSA will still verify cleanly. On the verifier side, any
sig-set that fails alongside a passing one is reported as a DWARNING in
paniclog (partial-sig-failure condition, §11.6).
signed-input and are combined into a single
`s=sel1:alg1:sig1,sel2:alg2:sig2` value on one `DKIM2-Signature`
header. Per §11.6 the verifier checks
every sig-set; Momentum's overall verdict passes if any one validates,
so a receiver that only supports RSA will still verify cleanly. On the
verifier side, any sig-set that fails alongside a passing one is
reported as a DWARNING in paniclog (partial-sig-failure condition,
§11.6).

**If any sig-set fails**, the entire `sign()` call returns
`(nil, error_string)` — no partial signature is produced.
Expand Down
26 changes: 14 additions & 12 deletions content/momentum/4/dkim2/verify.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
lastUpdated: "07/01/2026"
lastUpdated: "07/27/2026"
title: "DKIM2 Verifying — verify()"
description: "Reference for the msys.validate.dkim2.verify() Lua API: verify options, result table, and SMTP response codes."
---
Expand Down Expand Up @@ -307,30 +307,32 @@ read the outcome without re-verifying or parsing `Authentication-Results:`:

| Context key | Type | Value |
|---|---|---|
| `dkim2_overall` | string | Verdict: `"pass"`, `"fail"`, `"permerror"`, `"temperror"`, or `"none"`. See the [SMTP response codes](/momentum/4/dkim2/verify#smtp-response-codes-111-guidance) table. |
| `dkim2_overall` | string | Verdict: `"pass"`, `"fail"`, `"permerror"`, `"temperror"`, or `"none"`. See the [SMTP response codes](/momentum/4/dkim2/verify#smtp-response-codes-104-guidance) table. |
| `dkim2_n_sigs` | string | Number of `DKIM2-Signature` headers found on the message. Parse with `tonumber()`. |
| `dkim2_highest_mf` | string | §12 DSN target: the `mf=` (bare MAIL FROM) of the highest-`i=` signature — the address a bounce would be addressed to. `"<>"` for the null sender, for which §12 says a DSN MUST NOT be sent. Not set when no signature carried an `mf=`. |

These keys are not set until `verify()` runs.

### SMTP response codes (§11.1 guidance)
### SMTP response codes (§10.4 guidance)

Momentum leaves the decision of whether to accept, reject, or defer a
message — and which SMTP reply code to use — entirely to the operator's
Lua hook. The `overall` field of the verify result maps to the following
SMTP behaviour as required by §11.1 of the DKIM2 spec:
SMTP behaviour as required by §10.4 of the DKIM2 spec:

| `overall` | Meaning | §11.1 guidance | Suggested action |
| `overall` | Meaning | §10.4 guidance | Suggested action |
|---|---|---|---|
| `pass` | All verifiable signatures passed | — | Accept |
| `none` | No DKIM2 signatures present, or all use unsupported algorithms (§3.4) | — | Local policy |
| `fail` | Verified but wrong: hash/sig mismatch or policy violation (`donotmodify`/`donotexplode`, etc.) | SHOULD 550/5.7.x; **MUST NOT 4xx** | Reject or accept per policy |
| `permerror` | Could not verify: key missing/revoked/invalid, key/algorithm mismatch (`overall_reason="key_alg_mismatch"`), syntax error, chain integrity failure (`overall_reason` is one of `mi_syntax`/`mi_missing_tag`/`mi_revision_ahead`/`mi_revision_gap`/`mi_unsigned`/`i_sequence_broken`/`custody_break`/`recipe_chain_mismatch`, or the generic `chain_broken`), or `d=`/`mf=` domain mismatch (`overall_reason="d_mf_mismatch"`) (§11.1 / §11.4 PERMERROR) | SHOULD 550/5.7.x; **MUST NOT 4xx** | Reject (permanent) |
| `permerror` | Could not verify: key missing/revoked/invalid, key/algorithm mismatch (`overall_reason="key_alg_mismatch"`), syntax error, chain integrity failure (`overall_reason` is one of `mi_syntax`/`mi_missing_tag`/`mi_revision_ahead`/`mi_revision_gap`/`mi_unsigned`/`i_sequence_broken`/`custody_break`/`recipe_chain_mismatch`, or the generic `chain_broken`), or `d=`/`mf=` domain mismatch (`overall_reason="d_mf_mismatch"`) (§11.1 / §11.4 PERMERROR) | SHOULD 550/5.7.x | Reject (permanent) |
| `temperror` | Transient key-fetch failure (DNS timeout / SERVFAIL) | MAY 451/4.7.5 | Defer (temporary) |

**Key rules from §11.1**:
- `fail` and `permerror` **MUST NOT** use a 4xx reply code.
- Only `temperror` warrants a temporary (4xx) failure code.
**Key rules from §10.4**:
- `fail` (cryptographic signature verification failure) **MUST NOT** use a
4xx reply code; other permanent errors (`permerror`) SHOULD also use 5xx.
- Only `temperror` (a temporary failure such as key-server unavailability)
warrants a temporary (4xx) failure code.

Example hook skeleton:

Expand All @@ -345,13 +347,13 @@ end
local overall = result.overall

if overall == "permerror" or overall == "fail" then
-- §11.1 SHOULD 550/5.7.x for permanent failures.
-- Note: "permerror" MUST NOT use 4xx.
-- §10.4 SHOULD 550/5.7.x for permanent failures;
-- "fail" (crypto failure) MUST NOT use 4xx.
vctx:set_code(550, "5.7.1 DKIM2 verification failed")
return msys.core.VALIDATE_DONE

elseif overall == "temperror" then
-- §11.1 MAY 451/4.7.5 for transient key-fetch failures
-- §10.4 MAY 451/4.7.5 for transient key-fetch failures
vctx:set_code(451, "4.7.5 DKIM2 key server temporarily unavailable")
return msys.core.VALIDATE_DONE

Expand Down
Loading