Setting up DKIM for my email server

2018-08-15

I was sending emails to potential apartments in Madison this morning, and I got one of these responses:

<rent@genericcorporateemail.com>: host
    genericcorporateemail-com.mail.protection.outlook.com[216.32.181.106] said: 550
    5.7.606 Access denied, banned sending IP [45.33.98.185]. To request removal
    from this list please visit https://sender.office.com/ and follow the
    directions. For more information please go to
    http://go.microsoft.com/fwlink/?LinkID=526655 (AS16012609) (in reply to
    RCPT TO command)

I followed the instructions, and for now I think the Microsoft Office 365 Delisting Service trusts me. Still, I want to encounter this less in the future, so I decided it is finally time to set up DKIM and DMARC for my email server.

What is DKIM?

DomainKeys Identified Mail is a system for cryptographically signing outgoing mail. The public key is then placed in the sending domain's DNS record, so that anyone receiving can verify that the messages were actually sent from the right host. This prevents malicious senders from spoofing your domain or IP, because they won't know your public key, and so they can't sign mail properly as if it came from you.

I have an SPF policy set up, so a malicious sender would not just be able to spoof my domain, they would also have to appear to be coming from my IP. This should prevent most problems, but it becomes an issue if a sender manages to convince a receiving server that they're from my IP. I imagine that this could happen if the email is sent via a compromised relay that lies about the IP of the email's origin. DKIM will make it possible for recipients to reject the email on the basis of its signature.

The next step after implementing DKIM will be DMARC. Domain-based Message Authentication, Reporting and Conformance is another policy for the domain's DNS record that tells the world what SPF and DKIM settings to expect for all mail from the domain. If you just add DKIM without DMARC, you sign your own mail, but you don't make it clear that that's the only acceptable mail with your domain on it. DMARC tells other email servers to only allow mail that meets your own high standards. DKIM is a prerequisite for using DMARC effectively, but not vice versa, so I'm fine implementing DKIM on its own for now and leaving DMARC as a project for another day.

Implementing DKIM

Because I used the mail setup tutorial from workaround.org to set up my mail, I went there first to find help setting up DKIM. First I found this fairly detailed article that relies on a package called rspamd. It turns out that rspamd is not currently available from the Debian or Ubuntu repositories, though, so installing it on my server would have involved manually installing the package. Because I have more trust for packages from standard sources and did not want to take the time to read over the install script for rspamd, I decided to move on.

I then discovered some references to opendkim, and eventually came across this helpful how-to article from DigitalOcean. It is the most detailed step-by-step walkthrough that I could find; several other places offered a rough overview, but left details of the installation and configuration out. For everything on my server, I was able to follow along using just the default suggestions. My only modification was to generate a 2048 bit key instead of the default 1024:

# opendkim-genkey -b 2048 -s mail -d nathanmorrison.net

The longest key whose public key fits on one line of a DNS record is 1024 bit, so my domain's public key needed to be broken across multiple lines. Getting the DNS record formatted correctly was a bit tricky. My first issue was that the automatically generated public key file contained extra information, and I misunderstood how to enter it in the Linode DNS manager. The mail.txt file that opendkim-genkey created is

mail._domainkey IN  TXT ( "v=DKIM1; k=rsa; "
      "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwu5MhxfsnOERU61zNJ/1DZzvVIh9qRt7mCzT5VIzSTom416TSioAGNxvfv/I7PXPhkRbt+Efyg5+akucmKKzarTUZ/5t9oSgzPQmqReWPM5u82K6+2lhYREHLNZwM63lr/kHFqYuBlNpPfsrymhJvFCF/DTQZRjWwF05IRpOtw/bQ17OzqMr7jxNR+eeiDGDAo2tvPdwu18Ib+"
      "aZtM+3u77Co884p4im+7KdF6wHeie5tD3PlxC1bMqWuOG3+bJQIuI+uvIv+y/rV6YVD0Bq8A1crkE3iYV8s+9L0keTiEN9NDOo6JZj9gnDG0uKSeXtB3CncV/x6eEv6iMaSP/OOwIDAQAB" )  ; ----- DKIM key mail for nathanmorrison.net

