The Schwarrisons

Using Postfix and DKIM

OpenDKIM when paired with Postfix allows for email to be delivered to Outlook and Gmail recipients whilst avoiding the Spam folders.

What was DomainKeys?

DomainKeys is an email authentication technology developed by Yahoo that uses cryptographic techniques to verify the sender’s domain in email messages. It helps protect against email spoofing and phishing by allowing the recipient to confirm that the message was sent from an authorised server for the domain in the “From” address. DomainKeys was later enhanced and evolved into DomainKeys Identified Mail (DKIM), which is now the industry standard.

DKIM Improvements

DKIM (DomainKeys Identified Mail) is an improved and more robust version of DomainKeys, designed to enhance email authentication, security, and interoperability. Here’s how DKIM is better than DomainKeys:

Improved Security and Flexibility

DKIM supports stronger cryptographic algorithms (such as RSA with larger key sizes), and allows for easier key rotation, allowing updates their keys without disrupting email flow,

Signature Flexibility

DKIM allows selective signing of headers and can sign multiple headers, giving administrators greater control over what part of the email is authenticated. DKIM also contains a hash of the email body in the signature, ensuring that the content hasn't been modified in transit.

Interoperability and Adoption

DKIM was developed by the IETF as a standardised protocol, which has helped make it more widely adopted and supported. Working with DMARC (Domain-based Message Authentication, Reporting, and Conformance) it creates an additional layer of protection against phishing and spoofing.

Adoption by Major Email Providers

Importantly, DKIM is required by major providers (like Google, Microsoft, and Yahoo), to ensure better deliverability and security for DKIM-signed emails,

What is OpenDKIM

OpenDKIM (Open DomainKeys Identified Mail) is an open-source implementation of DKIM, which stands for DomainKeys Identified Mail. It is a standard used to authenticate email messages, verify their integrity, and prevent email spoofing by adding cryptographic signatures to outgoing emails. OpenDKIM’s primary purpose is to sign, verify, and validate emails to improve email deliverability and security.

Configuring OpenDKIM

Install OpenDKIM using apt

root@docs:~# apt-get install opendkim
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  dns-root-data libevent-2.1-7t64 libhashkit2t64 liblua5.3-0 libmemcached11t64 libmilter1.0.1 libopendbx1-sqlite3 libopendbx1t64 libopendkim11 librbl1 libunbound8 libvbr2
  opendkim-tools
The following NEW packages will be installed:
  dns-root-data libevent-2.1-7t64 libhashkit2t64 liblua5.3-0 libmemcached11t64 libmilter1.0.1 libopendbx1-sqlite3 libopendbx1t64 libopendkim11 librbl1 libunbound8 libvbr2 opendkim
  opendkim-tools
0 upgraded, 14 newly installed, 0 to remove and 2 not upgraded.
Need to get 1250 kB of archives.
After this operation, 3914 kB of additional disk space will be used.
Do you want to continue? [Y/n] y

Add postfix to the opendkim group created during installation of the packages

sudo gpasswd -a postfix opendkim

Edit the /etc/opendkim.conf file locating:

  1. LogWhy and change it to Yes, this will add additional logging showing why decisions were taken
  2. Canonicalization and change it to simple (note the American Spelling)

Under OversignHeaders insert the following lines

AutoRestart         yes
AutoRestartRate     10/1M
Background          yes
DNSTimeout          5
SignatureAlgorithm  rsa-sha256

Append the following to the bottom of the configuration file

# Map domains in From addresses to keys used to sign messages
KeyTable           refile:/etc/opendkim/key.table
SigningTable       refile:/etc/opendkim/signing.table

# Hosts to ignore when verifying signatures
ExternalIgnoreList  /etc/opendkim/trusted.hosts

# A set of internal hosts whose mail should be signed
InternalHosts       /etc/opendkim/trusted.hosts

Once completed the file should be similar to this

# This is a basic configuration for signing and verifying. It can easily be
# adapted to suit a basic installation. See opendkim.conf(5) and
# /usr/share/doc/opendkim/examples/opendkim.conf.sample for complete
# documentation of available configuration parameters.

Syslog                  yes
SyslogSuccess           yes
LogWhy                  yes

# Common signing and verification parameters. In Debian, the "From" header is
# oversigned, because it is often the identity key used by reputation systems
# and thus somewhat security sensitive.
Canonicalization        relaxed/simple
Mode                    sv
SubDomains              no
OversignHeaders         From

AutoRestart         yes
AutoRestartRate     10/1M
Background          yes
DNSTimeout          5
SignatureAlgorithm  rsa-sha256

# Signing domain, selector, and key (required). For example, perform signing
# for domain "example.com" with selector "2020" (2020._domainkey.example.com),
# using the private key stored in /etc/dkimkeys/example.private. More granular
# setup options can be found in /usr/share/doc/opendkim/README.opendkim.
#Domain                 example.com
#Selector               2020
#KeyFile                /etc/dkimkeys/example.private

