Provided by: dkim-rotate_1.1_all
NAME
dkim-rotate - Principles of Operation
INTRODUCTION
dkim-rotate is a tool for managing DKIM (email antispam) keys in a manner that avoids unnecessarily making emails nonrepudiable. Problem statement Using a static or nearly-static DKIM signing key enables anyone who obtains a copy of an email to verify its authenticity. This can be used to verify the authenticity of data from a data breach, for example. This is not a desirable property, from the point of view of an email system’s users, and wasn’t an intended consequence of DKIM’s antispam function. For fuller discussion of the nonrepudiability problem with DKIM, see the blog post Ok Google: please publish your DKIM secret keys, referenced in the SEE ALSO section. Solution - function of dkim-rotate We periodically generate a new key. We deadvertise old keys (removing them from the set advertised in the DNS), We publish the private halves of old keys. The overall result is that because old emails are forgeable (by anyone, since the private key has been published), emails become no longer nonrepudiable. We add appropriate warnings, and alter the DNS, to alert naive verifiers to the situation. Output and state files dkim-rotate will maintain and update the following output files and directories: /var/lib/dkim-rotate/instance/zone Zonefile in standard master file syntax. Created by taking the config file, editing the serial number before ;!SERIAL, and appending TXT RR definitions. The appended RRs have single-character alphabetic labels, the selectors. See dkim- rotate(5). /var/lib/dkim-rotate/instance/priv/keyname.pem Private key for use by the MTA. See MTA CONFIGURATION. /var/lib/dkim-rotate/instance/exim File in format suitable for Exim ${lsearch }. See MTA CONFIGURATION. /var/lib/dkim-rotate/instance/pub/ Directory where private keys are published (deliberately leaked). See KEY PUBLICATION. /var/lib/dkim-rotate/instance/state Principal state file. /var/lib/dkim-rotate/instance/... Other state and temporary files are stored here. (Each dkim-rotate instance is completely separate; they do not share state, or configuration.)
SELECTORS
dkim-rotate maintains a collection of DKIM keys. The (currently advertised) keys each have a “selector”, dkim-rotate uses a small fixed set of selectors, in rotation. Each selector is an (ASCII lowercase) letter, so dkim-rotate supports use of up to 26 selectors. The default is 12. Current keys - selectors A DKIM signature found in an email indicates where to find the key. It includes a “selector”, which is a set of DNS labels to be prepended to the base DKIM domain for the mail domain which originated the email and by whose authority the message is being signed. A selector can be reused as soon as the key which was previously using that selector should longer be advertised. When creating keys, dkim-rotate will automatically choose a suitable available selector. The selector in DKIM terms is (usually) the dkim-rotate selector plus a fixed label indicating the signing authority (ie, the dkim-rotate instance). dkim-rotate itself does not know the actual DKIM selectors; the suffix is added in the MTA and DNS configurations. DNS selector advertisement dkim-rotate outputs a DNS zonefile, complete with serial number, as /var/lib/dkim- rotate/instance/zone. Usually, this will be published directly by a nameserver, as a dedicated DNS zone, not used for other purposes. This allows the management of the mail domains’ zones to be separated from the DKIM system. Let us imagine that the dkim-rotate output zone is dkim-rotate.example.net. Within that zone, dkim-rotate will create DKIM TXT records, which look like this in the output zone file: k IN TXT "v=DKIM1; h=sha256; s=email; n=...; p=..." This implies the following RRset: k.dkim-rotate.example.net. IN TXT "v=DKIM1; ..." A mail domain (let us imagine, example.com), which wishes to indicate that this system is authorised to make DKIM signatures, will use a set of CNAMEs to delegate that authority: $ORIGIN example.com. a.example-net._domainkey CNAME a.dkim-rotate.example.net. b.example-net._domainkey CNAME b.dkim-rotate.example.net. c.example-net._domainkey CNAME c.dkim-rotate.example.net. d.example-net._domainkey CNAME d.dkim-rotate.example.net. e.example-net._domainkey CNAME e.dkim-rotate.example.net. f.example-net._domainkey CNAME f.dkim-rotate.example.net. g.example-net._domainkey CNAME g.dkim-rotate.example.net. h.example-net._domainkey CNAME h.dkim-rotate.example.net. i.example-net._domainkey CNAME i.dkim-rotate.example.net. j.example-net._domainkey CNAME j.dkim-rotate.example.net. k.example-net._domainkey CNAME k.dkim-rotate.example.net. l.example-net._domainkey CNAME l.dkim-rotate.example.net. So, overall, we have something like this: example.com. MX mx0.example.com. k.example-net._domainkey.example.com. CNAME k.dkim-rotate.example.net. k.dkim-rotate.example.net. TXT "v=DKIM1; ..." DNS output file and nameserver configuration The zonefile is written to /var/lib/dkim-rotate/instance/zone. After it has been updated, dkim-rotate runs rndc reload (or the configured dns_reload command). If this all occurs successfully, dkim-rotate assumes that dns_lag later, the new DNS records (and any deletions) are available everywhere. dkim-rotate does not use DNS Dynamic Update.
MTA CONFIGURATION
dkim-rotate provides the selector, and the private key, to the MTA. This is done by writing /var/lib/dkim-rotate/instance/exim. This is in a key-colon-value format, which is convenient for use by Exim’s lsearch lookup facility. After this file is updated, dkim-rotate runs the configured mta_reload command. This is just true (a no-op) by default (and Exim doesn’t need it). MTA configuration output format The output file is lines of the form: key: value It may also contain #-comment lines. The values are literal text, without any quotes (and therefore cannot contain newlines). The keys are; privkey The filename of the private key to use. This will be in the form /var/lib/dkim- rotate/instance/priv/hex.pem. selector The selector under which the corresponding public key is advertised. header_note Some text which it would be useful to put into the email headers. It starts NOTE REGARDING DKIM KEY COMPROMISE. This could be put into a note= or warning= tag in the actual DKIM-Signature header. If that is not possible (e.g. Exim doesn’t support it) it could be put into DKIM- Signature-Warning, say. It is probably a good idea to arrange that it is itself covered by the signature, to make it more complicated for an adversary to strip it out. url, readme_url URLs for the instance’s public WWW directory, the README.txt file, corresponding to this instance. key_reveal_url The URL at which the private key will be revealed to the world, after the key has been retired. (Obviously, when this URL appears in the /exim file, the URL is not yet valid.) Example Exim configuration DKIM signing is done with additional options on the smtp transport. The mailserver ought not to be a signing oracle for arbitrary incoming emails which are being relayed (eg via forward files) — only for emails generated locally, or from appropriately authorised places. And we should choose, for the signing domain, the domain which appears in the From: header, and sign only if DKIM is enabled for that domain. The required config looks something like this: smtp: driver = smtp # ... other options ... # lookup fd caching ensures coherence of all of these, see exim 4.94 spec 9.8 dkim_domain = ${if and{ \ { match_domain {${domain:$h_from:}} {+dkim_domains} } \ { !def:h_dkim-signature: } \ { !def:h_list-id: } \ { or{ \ { def:authenticated_id } \ { match_ip {$sender_host_address} {+relay_hosts} } \ }} \ } {${domain:$h_from:}} {} } dkim_selector = ${lookup {selector} lsearch {/var/lib/dkim-rotate/example-net/exim} }.example-net dkim_private_key = ${lookup {privkey} lsearch {/var/lib/dkim-rotate/example-net/exim} } dkim_sign_headers = _DKIM_SIGN_HEADERS : DKIM-Signature-Warning headers_add = ${if and{ \ { match_domain {${domain:$h_from:}} {+dkim_domains} } \ { !def:h_dkim-signature: } \ { !def:h_list-id: } \ { or{ \ { def:authenticated_id } \ { match_ip {$sender_host_address} {+relay_hosts} } \ }} \ } {DKIM-Signature-Warning: ${lookup {header_note} lsearch {/var/lib/dkim-rotate/example-net/exim} }} } Example Exim configuration (perl version) It is a shame that Exim doesn’t seem to have better and more cooked facilities for controlling dkim signing. The required configuration is quite annoying repetitive. The following Perl can generate something like the config above: sub dkim_lookup { "\${lookup {$_[0]} lsearch {/var/lib/dkim-rotate/example-net/exim} }" } my $dkim_domain_expr = "\${domain:\$h_from:}"; my $dkim_condition = <<END; and{ \\ { match_domain {$dkim_domain_expr} {+dkim_domains} } \\ { !def:h_dkim-signature: } \\ { !def:h_list-id: } \\ { or{ \\ { def:authenticated_id } \\ { match_ip {\$sender_host_address} {+relay_hosts} } \\ }} \\ END my $dkim_smtp_options = <<END; # lookup fd caching ensures coherence of all of these, see exim 4.94 spec 9.8 dkim_domain = \${if $dkim_condition } {$dkim_domain_expr} {} } dkim_selector = ${\ dkim_lookup('selector')}.example-net dkim_private_key = ${\ dkim_lookup('privkey')} dkim_sign_headers = _DKIM_SIGN_HEADERS : DKIM-Signature-Warning headers_add = \${if $dkim_condition } {DKIM-Signature-Warning: ${\ dkim_lookup('header_note')}} } END $dkim_smtp_options =~ s{^(.*\S)\s*\\$}{ sprintf "%-70s\t\\", $1 }mge;
KEY PUBLICATION
dkim-rotate publishes secret keys by writing them to a directory /var/lib/dkim- rotate/instance/pub/. This is an ever-growing archive. Nothing ahould be deleted from it. This directory should be made available via webserver, and the corresponding URL configured via the pub_url config directive. dkim-rotate will make subdirectories 00 to ff here. These are radix prefix directories which exist both to avoid the creation of a very large single directory of key files, and to make it harder to enumerate the private keys. In particular, these subdirectories are not globally-readable, although they are globally- executable. The webserver should run without privilege, so that the individual keys can be read, but the directories cannot be listed (and won’t be archived by any crawlers). README dkim-rotate will make a README.txt file in the pub/ directory. (Currently there is no way to configure the contents of this file.)
KEY LIFECYCLE
Key statuses and lifecycle abbrev meaning time_t (in selector how many statefile) ────────────────────────────────────────────────────────────────────────────────── -1 advertised; not first advertised advertised 0/1 yet used (DNS) +0 signing (first used; not advertised 0/1; usually 1 relevant) (DNS) +N.. emails last used for advertised [0 .. percolating signing (DNS) sel_limit] +X.. deadvertisment last advertised archival only [0 ..]; propagating usually 0/1 R revealed (not longer in archival only many statefile) Example key lifecycle -0200 [0][1] generate and advertise dns_lag (4h) + 2h slop +0400 start signing 1d key rollover interval +0400 +1d stop signing email_lag (3.5d timeout + 4h retry) + 2h slop -0200 +5d deadvertise dns_lag (4h) + 2h slop +0400 +5d reveal [0] -0200 means “2200 the previous day” [1] If a free selector is already available, this might be generated and advertised at +0400 -1d. Example cron configuration #mins hrs dom mon dow command 26 22 * * * dkim-rotate --minor 26 4 * * * dkim-rotate --major These jobs should be scheduled in a suitable local time (in the timezone of the mail server’s users), because it is good for all mails sent on a particular calendar day to become un-nonrepudiable (and un-deliverable) at once. To cope nicely with timezone changes the interval between --minor and the main run should be at least dns_lag + 1h + an allowance for processing time etc. The suggested configuration has a 6h interval, which suits the default dns_lag of 4h.
AUTHOR
Copyright 2022 Ian Jackson and contributors to dkim-rotate. There is NO WARRANTY. SPDX-License-Identifier: GPL-3.0-or-later
SEE ALSO
dkim-rotate(5) Configuration file dkim-rotate(1) Command line reference RFC6376 DKIM Signatures Ok Google: please publish your DKIM secret keys article by Matthew Green <https://blog.cryptographyengineering.com/2020/11/16/ok-google-please-publish-your- dkim-secret-keys/> dkim-rotate(7)