What is DKIM?

DKIM is short for Domain Keys Identified Mail and is an internet standard, that ensures, that an email is in fact authorized by the owner of the domain, and, that its content is authentic and has not been modified. DKIM is available for FreeBSD as the OpenDKIM package or port. OpenDKIM is a milter for Sendmail, which is the default mail server in FreeBSD.

Example of an CEO scam email, that is being contructed with false sender header fields in an Alpine mail client. DKIM authentication and SPF authorization prevents this kind of domain abuse. This can be implemented with an SPF TXT record for BIND DNS and OpenDKIM milter for Sendmail with a DKIM TXT record for BIND DNS on FreeBSD
Example of an CEO scam email, that is being contructed with false sender header fields. DKIM authentication and SPF authorization prevents this kind of domain abuse.

How does DKIM work?

The mail server of the sender automatically signs the email with a private key and adds a DKIM signature header field to the email. The signature is automatically verified with a public key by the mail server of the receiver. The public key is linked to the domain, because it is stored in a TXT record in DNS for the domain.

OpenDKIM can sign outgoing email and validate incoming email. OpenDKIM can use a key to sign a domain. OpenDKIM can use the same key to sign multiple domains. OpenDKIM can use multiple keys to sign multiple domains and subdomains with the use of key and signing tables.

What is the difference between DKIM and SPF?

DKIM is installed on the mail server. SPF is installed on the DNS server. DKIM ensures, that the email message is authentic. The sender information and the message has not been forged nor modified during transmission. SPF ensures, that the email was sent by an authorized mail server. The email was not sent from a spam server nor unauthorized mail server.

Install OpenDKIM for FreeBSD.

If you want to implement DKIM on your FreeBSD mail server, then install OpenDKIM from packages or ports.

# pkg search opendkim
opendkim-2.10.3_12             DKIM library and milter implementation
# pkg install opendkim
Message from opendkim-2.10.3_12:
In order to run this port, write your opendkim.conf and:
if you use sendmail, add the milter socket `socketspec' in
/etc/mail/<your_configuration>.mc:
INPUT_MAIL_FILTER(`dkim-filter', `S=_YOUR_SOCKET_SPEC_, F=T, T=R:2m')

Note, that the message suggests, that the milter identifier is dkim-filter. This is unfortunately not correct. The milter identifier, that Sendmail will use for OpenDKIM, is opendkim.

Generate DKIM keys on FreeBSD mail server.

The DKIM package and port comes with the DKIM filter key generation tool opendkim-genkey. The tool generates a private key for signing messages and a public key for verifying messages. The public key comes in the form of a TXT record. The format is compatible with a zone file for BIND DNS.

Create a directory for storing the DKIM keys and generate a DKIM key. The option -d is a list of domains, that OpenDKIM will sign with this key. The option -D will include subdomains of those domains. See more about options in the manual for OpenDKIM. If OpenDKIM will be using multiple keys, then organize the keys. Note, that OpenDKIM runs as a non-priviledged user and group.

# cd /usr/local/etc/mail
# mkdir -m 0755 opendkim-keys && cd opendkim-keys
# mkdir -m 0755 foobar.com && cd foobar.com
# opendkim-genkey -b 2048 -d foobar.com -s default
# chown mailnull:mailnull default.*
# chmod 0440 *

Add DKIM TXT record to domain name zone in BIND DNS server.

In this example, a DKIM public domain key, that was generated and written as a TXT record, is added to the domain name zone in BIND DNS on a FreeBSD server. The serial number is updated and the final zone reviewed. A lower time-to-live (TTL) might be used in the initial phase. The zone is checked by BIND DNS before BIND is restarted.

# cat default.txt >> /usr/local/etc/namedb/master/foobar.com
# nano /usr/local/etc/namedb/master/foobar.com
# named-checkzone foobar.com /usr/local/etc/namedb/master/foobar.com
zone foobar.com/IN: loaded serial 13371377
# service named restart

Example of BIND DNS zone file with DKIM TXT record.

# cat /usr/local/etc/namedb/master/foobar.com
$TTL                                            13M
$ORIGIN                                         foobar.com.
@                       IN      SOA             dns.foobar.com. hostmaster.foobar.com. (
                                                13371337
                                                8H
                                                2H
                                                4W
                                                1D )
