Do you use let's encrypt?





5708 votes ~ 21 comments

 

DKIM-signing outgoing mail with exim4

Posted by Steve on Wed 29 Jul 2015 at 06:28

There have been several systems designed to prevent mail spoofing over the years, the two most prominent solutions are DKIM and SPF. Here we're going to document the setup of using DKIM to signing outgoing mails with Debian's default mail transfer agent, exim4.

DKIM Overview

DKIM uses the same notion of public and private keys as you would be familiar with from GPG, only the distribution is different:

  • 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.

To put it another way, the intention of DKIM is that you can use it to prevent spoofing - As people without access to your private key cannot fake the appropriate signature.

(See also DMARC, which we discuss later.)

DKIM Implementation

Implementation falls into three phases:

  • Generate a public/private key which will be used for signing, and validation.
  • Configure Exim to sign outgoing messages with the private key.
  • Publish the public key in DNS.

To generate a key-pair you can use openssl, as follows:

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

This will result in two files:

  • example.com-private.pem
    • The private file you need to keep secure, and which exim will read to sign messages.
  • example.com.pem
    • The public key you'll publish in DNS.

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, so my most recent selector was called 20150726.

Once you have chosen your selector you the 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:

20150726._domainkey.example.com

The value of the DNS record should be the public key we generated earlier, which will look something like this:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC30aRx6rlDA7LkhsM1AtuW8LoB
rjo6RZH3yS7nC9EgqV5ntFIzQyCo88hNBz72XwwFAAGKuCVIwcxV06lAHWnUTt+Z
yjJlP/4XJo9JH76ZJu9vUTaHw753IY3SZR+xEnJuyBr/LZknAEFqHuDP7V3+B6SW
uBElSFFnImnP7oeMQQIDAQAB
-----END PUBLIC KEY-----

To store this in DNS, as TXT record, remove the first and last line, and then remove line-breaks, in our case that means we set the following record:

20150726._domainkey.example.com IN TXT "k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC30aRx6rlDA7LkhsM1AtuW8LoBrjo6RZH3yS7nC9EgqV5ntFIzQyCo88hNBz72XwwFAAGKuCVIwcxV06lAHWnUTt+ZyjJlP/4XJo9JH76ZJu9vUTaHw753IY3SZR+xEnJuyBr/LZknAEFqHuDP7V3+B6SWuBElSFFnImnP7oeMQQIDAQAB"

With that entry stored in DNS we can now configure exim to sign the mail, and we do that by creating some configuration values that the stock Debian-configuration file for exim4-daemon-heavy will use.

Crate the file /etc/exim4/conf.d/main/00_local_macros with the following contents:

DKIM_CANON = relaxed
DKIM_SELECTOR = 20150726
DKIM_DOMAIN = example.com
DKIM_FILE = /etc/exim4/dkim/example.com-private.pem

(Obviously you need to move the private key to the named path, such that exim4 can read it too)

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

At this point you can send an email to another host, and it should be signed.

Handling Multiple Domains

The previous example used a single domain, example.com, but in most mail-servers you'll handle mail for multiple domains.

The solution here is 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}}

The net result of this is that if your mailserver sends mail from "steve@example.com" the key-file will be loaded from /etc/exim4/dkim/example.com.pem, but if you send mail from "steve@me.com" the private-key will be loaded from /etc/exim4/dkim/me.com.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.

DMARC

The preceeding work will allow your outgoing emails to be signed via DKIM, and will allow the mailservers 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.

I'll cover SPF and DMARC in a follow-up article, but I wanted to briefly mention both here as being related technologies.

 

 


Re: DKIM-signing outgoing mail with exim4
Posted by ajt (79.77.xx.xx) on Thu 30 Jul 2015 at 20:48
[ View Weblogs ]

I spent ages trying to get DKIM and SPF to work on my box. Most of the instructions on the web were old/wrong or just didn't work. In the end I did get Exim4 to add a DKIM signature to my outgoing emails - I can't even remember what I did to get it going in the end.

I did find the mail-tester.com web site useful though for tetsing purposes.

