User Tools

Site Tools


networking:dns:bind:dkim_domain_keys_identified_mail

Networking - DNS - Bind - DKIM (Domain Keys Identified Mail)

Domain Keys Identified Mail (DKIM) helps you protect your company from email spamming and phishing attempts.

It provides a method for validating a domain name identity that is associated with a message through cryptographic authentication.

  • A public key for each DKIM-protected domain is stored in DNS.
  • The private key for each DKIM-protected domain is stored on your mailserver.

When a mail is sent a number of fields are hashed together, and that hash is then signed with the private key.

To validate an incoming message the receiver can see the from-address, perform the DNS-lookup necessary to find the public-key, and then repeat the hashing-process to compute the signature. If it matches then the result will be a “pass”.

The intention is that only somebody who has control of the DNS for a domain can send mail from it - because all others will be missing the private key, and unable to sign the message.


DKIM signature

A DKIM signature is recorded as an RFC2822 header field for the signed message.

For example:

DKIM-Signature a=rsa-sha1; q=dns;
     d=example.com;
     i=user@eng.example.com;
     s=jun2005.eng; c=relaxed/simple;
     t=1117574938; x=1118006938;
     h=from:to:subject:date;
     b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSb
     av+yuU4zGeeruD00lszZVoG4ZHRNiYzR

and configured as

selector._domainkey.example.com IN TXT "v=DKIM1;p=MIG…QIDAQAB"

Where the following parts are defined:

  • `v=DKIM1`: This optional fragment identifies the record as a DKIM version 1 public key.
  • `p=MIG…QIDAQAB`: This fragment contains the public key in Base64 encoding.

Setting up DKIM

The process of setting up DKIM involves several tasks:

  • Create a selector. This is a simple, user-defined text string that you will associate with a public key in a later step.
  • Generate a public/private key pair by using a tool such as ssh-keygen on Linux or PuTTYgen on Windows.
  • Publish the selector and public key by creating a DNS TXT record.
  • Attach the token to each outgoing email.

Create a selector

Before storing the public key in DNS you need to pick a “selector”. A domain might have multiple keys involved, and to differentiate between them a label is used, that label is called a selector.

To make it obvious when a key was generated I recommend you use a date, e.g. 20161117.

Once you have chosen your selector you need to publish the key in DNS. If you're protecting the domain example.com you need to publish a TXT record with the name:

20161117._domainkey.example.com

Generate public and private DKIM keys

openssl genrsa -out example.com.private.pem 1024
openssl rsa -in example.com.private.pem -out example.com.public.pem -pubout -outform PEM

or

openssl genrsa -out example.com.private.pem 1024 -outform PEM
openssl rsa -in example.com.private.pem -out example.com.public.pem -pubout -outform PEM

IMPORTANT: Do NOT use RSA keys larger than 2048-bits.

RFC3766 specifically states:

  • Verifiers MUST be able to validate signatures with keys ranging from 512 bits to 2048 bits, and
  • They MAY be able to validate signatures with larger keys.
  • Verifier policies may use the length of the signing key as one metric for determining whether a signature is acceptable.

The practical constraint is that large (e.g., 4096 bit) keys may not fit within a 512-byte DNS UDP response packet.

Records greater than the UDP 512-byte limit will still be handled as DNS, but will use TCP. This should be transparent to the user, but sometimes buggy firewall appliances (such as Cisco PIX/ASA) will filter/block these larger queries.

Since short RSA keys more easily succumb to off-line attacks, signers MUST use RSA keys of at least 1024 bits for long-lived keys.

Factors that should influence the key size choice include the following:

  • The security constraint that keys smaller than 1024 bits are subject to off-line attacks.
  • Larger keys impose higher CPU costs to verify and sign email.
  • Keys can be replaced on a regular basis, thus their lifetime can be relatively short.
  • The security goals of this specification are modest compared to typical goals of other systems that employ digital signatures.

This will result in two files:

