Vulnerability analysis · DKIM
A DKIM key record published as s=tlsrpt must be ignored by an email verifier. Rspamd returns R_DKIM_ALLOW for it. OpenDKIM and dkimpy both reject the same record.
| Software | Rspamd |
|---|---|
| Vendor | Rspamd project (Vsevolod Stakhov) |
| Source | https://github.com/rspamd/rspamd |
| Affected | Rspamd 4.1.2. Originally isolated on 3.10.2 and re-confirmed on 4.1.2 on 2026-07-28. OpenDKIM 2.11.0 and dkimpy 1.1.8 both enforce the tag correctly. Newest published release checked on 2026-07-30. |
| Where | DKIM public-key record handling, the s= service-type tag of RFC 6376 section 3.6.1 |
| Weakness | CWE-347 (Improper Verification of Cryptographic Signature) / CWE-20 (Improper Input Validation) |
| Reachable by | Remote. The victim key record must declare a non-email service type, and the attacker must present a signature made with that key. |
| CVSS v3.1 | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N (3.7 (Low)) |
| Verification | Reproduced on 2026-07-28 against the current release (run 20260728-120957) with a scripted reproducer, negative controls, and a second independent implementation as the differential oracle. Three deterministic repetitions with s=email, s=* and s=email:tlsrpt controls, independently re-verified with dkimpy as a third oracle. |
A DKIM public-key record can name the services that may use the key. RFC 6376 section 3.6.1 defines the s= tag for that purpose and gives it a default of *, meaning all services. The same section states: "Verifiers for a given service type MUST ignore this record if the appropriate type is not listed." Email verification is service type email. A record that lists only some other service, or a service nobody recognizes, therefore leaves an email verifier with no key at all, and a signature with no key fails.
Rspamd never looks at the tag. Its DKIM verdict does not change when the tag changes, so a key its owner scoped away from email still produces R_DKIM_ALLOW. OpenDKIM refuses the same record with the reason not an e-mail key. dkimpy raises a ValidationError.
One valid RSA signature was used for every trial. The only thing that varied was the s= tag in the key record.
Key record s= | OpenDKIM | Rspamd | dkimpy | Required |
|---|---|---|---|---|
s=email (control) | pass | R_DKIM_ALLOW | pass | pass |
s=* (control, the RFC default) | pass | R_DKIM_ALLOW | pass | pass |
s=tlsrpt (a non-email service) | fail, reason="not an e-mail key" | R_DKIM_ALLOW | fail (ValidationError) | fail |
s=foo (an unknown service) | fail | R_DKIM_ALLOW | fail | fail |
Two independent implementations agree on the required verdict. OpenDKIM even names the cause in its own reason string. Rspamd returns the same verdict in all four rows. That uniformity is the evidence: the tag is not being parsed and then mishandled, it is not being read.
The s=foo row carries as much weight as the s=tlsrpt row. An unrecognized service type is not a case to be waved through. Section 3.6.1 tells the verifier to ignore any record whose list omits its own service type, so a value the verifier does not understand must render the record unusable for email.
sel._domainkey.signer.example. IN TXT "v=DKIM1; k=rsa; s=tlsrpt; p=<base64 RSA public key>"
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=signer.example; s=sel;
h=from:to:subject:date; bh=<body hash>; b=<signature>
Expected, per RFC 6376 section 3.6.1: the record does not list the email service type, so an email verifier ignores it, finds no key, and fails the signature.
Observed: Rspamd reports R_DKIM_ALLOW. OpenDKIM reports dkim=fail with reason="not an e-mail key". dkimpy raises a ValidationError.
Swapping s=tlsrpt for s=email, s=* or s=email:tlsrpt makes all three implementations pass. The service-type tag is the only variable in play.
An operator publishes s= to pin a key to one job. A key issued to a reporting service or some other non-mail system, and labeled as such, is supposed to stay useless for signing mail even if the private half leaks to a host that has no business sending mail. Rspamd drops that boundary. Any signature made with such a key is accepted as a valid email signature.
The limit of the claim is the same as for the other key-record findings, and it is worth stating plainly. The party that publishes s= is the party that owns the key. The missing check therefore gives an attacker no way to forge a signature for a domain they do not control. What it takes away is the operator's ability to separate key material by purpose, which is the whole point of the tag. A narrowly scoped non-mail key, once compromised, becomes a mail-signing capability on any Rspamd receiver and on no conforming one.
Read the s= list out of the key record before using the key. Apply the RFC 6376 section 3.6.1 default of * when the tag is absent. Reject the record for email use when email does not appear in the list. Treat a service type that does not parse, or that the verifier does not recognize, as grounds for rejection rather than as permission.
This finding sits in the Rspamd DKIM key-record constraint cluster alongside rspamd-key-hash-list-ignored (the h= hash list) and rspamd-auid-subtree-unchecked (the i= AUID subtree check). All three have one shape: the specification names a constraint, and the verifier does not apply it. They are best disclosed and fixed as a group.
Few deployments publish a non-default s=. That bounds exposure in the field. It does not affect the conformance claim. Confirmation against the upstream rspamd_dkim source is still an open step; what is recorded here is behavior.