Distroname and release: Debian Squeeze

Postfix with DKIM (OpenDKIM) and SPF

DKIM, and SPF are great ways for securing that your domain is not being used for spoof mails.
You should note, that this is only working as intended if the receiving server does verification of DKIM and SPF.

DKIM and SPF

DKIM and SPF are two completely different things. DKIM, is a way of signing the email itself. If the SMTP server at the recievers end then supports DKIM verifying, it can verify the public signature from the DNS TXT value with the value in the e-mail.
SPF, is a way of telling the e-mail systems which hosts are allowed to send e-mails from a specific domain. If the SMTP server at the receivers end supports SPF, it can check if the e-mails come from one of the allowed servers, and take action.

There's some quite good info here.
http://www.socketlabs.com/articles/show/email-authentication-guide?page=1

Installing DKIM for use with postfix

apt-get install opendkim

Generating DKIM keys

This example, will setup DKIM keys for 2 domains. You will likely just need 1. At least to start with.

Create signing table file. Multiple domains are allowed like so.
First part is the domain, with all emails, for which we should sign. Second part is the shortname.
We need the shortname when specifying which key to use in the key.table file
/etc/opendkim/signing.table
*@example.com    example.com
*@example2.com       example2.com 
Generate keys. Notice that we use the dateformat YYYYMM as selector(naming).
mkdir -p /etc/opendkim/keys/{example.com,example2.com}
opendkim-genkey -b 2048 -h sha256 -r -s$(date +%Y%m) -d example.com -D /etc/opendkim/keys/example.com/ -v
opendkim-genkey -b 2048 -h sha256 -r -s$(date +%Y%m) -d example2.com -D /etc/opendkim/keys/example2.com/ -v
Restrict permissions to files
chown opendkim:root /etc/opendkim/keys/ -R
chmod 440 /etc/opendkim/keys/ -R
cd /etc/opendkim/
chmod 755 keys/example.com/
chmod 755 keys/example2.com/
Create an key.table file
The '202202' is the selector(-s) from the opendkim-genkey command earlier. Make sure you set the correct selector here. (todays YYYYMM).
/etc/opendkim/key.table
example.com      example.com:202202:/etc/opendkim/keys/example.com/202202.private
example2.com     example2.com:202202:/etc/opendkim/keys/example2.com/202202.private
Create an signing.table file. This file tells with domains/emails/subdomains to sign. Notice name reference as second pharagraph, must match domain defined.
*@example.com        example.com
*@example2.com       example2.com
Create an trustedhosts.lst file.
We will use this to tell from which IPs we should sign mails with DKIM. And also which IP we should ignore DKIM checks from.
/etc/opendkim/trustedhosts.lst
127.0.0.1
::1
localhost
10.10.10.0/24
10.10.11.0/24
Test the key:
opendkim-testkey -d example.com -s 202202 -vvv
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: checking key '202202._domainkey.example.com'
opendkim-testkey: key secure
opendkim-testkey: key OK
Sample confguration!
You should read up on the "ON-" settings, in the buttom, and adapt them to your needs.
/etc/opendkim.conf
Syslog			yes
LogResults      yes
LogWhy          yes
SyslogSuccess   yes
UMask			007

Mode			sv
SubDomains		yes

Socket                  inet:8892@localhost
PidFile               /var/run/opendkim/opendkim.pid

OversignHeaders		From

TrustAnchorFile       /usr/share/dns/root.key

UserID                opendkim

KeyTable            refile:/etc/opendkim/key.table
SigningTable        refile:/etc/opendkim/signing.table

ExternalIgnoreList  /etc/opendkim/trustedhosts.lst
InternalHosts       /etc/opendkim/trustedhosts.lst

Nameservers 10.10.10.1

On-Default accept
On-BadSignature reject
On-DNSError tempfail
On-InternalError tempfail
On-KeyNotFound accept
On-NoSignature accept
On-Security tempfail
On-SignatureError reject
InternalHosts = Internal hosts, for which emails we sign with DKIM
ExternalIgnoreList = External hosts, for which we do NOT verify, but sign instead, since we use the same file as for InternalHosts!
systemctl restart opendkim
Setup postfix to listen on the DKIM filter, by adding the following to the main.cf file.
/etc/postfix/main.cf
smtpd_milters                   = 
	inet:localhost:8892

non_smtpd_milters               = 
	inet:localhost:8892
