Repository navigation
feat(dkim2): add experimental DKIM2 signing and verification - #172
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implements DKIM2 as specified by the IETF drafts draft-ietf-dkim-dkim2-spec-06, draft-ietf-dkim-dkim2-dns-00 and draft-ietf-dkim-dkim2-bcp-01. DKIM2 is not an RFC yet, so the README and docs/dkim2.md name the exact draft versions. - dkim2Sign() and Dkim2SignStream add a DKIM2-Signature, and a Message-Instance when the message is new or has changed. A changed message needs a Recipe, which is checked against the previous instance before signing. RSA and Ed25519 keys, several keys per signature, sha256 and sha512 hashes, mf=/rt= or nd=, flags and nonce - dkim2Verify() recreates every instance with the Recipes and checks every hash and signature, the chain of custody between the hops and against the SMTP envelope, timestamps, donotmodify, donotexplode and replay (through a callback). Failures use the human-readable strings of the specification - Key records are read with the DKIM2 rules (one record per selector, retired tags ignored, k= has to match the algorithm) - authenticate() verifies DKIM2 when the dkim2 option is set and adds a dkim2 Authentication-Results entry (draft-gondwana-dkim2-authres-00). DKIM2 does not take part in DMARC - CLI: dkim2-sign command, report --dkim2 --rcpt-to - Type definitions, docs/dkim2.md, and tests with an independent reference implementation, property tests and mutation testing config Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
The chain of custody check against the delivery (section 11.4) only runs for the parts of the envelope the caller passes as mailFrom and rcptTo. Without them an unchanged copy of the message sent to anyone passes too, so status.warnings now lists mail-from-not-checked and/or rcpt-to-not-checked. The warnings do not change the result and are not written into Authentication-Results, like the DKIM1 warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- mailauth dkim2-verify verifies only the DKIM2 header fields, with the SMTP envelope (--mail-from, --rcpt-to), --dns-cache, --time, --max-age and --max-instances, and prints the JSON report or, with -o, the Authentication-Results entry - mailauth dkim2-hash prints the Message-Instance h= hash-sets of a message, backed by the new public dkim2Hash() library function - cli.md documents dkim2-sign, dkim2-verify, dkim2-hash and the DKIM2 options of report - the --dns-cache resolver that report, spf and seal each had a copy of is now shared in lib/commands/resolver.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the 42 test vectors of turscar/dkim2tests (BSD 2-Clause, made with the independent turscar/dkim2 implementation) as a JSON fixture and a test that verifies every vector, compares the section 9.6 canonical form, and signs every passing original with the same Message-Instance hashes and a signature that verifies with the vector's keys. Fixes found by the vectors: - a DKIM2-Signature with only unknown signature algorithms is a FAIL, as section 11.6 says when no signature can be checked, matching turscar/dkim2 (it was a PERMERROR) - the signature input leaves a malformed s= value as it is instead of rewriting it The flags_whitespace vector expects flags its signed message does not have, which the test records as a known vector defect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cross-checked with stalwartlabs/mail-auth (draft-ietf-dkim-dkim2-spec-04) in both directions: messages signed by either implementation verify with the other. The 16 messages signed by mail-auth are added as a fixture, with the harness that made them. The differences found are draft revision differences where mailauth follows spec-06, documented in docs/dkim2.md: - mail-auth fails a message with a null body Recipe, which spec-06 section 5.2 and draft-ietf-dkim-dkim2-bcp-01 treat as legitimate - older interop vectors of its corpus encode mf=/rt= without angle brackets and sign Received-SPF, both contrary to spec-06 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Checked against croessner/dkim2, a Go implementation built for the same drafts (draft-ietf-dkim-dkim2-spec-06, draft-ietf-dkim-dkim2-dns-00). Its public verification vectors are added to the interop test, and its canonicalization, Recipe, chain of custody, DNS and crypto vectors give the same results. - a DKIM2-Signature with only unknown signature algorithms is a PERMERROR again: nothing could be verified and nothing was incorrect, which section 11.1 defines as PERMERROR. The turscar vector written for spec-02 expects FAIL and is recorded as a spec revision difference - new opt-in maxFutureTime option (dkim2-verify --max-future) rejects timestamps too far in the future (section 8.4 makes it a MAY) - RSA keys over 8192 bits are not used for verifying or signing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efault maxFutureTime now defaults to 300 seconds, as croessner/dkim2 does (section 8.4 lets verifiers ignore signatures from the future, and draft-ietf-dkim-dkim2-bcp-01 section 3.6 lets them treat such timestamps with suspicion). false turns the check off, and so does dkim2-verify --max-future 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds experimental DKIM2 support, built against these Internet-Drafts (DKIM2 is not an RFC yet, the versions are stated in the README,
cli.mdanddocs/dkim2.md):dkim2Authentication-Results method from draft-gondwana-dkim2-authres-00What's included
Signing:
dkim2Sign()/Dkim2SignStream.mf=/rt=ornd=, flags and nonce.Verification:
dkim2Verify()recreates every instance through the Recipes and checks:maxFutureTime)donotmodify/donotexplodeResults are PASS/FAIL/PERMERROR/TEMPERROR with the human-readable strings from §11. When the caller doesn't pass the envelope,
status.warningsreports which part was not checked.Hashing:
dkim2Hash()returns a message's Message-Instance hashes.Keys: DKIM1 key records read with the DKIM2 rules: one record per selector, retired tags ignored,
k=must match the algorithm, RSA 1024–8192 bits with exponent 65537.authenticate(): opt-indkim2option (plusrcptTo,dkim2Options) that adds adkim2=A-R entry. DKIM2 is not fed to DMARC, which is still an open WG question (BCP §7.2).CLI:
mailauth dkim2-sign,mailauth dkim2-verifyandmailauth dkim2-hashreport --dkim2 --rcpt-to--dns-cacheresolver is now shared by all commands.DSN generation and propagation (§12) is left to the MTA.
Interoperability
Checked against three independent implementations. The vectors and messages are part of the test suite, with their licenses. Details are in
docs/dkim2.md.dkim2=none.nd=hops.mf=/rt=and signReceived-SPF.Testing
authenticate(), and DKIM1/DKIM2 coexistence.test/helpers/dkim2-reference.js) cross-checks hashes, the signature input and Recipe application.lib/dkim2scored 89%.lib/dkim2is now instryker.config.json./security-reviewfound that a hop withmf=<>skipped the chain-of-custody check, which allowed replay. It is fixed and has a regression test.Also bumps nodemailer to 10.1.0 and prettier to 3.9.10 (
npm run update).🤖 Generated with Claude Code