@                       IN      NS              dns1.foobar.com.
@                       IN      NS              dns2.foobar.com.
@                       IN      MX      10      mail1.foobar.com.
@                       IN      MX      20      mail2.foobar.com.
@                       IN      TXT             "v=spf1 mx -all"
default._domainkey      IN      TXT             ( "v=DKIM1; k=rsa; "
          "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3xl2FNEbcSLuGC0ju8nHxE2M3Tmwq8yK77rI5lgCZNr22IWKqIy9uJg+w94SKoMLZxSx6SfUc/hry3q4x9Q85rgLsRbAZ7qXHt8v4mswZ26zz+ojtspjk1LkLH73AaO4/HUB0AqZYdolAXnnGz25rsvUBPSvoLPaYHgzOqsksDaooCGBeZMpulcQ5YuIUs8lEmGW7GdQFIJ/AK"
          "zlYV2jD1GxlA03nDAkqXO4m+H8roGv75YXXx9IvyXgr0DB13PKNzH5yW5iOcrDBmsVVX65MzT/+Llj2w54/vwqD2R8lsFWuRW+jmWZ+a9iyAjhnbGREoShLv/o42CO+3RhzL1JqwIDAQAB" )  ; ----- DKIM key default for foobar.com
@                       IN      A               13.37.13.37
dns1                    IN      A               13.13.13.13
dns2                    IN      A               37.37.37.37
mail1                   IN      A               13.37.13.38
mail2                   IN      A               13.37.13.39
www                     IN      A               13.37.13.40

Test DNS with DKIM TXT record.

Test the DNS by making a DNS query with the DNS lookup utilify dig for the new TXT record, that contains the DKIM public domain key.

Be aware, that a DNS query consist of a name and a type. The name is a domain name, that is looked up, and the type is the type of resource record (RR), that is looked up. If just the name foobar.com was looked up, then that name would be different from the name default._domainkey.foobar.com, that has the DKIM key, and would not return the DKIM key.

# dig @localhost -n default._domainkey.foobar.com -t TXT
default._domainkey.foobar.com. 60 IN    TXT     "v=DKIM1; k=rsa; " "p=MIIBIjANBgkqhkiG9w0BAQEF1337AQ8AMIIBCgKCAQEA3xl2FNEbcSLuGC0ju8nHxE2M3Tmwq8yK77rI5lgCZNr22IWKqIy9uJg+w94SKoMLZxSx6SfUc/hry3q4x9Q85rgLsRbAZ7qXHt8v4mswZ26zz+ojtspjk1LkLH73AaO4/HUB0AqZYdolAXnnGz25rsvUBPSvoLPaYHgzOqsksDaooCGBeZMpulcQ5YuIUs8lEmGW7GdQFIJ/AK" "zlYV2jD1GxlA03nDAkqXO4m+H8roGv75YXXx9IvyXgr0DB13PKNzH5yW5iOcrDBmsVVX65MzT/+Llj2w54/vwqD2R8lsFWuRW+jmWZ+a9iyAjhnbGREoShLv/o42CO+3RhzL1JqwIDAQAB"

When the updated DNS zone has been adopted by DNS servers on the internet, then DNS and DKIM for the domain can be tested on the internet as well.

In this example, a DNS query is made from the csh shell on a FreeBSD desktop computer, different from the server. It should match the test, that was done earlier.

% dig -n default._domainkey.foobar.com -t TXT

The DNS query can also be made, so that it asks a specific DNS server.

% dig @foobar -n default._domainkey.foobar.com -t TXT

Configure OpenDKIM on FreeBSD.

Find the configuration file for OpenDKIM. We can only try to guess the filename.

# find / -type f -name 'opendkim.c*'
/usr/local/etc/mail/opendkim.conf.sample

Use the sample file to create a new configuration file.

# cp /usr/local/etc/mail/opendkim.conf.sample /usr/local/etc/mail/opendkim.conf

Edit the configuration by reading the comments and editing as necessary. Many options has default values. Port 8891 seems to be widely used.

# nano /usr/local/etc/mail/opendkim.conf

Confirm effective options.

# cat /usr/local/etc/mail/opendkim.conf | grep -v '^#' | grep -v '^$'
Domain                  foobar.com
InternalHosts           refile:/usr/local/etc/mail/opendkim-internalhosts
KeyFile                 /usr/local/etc/mail/opendkim-keys/foobar.com/default.private
Selector                default
Socket                  inet:8891@localhost
Syslog                  Yes
SyslogSuccess           yes

