Hi Edmund,

Yes, your implementation is correct, this an issue in the Go and Python 
libraries which are producing non-conformant output.
It’s just that my implementation is more permissive and accepts recipe-less 
instances, but perhaps it shouldn’t.


Best,
Mauro


> On 5 Jul 2026, at 16:39, Edmund Lodewijks <[email protected]> wrote:
> 
> Hi Mauro,
> 
> Thank you very much for your reply and for adding PhoenixDKIM to the interop 
> test suite. I will definitely have to keep up with the moving spec now, which 
> is great!
> 
> With regards to the failure you noticed: Isn't that exactly what spec-03 
> requires? §9.1.3 now reads as a MUST — a Message-Instance has to carry either 
> recipes that recreate the message at the current top hashes, or an explicit 
> null recipe to say reconstruction isn't possible. A non-first instance with 
> no r= at all is neither of those, so PhoenixDKIM treats the chain as broken 
> there rather than guessing at what the hop did.
> 
> Those two forms are handled as follows by PhoenixDKIM:
> 
> - an explicit null recipe: It stops the backward walk and passes on what it 
> verified down to that hop;
> - an empty/identity recipe: It reconstructs prev == current and hash-checks, 
> which a genuinely transparent hop passes cleanly.
> 
> So, I don't think the Go/Python output is far off — it's just omitting r= 
> entirely on the transparent second hop, where the spec wants one of those two.
> 
> Alternatively, per §9.2.5, a hop that changes nothing MAY simply not add an 
> instance at all, which is what PhoenixDKIM's own signer does on a plain 
> re-sign.
> 
> Of course, there is this ambiguity in wording between two paragraphs:
> 
> §9.1.3 says MUST (recipes or null recipe), but §9.1.6 still says SHOULD ("all 
> other Message-Instance header fields SHOULD contain at least one recipe").
> 
> I went with the MUST, as that one was introduced in spec-03.
> 
> Am I doing this correctly, or am I missing something?
> 
> Kind regards,
>    Edmund
> 
> 
> On 2026/07/04 19:31, Mauro De Gennaro wrote:
>> Hi Edmund,
>> Thank you for putting the message through PhoenixDKIM.
>> Regarding the failed DKIM2 signature from my previous email you tried, that 
>> was entirely expected as the IETF DKIM lists do not handle DKIM2 signatures 
>> yet. If you want to exercise real interop, you have two options. One is to 
>> install the Rust toolchain and run mail-auth directly. The other, and 
>> probably the quicker one, is the playground at https://mail-auth.stalw.art. 
>> It will sign a message with any Ed25519 or RSA key you provide, straight 
>> from the browser, so you can feed the output into PhoenixDKIM without 
>> building anything.
>> On folding: I went ahead and added PhoenixDKIM to the mail-auth interop test 
>> suite, and everything passed, including the folded cases. When mail-auth 
>> emits folded DKIM2-Signature and Message-Instance headers, PhoenixDKIM 
>> unfolds and verifies them correctly across single-hop and multi-hop chains, 
>> for both Ed25519 and RSA. So your handling of whitespace in the base64 looks 
>> solid from this side too.
>> I did find a failure in PhoenixDKIM but not against mail-auth. The interop 
>> test suite builds a single container with four implementations (mail-auth, 
>> the Go and Python references, and now PhoenixDKIM), then runs a matrix: 
>> every implementation signs a message, and every implementation verifies each 
>> result, across single-hop, two-hop and three-hop chains. In the plain matrix 
>> the message is relayed unchanged between hops, so the intermediate hops are 
>> transparent re-signs.
>> That last detail is what trips PhoenixDKIM when the producer is the Go or 
>> Python tool. On a transparent re-sign, those two add a fresh 
>> Message-Instance (m>=2) with no recipe, and PhoenixDKIM's verifier rejects 
>> any non-first Message-Instance that carries no recipe, with:
>> PERMERROR: Message-Instance m=N carries no recipe
>> It failed on all 14 multi-hop combinations where a relaying (non-originator) 
>> hop was produced by Go or Python. For what it is worth, I think 
>> PhoenixDKIM's verifier is on reasonable ground here, and mail-auth agrees 
>> with you in spirit. Section 9.1 says a forwarder that leaves the hashes 
>> unchanged SHOULD NOT add a new Message-Instance at all. mail-auth follows 
>> that: on an unmodified hop it adds only a DKIM2-Signature and reuses the 
>> existing m, so there is never a recipe-less instance to reject. 
>> PhoenixDKIM's own signer does the same. It is really the Go and Python 
>> signers that emit the recipe-less instance, and then your verifier flags it.
>> To reproduce without the full suite: sign a message as originator with the 
>> Go tool, then run the Go signer again over that output without touching the 
>> body (a transparent second hop), then verify with phoenixdkim2-verify. You 
>> will get the PERMERROR above. The Python signer behaves the same way. If you 
>> would rather see all 14 at once, run the mail-auth interop matrix with the 
>> skip guard I added (skip_phoenix_vs_gopy) disabled:
>> cargo test --lib dkim2::interop_test::dkim2_interop_matrix -- --ignored 
>> --nocapture
>> Finally, to answer your question about Stalwart, no, DKIM2 is not enabled by 
>> default, and it will not be until DKIM2 becomes an RFC. Anyone who wants to 
>> try it has to turn it on manually, which is only a few clicks. The reason 
>> for the caution is that the draft is still moving. It changed in ways that 
>> broke signatures in the past, and it may well change again, so I would 
>> rather users opt in knowingly than have signatures silently break under them 
>> on an upgrade.
>> Thanks again for the testing and the thoughtful feedback. The PhoenixDKIM 
>> link is noted, I will keep testing against it.
>> Best,
>> Mauro
> 
> 
> -- 
> Edmund Lodewijks <[email protected]>
> TZ: UTC+2 / GMT+2
> 
> _______________________________________________
> Ietf-dkim mailing list -- [email protected]
> To unsubscribe send an email to [email protected]

_______________________________________________
Ietf-dkim mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to