Now we have to setup the DKIM TXT record on our public DNS for our domain. Inspect the generated file from "opendkim-genkey" command, and note the values for TXT and the key. (I have stripped quite a lot of the key)
/etc/opendkim/keys/example.com/202202.txt
202202._domainkey IN TXT "v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC....AB" \
; ----- DKIM default for example.com
Now create a TXT DNS record at your DNS host, as this. (again, the key is stripped). Note I have added t=y which makes DKIM run in test mode. This means that even if the verification fails, the mail will still be delivered. When eveything is OK, remove the t=y parameter.
host:		202202._domainkey
string:		v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC....AB; t=y
All done, now restart opendkim and postfix.
systemctl restart opendkim
systemctl restart postfix

Testing

The absolute easiest way to test the signing function, is to send an e-mail to an g-mail account.
If it is successfull, you should get an "signed by example.com" in the detailed view of the e-mail.
The reason to do so, is that gmail supports DKIM verifying. If the SMTP reciever does not support DKIM, have no worries. The e-mail will be delivered, but not checked for DKIM.

If you have logging enabled you could check the mail.log file.
This does not verify if it is working as inteded, just if the signature is added to the e-mail!
If it is not working, it could be that the signature is wrong on the DNS host, but the header will still be added.
So until you are sure that everything is working, add the "t=y" parameter to the DKIM txt DNS entry, as mentioned earlier.

This shows, that signing is OK.
tail -f /var/log/mail.log |grep -i dkim
Sep 23 17:13:50 myServer dkim-filter[3079]: C27E761E5A8 "DKIM-Signature" header added
For verification, try to send an e-mail from the gmail account, and then check the mail.log for messages.
tail -f /var/log/mail.log |grep -i dkim
Sep 23 17:05:18 myServer dkim-filter[3079]: 982BD39E5B9 DKIM verification successful

Rotating / Changing DKIM keys

Generate an new key, and set correct permissions.
opendkim-genkey -s202202 -d example.com -D /etc/opendkim/keys/example.com/
chown opendkim:root /etc/opendkim/keys/example.com/202202*
chmod 440 /etc/opendkim/keys/example.com/202202*
Open the /etc/opendkim/keys/example.com/202202.txt file, and copy/paste the p= value to new new TXT record in your DNS configuration.
NOTE, there might be two entries like the ones below. If thats the case, copy the two lines to ONE line, and omitting the middle "" ones.
 ""p=MIIBIjA..............""
 ""uFKAZV0cO......4QIDAQAB""
So the full string will now be:
p="MIIBIjA.....4QIDAQAB"
update the keytable file "/etc/opendkim/key.table", so the new selector used above matches, AND the filename:
Notice, there can be multiple entries here.
example.com      example.com:202202:/etc/opendkim/keys/example.com/202202.private

Known issues and possible fixes with DKIM

DNS fail

Mar  2 13:13:56 server opendkim[617]: D312E5FCD1: signature=He2oEYVm domain=linkedin.com selector=d2048-201806-01 result="key DNS query failed"; signature=M7nqbPYW domain=maile.linkedin.com selector=proddkim1024 result="key DNS query failed"
Mar  2 13:13:56 server opendkim[617]: D312E5FCD1: key retrieval failed (s=d2048-201806-01, d=linkedin.com): 'proddkim1024._domainkey.maile.linkedin.com' query timed out
Mar  2 13:13:56 server postfix/qmgr[609]: D312E5FCD1: from=, size=33878, nrcpt=1 (queue active)
And/Or something like this.
Mar  2 14:15:26 mailobie opendkim[27325]: 279415FB5E: signature=r0rl9Qd3 domain=gmail.com selector=20161025 result="key DNS query failed"
Mar  2 14:15:26 mailobie opendkim[27325]: 279415FB5E: key retrieval failed (s=20161025, d=gmail.com): '20161025._domainkey.gmail.com' query timed out
Also when we are testing the key, we get an timeout error.
opendkim-testkey -d gmail.com -s 202202 -vvv
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: checking key '202202._domainkey.gmail.com'

opendkim-testkey: '202202._domainkey.gmail.com' query timed out
If we test with dig, it works.
dig 20161025._domainkey.gmail.com TXT
The issue is likely because "unbound" is not running, and opendkm in Debian is compiled with unbound.
ref: https://wiki.debian.org/opendkim#DNS_resolution