Make the list of internal hosts, that should be signed. The default is 127.0.0.1.

# nano /usr/local/etc/mail/opendkim-internalhosts
127.0.0.1
13.37.13.37
# chmod 0755 /usr/local/etc/mail/opendkim-internalhosts

The use of InternalHosts is probably not necessary.

Start OpenDKIM milter on FreeBSD.

Configure the OpenDKIM milter as a service in FreeBSD for automatic start.

# nano /etc/rc.conf
milteropendkim_enable="YES"
milteropendkim_flags="-x /usr/local/etc/mail/opendkim.conf"

Identify the service name, that starts OpenDKIM. Note, that developers went for a discreet hyphen on this one.

# service -e | grep dkim
/usr/local/etc/rc.d/milter-opendkim

Start the OpenDKIM milter.

# service milter-opendkim start
Starting milteropendkim.

Check system log and mail log for errors.

# tail /var/log/messages

Confirm, that OpenDKIM is running and listening on the port.

# sockstat -4 -l
mailnull opendkim   81337 3  tcp4   127.0.0.1:8891        *:*

Configure Sendmail for OpenDKIM milter.

When OpenDKIM is configured and running as a service, then it is time to implement the OpenDKIM milter in Sendmail. Read more about the Sendmail configuration format in the Makefile in /etc/mail and the M4 stream configuration format in README in /usr/share/sendmail.

# cd /etc/mail/
# nano `hostname`.mc
INPUT_MAIL_FILTER(`opendkim', `S=inet:8891@localhost, F=T, T=R:2m')
# make
/usr/bin/m4 -D_CF_DIR_=/usr/share/sendmail/cf/   /usr/share/sendmail/cf/m4/cf.m4 foobar.com.mc > foobar.com.cf
# make install
install -m 444 foobar.com.cf /etc/mail/sendmail.cf
install -m 444 foobar.com.submit.cf /etc/mail/submit.cf
# make restart

Test OpenDKIM signing.

OpenDKIM works, when the receiving mail server is able to lookup the DKIM key in DNS and then confirm the signature in the header of the email.

Send a test email from a local mail client on the mail server (localhost) to a recipient, that can read the headers of the email. Confirm, that there are no errors in mail log. Confirm, that the received email contains a DKIM signature, and, that it passed the authentication.

# tail /var/log/maillog

Send a test mail from a remote host, that is allowed to relay mail via the mail server with OpenDKIM, to a recipient, that can read the headers of the email. Confirm, that there is a DKIM signature, and, that it passed the authentication.

GMail can be used to examine the headers and DKIM authentication of an email. This is done by opening the email and selecting to view it as original message.

Example of email header with passed DKIM authentication results and DKIM signature.

The following is an example from an email in GMail. The email header contains ARC authentication results, that show, that the email passed DKIM and SPF authentication. The email header also contains the DKIM signature.

ARC-Authentication-Results: i=1; mx.google.com;
       dkim=pass header.i=@foobar.com header.s=default header.b=13371337;
       spf=pass (google.com: domain of  designates 13.37.13.37 as permitted sender)
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=foobar.com; s=default; t=1707280015; bh=ATFNXTdps/lpxZOwwqUpGoGBuiD+IO1337KfY2wcvwM=; h=Date:From:To:Subject; b=XL7nF+IGeTa521337caPAO1337NEXmtIm4uknaxCLa7/klLML3vtRzLD1337NJlKu
	 /mANY71yXFLt7Evq3my367s0jKNhXhhNY1Sqzb3sEK9nq/DBYvLmEJn4ug7+FmU5+1
	 62HI7w/7Pwo9i1JCOWe92IoKPekJ813378CpeD97Yjp/MJz7iPVMP16vs3sdkVGl2t
	 U+m7P2qEOpixzSgMLBIiBkCTDAE+ZJW+Asz80aDxX8qtDw1Top1zLoNYIU37ZwgskC
	 9xvywBEUhFwqP/FKJH/nBthkQbDwNC01uU7LMsi98lBtSC6Kb0RfUyqf5fagM89Z8n
	 uFpjX1o2mrFng==

How to raise log levels of Sendmail and OpenDKIM milter.

The log levels of Sendmail and OpenDKIM milter can be raised to include more details and help identify problems. This is done by configuring the Sendmail configuration file.

define(`confLOG_LEVEL', `9')
define(`confMILTER_LOG_LEVEL', `18')

More about DKIM.