FileDescription
example.com.private.pemThe private file you need to keep secure, and which your mail server will read to sign messages.
example.com.public.pemThe public key you'll publish in DNS.

Create DKIM DNS record

The public key is in the /etc/exim4/example.com.public.pem file.

cat /etc/exim4/example.com.public.pem

Result

-----BEGIN PUBLIC KEY-----
abCdefGhijKLm
noPQRsTuvWxyZ
-----END PUBLIC KEY-----

The DKIM DNS record is a TXT record containing the public DKIM key without line breaks.

20161117._domainkey.example.com TXT IN "k=rsa; p=abCdefGhijKLmnoPQRsTuvWxyZ"

TIP: Long DKIM strings can be split into separate text fields to make them easier to maintain, however note that there is overhead for each split.

Due to the 255 character limit on the DNS UDP packets, the text fields should be split into parts of 255 characters or less.

There are two formats for long fields.

  • TXT “part one” \ “part two”
  • TXT ( “part one” “part two” )

Both of which will combine as “part onepart two”.

For example, if the public key file contains:.

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD78Ki2d0zmOlmjYNDC7eLG3af12KrjmPDeYRr3
q9MGquKRkRFlY+Alq4vMxnp5pZ7lDaAXXwLYjN91YY7ARbCEpqapA9Asl854BCHMA7L+nvk9kgC0
ovLlGvg+hhqIPqwLNI97VSRedE60eS+CwcShamHTMOXalq2pOUw7anuenQIDAQAB

the DNS zone file entry could appear as follows:

20161117._domainkey        IN      TXT     ("v=DKIM1; t=s; p=" 
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD78Ki2d0zmOlmjYNDC7eLG3af12KrjmPDeYRr3"
"q9MGquKRkRFlY+Alq4vMxnp5pZ7lDaAXXwLYjN91YY7ARbCEpqapA9Asl854BCHMA7L+nvk9kgC0"
"ovLlGvg+hhqIPqwLNI97VSRedE60eS+CwcShamHTMOXalq2pOUw7anuenQIDAQAB")

Notice how each line is wrapped in quotation marks.


DKIM Tags

Example DKIM header:

DKIM-Signature:
v=1;
a=rsa-sha256;
c=relaxed/relaxed;
d=sharewiz.net;
s=sel42;
t=1399817581;
bh=Pl25…dcMqN+E=;
h=Message-ID:Date:Subject:From:To:MIME-Version:Content-Type;
b=Xp/nL93bv6Qo73K…KmskU/xefbYhHUA=
  • v=1: This indicates the DKIM version in use. For the time being this must be “v=1”.
  • a=rsa-sha256: The algorithm suite that was used to generate the crypto signature. The current specification defines `rsa-sha1` and `rsa-sha256`. The latter is recommended. Note that while the specification leaves room for defining other algorithms, using an algorithm not known to an ISP negates the effect of using DKIM in the first place because the recipient is required to ignore unknown signatures.
  • c=relaxed/relaxed: The “canonicalization” scheme used to pre-process the headers prior to calculating the crypto signature, to protect against small (and normal) transformations that the headers could be subjected to in transit, such as line folding. The specification provides for two algorithms: “simple”, which means don’t change headers in any way; and “relaxed” that normalizes the headers dealing with character case, whitespace folding and line breaks to try and be more robust when headers are changed in transit. Note that the `c=` fragment defines two algorithms. One is used for processing the headers covered by the signature, the other is used for processing of the message’s body.
  • d=sharewiz.net: The domain name of the organization that claims responsibility for issuing this DKIM signature – in this example that is sharewiz.net. Typically the signing organization is either the ESP sending on behalf of a customer or the email author (domain) itself. Note that a sender responsible for various mail streams can use separate signatures thanks to the “selector”, discussed next.
  • s=sel42: The “selector” used to find the corresponding Public Key to validate the signature. The DKIM validator will fetch the public key by issuing a DNS query for the TXT record located at ._domainkey.
  • t=1399817581: The UTC timestamp of signature creation, expressed as the number of seconds since 00:00:00 on January 1st, 1970 (Unix Epoch time).
  • bh=Pl25…dcMqN+E=: A crypto hash of the body part of the message. Note that an author can choose to protect only a part of the content, by using `l=` to specify the number of octets to consider for generating this hash. This is often used in scenarios where a very large number of signatures must be done and the processing cost of signing the whole messages is prohibitive. Note that by not covering the complete contents, it would be possible for the message to be tampered with without detection.
  • h=Message-ID:Date:Subject:From:…: The colon-separated list of header lists that were signed. This allows the sender to omit headers that will be changed or stripped by expected mail handling while including headers that are relevant, such as message identifiers, source and destination address, abuse handling information, etc.
  • b=Xp/nL93bv6Qo73K…KmskU/xefbYhHUA=: The crypto signature data itself, encoded in Base64 and possibly with whitespace inserted to conform to line length limitations.

