SMTP, SPF, DKIM, DMARC, TLS


Did you know anyone can send an email impersonating your email address? Back in 1971 when ’email’ was invented and later when it was connected to the Internet both email users knew each other.

Since then spammers and other bad actors started to abuse this ‘feature’. And since then some new mechanisms have been added to permit validation around senders authenticity.

SPF DNS records say which mail servers are permitted to send an email for a particular email domain.

DKIM permits an email server to sign/hash key headers in an email. And DKIM DNS records permit a recipient to verify that the email is valid.

DMARC DNS records let you tell email servers that receive emails from your email domain what to do if the SPF and DKIM rules are not followed (e.g. don’t accept the emails, or to send you a report).

TLS is a security option that permits emails between servers an be sent over an encrypted connection (helping to avoid man in the middle attacks).

Email recipients are becoming tougher on senders that don’t implement these mechanisms. Implement these mechanisms to give your emails a higher chance of being seen by their recipients.

On a debian-based distro the setup will look like the following. Note that were we use example.com you would use your own domain name.

Next you will need to configure opendkim. For email servers with just the one email domain you can use something like this:

/etc/opendkim.conf:
Domain                  $domain
KeyFile                 /etc/opendkim/mail.private
Selector                mail
Socket                  inet:8892@localhost

For email servers with more than one domain, you will create a couple of files that map each domain to its key.

/etc/opendkim.conf:
KeyTable     /etc/opendkim/dkim-keytable
SigningTable refile:/etc/opendkim/dkim-signingtable
<meta charset="utf-8">Socket       inet:8892@localhost
#Domain      comment this out
#Selector    comment this out

You can then create those map files:

domains="example.com anothersite.com andonemore.com"

for i in $domains; do 
  echo "mail._domainkey.$i $i:mail:/etc/opendkim/mail.private"
done | tee /etc/opendkim/dkim-keytable
# creates lines like:
# mail._domainkey.example.com example.com:mail:/etc/opendkim/mail.private
# mail._domainkey.example.com is the DNS record name 'mail' is the 'selector',  _domainkey is a hardcoded string you just use, example.com is the domain.   
# example.com:mail:/etc/opendkim/mail.private says that the example.com mail selector private key is in /etc/opendkim/mail.private
# All the domains in /etc/opendkim/dkim-keytable will be pointed to use the same private key

for i in $domains; do 
  echo "*@$i mail._domainkey.$i"
done | tee /etc/opendkim/dkim-signingtable
# creates lines like:
# *@example.com mail._domainkey.example.com
# mail._domainkey.example.com is the DNS selector record (and infers the private key to be used).
# *@example.com means that opendkim will use this private key for all users on that domain.

# re-set file ownerships
[ -d /etc/opendkim/ ] || mkdir -p /etc/opendkim/ && chown -R opendkim /etc/opendkim/

You can now tell postfix to delegate DKIM stuff to the opendkim service. Something like the following should work well:

/etc/postfix/main.cf:
#dkim
milter_default_action = accept
milter_protocol = 2
smtpd_milters = inet:localhost:8892
non_smtpd_milters = inet:localhost:8892

If you don’t have pre-existing values you can set them automatically:

echo "Checking current postfix config for existing settings:"
if postconf | egrep 'milter' ; then
  echo "Existing milter commands.  You would want to manually edit the config."
else 
  echo "Wiring up postfix for opendkim"
  # be a bit forgiving should opendkim fail
  postconf milter_default_action=accept
  postconf milter_protocol=2
  # to match the Socket set in <meta charset="utf-8">/etc/opendkim.conf
  postconf smtpd_milters=inet:localhost:8892
  postconf non_smtpd_milters=inet:localhost:8892
  postconf compatibility_level=2
fi

Restart services and check things are working ok:

postfix reload
systemctl restart opendkim
[ -f <meta charset="utf-8">/var/log/mail.info ] && sleep 10 && tail -n 10 /var/log/mail.info

You can now send an email. And the email you send should be signed. You should see a DKIM header in the email like:

DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=example.com; s=mail;
	t=1629841080; bh=7HNxVOxaCadHB5mfOkjUFQDbJWo7ndcitTqzHa6lAgs=;
	h=Date:From:Reply-To:To:Subject:From;
	b=P+ShZzCVaqpnBOokByPmEDG3qkCQvO4UNI6jcWOR0LNCf25iklI2bzHo487s0+MGk
	 Iht0q5u+kTMINvo6b9D8N9lXyQOiB5wFtsZgYb6prfTtgjD2QZWMjzNHZhKDu0dvVN
	 EiAG+Udj1G4tdQiwmWeUsxDnFtLQeBZ234F9Jlu+f0U0xctEowMIap5rJH+my09M9K
	 FLDDPdGTOvU/q5G8PrpkI/EMVkL+LO3n7Hu4dT+9ue6E+Ed+C2wYW4W70ylm+CuOjb
	 MjjdOZBlr8uHRRAw2Si2HRLY8PJ68XlIP2d4Jcuev7zPNfCSONKE1RGtet77fT81Ix
	 /wf9ClwpQBdBw==

The a=rsa-sha256 algorithm is recommended by the current RFC. If a DKIM validation tool gives you a ‘insecure signature algorithm’ warning, you may wish to use the above opendkim-genkey to create a different key.

The h=Date:From:Reply-To:To:Subject:From tells the receiver which email header fields are included in the hash. If one of these headers is changed, the hash would no longer be valid.