I think that might be the format for the DNS record I would use if I had configured my own DNS server and could set up the record with plain text files. Linode's DNS manager uses a web interface that uses two single-line HTML forms for "Name" and "Value". "Name" refers to a subdomain, so it took me a bit to figure out that the field should be mail._domainkey. I of course don't have any machine or service listening under that subdomain, but it turns out that when a receiving server goes to verify the DKIM signature, it does a lookup on mail._domainkey.nathanmorrison.net and uses the TXT record it finds.

The value of the TXT record needs to be each line that is within the quotes, one after the other, and separated by newlines. Linode's DNS manager will accept multiple lines, thankfully, but the single-line HTML form displays newlines as spaces. I made the mistake of pasting in the whole file first, then attempting to edit out the extra characters by hand in the web form. That did not go well. In the end, I decided not to leave it up to chance. I downloaded the file and ran

$ cat mail.txt | sed 's/.*"\(.*\)".*/\1/' | xclip -selection clipboard

which prints straight to the clipboard what's contained within the quotes line by line. Pasting straight from that created a well-formed DKIM record.

Testing DKIM

My first test was to send mail to a helpful friend who also had DKIM implemented on his server see if his was able to verify my signature. After I did that, I also found this handy tool that's free to use on the web. I sent the tool an email containing

Subject: testing

testing

Here's the part of the header that relates to DKIM:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=nathanmorrison.net;
    s=mail; t=1534314875;
    bh=15pFrAvOGi+eHKJgB6psh6iIBCbvYSuhPj+wQn6C7Ss=;
    h=To:From:Subject:Date:From;
    b=rPlYoKgsV0eAiKDg/uObmeEruC1KurH17JMy0fpiOp1r9+2U8Upku/PARRjJfSntr
     85zdi5umosWk4wYv07R+TJckJG/3ou78JQWnxhlNxNLdYFxcIDlo/wpdQLdqfFI2Lf
     Pn3P5gxGElIGcidLE1WGqtcbJa2xDTUONQD5q37xVPG7IGyp+eDMWiKN5YSeAEsIV1
     Ta//lgTGPPLiHXE6qu43p0cYLA8MIaWX7a4Rzil2e/wSHMZiJC8uJEw0RzBI0USLpn
     wg10uZRNGmC/w1Ae1sjJnfc/1VuhkFIMt2U39Cm9cEWwm9D+08DXFYYgVTzCQHbO0J
     q77Wvne1XW1cw==

The actual signature is the attribute labeled b. You can also see that it specifies the selector, s=mail, and the domain, d=nathanmorrison.net, which tells the receiving server to look up mail._domainkey.nathanmorrison.net. (Multiple selectors would make multiple keys possible, though I'm not sure what the use case would be.) The signing algorithm is specified as a=rsa-sha256. (That is defined in opendkim.conf.) The canonicalization for signing is c=relaxed/simple, which means that the header is counted in a relaxed way where small changes such as whitespace do not change the signature, while the body is counted more strictly, character by character.

Here is my result:

Public Key DNS Lookup

Building DNS Query for mail._domainkey.nathanmorrison.net Retrieved this publickey from DNS: v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwu5MhxfsnOERU61zNJ/1DZzvVIh9qRt7mCzT5VIzSTom416TSioAGNxvfv/I7PXPhkRbt+Efyg5+akucmKKzarTUZ/5t9oSgzPQmqReWPM5u82K6+2lhYREHLNZwM63lr/kHFqYuBlNpPfsrymhJvFCF/DTQZRjWwF05IRpOtw/bQ17OzqMr7jxNR+eeiDGDAo2tvPdwu18Ib+ aZtM+3u77Co884p4im+7KdF6wHeie5tD3PlxC1bMqWuOG3+bJQIuI+uvIv+y/rV6YVD0Bq8A1crkE3iYV8s+9L0keTiEN9NDOo6JZj9gnDG0uKSeXtB3CncV/x6eEv6iMaSP/OOwIDAQAB

Validating Signature

result = pass

It works!

All in all, this little project took me probably three hours over a morning while I was also taking care of other things. Even though I didn't get the DNS record right the first time, I would still rate this as an easy project. Everyone running their own email server should definitely implement DKIM.