RFC-6376 § 3.5 contains a detailed description of all the fragments and flags that can be added to customize the extent of protection that DKIM provides through its signatures.


Test DNS

dig 20161117._domainkey.example.com txt

Exim4 Config

With that entry stored in DNS we can now configure exim to sign the mail.

After line CONFDIR = /etc/exim4, add:

For split config, create the file /etc/exim4/conf.d/main/00_local_macros with the following contents:

/etc/exim4/conf.d/main/00_local_macros
DKIM_CANON = relaxed
DKIM_SELECTOR = 20161117
DKIM_DOMAIN = example.com
DKIM_PRIVATE_KEY = /etc/exim4/dkim/example.com.private.pem

or

/etc/exim4/conf.d/main/00_local_macros
DKIM_CANON = relaxed
DKIM_SELECTOR = 20161117
DKIM_DOMAIN = ${sender_address_domain}
DKIM_PRIVATE_KEY = CONFDIR/dkim.private

or

/etc/exim4/conf.d/main/00_local_macros
# DKIM
DKIM_SELECTOR = 20161117
# Get the domain from the outgoing mail.
DKIM_DOMAIN = ${lc:${domain:$h_from:}}
# If key exists then use it, if not don't.
DKIM_PRIVATE_KEY=${if exists{/etc/exim4/dkim/${dkim_domain}.private.pem} {/etc/exim4/dkim/${dkim_domain}.private.pem}}

Ensure the private key in placed into the named path, and has sufficient permissions such that exim4 can read it.


Once you've updated your configuration template you need to rebuild the real configuration, and restart exim such that it takes effect:

update-exim4.conf
service exim4 restart

Handling Multiple Domains

The previous example used a single domain, example.com, but to handle mail for multiple domains, you need to update your 00_local_macros file to allow conditional lookup of the domain and corresponding key:

DKIM_CANON = relaxed
DKIM_SELECTOR = 20150726
 
# Get the domain from the outgoing mail.
DKIM_DOMAIN = ${sg{${lc:${domain:$h_from:}}}{^www\.}{}}
 
# The file is based on the outgoing domain-name in the from-header.
DKIM_FILE = /etc/exim4/dkim/{DKIM_DOMAIN}.pem
 
# If key exists then use it, if not don't.
DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}}

NOTE: The DKIM_FILE statement may need to be set as

DKIM_FILE= /etc/exim4/dkim/$dkim_domain.pem

The {DKIM_DOMAIN} doesn't expand to the right path but the $dkim_domain does.

Check if DKIM_FILE should actually be DKIM_PRIVATE_KEY?

or

# DKIM
DKIM_SELECTOR = 20161117
 
# Get the domain from the outgoing mail.
DKIM_DOMAIN = ${lc:${domain:$h_from:}}
 
# If key exists then use it, if not don't.
DKIM_PRIVATE_KEY=${if exists{/etc/exim4/dkim/${dkim_domain}.private.pem} {/etc/exim4/dkim/${dkim_domain}.private.pem}}

