This package works well and even has some functional tests. However, the documentation is not complete or well-organised.
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
selectorIndicates number of CNAME
s (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
durationTime 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
durationTime 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
directoryWhere to store statefiles, MTA configuration, zone files, etc. Default is /var/lib/dkim-rotate/
instance (ie derived from config leafname).
! dkim_txt
tag listThe 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
integerNumber of bits in generated RSA keys. (Currently, only RSA is supported.) Defaults to 2048.
! dns_reload
shell commandCommand to run to reload the nameserver, after the zonefile has been updated. Default: rndc reload >/dev/null
.
! mta_reload
shell commandCommand to run to reload the MTA, after the DKIM signing configuration has been updated. Default: true
.
! pub_url
url-stringURL 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
identifierGroup 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
.
-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
.
#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.
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 |
Lockfile /var/lib/dkim-rotate/
instance/lock
/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
selectorSelector of first key
entry. (As a number.)
sel_limit
numberValue 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
numberLast DNS serial number used. Each attempt to publish needs to increment this.
status
statusIndicates 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 dataDefines 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.
/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.
/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 is derived from the public key value, as follows:
p=
base64stringEg, 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).
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.
Preparation/cleanup:
pub/
directories exist.Check if last +X
can be revealed.
dns_lag
elapsed.R
;
pub/
+X
.Check if last +N can be deadvertised.
email_lag
elapsed.+X
.+N
.Check if actual selector limit can be adjusted towards intended. Requirements:
+X
, so that seeing sel_limit
decrease is suffiient to know one can change/remove the “upstream” DNS CNAME
s.)This is fiddly. Instead we use the following conditions:
sel_offset
is zero.Possibly advance -1
key to become newly-in-use +0
.
dns_lag
(for -1
) has elapsed.+0
becomes +N
-1
becomes +0
Possibly create -1
key.
-1
key.+X
is OK) (not used to publish another key).max_selector
(the intended sel_limit
). Again, this condition is fiddely: instead, we check that the proposed new number of keys doesn’t exceed max_selector
.dkim-rotate is Copyright 2022 Ian Jackson and contributors to dkim-rotate. SPDX-License-Identifier: GPL-3.0-or-later
. There is NO WARRANTY.