PACKAGE STATUS

This package works well and even has some functional tests. However, the documentation is not complete or well-organised.

CONFIGURATION

Config file is /etc/dkim-rotate/instance.zone for any instance (DNS hostname chracter set plus _, starting with a letter). There may be multiple instances; by default all will be processed.

Conventionally, the usual default instance is /etc/dkim-rotate/dkim.conf.

Each config is the fixed parts of a zonefile, minus the DKIM TXT RRs.

The string ;!SERIAL must appear, immediately after some digits. It should appear once, in the SOA. The digits will be replaced by a suitable serial number, and are used as the starting point for a new instance.

The file also contains special directives, on lines starting with !, which contain the configuration for dkim-rotate. (These will be stripped out so that they don’t appear in the output zonefile gneerated by dkim-rotate.)

! max_selector selector

Indicates number of CNAMEs (selectors) to use. selector should be a lowercase letter (indicating that a to selector inclusive can be used) or a positive integer (incidating that that many should be used). (If this number is revised downwards after deployment, it may take a little while for the extra names to stop being used.)

Default is 12 (or, equivalently, l).

! dns_lag duration

Time after successful installation of new zonefile, and nameserver reload, after which the new DNS entries are expected to be visible to all clients.

duration is a number (possibly with decimal fraction) followed by one of the units s m h d w.

Default is 4h.

! email_lag duration

Time after successful installation of new MTA configuration, and config reload, after which we will assume that all messages signed with the key indicated by the previous configration have been delivered, or bounced.

Default is 88h.

! instance_var_dir directory

Where to store statefiles, MTA configuration, zone files, etc. Default is /var/lib/dkim-rotate/instance (ie derived from config leafname).

! dkim_txt tag list

The values for the TXT RR, in domainkeys tag-list format, not including the p=. (see RFC6376).

dkim-rotate will append ; p=... to this. Default is v=DKIM1; h=sha256; s=email.

! rsa_bits integer

Number of bits in generated RSA keys. (Currently, only RSA is supported.) Defaults to 2048.

! dns_reload shell command

Command to run to reload the nameserver, after the zonefile has been updated. Default: rndc reload >/dev/null.

! mta_reload shell command

Command to run to reload the MTA, after the DKIM signing configuration has been updated. Default: true.

! pub_url url-string

URL at which our published private keys are available. This should be a public URL at which the directory instance_var_dir/pub/ is accessible.

A warning will be generated if a supposedly-revealed key is found not to be accessible.

- can be used to indicate “none”. This will also suppress the corresponding note in the generated TXT RRs.

! mta_group identifier

Group that ought to own /var/lib/dkim-rotate/INSTANCE/priv. This should be the group of the MTA.

The permissions 0750 will be used if dkim-rotate needs to create the diredtory. (Otherwise it will just be checked that it doesn’t have global execute.)

If mta_group is set to -, dkim-rotate will skip the chgrp, and (if need be) create the directory as 0700.

MODEL

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 7  * * *       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.

Key statuses and lifecycle, more formally

abbrev meaning time_t (in statefile) selector how many
-1 advertised; not yet used first advertised advertised (DNS) 0/1
+0 signing invalid future advertised (DNS) 0/1; usually 1
+N.. emails percolating last used for signing advertised (DNS) [0 .. sel_limit]
+X.. deadvertisment propagating last advertised archival only [0 ..]; usually 0/1
R revealed (not longer in statefile) archival only many

STATE

Lockfile /var/lib/dkim-rotate/instance/lock

primary statefile /var/lib/dkim-rotate/instance/state

Updated with rename(2).

Text file, line based, with the these keywords. All of the following entries must appear, in this order:

sel_offset selector

Selector of first key entry. (As a number.)

sel_limit number

Value that selectors are modulo. Indicates the maximum selector that we are currently using, or might use.

Normally this is equal to the max_selector config key.

last_serial number

Last DNS serial number used. Each attempt to publish needs to increment this.

status status

Indicates that subsequent key entries have this status. status is one of -1 +0 +N +X, and, again, one of each status must appear, once, in order. There must not be more than one key in status -1 or status +0.

key selector time_t keyname dkim dns data

Defines a key. Repeated zero or more times after or in between status.

selector must be the letter corresponding to (key number in list + sel_offset) % sel_limit).

time_t is in decimal. It may also be one or more of the two special values DNS or MTA, separated by commas. These means that the config update event referred to has not yet been completed.

After all of these has been completed, the value should be replaced with the time of the successful update. Eg, DNS means “DNS zonefile update is outstanding”; the new DNS zone should be generated and the nameserver reloaded, and then DNS replaced with the time of that update.

keyname is derived from the public key value.

dkim dns data (starting at the first nonwhitespace) is the combined content of the TXT strings tbat ought to be published in the DNS to advertise this key.

Outputs for MTA and nameserver

/var/lib/dkim-rotate/INSTANCE/zone

Zonefile in standard master file syntax. Created by taking the config file, replacing %SERIAL, and appending TXT RRs definitions. The appeneded RRs have single-character alphabetic labels, the selectors.

/var/lib/dkim-rotate/INSTANCE/exim

File in format suitable for exim ${lsearch }. Contents are:

selector: S
privkey: /var/lib/dkim-rotate/INSTANCE/priv/KEYNAME.pem

where S is the selector to use.

Private key - private storage and public archive

/var/lib/dkim-rotate/INSTANCE/pub/README.txt
/var/lib/dkim-rotate/INSTANCE/pub/HH/KEYNAME.pem
/var/lib/dkim-rotate/INSTANCE/priv/KEYNAME.pem

Here HH is the first two characters of keyname.

keyname

keyname is derived from the public key value, as follows:

  1. base64 decode the p= base64string
  2. run the result through MD5.
  3. represent the result in hex

Eg, base64 -d <contents-of-p-field | md5sum.

Note that this is not sufficient to use as a key identifier for verification purposes, but it is nicely uniformly distributed (so can’t be pre-guessed) and determinable from the public key (so that if someone could verify a DKIM signature they can know where to try to find the leaked key).

Permissions, visibility, and metadata

The directory priv/ must be readable by the MTA, but not otherwise accessible. With Exim on Debian that probably means it ought to be group-owned by Debian-exim.

The directories pub/HH should all executable but unreadable by the webserver. All of these directories should be created before any work is done. This prevents enumeration of the keys.

The individual private key files in pub/ will all have the fixed mtime with time_t 1000000000. This prevents a putative verifier from determining when a key first existed.

pub/README.txt explains the situation and is installed and maintained automatically.

ALGORITHM

  1. Preparation/cleanup:

  2. Check if last +X can be revealed.

  3. Check if last +N can be deadvertised.

  4. Check if actual selector limit can be adjusted towards intended. Requirements:

    This is fiddly. Instead we use the following conditions:

  5. Possibly advance -1 key to become newly-in-use +0.

  1. Possibly create -1 key.

dkim-rotate is Copyright 2022 Ian Jackson and contributors to dkim-rotate. SPDX-License-Identifier: GPL-3.0-or-later. There is NO WARRANTY.