The net result of this is that if your mailserver sends mail from “john@example.com” the key-file will be loaded from /etc/exim4/dkim/example.com.private.pem, but if you send mail from “john@me.com” the private-key will be loaded from /etc/exim4/dkim/me.com.private.pem - unless it is missing, and in that case the outgoing mail won't be signed at all.

The Exim4 magic The lookup uses the native facilities exim4 has for working with strings, etc, and to understand how it works you need to bear in mind that things work from the inside-out. The final result contains expressions that can can be written individually, here we just chain them together.

The initial selection is made via reading the “$h_from”, or the From: header of the outgoing email. The domain-part is extracted like so:

${domain:$h_from} - The domain-name of the From: header.

The next part uses ensures that is lower-cased:

${lc:XXXX} - The string "xxxx" in lower-case

So we can see that this will be the outgoing domain, in lower-case:

${lc:${domain:$h_from}}

Once that is present we can then wrap it up using the sg operation which allows search & replacement. In our case we remove any leading www. prefix from the value. This probably isn't required, but a lot of systems have hostnames setup with a www.prefix, and so this will let them sign with the domain “example.com” rather than “www.example.com”.

In conclusion this is probably a useful way of looking up the correct domain on a per-email basis:

DKIM_DOMAIN = ${sg{${lc:${domain:$h_from:}}}{^www\.}{}}

The next part merely uses the domain to find the file on-disk, if it exists. If it does: great. If not then the mail won't be signed:

DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}}

With this setup present you can generate keys for multiple domains, store the public-part in DNS, the private parts in a common-directory, and all will be well.

The only commonality here is that all the domains will need the same selector, but that could be worked around if you wished.


Use DMARC

ALERT: The preceding work will allow your outgoing emails to be signed via DKIM, and will allow the mail-servers which receive the mails to validate that they are not spoofed, or were altered in-transit.

However they do not prevent a remote attacker from spoofing mail and merely avoiding the use of DKIM. For example:

  • You might be sending out DKIM-signed messages.
  • An attacker would just send out messages without a DKIM-signature.

To prevent this we need to look at a second system called DMARC. DMARC allows you to publish, again in DNS, the fact that all of the mails for your domain should have (valid) DKIM signatures, and if they don't they can be dropped/ignored.

By mandating the use of DKIM you essentially kill spoofed mails for your domain(s), especially in combination with SPF.


Check that Mail Server supports DKIM

To check that Exim supports DKIM.

exim4 -bV

result:

Exim version 4.80 #3 built 24-Jul-2014 03:28:10
Copyright (c) University of Cambridge, 1995 - 2012
(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2012
Berkeley DB: Berkeley DB 5.1.29: (October 25, 2011)
Support for: crypteq iconv() IPv6 GnuTLS move_frozen_messages DKIM
Lookups (built-in): lsearch wildlsearch nwildlsearch iplsearch cdb dbm dbmjz dbmnz dnsdb dsearch nis nis0 passwd
Authenticators: cram_md5 plaintext
Routers: accept dnslookup ipliteral manualroute queryprogram redirect
Transports: appendfile/maildir/mailstore autoreply lmtp pipe smtp
Fixed never_users: 0
Size of off_t: 8
Configuration file is /var/lib/exim4/config.autogenerated

See the line starting with “Support for:“. This shows DKIM is compiled in.


Key Rotation Schedule

The first step towards publishing DKIM Public Keys is deciding on your key rotation schedule. Private keys, and the corresponding Public keys, must be rotated out of use periodically to limit the probability of a compromised or broken key being used.

Common practice involves rotating the keys at varying intervals. Some organizations rotate keys weekly, while other organizations can go up to a year or more before changing the keys.

DKIM Key Rotation is actually very simple thanks to the introduction of the key selectors (the `s=` fragment in the DKIM Signature header). The key selector allows introducing new keys at any point, maintaining the old keys around for a prudent period of time so that all in-transit messages have time to be processed and delivered.


Reference

networking/dns/bind/dkim_domain_keys_identified_mail.txt · Last modified: 2020/07/15 09:30 by 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki