Provided by: dkim-rotate_1.1_all bug

NAME

       dkim-rotate - Principles of Operation

INTRODUCTION

       dkim-rotate  is a tool for managing DKIM (email antispam) keys in a manner that avoids unnecessarily mak‐
       ing 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 de‐
       sirable 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 pub‐
       lish 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) let‐
       ter, 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 ad‐
       vertised.  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  sign‐
       ing  authority  (ie,  the dkim-rotate instance).  dkim-rotate itself does not know the actual DKIM selec‐
       tors; 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-ro‐
       tate 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 can‐
       not contain newlines).

       The keys are;

       privkey
              The  filename  of  the  private  key  to  use.   This will be in the form /var/lib/dkim-rotate/in‐
              stance/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-Warn‐
              ing, 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 sign‐
       ing.  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  percolat‐   last   used  for   advertised       [0          ..
                  ing                 signing            (DNS)            sel_limit]
         +X..     deadvertisment      last advertised    archival only    [0 ..]; usual‐
                  propagating                                             ly 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), be‐
       cause it is good for all mails sent on a particular calendar day to become un-nonrepudiable  (and  un-de‐
       liverable) 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)