--
"It's Not Magic, It's Work"
Adam

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Steve (91.156.xx.xx) on Fri 31 Jul 2015 at 05:05
[ View Steve's Scratchpad | View Weblogs ]

I saw your blog post, but you'd disabled comments, so I couldn't contribute anything useful.

For me, with jessie, it just worked, but even wheezy should be supported. I guess it's a mystery!

mail-tester.com is a lovely resource, and is what I'll point to when I write up DMARC :)

--
Steve

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by ajt (195.99.xx.xx) on Fri 31 Jul 2015 at 07:38
[ View Weblogs ]

Steve, comments are not disabled, so if you can't comment on the blog entry then there must be a bug somewhere...

--
"It's Not Magic, It's Work"
Adam

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Steve (46.43.xx.xx) on Fri 31 Jul 2015 at 08:14
[ View Steve's Scratchpad | View Weblogs ]

Weird, I'm sure that I saw they were - I didn't notice the entry at the time, but spotted it yesterday when I was looking at the current-tags for dkim, spf, etc.

(Your tags are broken due to "space" rather than ",", but as you say comments are enabled. I will have to investigate. Shortly.)

--
Steve

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by ajt (195.99.xx.xx) on Fri 31 Jul 2015 at 08:27
[ View Weblogs ]

Steve, when I looked comments weren't enabled, but when I hit edit, the option showed up as being "enabled". I did press save, and when I looked after that, comments were enabled.

Tags are odd, as I think I added them separately, but I'll still try to correct anyhow.

--
"It's Not Magic, It's Work"
Adam

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Steve (46.43.xx.xx) on Fri 31 Jul 2015 at 08:36
[ View Steve's Scratchpad | View Weblogs ]

I suspect the problem is that comments defaulted to being off - so the initial version of the entry had them denied, but then the edit made them be enabled. That's where I'll start looking from anyway.

Software is hard!

--
Steve

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by ajt (195.99.xx.xx) on Fri 31 Jul 2015 at 08:40
[ View Weblogs ]

Considering how good this platform is and how useful the site is I'm not going to complain!

--
"It's Not Magic, It's Work"
Adam

[ Parent | Reply to this comment ]

problem with single domain example
Posted by Anonymous (82.224.xx.xx) on Wed 23 Sep 2015 at 07:58
Debian 8.2 (jessie); exim 4.84-8

The single domain example didn't work for me:
DKIM_CANON = relaxed
DKIM_SELECTOR = 20150726
DKIM_DOMAIN = example.com
DKIM_FILE = /etc/exim4/dkim/example.com-private.pem

Exim didn't add the signature to outgoing mail.

When I added the following line, it worked:
DKIM_PRIVATE_KEY = /etc/exim4/dkim/example.com-private.pem

I found no reference to DKIM_FILE in the documentation.
www.exim.org/exim-html-current/doc/html/spec_html/ch-support_for_ dkim_domainkeys_identified_mail.html

I found no reference to DKIM_FILE in the exim config.
So I guess that it is a typo.

[ Parent | Reply to this comment ]

Re: problem with single domain example
Posted by Anonymous (81.187.xx.xx) on Thu 10 Mar 2016 at 13:33
Well I for one wish I'd seen this comment two hours ago...

Thanks!
William

[ Parent | Reply to this comment ]

Re: problem with single domain example
Posted by Anonymous (86.83.xx.xx) on Sat 29 Oct 2016 at 16:12
Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt25-2 (2016-04-08) x86_64 GNU/Linux
Exim 4.84_2

I also needed to change DKIM_FILE into DKIM_PRIVATE_KEY in /etc/exim4/conf.d/main/00_local_macros and switch from single configuration file to split configuration files using dpkg-reconfigure exim4-config to get DKIM signing to work.

[ Parent | Reply to this comment ]

Re: problem with single domain example
Posted by Anonymous (2a02:0xx:0xx:0xxx:0xxx:0xxx:xx) on Sun 19 Feb 2017 at 16:49
Thank you!

I also lost 2 hours on this.

Steve, please update the blog entry!

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (213.127.xx.xx) on Fri 9 Oct 2015 at 08:28
Hi Steve,

Thank you for this excellent post. I found that your instructions don't work entirely on my Ubuntu 14.04.3 box running Exim 4.82. The private key expansion doesn't work for whatever reason. I found the solution by changing the DKIM_FILE macro to the following:

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

The {DKIM_DOMAIN} you use doesn't expand to the right path on my box, but the $dkim_domain does. I guess that they should both resolve to the same string, but apparently they don't. Hope this helps out some peeps have the same issue.


Floris Luiten

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (81.174.xx.xx) on Thu 10 Dec 2015 at 13:04

I could not your config snippet working, lots of failed to expand dkim_private_key: missing or misplaced { or } in the mainlog.

Here's what worked for me (Debian 8/Jessie, Dec 2015)

# DKIM
DKIM_SELECTOR = 20151210
# 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}}

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (87.115.xx.xx) on Sat 26 Dec 2015 at 22:13
This guide will only work if you use split config files, if you use single file you should not create the 00_local_macros file and instead use an already existing config file as the single file generator won't see your file and will ignore your setting and you may end up like me wasting few days trying to debug the problem.

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (104.190.xx.xx) on Mon 22 Feb 2016 at 07:10
Can you please expand on this? I can't get DKIM to work at all. Where is this single file located, and how would I know if I have a non-spit configuration?

Thank You.

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (81.187.xx.xx) on Thu 10 Mar 2016 at 14:00
The single file (known as unsplit configuration) is here: /etc/exim4/exim4.conf.template

Check the db_use_split_config line in /etc/exim4/update-exim4.conf.conf to see if you're using split mode. Follow the instructions there or run "sudo dpkg-reconfigure exim4-config" to use the wizard which explains each option in lots of detail.

Another tip - on Debian, when you run sudo update-exim4.conf, the output is written to /var/lib/exim4/config.autogenerated. If something's not working,
check your changes have actually been copied there.

You can have a situation, as I did, where all the split config files existed but update-exim4.conf was set to ONLY use the single template.

Note also the comment about DKIM_FILE being the wrong syntax for the single domain example (you need DKIM_PRIVATE_KEY)

William

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (82.239.xx.xx) on Fri 1 Apr 2016 at 12:56
I confirm DKIM_PRIVATE_KEY otherwise it does not work. Took me a while to figure out.

Pierre

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (135.23.xx.xx) on Tue 17 May 2016 at 08:45
when I "sudo update-exim4.conf" produces: "command not found". Not sure what to do next. Please reply with instruction. Thank you.

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (87.103.xx.xx) on Mon 30 May 2016 at 21:15
The DKIM_FILE expression for multiple domains is slightly broken. In case of domains without DKIM certificate, the following error is produced: failed to expand dkim_private_key: missing or misplaced { or } This can be fixed by removing the {} braces from the DKIM_FILE expression: <pre>DKIM_FILE = /etc/exim4/dkim/DKIM_DOMAIN.pem</pre>

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (109.28.xx.xx) on Sun 20 Nov 2016 at 11:05
Thank you for your article, like I saw into comments (but a bit late), DKIM_FILE does not work (single domain) for me so I had to replace with DKIM_PRIVATE_KEY.
Please also note that if you selected "exim4.conf.localmacros" at installation, the local macro configuration file is "/etc/exim4/exim4.conf.localmacros" instead of "/etc/exim4/conf.d/main/00_local_macros"

Could you please update your article to reflect multiple comments, Thanks !

Pascal.

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (37.146.xx.xx) on Wed 30 Nov 2016 at 17:40
Hello! I did everything as written, but unfortunately it does not work. Letters fall into spam. Please help understand what is my mistake?
DKIM_DOMAIN = ${lc:${domain:$h_from:}}
DKIM_FILE = /etc/exim4/dkim/${lc:${domain:$h_from:}}.key
DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}}
DKIM_SELECTOR = mail
DKIM_CANON = relaxed
remote_smtp:

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (194.53.xx.xx) on Wed 11 Jan 2017 at 14:51

Thanks a lot for your write-up of SPF, DKIM and DMARC, with a little help from the other comments i got it working really fast. So i wanted to share my extension :-)

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