We can fix this, by adding "Nameservers IP" to the config file, (or install and configure unbound)
/etc/opendkim.conf
Nameservers 123.123.123.123
If this does not work, disable the TrustAnchorFile
Be aware that this might disable DNNSEC capabilities, and causing "opendkim-testkey: key not secure".

I've also seen the "opendkim-testkey: key not secure" error, when the domain used did not support DNSSEC. Dont worry about it, its a topic for another day.
/etc/opendkim.conf
#TrustAnchorFile       /usr/share/dns/root.key
Remember to reload opendkim after.

dkim-filter / dkim-milter and ClamSMTP issue

Doing my tests, I discovered that dkim-filter, added the signature twice to the e-mail header.
It also checked the verification twice. This was due to ClamSMTP, so I edited the /etc/postfix/master.cf file to not add the milter, when the e-mails is injected into postfix again.

Add "no_milters" to the recieve_overrride_options line to master.cf file.
/etc/postfix/master.cf
receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters

Installing SPF for postfix

Installtion, and setup is actually quite straight forward.
apt-get install postfix-policyd-spf-perl

Configuring SPF

First we will start by creating an TXT record. Below are the options.
  • ?all = neutreal, which is for testing purposes where we do not want other SMTPs to block us.
  • ~all = softfail, will normally mark the SPF check as failed, but still be delivered.
  • -all = hardfail, will mark the SPF check as failed, and reject the mail. After testing, this is want we want.
I'll recommend using a spf wizard / generator, just google for it, there are plenty out there.

Create it as an TXT record on your DNS host.
An example could looke like this, meaning that all MX's configured for this domain is allowed to send. It is also possible to add IP's if we want a server which is not an MX to allow to send mail.
Note we are using an hardfail here, this should only normally be used after testing!
"v=spf1 mx -all"
Now we will install the spfmilter/spffilter on the server running postfix.
All information here, are from the man page! "man postfix-policyd-spf-perl"

Add configuration to postfix master.cf file
/etc/postfix/master.cf
 spfcheck  unix  -       n       n       -       0       spawn
   user=policyd-spf argv=/usr/sbin/postfix-policyd-spf-perl
Add configuration to main.cf. Notice, that you will hightly likely have other configuration parameters here!
Be sure to add the check_policy_service under reject_unauth_destination, or you might become an open-relay!
/etc/postfix/main.cf
smtpd_recipient_restrictions =
     reject_unauth_destination,
     check_policy_service unix:private/spfcheck,
Set spfcheck timeout
postconf -e spfcheck_time_limit=3600
Restart postfix
/etc/init.d/postfix restart

Testing

Testing can be done by using the postfix-policyd-spf-perl binary!
/usr/sbin/postfix-policyd-spf-perl

request=smtpd_access_policy
protocol_state=RCPT
protocol_name=SMTP
helo_name=test.example.com
queue_id=
instance=71b0.45e2f5f1.d4da1.0
sender=foo@example.com
recipient=foo@example.com
client_address=[EXT. IP OF SERVER]
client_name=[FQDN of Mailserver]
[EMPTY LINE]
Output would show something like this!(ips whiped)..
action=PREPEND Received-SPF: pass (example.com: xxx.xxx.xxx.xxx is authorized to use 'foo@example.com' in \
'mfrom' identity (mechanism 'mx' matched)) receiver=mailobie; identity=mailfrom; \ 
envelope-from="foo@example.com"; helo=test.example.com; client-ip=xxx.xxx.xxx.xxx
Here is an real-life log, from the mailserver, on an ingoing e-mail.
Nov  2 10:36:20 mailobie postfix/policy-spf[4441]: Policy action=PREPEND Received-SPF: pass \
(hotmail.com: Sender is authorized to use 'example@hotmail.com' in \
'mfrom' identity (mechanism 'include:spf.protection.outlook.com' matched)) receiver=mailobie; identity=mailfrom; \
envelope-from="example@hotmail.com"; helo=EUR03-AM5-obe.outbound.protection.outlook.com; client-ip=40.92.70.19
And/Or test by sending an spoofed e-mail from example http://www.deadfake.com.
Do not trust the authors words! POC, tests and experience is key

Copyright LinuxLasse.net 2009 - 2022 All Rights Reserved.

Valid HTML 4.01 Strict Valid CSS!