#+title: Email Key Rotation #+language: en #+author: Vivien Kraus #+email: vivien@planete-kraus.eu I present to you this humble package that can help you rotate your DKIM keys and SRS secrets. DKIM rotation can be done in various different ways, but this package assumes that you have at least a couple of selectors, such as =selectorA= and =selectorB=. When the current selector is =selectorB=, the DNS entry for =selectorA= is the previous expired public key, and the DNS entry for =selectorB= is the current public key. In this case, after rotation, the DNS entry for =selectorB= does not change, but that of =selectorA= becomes the new public key, and the email server starts signing outbound emails with =selectorA= instead of =selectorB=, provided that you restart it. This package can: - rotate DKIM keys; - rotate SRS secrets for opensmtpd; - “materialize” the keys. When materialized, the DKIM key is saved as an openssl private key (so that it can be used by your DKIM signing SMTP server), the current selector is written to a file on its only line, the SRS secrets are written to an OpenSMTPD configuration file, and, if configured, the Gandi LiveDNS zone is updated. This package uses the =openssl= program to generate keys and secrets. You can customize this by parameterizing =current-openssl-binary= in /(email-key-rotation openssl)/, either to a program name to invoke, or to a procedure that accepts 1 argument (the command-line for openssl, without the program name). This package manages different secrets: both[fn::current and expired] DKIM keys, both SRS secrets, and the Gandi LiveDNS API key. Files containing these secrets are always written with mode =0600=, but not much else is considered for security. * Initialize the configuration You initialize the configuration by calling =initialize= in /(email-key-rotation)/. The arguments are: - the list of selectors, for instance ='(selectorA selectorB)=; - the file name for the private opensmtpd configuration file, or =#f= if you do not want to configure SRS; - the name of the file that will contain the current DKIM selector, or =#f= if you do not want to configure DKIM; - the name of the file that will contain the current DKIM private key, or =#f= if you do not want to configure DKIM; - the Gandi LiveDNS API key, or =#f= if you do not wish to publish DNS records on Gandi LiveDNS; - the Gandi domain name. This function returns a configuration object that you may save to disk with =materialize=, in /(email-key-rotation)/. This function takes the configuration object as the first argument, and an optional output port to save the configuration object itself. If the port is not set, then it is saved to the current Guile output port. If the port is set, *and* is set to a file port, then it will be made only readable by the current owner. * Rotate the configuration Every once in a while, say, every week, you would load the current configuration object, and rotate it with =rotate=, in /(email-key-rotation)/. The return value is a new configuration object that you can materialize. The configuration object is saved to disk as XML. You may convert it with =xml->sxml= in /(sxml simple)/, and then convert it to a configuration object with =sxml->configuration= in /(email-key-rotation)/, or use the shortcut =xml->configuration= in /(email-key-rotation)/. The latter accept only one optional argument, the port to read XML from. It defaults to the Guile current input port. * Use the Guix =email-key-rotation-service-type= The =guix= sub-directory of this repository holds the code to use email-key-rotation as a Guix service. In order to instantiate the =email-key-rotation-service-type=, you need a == object, that you can create with =make-email-key-rotation-configuration=. This function accepts one required argument, the name of the file where the rotation state will be written. It also accepts more optional keyword arguments: - =selectors=: a list af strings, they are selectors that will be used in turn to refer to DKIM keys; - =opensmtpd-conf=: the name of a private opensmtpd configuration file where SRS secrets are written; - =selector-file=: the name of the file where the current DKIM selector will be written; - =key-file=: the name of the file where the current DKIM private key will be written; - =gandi-key-file=: the name of the file where your Gandi API key is written; - =gandi-domain=: your domain name on Gandi LiveDNS; - =services-to-restart=: a list of strings, the Shepherd services that need to be restarted when the keys are rotated. For instance, your opensmtpd service, because the SRS secrets have changed, and your DKIM proxy, because it must change its key and selector. * About the code The code requires =guile-json=, and at run-time, the =openssl= binary. You can run tests by loading /(email-key-rotation run-tests)/: #+begin_src shell guile -L ./guile guile/email-key-rotation/run-tests.scm #+end_src ** Licensing terms This is email-key-rotation, a program to help rotate DKIM and SRS secrets. Copyright (C) 2024 Vivien Kraus This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . ** Clarifications to redistribute the package I am not a lawyer, but it seems to me that: - publishing (with this package) or retrieving DNS records (that have been written by this package) is not considered “Remote Network Interaction”; - transmitting e-mails over SMTP when one or both of the SMTP servers are configured with the help of this package is not considered “Remote Network Interaction”.