.\"t .\" Automatically generated by Pandoc 2.9.2.1 .\" .TH "dkim-rotate" "7" "" "" "" .hy .SH NAME .PP \f[C]dkim-rotate\f[R] - Principles of Operation .SH INTRODUCTION .PP \f[C]dkim-rotate\f[R] is a tool for managing DKIM (email antispam) keys in a manner that avoids unnecessarily making emails nonrepudiable. .SS Problem statement .PP Using a static or nearly-static DKIM signing key enables anyone who obtains a copy of an email to verify its authenticity. .PP 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\[cq]s users, and wasn\[cq]t an intended consequence of DKIM\[cq]s antispam function. .PP For fuller discussion of the nonrepudiability problem with DKIM, see the blog post \f[I]Ok Google: please publish your DKIM secret keys\f[R], referenced in the SEE ALSO section. .SS Solution - function of dkim-rotate .PP 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. .PP The overall result is that because old emails are forgeable (by anyone, since the private key has been published), emails become no longer nonrepudiable. .PP We add appropriate warnings, and alter the DNS, to alert naive verifiers to the situation. .SS Output and state files .PP dkim-rotate will maintain and update the following output files and directories: .TP \f[B]\f[CB]/var/lib/dkim-rotate/\f[B]\f[R]\f[I]instance\f[R]\f[B]\f[CB]/zone\f[B]\f[R] Zonefile in standard master file syntax. Created by taking the config file, editing the serial number before \f[C];!SERIAL\f[R], and appending \f[C]TXT\f[R] RR definitions. The appeneded RRs have single-character alphabetic labels, the selectors. See dkim-rotate(5). .TP \f[B]\f[CB]/var/lib/dkim-rotate/INSTANCE/priv/\f[B]\f[R]\f[I]keyname\f[R]\f[B]\f[CB].pem\f[B]\f[R] Private key for use by the MTA. See MTA CONFIGURATION. .TP \f[B]\f[CB]/var/lib/dkim-rotate/\f[B]\f[R]\f[I]instance\f[R]\f[B]\f[CB]/exim\f[B]\f[R] File in format suitable for Exim \f[C]${lsearch }\f[R]. See MTA CONFIGURATION. .TP \f[B]\f[CB]/var/lib/dkim-rotate/\f[B]\f[R]\f[I]instance\f[R]\f[B]\f[CB]/pub/\f[B]\f[R] Directory where private keys are published (deliberately leaked). See KEY PUBLICATION. .TP \f[B]\f[CB]/var/lib/dkim-rotate/\f[B]\f[R]\f[I]instance\f[R]\f[B]\f[CB]/state\f[B]\f[R] Principal state file. .TP \f[B]\f[CB]/var/lib/dkim-rotate/\f[B]\f[R]\f[I]instance\f[R]\f[B]\f[CB]/\f[B]\f[R]\&... Other state and temporary files are stored here. .PP (Each dkim-rotate \f[I]instance\f[R] is completely separate; they do not share state, or configuration.) .SH SELECTORS .PP \f[C]dkim-rotate\f[R] maintains a collection of DKIM keys. The (currently advertised) keys each have a \[lq]selector\[rq], 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. .SS Current keys - selectors .PP A DKIM signature found in an email indicates where to find the key. It includes a \[lq]selector\[rq], 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. .PP 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. .PP 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. .SS DNS selector advertisement .PP dkim-rotate outputs a DNS zonefile, complete with serial number, as \f[C]/var/lib/dkim-rotate/\f[R]\f[I]instance\f[R]\f[C]/zone\f[R]. .PP 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\[cq] zones to be separated from the DKIM system. .PP Let us imagine that the dkim-rotate oiutput zone is \f[C]dkim-rotate.example.net\f[R]. Within that zone, dkim-rotate will create DKIM TXT records, which look like this in the output zone file: .IP .nf \f[C] k IN TXT \[dq]v=DKIM1; h=sha256; s=email; n=...; p=...\[dq] \f[R] .fi .PP This implies the following RRset: .IP .nf \f[C] k.dkim-rotate.example.net. IN TXT \[dq]v=DKIM1; ...\[dq] \f[R] .fi .PP A mail domain (let us imagine, \f[C]example.com\f[R]), which wishes to indicate that this system is authorised to make DKIM signatures, will use a set of CNAMEs to delegate that authority: .IP .nf \f[C] $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. \f[R] .fi .PP So, overall, we have something like this: .IP .nf \f[C] example.com. MX mx0.example.com. k.example-net._domainkey.example.com. CNAME k.dkim-rotate.example.net. k.dkim-rotate.example.net. TXT \[dq]v=DKIM1; ...\[dq] \f[R] .fi .SS DNS output file and nameserver configuration .PP The zonefile is written to \f[C]/var/lib/dkim-rotate/\f[R]\f[I]instance\f[R]\f[C]/zone\f[R]. .PP After it has been updated, dkim-rotate runs \f[C]rndc reload\f[R] (or the configured \f[C]dns_reload\f[R] command). .PP If this all occurs successfully, dkim-rotate assumes that \f[C]dns_lag\f[R] later, the new DNS records (and any deletions) are available everywhere. .PP dkim-rotate does not use DNS Dynamic Update. .SH MTA CONFIGURATION .PP dkim-rotate provides the selector, and the private key, to the MTA. .PP This is done by writing \f[C]/var/lib/dkim-rotate/\f[R]\f[I]instance\f[R]\f[C]/exim\f[R]. This is in a key-colon-value format, which is convenient for use by Exim\[cq]s \f[C]lsearch\f[R] lookup facility. .PP After this file is updated, dkim-rotate runs the configured \f[C]mta_reload\f[R] command. This is just \f[C]true\f[R] (a no-op) by default (and Exim doesn\[cq]t need it). .SS MTA configuration output format .PP The output file is lines of the form: .IP .nf \f[C] key: value \f[R] .fi .PP It may also contain \f[C]#\f[R]-comment lines. The values are literal text, without any quotes (and therefore cannot contain newlines). .PP The keys are; .TP \f[B]\f[CB]privkey\f[B]\f[R] The filename of the private key to use. This will be in the form \f[C]/var/lib/dkim-rotate/\f[R]\f[I]instance\f[R]\f[C]/priv/\f[R]\f[I]hex\f[R]\f[C].pem\f[R]. .TP \f[B]\f[CB]selector\f[B]\f[R] The selector under which the corresponding public key is advertised. .TP \f[B]\f[CB]header_note\f[B]\f[R] Some text which it would be useful to put into the email headers. It starts \f[C]NOTE REGARDING DKIM KEY COMPROMISE\f[R]. .RS .PP This could be put into a \f[C]note=\f[R] or \f[C]warning=\f[R] tag in the actual \f[C]DKIM-Signature\f[R] header. .PP If that is not possible (e.g.\ Exim doesn\[cq]t support it) it could be put into \f[C]DKIM-Signature-Warning\f[R], say. It is probably a good idea to arrange that it is itself \f[I]covered by\f[R] the signature, to make it more complicated for an adversary to strip it out. .RE .TP \f[B]\f[CB]url\f[B]\f[R], \f[B]\f[CB]readme_url\f[B]\f[R] URLs for the instance\[cq]s public WWW directory, the \f[C]README.txt\f[R] file, corresponding to this instance. .TP \f[B]\f[CB]key_reveal_url\f[B]\f[R] 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 \f[C]/exim\f[R] file, the URL is not yet valid.) .SS Example Exim configuration .PP DKIM signing is done with additional options on the \f[C]smtp\f[R] transport. The mailserver ought not to be a signing oracle for arbtrary incoming emails which are being relayed (eg via forward files) \[em] only for emails generated locally, or from appropriately authorised places. And we should choose, for the signing domain, the domain which appears in the \f[C]From:\f[R] header, and sign only if DKIM is enabled for that domain. .PP The required config looks something like this: .IP .nf \f[C] 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{ \[rs] { match_domain {${domain:$h_from:}} {+dkim_domains} } \[rs] { !def:h_dkim-signature: } \[rs] { !def:h_list-id: } \[rs] { or{ \[rs] { def:authenticated_id } \[rs] { match_ip {$sender_host_address} {+relay_hosts} } \[rs] }} \[rs] } {${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{ \[rs] { match_domain {${domain:$h_from:}} {+dkim_domains} } \[rs] { !def:h_dkim-signature: } \[rs] { !def:h_list-id: } \[rs] { or{ \[rs] { def:authenticated_id } \[rs] { match_ip {$sender_host_address} {+relay_hosts} } \[rs] }} \[rs] } {DKIM-Signature-Warning: ${lookup {header_note} lsearch {/var/lib/dkim-rotate/example-net/exim} }} } \f[R] .fi .SS Example Exim configuration (perl version) .PP It is a shame that Exim doesn\[cq]t seem to have better and more cooked facilities for controlling dkim signing. The required configuration is quite annoying repetitive. .PP The following Perl can generate something like the config above: .IP .nf \f[C] sub dkim_lookup { \[dq]\[rs]${lookup {$_[0]} lsearch {/var/lib/dkim-rotate/example-net/exim} }\[dq] } my $dkim_domain_expr = \[dq]\[rs]${domain:\[rs]$h_from:}\[dq]; my $dkim_condition = < .RE