i wished to work around using the same selector for different domains so with some trial-and-error and reading of man-pages i got my config working smoothly

i put the selector in a file called domain.tld.selector in /etc/ssl/dkim/ together with the domain.tld.pem and domain.tld-private.pem

so the domain.tld.selector contains the YearMonthDay of when i generated it

it is recommended to never re-use a selector, so with this custom selector per domain i can renew just a single private key without having to change all of the selectors

DKIM_DOMAIN = ${lc:${domain:$h_from:}}
DKIM_CANON = relaxed

DKIM_SEL_FILE = /etc/ssl/dkim/DKIM_DOMAIN.selector
DKIM_SELECTOR = ${if exists{DKIM_SEL_FILE}{${readfile{DKIM_SEL_FILE}{}}}{0}}

DKIM_KEY_FILE = /etc/ssl/dkim/DKIM_DOMAIN-private.pem
DKIM_PRIVATE_KEY = ${if exists{DKIM_KEY_FILE}{DKIM_KEY_FILE}{0}}

i also cleaned up the DKIM_DOMAIN, since i think it's highly unlikely that a user by mistake uses www in his email-domain name

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (88.176.xx.xx) on Sat 14 Jan 2017 at 08:41

Also, don't forget the trailing dot at the end of the DNS name (it has been forgotten in the example) :

20150726._domainkey.example.com. IN TXT "k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC30aRx6rlDA7LkhsM1AtuW8Lo Brjo6RZH3yS7nC9EgqV5ntFIzQyCo88hNBz72XwwFAAGKuCVIwcxV06lAHWnUTt+Z yjJlP/4XJo9JH76ZJu9vUTaHw753IY3SZR+xEnJuyBr/LZknAEFqHuDP7V3+B6SWu BElSFFnImnP7oeMQQIDAQAB"

[ Parent | Reply to this comment ]

Re: DKIM-signing outgoing mail with exim4
Posted by Anonymous (89.84.xx.xx) on Wed 18 Jan 2017 at 13:45
Why not use "DKIM_DOMAIN = $sender_address_domain" rather than a lookup (instructional purpose of the "exim4 magic" aside) ?

[ Parent | Reply to this comment ]