DKIM signature in Postfix on Debian

Introduction

DKIM works by interfacing a proxy in postfix to add priv/pub signature to out-going emails using a private key and opendkim.

Matching public key will be exported in the DNS server so receiving SMTP can verify in the signature in the metadata matches the public key.

It helps for non being consider as a spamer.

A friend asked me to setup this on his two servers with two different domains so we’ll be setting up the whole thing here with separate keys pair for each domain/each server and we’ll allow both servers to sign emails for these domains.

Install required packages

apt install opendkim opendkim-tools rename dnsutils

Create pub/priv keys sets

According to /usr/share/doc/opendkim/README.Debian.gz there is a tool named opendkim-genkey that can help generating the key pairs. This tool is in opendkim-tools package.

As we want to allow different servers to sign messages with different key but for the same domain, we’ll use the origin server short hostname as selector. It means the signature will be somehow prefixed with this selector, indicating the receiver SMTP which DNS entry should be queried for getting associated public key. By doing so, we’ll be able to export different public key for each server.

opendkim-genkey -b 1024 -d domain1.com -s `hostname -s` --directory=/etc/dkimkeys/
rename "s/`hostname -s`/`hostname -s`_domain1.com/" /etc/dkimkeys/`hostname -s`.{txt,private}

opendkim-genkey -b 1024 -d domain2.com -s `hostname -s` --directory=/etc/dkimkeys/
rename "s/`hostname -s`/`hostname -s`_domain2.com/" /etc/dkimkeys/`hostname -s`.{txt,private}

chown opendkim:opendkim /etc/dkimkeys/`hostname -s`_*
chmod 0600 /etc/dkimkeys/`hostname -s`_*

Now you should see the private keys as well as public key as a bind9 snippet in /etc/dkimkeys:

drwx------   2 opendkim opendkim 4.0K May 29 22:40 .
 drwxr-xr-x 109 root     root     8.0K May 29 22:11 ..
 -rw-r-----   1 root     opendkim  887 May 29 22:39 ns1234_domain1.com.private
 -rw-r-----   1 root     opendkim  329 May 29 22:39 ns1234_domain1.com.txt
 -rw-r-----   1 root     opendkim  887 May 29 22:40 ns1234_domain2.com.private
 -rw-r-----   1 root     opendkim  339 May 29 22:40 ns1234_domain2.com.txt
 -rw-r--r--   1 root     root      664 Nov  7  2015 README.PrivateKeys

Register public key in DNS zones

Now you need to add the public signature in your DNS zone. In this example, the primary bind server for both domain1.com and domain2.com is running on the server itself so we can just do:

cat /etc/dkimkeys/`hostname -s`_domain1.com.txt >> /etc/bind/zones/domain1.com
cat /etc/dkimkeys/`hostname -s`_domain2.com.txt >> /etc/bind/zones/domain2.com

This is very unlikely that method is suitable for you, but you get the idea right ?

Don’t forget to bump DNS zone serial number and reload bind

sed -i "s/20\(1\|2\)[0-9][0-9]\{2\}[0-9]\{2\}[0-9]\{2\}/`date "+%Y%m%d01"`/" /etc/bind/zones/domain1.com
sed -i "s/20\(1\|2\)[0-9][0-9]\{2\}[0-9]\{2\}[0-9]\{2\}/`date "+%Y%m%d01"`/" /etc/bind/zones/domain2.com
systemctl reload bind9

We can now check with dig that our DNS server now expose the public key:

dig -t TXT `hostname -s`._domainkey.domain1.com @127.0.0.1

Should return something like:

;; QUESTION SECTION:
;ns1234._domainkey.domain1.com. IN	TXT

;; ANSWER SECTION:
ns1234._domainkey.domain1.com. 600 IN TXT	"v=DKIM1; h=sha256; k=rsa; " "p=aaa1111bbbb3333cccc"

Configure OpenDKIM

Now we need to create a KeyTable file to match domain, selector and private key file. We also need a SigningTable to actually ask for signature to be added to outgoing emails.

In /etc/opendkim.conf add the following entries at bottom of files:

LogWhy                yes
KeyTable              refile:/etc/dkimkeys/KeyTable
SigningTable          refile:/etc/dkimkeys/SigningTable

Then we we’ll create /etc/dkimkeys/KeyTable file:

echo -e "domain1.com domain1.com:`hostname -s`:/etc/dkimkeys/`hostname -s`_domain1.com.private" >> /etc/dkimkeys/KeyTable
echo -e "domain2.com domain2.com:`hostname -s`:/etc/dkimkeys/`hostname -s`_domain2.com.private" >> /etc/dkimkeys/KeyTable

The file now looks like:

domain1.com domain1.com:ns1234:/etc/dkimkeys/ns1234_domain1.com.private
domain2.com domain2.com:ns1234:/etc/dkimkeys/ns1234_domain2.com.private

Now we create /etc/dkimkeys/SigningTable file:

echo "*@domain1.com domain1.com" >> /etc/dkimkeys/SigningTable
echo "*@domain2.com domain2.com" >> /etc/dkimkeys/SigningTable

The file should content:

*@domain1.com domain1.com
*@domain2.com domain2.com

OpenDKIM is now configured, restart it

systemctl restart opendkim

Integrate with Postfix

On Debian systems Postfix is chrooted so there are a few additionnal steps to get it working correctly:

In /etc/opendkim.conf change the socket path to Postifx chroot:

Socket                  local:/var/spool/postfix/var/run/opendkim/opendkim.sock

Create proper folder in Postfix chroot and give proper permissions

mkdir -p /var/spool/postfix/var/run/opendkim
chown opendkim:opendkim /var/spool/postfix/var/run/opendkim

Add Postfix to opendkim group so it can write to the socket:

adduser postfix opendkim

Enable filtering in postfix (postconf commands will edit /etc/postfix/main.cf):

Missing trailling / is not a typo !

postconf -e milter_protocol=6
postconf -e milter_default_action=accept
postconf -e smtpd_milters=unix:var/run/opendkim/opendkim.sock
postconf -e non_smtpd_milters=unix:var/run/opendkim/opendkim.sock

Restart both services:

systemctl restart opendkim
systemctl restart postfix

Testing

You can send an email from the server itself using following commands:

echo "This is a test" | mail -s "Test DKIM domain1.com" -a "From: test@domain1.com" your@real.email
echo "This is a test" | mail -s "Test DKIM domain2.com" -a "From: test@domain2.com" your@real.email

My your@real.email server runs Postfix with Amavis so I can check the header of the email I just received and I can confirm valid DKIM signature has been seen:

X-Spam-Status: No, [...] DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VERIFIED=-0.5 [...]