Hi all,

I wanted to share some news from implementing DKIM2, along with a few findings 
that came out of interop testing. Incidentally, this message is itself DKIM2 
signed, so if your tooling supports it, feel free to use it as a small 
real-world sample.

I have added DKIM2 support (draft-ietf-dkim-dkim2-spec-03) to mail-auth, the 
Rust library for email authentication, which lives at 
https://github.com/stalwartlabs/mail-auth. One thing worth mentioning is that 
mail-auth also compiles to WebAssembly, so it can be used from JavaScript 
applications and other WASM environments. To make it easy to try, I put 
together a playground at https://mail-auth.stalw.art/ that signs and validates 
DKIM2, as well as DMARC, entirely from the browser, resolving the records using 
DNS-over-HTTP.

DKIM2 is now also supported in Stalwart mail server 
(https://github.com/stalwartlabs/stalwart/), which uses mail-auth internally. 
Beyond signing and verification, I have implemented inbound DSN validation for 
DKIM2 as described in the draft, so that delivery status notifications coming 
back along the chain can themselves be authenticated.

On interoperability, I tested mail-auth against Bron's Python implementation 
(https://github.com/dkim2wg/interop.git) and Steve's Go implementation 
(https://forge.turscar.ie/turscar/dkim2.git). The integration tests are at 
https://github.com/stalwartlabs/mail-auth/blob/main/src/dkim2/interop_test.rs 
and they exercise signing, verification and recipe generation across all three 
implementations, in both directions, including multi-hop chains. The good news 
is that with header folding disabled, every combination passes: signing, 
verifying and recipe based reconstruction all agree across the three 
implementations.

I did hit one issue, when mail-auth folds the DKIM2-Signature and 
Message-Instance header fields, which is expected behaviour for long fields 
such as the base64 signature, both the Python and the Go implementations fail 
to parse them. The draft describes in section 2.12 that folding whitespace is 
allowed inside base64 strings and must be ignored when the value is used, so I 
believe mail-auth's output is conformant here, and it round-trips cleanly 
through my own parser. In Steve's Go library the mf= and rt= values are base64 
decoded in strict mode without first removing the folding whitespace, which 
trips on the embedded space or tab. In Bron's Python library the s= value is 
split on the colon without unfolding first, so the algorithm token ends up 
carrying the leading CRLF and tab, and the algorithm comparison then fails. 
Neither looks hard to fix, and as noted, disabling folding on my side makes all 
tests pass.

Finally, a couple of small notes on the spec itself. Overall I found it clear, 
but a few of the sections that describe algorithms in prose would benefit from 
some accompanying pseudocode, in particular the parts covering recipe 
application and the chain walk during verification. It would remove a little 
ambiguity for implementers. The other point concerns header field names in 
recipes. It is not entirely clear from the text whether the JSON keys must be 
lowercased or may retain their original case. Steve's implementation expects 
lowercase and rejects mixed case, whereas I initially generated recipes 
preserving the original header case, which the current wording about matching 
without regard to case seemed to permit. A sentence stating the expected 
behaviour would avoid this divergence.

Thanks to everyone working on this. It has been a pleasure to implement, and I 
am happy to help with test vectors or further interop testing.


Best,
Mauro

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

Reply via email to