# In Debian, opendkim runs as user "opendkim". A umask of 007 is required when
# using a local socket with MTAs that access the socket as a non-privileged
# user (for example, Postfix). You may need to add user "postfix" to group
# "opendkim" in that case.
UserID                  opendkim
UMask                   007

# Socket for the MTA connection (required). If the MTA is inside a chroot jail,
# it must be ensured that the socket is accessible. In Debian, Postfix runs in
# a chroot in /var/spool/postfix, therefore a Unix socket would have to be
# configured as shown on the last line below.
Socket                  local:/run/opendkim/opendkim.sock
#Socket                 inet:8891@localhost
#Socket                 inet:8891
#Socket                 local:/var/spool/postfix/opendkim/opendkim.sock

PidFile                 /run/opendkim/opendkim.pid

# Hosts for which to sign rather than verify, default is 127.0.0.1. See the
# OPERATION section of opendkim(8) for more information.
#InternalHosts          192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12

# The trust anchor enables DNSSEC. In Debian, the trust anchor file is provided
# by the package dns-root-data.
TrustAnchorFile         /usr/share/dns/root.key
#Nameservers            127.0.0.1

# Map domains in From addresses to keys used to sign messages
KeyTable           refile:/etc/opendkim/key.table
SigningTable       refile:/etc/opendkim/signing.table

# Hosts to ignore when verifying signatures
ExternalIgnoreList  /etc/opendkim/trusted.hosts

# A set of internal hosts whose mail should be signed
InternalHosts       /etc/opendkim/trusted.hosts

Now the initial setup for OpenDKIM has been completed, you will need to set up the Key Table, Signing Table, and Trusted Hosts Files. Before doing this, you will need to decide on what zone you are going to sign. It will be in the format of <string>.domainkey where <string> is an arbitrary string, in the following example I will be using the FQDN of home._domainkey.schwetz.au.

Before getting to signing, we have a little housekeeping to do. Starting with the creation of the directory structure, then set the owner of the directory to opendkim:opendkim and set it so that only the OpenDKIM user can access the keys.

sudo mkdir /etc/opendkim
sudo mkdir /etc/opendkim/keys

sudo chown -R opendkim:opendkim /etc/opendkim
sudo chmod go-rw /etc/opendkim/keys

Now it is time to edit the signing table by editing the /etc/opendkim/signing.table adding two lines to the file. This tells OpenDKIM that if the sender matches the domain schwetz.au or <subdomain>.schwetz.au the email will be signed.

*@schwetz.au
*@*.schwetz.au

Edit the /etc/opendkim/key.table and add the following line telling OpenDKIM where it can locate the private key, ensure that it is all one line.

home._domainkey.schwetz.au  home:/etc/opendkim/keys/schwetz.au/home.private

Next we need to update the /etc/trusted_hosts to allow the hosts that we trust to be signed. I permit my lan subnet (10.0.0.0/8) to be signed

127.0.0.1
localhost
10.0.0.0/8
.schwetz.au
Do not enter an asterisk in any domain names that you add to this file, there should only be a dot before the domain name.

Now it is time to create the public/private key pair that will be used to sign and authenticate the message as trusted. The first step is to create the folder to hold the keys, standard is to use the domain name.

sudo mkdir /etc/opendkim/keys/schwetz.au/

Generate the keypair using the opendkim-genkey tool.

sudo opendkim-genkey -b 2048 -d schwetz.au -D /etc/opendkim/keys/schwetz.au -s home -v

The above command will create 2048 bit keys. -d (domain) specifies the domain. -D (directory) specifies the directory where the keys will be stored, and we use default as the selector -s, also known as the name. Once the command is executed, the private key will be written to home.private file and the public key will be written to home.txt file.

Make opendkim as the owner of the private key and change the permissions so that only the opendkim user can read and write the file.

sudo chown opendkim:opendkim /etc/opendkim/keys/your-domain.com/default.private

Below is an example of the public key. We will prepare this so that it can be published to the internet.

root@docs:~# cat /etc/opendkim/keys/schwetz.au/home.txt
home._domainkey IN      TXT     ( "v=DKIM1; h=sha256; k=rsa; "
          "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkgruYh6NHEwOPnJNrVACjUXcr8BMt1TwaUuy0/Rd3sMtMCX9eYn0YofOcnSoHqc/bYLI9RpnDCLHi2OK9xg3hSiQwtz1SaHVp42O2RE1Ne5BeDqQSl6l86F91V5Aiz42eVH2DkvCs8eVUvj/1/kOR/3igUMExrM23ctivXwzUGIElOgOByPTXr8anYNb5A+ZWhkzgvWBXuBL7/"
          "Ioxddo+JtMbr5hO2gwQ+lAiQstoYx9EyCiC3czAQXIdtV5+kJVJOyoP7DlF2RIDEM0DaTD6NZ6e9IYxmiak688Ye+xdZxUmJpUhl+F9O792tqfVVqxaFEofTzqrcyudFuqZUZepwIDAQAB" )  ; ----- DKIM key home for schwetz.au

