Implementing your own DKIM Reputation client

  1. Compute DKIM check results for an ingoing email, e.g. use opendkim or dkimproxy to add an Authentication-Results-Header to an incoming email
  2. Calculate different identity-triples for
    1. Multiple signing domains (these have to be "reduced" to registered domains, see "Compute registered domain names from signing domains")
    2. Multiple From header
    3. Optional Sender header
  3. For each identity-triple initiate a DNS request to hex(md5(user-part)).hex(md5(domain)).hex(md5(signingdomain)).al.dkim-reputation.org
  4. See "Proposal to calculate current reputation points"
  5. Recommendation: select max(rep_final, ...) and use this value as a part of your internal spam/ham score calculation.

Request syntax

1) Either hex(md5(<user-part>)).hex(md5(<domain>)).hex(md5(<signingdomain>)) for user-level DKIM reputation
2) or *.hex(md5(<signingdomain>)) for signingdomain-level DKIM reputation

<user-part>, <domain> and <signingdomain> should be trimmed strings (they shouldn't contain a trailing \20 character).


In case of 1) user-part can be
a)    either the user-part of a From header
b)    or the user-part of the optional Sender header
c)    and/or the concatenation of the user-parts of the optional Sender header and one From header with a $-separator, see the following example:

Example for ingoing "identity" parameters with multiple From and Sender:

authenticationResultsArr = "myverifier; dkim=pass header.i=something@signer.net; dkim=pass header.d=@secondsigner.com";
fromHeader = "From: Spammer <rule@theworld.com>, Spammersfriend <friend@secondsigner.com>";
senderHeader = "Sender: neatAccount@SecondSigner.com";

Example for a list of computed identities:


s = signing domain, u = user-part, d = originator domain
Array (
    Array(s=signer.net, u=neataccount, d=secondsigner.com), // = Sender
    Array(s=signer.net, u=neataccount$rule, d=secondsigner.com$theworld.com), // = Sender$From
    Array(s=signer.net, u=neataccount$friend, d=secondsigner.com), // = Sender$From
    Array(s=secondsigner.com, u=neataccount, d=secondsigner.com), // = Sender
    Array(s= secondsigner.com, u=neataccount$rule, d=secondsigner.com $theworld.com), // = Sender$From
    Array(s= secondsigner.com, u=neataccount$friend, d=secondsigner.com), // = Sender$From
)

IMPORTANT: Compute registered domain names from signing domains

We use the effective TLD list at http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/src/effective_tld_names.dat?raw=1 to calculate the registered domain from the signing domain in the Authentication-Results header. The signing domain above is set to the registered domain (calculated by the effective TLDs).

We provide libraries in C, Perl and PHP at http://www.dkim-reputation.org/regdom-libs/ that facilitate the computation of registered domains.

Example:

From: joe@mail.foo.com
DKIM-Signature: ... d=mail.foo.com ...
is added as an RR to al.dkim-reputation.org:

hex(md5("joe")).hex(md5("mail.foo.com")).hex(md5("foo.com"))

Why md5 hashes?

1)    we have to anonymize user addresses and domains because of data privacy reasons. Only specific requests on a given user/domain/signingdomain will return a result or NXDOMAIN.
2)    we have to guarantee that a domain name and its subdomains are within the allowed character length ranges

Side note: md5 hashes can overlap but we consider this as a theoretical problem only (you can try to calculate 2 valid domain names with the same md5 hash). This doesn?t matter at all for bad reputations but might become valuable with good reputations.

Sample Requests

Email: good@example.com Signingdomain: example.com (best reputation)

dig TXT 755f85c2723bb39381c7379a604160d8.5ababd603b22780302dd8d83498e5172.5ababd603b22780302dd8d83498e5172.al.dkim-reputation.org

Email: bad@example.com Signingdomain: example.com (worst reputation)

dig TXT bae60998ffe4923b131e3d6e4c19993e.5ababd603b22780302dd8d83498e5172.5ababd603b22780302dd8d83498e5172.al.dkim-reputation.org


Response syntax

Example: rep=285;time=20080708010153;wppd=1


Proposal to calculate current reputation points

if (rep>=0) { // bad/neutral reputation
    rep_final = max(rep - (today() - time) * wppd, 0)
} else { // good reputation (not used at time)
    rep_final = rep
}