% dkim-rotate internals

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

### Private key - private storage and public archive

```
README.txt
/var/lib/dkim-rotate/INSTANCE/pub/HH/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
---------

 0.
   Preparation/cleanup:
   * Check that `pub/` directories exist.
   * Update the README.txt.

 1.
   Check if last `+X` can be revealed.

   * Preconditions:
      * `dns_lag` elapsed.

   * If so:
      * Reveal: it becomes status `R`;
        * touch it to obliterate the mtime
        * install it in `pub/`
      * Repeat check for new last `+X`.

 2.
   Check if last +N can be deadvertised.

   * Preconditions:
      * `email_lag` elapsed.
   * If so:
      * Deadvertise, make into `+X`.
      * Repeat check for new last `+N`.

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

    * No key's selector would change due to change of modulus.
    * Selectors being abolished are not actually used.
      (Used includes keys in state `+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.
    * The new limit is at least equal to the number of keys we have.

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

    * Preconditions:
      * `dns_lag` (for `-1`) has elapsed.
      * We are not a "secondary" run.

  * If so:
     * Current `+0` becomes `+N`
     * Current `-1` becomes `+0`
     * MTA updated accordingly

 5.
   Possibly create `-1` key.

   * Preconditions:
     * No existing `-1` key.
     * The intended selector is available (conflict with `+X` is OK)
       (not used to publish another key).
     * The intended selector is within
       the config's `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`.

  * If so:
     * Generate new key, and establish its _keyname_.


COPYRIGHT AND AUTHORSHIP
========================

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