When the public key is saved by OpenDKIM it saves it split across multiple lines, and these need to be recombined, and in all of the guides that I have seen do not advise how to do it, and many years ago it took me a couple of attempts to get this part correct.

To accomplish this, remove:

  1. everything on the first line that prepends v=DKIM
  2. from after k=rsa; until the second " (inclusive)
  3. from the " and following " (inclusive)
  4. " ) ; --— DKIM key home for schwetz.au
v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkgruYh6NHEwOPnJNrVACjUXcr8BMt1TwaUuy0/Rd3sMtMCX9eYn0YofOcnSoHqc/bYLI9RpnDCLHi2OK9xg3hSiQwtz1SaHVp42O2RE1Ne5BeDqQSl6l86F91V5Aiz42eVH2DkvCs8eVUvj/1/kOR/3igUMExrM23ctivXwzUGIElOgOByPTXr8anYNb5A+ZWhkzgvWBXuBL7/Ioxddo+JtMbr5hO2gwQ+lAiQstoYx9EyCiC3czAQXIdtV5+kJVJOyoP7DlF2RIDEM0DaTD6NZ6e9IYxmiak688Ye+xdZxUmJpUhl+F9O792tqfVVqxaFEofTzqrcyudFuqZUZepwIDAQAB

You will then need to add the created public key to your DNS hosting, I use Cloudflare zone file and added the TXT record from above.

Screen Capture showing the home._domainkeys public key being published via the cloudflare dashboard
The public key needs to be added to your zone file
root@docs:~# dig txt home._domainkeys.schwetz.au

; <<>> DiG 9.18.28-0ubuntu0.24.04.1-Ubuntu <<>> txt home._domainkeys.schwetz.au
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54675
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;home._domainkeys.schwetz.au.   IN      TXT

;; ANSWER SECTION:
home._domainkeys.schwetz.au. 300 IN     TXT     "v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkgruYh6NHEwOPnJNrVACjUXcr8BMt1TwaUuy0/Rd3sMtMCX9eYn0YofOcnSoHqc/bYLI9RpnDCLHi2OK9xg3hSiQwtz1SaHVp42O2RE1Ne5BeDqQSl6l86F91V5Aiz42eVH2DkvCs8eVUvj/1/kOR/3igUMExrM23ctivXwzUGIElOgOByPTXr8" "anYNb5A+ZWhkzgvWBXuBL7/Ioxddo+JtMbr5hO2gwQ+lAiQstoYx9EyCiC3czAQXIdtV5+kJVJOyoP7DlF2RIDEM0DaTD6NZ6e9IYxmiak688Ye+xdZxUmJpUhl+F9O792tqfVVqxaFEofTzqrcyudFuqZUZepwIDAQAB"

;; Query time: 15 msec
;; SERVER: 10.69.6.1#53(10.69.6.1) (UDP)
;; WHEN: Tue Nov 12 10:42:33 UTC 2024
;; MSG SIZE  rcvd: 490

Testing Deployment

Enter the following command on the server to test that the key has been deployed successfully

opendkim-testkey -d schwetz.au -s home -vvv

You will then be presented with the verification

root@docs:~# opendkim-testkey -d schwetz.au -s home -vvv
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: checking key 'home._domainkey.schwetz.au'
opendkim-testkey: key secure
opendkim-testkey: key OK
If the bottom line says opendkim-testkey: key OK then the key has been deployed correctly
⚠️
Your test may return opendkim-testkey: Key no secure, this is not as bad as it looks, it means that the key is not secured with DNSSEC, you can continue.

Connect OpenDKIM to Postfix

The postfix SMTP daemon shipped with Ubuntu runs in a chroot jail, which means the SMTP daemon resolves all filenames relative to the Postfix queue directory (/var/spool/postfix)

Create a directory to hold the OpenDKIM socket file and allow only opendkim user and postfix group to access it.

sudo mkdir /var/spool/postfix/opendkim
sudo chown opendkim:postfix /var/spool/postfix/opendkim

Edit the OpenDKIM configuration file /etc/opendkim.conf and locate the line

Socket    local:/run/opendkim/opendkim.sock

replacing it with the following line

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

Edit the /etc/postfix file and add the following milter configuration to the bottom of the file

# Milter configuration
milter_default_action = accept
milter_protocol = 6
smtpd_milters = local:opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters

One you have saved and closed the file restart OpenDKIM and Postfix services

sudo systemctl restart opendkim postfix
About the author
Stephen Schwetz

The Schwarrisons

Herping the derp!!

The Schwarrisons

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to The Schwarrisons.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.