mirror of
https://github.com/mailscope/kumomta.git
synced 2026-09-07 11:08:56 +00:00
* Add support for custom key names in HashiCorp Vault secrets Co-authored-by: Wez Furlong <wez@wezfurlong.org>
260 lines
9.6 KiB
Markdown
260 lines
9.6 KiB
Markdown
# Configuring DKIM Signing
|
|
|
|
## What it is
|
|
|
|
DomainKeys Identified Mail (DKIM) is a mechanism that allows verification of
|
|
the source and contents of email messages. Using DKIM, sending domains can
|
|
include a cryptographic signature in outgoing email messages. A message's
|
|
signature may be verified by an MTA during transit and by the Mail User Agent
|
|
(MUA) upon delivery. A verified signature indicates the message was sent by the
|
|
sending domain and the message was not altered in transit. When a DKIM
|
|
signature fails verification that indicates the message may have been altered
|
|
during transit or that the sender is fraudulently using the sending domain
|
|
name.
|
|
|
|
The 2011 DKIM specification is located here:
|
|
[rfc6376](https://datatracker.ietf.org/doc/html/rfc6376).
|
|
|
|
This diagram gives a graphical view of how DKIM works.
|
|
|
|
```mermaid
|
|
---
|
|
title: DKIM Process flow
|
|
---
|
|
graph TD
|
|
SENDER["Sender"]
|
|
SMTA["Sending MTA"]
|
|
SIGN["Sign Message Using Private Key"]
|
|
VALIDATE["Validate Signature using Public Key"]
|
|
RMTA["Receiving MTA"]
|
|
MBOX["User Mailbox"]
|
|
SPAM["Spam Folder"]
|
|
DNS
|
|
SMTA --> SIGN
|
|
SIGN --> RMTA
|
|
RMTA --> VALIDATE
|
|
SENDER -- Publish Public Key --> DNS
|
|
DNS -- Get Sender's Public Key --> RMTA
|
|
VALIDATE -- Valid Signature --> MBOX
|
|
VALIDATE -- Invalid Signature --> SPAM
|
|
|
|
style SMTA fill:orange,color:black
|
|
style RMTA fill:skyblue,color:black
|
|
style DNS fill:#A2E4B8,color:black
|
|
style MBOX fill:#E8DD8E,color:black
|
|
|
|
```
|
|
|
|
### Enabling DKIM signing in KumoMTA
|
|
|
|
A system administrator with access to manage DNS generates a public/private key
|
|
pair to use for signing all outgoing messages for the domain (multiple key
|
|
pairs are allowed). The public key is published in DNS, and the private key is
|
|
made available to their DKIM-enabled outbound email servers. This is step "1"
|
|
in the diagram.
|
|
|
|
When an email is sent by an authorized user within the domain, the DKIM-enabled
|
|
email system uses the stored private key to generate a digital signature of the
|
|
message. This signature is included in a DKIM-Signature header and prepended to
|
|
the email. The email is then sent on to the recipient's mail server. This is
|
|
step "2" in the diagram.
|
|
|
|
|
|
### Generating DKIM Keys
|
|
|
|
Generate public and private keys for each signing domain and create the DKIM
|
|
public key DNS records for those domains.
|
|
|
|
The OpenSSL cryptography toolkit can be used to generate RSA or Ed25519 keys
|
|
for DKIM. The most recent rfc indicates that Ed25519 should be used, but be
|
|
aware that Ed25519 DKIM support is currently very limited with most email
|
|
services. RSA keys are also supported and are still more widely accepted.
|
|
As an example, the following openssl commands are used to generate RSA
|
|
public and private keys for the a domain you choose with a selector you
|
|
choose. The files can be stored in any directory such as `~/kumomta/keys/`,
|
|
but the default is `/opt/kumomta/etc/dkim/`.
|
|
|
|
Replace the domain and selector with your own, then generate signing keys with:
|
|
|
|
```console
|
|
$ export DOMAIN=<your_domain>
|
|
$ export SELECTOR=<your_selector>
|
|
$ sudo mkdir -p /opt/kumomta/etc/dkim/$DOMAIN
|
|
$ sudo openssl genrsa -f4 -out /opt/kumomta/etc/dkim/$DOMAIN/$SELECTOR.key 1024
|
|
$ sudo openssl rsa -in /opt/kumomta/etc/dkim/$DOMAIN/$SELECTOR.key \
|
|
-outform PEM -pubout -out /opt/kumomta/etc/dkim/$DOMAIN/$SELECTOR.pub
|
|
$ sudo chown kumod:kumod /opt/kumomta/etc/dkim/$DOMAIN -R
|
|
```
|
|
|
|
Any DKIM verification implementations must support key sizes of 512, 768, 1024,
|
|
1536, and 2048 bits. A signer may choose to sign messages using any of these
|
|
sizes and may use a different size for different selectors. Larger key sizes
|
|
provide greater security but impose higher CPU costs during message signing and
|
|
verification. It is not recommended to use a key size lower than 1024 unless
|
|
absolutely necessary. Note that Google _requires_ senders to sign with a 1024
|
|
bit or greater key size.
|
|
|
|
The resulting RSA public key should look similar to:
|
|
|
|
```txt
|
|
-----BEGIN PUBLIC KEY-----
|
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDnkmt7Vty2iLsVCpNCx4+tbufL
|
|
xwe+P13AmzYYa9SHIV2Is3G+U4vRlAEg1McK1ssrsjF5GWGSKSeDrYJY06I8ruZS
|
|
CpPIHQo85GAkmGbBPHMhZuk8x5XSgI8VkjAZDbiJAwg1U6MV5deWqrzDC8OJ3+RK
|
|
KPrbKH5ubT9V9pLKawIDAQAB
|
|
-----END PUBLIC KEY-----
|
|
```
|
|
|
|
Once the public and private keys have been generated, create a DNS text record
|
|
for `<SELECTOR>._domainkey.<DOMAIN>` (IE: `dkim1024._domainkey.example.com`). The
|
|
DNS record contains several DKIM "tag=value" pairs and should be similiar
|
|
to the record shown below:
|
|
|
|
for RSA256:
|
|
|
|
```txt
|
|
default._domainkey.example.com. 86400 IN TXT
|
|
"v=DKIM1; k=rsa; h=sha256; p=MIbBa...DaQAB"
|
|
```
|
|
|
|
DKIM DNS text record tags are defined below. Do not include the quotes below
|
|
when including a tag value in the DNS text record.
|
|
|
|
* `v=` **DKIM key record version**. The value of this tag must be set to "DKIM1".
|
|
|
|
* `k=` **Key type**. This tag defines the syntax and semantics of the p= tag
|
|
value. Currently, this tag should have the value "rsa" or "ed25519" as
|
|
appropriate.
|
|
|
|
* `h=` **Hash algorithm**. Currently, this tag should have the value "sha1"
|
|
or "sha256" if using RSA.
|
|
|
|
* `t=` **Flags**. The only value currently defined is "y". If specified, this
|
|
tag indicates the signing domain is testing DKIM.
|
|
|
|
* `p=` *DKIM public key value** generated as described above.
|
|
|
|
* `s=` **Service Type**. If specified, this tag should be set to "*" or
|
|
"email" which represents all service types or the email service type.
|
|
Currently, "email" is the only service using this key.
|
|
|
|
* `n=` **Notes**. If specified, the value of this tag is quoted-printable
|
|
text used as a note to anyone reading the DNS text record. The tag is not
|
|
interpreted by DKIM verification and should be used sparingly because of
|
|
space limitations of the DNS text record.
|
|
|
|
## Using the dkim_sign.lua Policy Helper
|
|
|
|
To simplify DKIM configuration using a TOML configuration file, you can use the
|
|
`dkim_sign.lua` policy helper.
|
|
|
|
The policy helper is configured to look for keys under the default path of
|
|
`/opt/kumomta/etc/dkim/DOMAIN/SELECTOR.key` but can be overridden on a
|
|
per-domain basis if needed.
|
|
|
|
To use the policy helper, add the following to your default policy:
|
|
|
|
```lua
|
|
local dkim_sign = require 'policy-extras.dkim_sign'
|
|
local dkim_signer = dkim_sign:setup { '/opt/kumomta/etc/dkim_data.toml' }
|
|
|
|
kumo.on('smtp_server_message_received', function(msg)
|
|
-- SIGNING MUST COME LAST OR YOU COULD BREAK YOUR DKIM SIGNATURES
|
|
dkim_signer(msg)
|
|
end)
|
|
|
|
kumo.on('http_message_generated', function(msg)
|
|
-- SIGNING MUST COME LAST OR YOU COULD BREAK YOUR DKIM SIGNATURES
|
|
dkim_signer(msg)
|
|
end)
|
|
```
|
|
|
|
The preceding policy example sets up the `dkim_sign` helper and adds calls for
|
|
signing to the events that fire for message arrival. The call to the
|
|
`dkim_signer` function must be placed last in the events to ensure that no
|
|
further manipulation of the messages occur after signing.
|
|
|
|
In addition create and populate the configured `dkim_data.toml` file, located
|
|
at `/opt/kumomta/etc/dkim_data.toml` in this example.
|
|
|
|
{% call toml_data() %}
|
|
[base]
|
|
# If these are present, we'll use hashicorp vault instead
|
|
# of reading from disk
|
|
vault_mount = "secret"
|
|
vault_path_prefix = "dkim"
|
|
# Optional: specify a custom key name (defaults to "key")
|
|
# vault_key = "private_key"
|
|
|
|
# To do double or triple signing, add each additional
|
|
# signature name to this list and see the `signature."MyESPName"`
|
|
# block below
|
|
additional_signatures = ["MyESPName"]
|
|
|
|
# Default selector to assume if the domain/signature block
|
|
# doesn't specify one
|
|
selector = "dkim1024"
|
|
|
|
# The default set of headers to sign if otherwise unspecified
|
|
# This recommended set comes from section 5.4.1 of RFC 6376
|
|
# See https://datatracker.ietf.org/doc/html/rfc6376#section-5.4
|
|
headers = [
|
|
"From", "Reply-To", "Subject", "Date", "To", "Cc",
|
|
"Resent-Date", "Resent-From", "Resent-To", "Resent-Cc",
|
|
"In-Reply-To", "References", "List-Id", "List-Help",
|
|
"List-Unsubscribe", "List-Subscribe", "List-Post",
|
|
"List-Owner", "List-Archive"
|
|
]
|
|
|
|
# Automatic oversigning can help prevent certain DKIM replay attacks by asserting
|
|
# absence of any additional copies of a given header.
|
|
over_sign = true
|
|
|
|
# Domain blocks match based on the sender domain of the
|
|
# incoming message
|
|
[domain."example.com"]
|
|
selector = 'dkim1024'
|
|
headers = ["From", "To", "Subject", "Date", "Sender"]
|
|
algo = "sha256" # or "ed25519". Omit to use the default of "sha256"
|
|
|
|
# optional overridden filename.
|
|
# Default is "/opt/kumomta/etc/dkim/DOMAIN/SELECTOR.key"
|
|
filename = "/full/path/to/key."
|
|
|
|
# The signature block is independent of the sender domain.
|
|
# They are consulted based on the value of `base.additional_signatures`
|
|
# above.
|
|
# In addition to the same values that are found in the `domain` block,
|
|
# the following keys are supported
|
|
[signature."MyESPName"]
|
|
# Policy is interpreted differently for these
|
|
policy = "Always" # Always add this signature
|
|
#policy = "OnlyIfMissingDomainBlock" # Use this as a fallback
|
|
|
|
# specifies the signing domain for this signature block
|
|
domain = "myesp.com"
|
|
{% endcall %}
|
|
|
|
## Implementing DKIM Signing using Lua
|
|
|
|
Configure KumoMTA to sign emails passing through the MTA with DKIM signatures.
|
|
This is done with Lua in policy. The sample `init.lua` policy provided with
|
|
KumoMTA declares a basic working DKIM signer that you can copy and modify as
|
|
needed. This signs a message with `RSA256` using a selector named `default` on
|
|
headers `From`, `To`, and `Subject` using the DKIM key located at
|
|
example-private-dkim-key.pem. ([More
|
|
documentation](../../reference/kumo.dkim/rsa_sha256_signer.md))
|
|
|
|
```lua
|
|
local signer = kumo.dkim.rsa_sha256_signer {
|
|
domain = msg:from_header().domain,
|
|
selector = 'default',
|
|
headers = { 'From', 'To', 'Subject' },
|
|
key = 'example-private-dkim-key.pem',
|
|
}
|
|
```
|
|
|
|
Where you want to enable dkim signing, simply call that signer in policy.
|
|
|
|
IE: `msg:dkim_sign(signer)`
|