While your emails are now signed, recipients cannot verify if they have the correct signature. To do this they need to do a DNS lookup based on the s=mail selector from the header. For the above email they would look up the DNS TXT record for mail._domainkey.example.com.

The DNS entry to add will be per:

cat /etc/opendkim/mail.txt

Will give you a DNS entry like the following:

mail._domainkey	IN	TXT	( "v=DKIM1; h=sha256; k=rsa; "
	  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApzF/LbHdEMuSFS9x+G5u9p4Nk5vb+P9J2CGsIONVkM0wCpVVY0hacIYSXInb7P5ac3XzHLRr7LLWrLNMVf/luIVtF0DvpH9FcuNAlDudqkmGcBMiRPH33H7Ylc5LMLnQ1ZdNf+F11nYbB0nweaMm0SsZfe3gpG9luyVaGGwHNE96q9VBA5AaxmQ6VEdLUSYjA0gaSFs2i0IDxX"
	  "D+wYgK6i/bFt3N7Hprv8U5s/+1snACXoUh5+WJR95w1zY/vIiecN8is/2Ue9gl4UdiDqFnfGGtEbrprmCtgVL4AdYtk3HhFmENUj39hbOcLX2hs5oi/cmaM+9Kq6xlrmZChTvs2QIDAQAB" )  ; ----- DKIM key mail for example.com

v=DKIM1 just marks it as a DKIM DNS entry. h=sha256; k=rsa indicates the hashing algorithm used. And the p field gives the public key. Since some DNS servers have a limit on record sizes the key is split into a few quote delimited strings.

You could add the DNS entry with a name of mail._domainkey.example.com (or your domain).

Now is an opportune time to add an SPF record, which lets recipients check which mail servers they should accept emails from. To do this you will need to know which servers are permitted to send for your email domain. Will it be a webserver? Plus a dedicated mail server? Perhaps you have 3rd party tools like sendgrid?

Then add the SPF record, e.g. for example.com create a DNS record for example.com of:

v=spf1 a mx a:example2.com -all

v=spf1 is a marker that this is an SPF record. a will match if the sender resolves to a sender’s address. mx if that sender matches one of the domain’s email (MX) servers a:example2.com will match if the send resolves to an example2.com IP address. Another common match is IP4.

Next, add a DMARC DNS record for example _dmarc.example.com. This is an instruction to receivers to send reports on whether emails they receive from email addresses in your domain match your SPF/DKIM settings.

v=DMARC1; p=none; adkim=r; sp=none; pct=100; ri=86400; rua=mailto:postmaster@example.com; ruf=mailto:postmaster@example.com

The _dmarc in the DNS record name is the location that DMARC record is expected.

  • v=DMARC1 tags the DNS record as a DMARC entry.
  • p=none sets the policy to apply to email that fails the DMARC test. Valid values can be ‘none’, ‘quarantine’, or ‘reject’.
  • adkim=r sets the alignment mode for DKIM. Indicates whether strict or relaxed DKIM Identifier Alignment mode is required by the Domain Owner. Valid values can be ‘r’ (relaxed) or ‘s’ (strict mode).
  • sp=none sets the sub-domain policy (Requested Mail Receiver policy) for all subdomains. Valid values can be ‘none’, ‘quarantine’, or ‘reject’.
  • pct=100 sets the percentage of messages from the Domain Owner’s mail stream to which the DMARC policy is to be applied. Valid value is an integer between 0 to 100.
  • ri=86400 sets the reporting Interval (in seconds) is a request to Receivers to generate aggregate reports separated by no more than the requested number of seconds.
  • rua=postmaster@example.com sets the addresses to which aggregate feedback is to be sent. Comma separated plain-text list of DMARC URIs.
  • ruf=mailto:postmaster@example.com tells the receiver to send forensic reports for each failure
  • fo=0 is the failure reporting option default and will result in a failure report being sent if both SPF and DKIM mechanisms fail to get a ‘pass’. fo=1 will result in a failure report if either of the mechanisms fail.

After you have created this record you will get daily emails from the likes of gmail and yahoo telling you things like which emails are sending emails from your domain. And pass/fail results for DKIM and SPF. You can do this to check your changes are working OK.

If you have DMARC setup on one domain (say example.com) with an rua/ruf feedback address to go to another domain (say to reports@otherdomain.com) you would need to let DMARC reporters know it is OK to email that other domain. They do this by checking a DNS record on the other domain. They will check a DNS name of example.com._report._dmarc.otherdomain.com and expect a value of v=DMARC1

You should now be able to do an end-to-end test. Try emailing something to a gmail email address.

The TLS setting made above should now result in gmail showing a security padlock in the email header drop down saying ‘security: Standard encryption (TLS)’

If you click on the vertical …. ‘More’ button you can see a Show Original option. That should then report:

SPF:	PASS with IP 74.50.48.206 Learn more
DKIM:	'PASS' with domain rimuhosting.com Learn more
DMARC:	'PASS' Learn more

If those results are PASSING, and after you are happy with the DMARC results you get after a period of time you may wish to revise your SPF and DMARC records.

The SPF record above has ~all (tilde all) which means that if there is no match the email should be accepted, but flagged as bad. -all (minus all) means to reject emails with no match.

The DMARC record could also be changed from p=none to p=reject.

Congratulations. Recipients can now verify you sent the email.

Notes/implications:

  • A person with DNS access can affect delivery of emails.
  • DMARC, DKIM, and SPF are hints to a receiver about when to accept or reject emails, and when to report about passes/fails. They are hints and no receiver is compelled to enforce them.