Implementing DKIM with Exim

This article was updated in February 2014 to reflect changes in policy and reporting options. The earlier ADSP (Author Domain Signing Practices) information has been removed.

DomainKeys Identified Mail (DKIM) provides a method to confirm the origin of an e-mail. DKIM also provides some protection against tampering. Unlike SPF, this validation applies to the contents of the message when it is signed. Like SPF, the information required for validation is added to DNS.

This article covers signing outgoing email for the domain. This implementation is based on Exim4 running on Ubuntu. DNS record descriptions are for bind. Recent versions of Exim now implement DKIM. This post documents my implementation of DKIM using Exim and Bind on Ubuntu or Debian. This implementation provides support for signing for the domain, but not for the author. Author signing should be done by the author using appropriate tools.

DomainKeys uses a _domainkey subdomain within the domain to which it is applied. Signing keys become selector TXT_domainkey subdomain. Multiple signing keys may exist for various reason such as server-specific keys, time-based key rotation, or originator based keys.

Signing Outgoing Mail

This configuration uses a single selector named rsa1. The private key file is named accordingly.  You should choose a different name, and replace rsa1 with your own selector. When replacing your key, always use a new selector.

Generating a Signing Key

Before signing your mail you will need to generate a key and extract the public key. You will need to keep the private key as long as you are signing with it. If you generate multiple keys, rename the private key according to your naming convention. The following commands will generate a private key in dkim.private.rsa1, and a base64 encoded public key in dkim.public.rsa1. Once you have configured bind you can delete the dkim.public.rsa1 file.  Change the value of SELECTOR each time you use the script. The keys must be generated in (or moved to) your configuration directory (/etc/exim4) or some other location where your mail server can read it. (Use a key size of 2048 or larger, smaller key sizes may not validate.)

SELECTOR=rsa1
cd /etc/exim4
openssl genrsa -out dkim.private.$SELECTOR 2048
openssl rsa -in dkim.private.$SELECTOR -out dkim.public.der -pubout -outform DER
base64 < dkim.public.der > dkim.public.$SELECTOR 
rm dkim.public.der
chown root:Debian-exim dkim.private.$SELECTOR
chmod 440 dkim.private.$SELECTOR

Configuring Bind

You will need to add a TXT record to each domain for which you are signing email messages. For multiple domains signed with the same key, I find it simpler to separate the DKIM configuration into its own file. (This requires manually updating the serial numbers on the including domains.) Add the following lines to the zone file for each domain being signed.

;; Enable DKIM verification
$INCLUDE /etc/bind/dkim.conf

WARNING: You will need to increment the serial number in each of these zone files each time you update the include file. All signing keys will be valid for all configured domains. If you don’t want this to be the case, add the records directly to the applicable zone file(s).

Create your first DKIM configuration. In the rsa1._domainkey selector record, replace <DKIM.PUBLIC.KEY> with the text from your dkim.public.base64 file. Ensure you don’t have any white space in the key.

;; Common DKIM information for signed domains
;;
;; See: http://tools.ietf.org/html/rfc6376
rsa1._domainkey                 IN      TXT     "v=DKIM1; k=rsa; t=y; p="
;; EOF

Reload or restart bind. Once all your secondary name servers have refreshed you will be ready to configure DKIM working in test mode. There are several sites such as check-auth@verifier.port25.com which will send you a validation report.

If you use a large key size (2048 is a practical limit for UDP transmission), then you may need to split the selector’s record. A TXT string can not contain more than 255 characters. A longer string can be split into parts by breaking it up into quoted parts. Use parentheses around the definition if you split it across lines. These parts a consolidated into one string without intervening white space. For example “abcdef” could be split using either of these techniques:

oneline    in TXT  "v=DKIM1; k=rsa; t=y; p=" "abc" "def"
manylines  in TXT ("v=DKIM1; k=rsa; t=y; p="
 "abc"
 "def")

The instructions above provide the public key in a format that is easy to convert to the manylines format. You will need to add quotes at the beginning and end of each line. Then wrap the whole text starting with "v=DKIM1; in brackets.

Configuring Exim4

Update your Exim configuration. If you are using the Ubuntu (Debian) configuration this is done by defining macros. Using the split configuration these can be added to a file in /etc/exim4/conf.d/main. I use the filename 00_localmacros. Using the unsplit configuration add the definitions near the beginning of /etc/exim4/exim4.conf.template. These macros will alter the configuration of the remote_smtp transport to sign outgoing email. The selector will be automatically set to the extension of the private key file. If you are signing for only one domain, you may want to hard-code the DKIM_DOMAIN value to your domain.

# Enable DKIM
DKIM_CANON = relaxed
DKIM_DOMAIN = ${sender_address_domain}
DKIM_PRIVATE_KEY = CONFDIR/dkim.private.rsa1
DKIM_SELECTOR = ${extract{-1}{.}{DKIM_PRIVATE_KEY}}

If you are using another configuration method then you will need to add the DKIM specification to your external SMTP router(s). This example is based on the Debian/Ubuntu remote_smtp transport that configures DKIM automatically using the macros defined above. If you are using another platform, or a smart host you may have to add the DKIM configuration options shown here:

remote_smtp:
    driver = smtp
    dkim_canon = DKIM_CANON
    dkim_domain = DKIM_DOMAIN
    dkim_private_key = DKIM_PRIVATE_KEY
    dkim_selector = DKIM_SELECTOR
    ...

Restart or reload Exim to begin signing your outgoing email. You may want to check the headers of an email sent to an external address to verify that it is being signed correctly. Gmail makes it easy to see the raw message and adds a header indicating if the signature is valid.

Setting your policy

DKIM is not MIME transparent, and any MIME type conversions are likely to break the validation. If you send 8bit mime types, expect validation to fail when you send to sites which only support 7 bit formats.

The selector record above indicates that it is in testing mode (t=y). This should make sure that validation failure does not get your email bounced. When you are confident, in your configuration, you may want to switch to  indicate the selector (t=s) is strictly for the specified domain, or by removing the t tag entirely.

Adjust your zone configuration(s) and increment your DNS serial number(s) before reloading bind. Once your changes have propagated through the Internet your new policy will be in place.

Many large mail domains such as Gmail and Yahoo will provide feedback on your DKIM signing if you add the appropriate DMARC records to your domain’s DNS entries. I will cover DMARC in a separate posting.

RFC 6651 “Extensions to DomainKeys Identified Mail (DKIM) for Failure Reporting” provides a means to request feedback on signature failures. This requires adding a report record to the <_domainkeys> subdomain. This may point to an email alias rather than a real mailbox. The version of Exim I am currently using does not support this option.

Maintaining Your Keys

You may want to periodically replace your signing key. You may have as many keys and selectors as required. There are points in the process where you should wait before proceeding. Use a new selector name and generate your new key and add it to bind as described above. Add a selector record, keeping the existing policy and selector records. You may want to add a comment in your zone file indicating when you implemented the new selector. Reload or restart bind.

You will need to wait for your changes to propagate to all of your authoritative name servers before updating Exim prepare your new key and selector record using a new selector. Once all your secondary servers are serving the new selector, you can update Exim. Update the DKIM_PRIVATE_KEY macro to use the new key. Reload or restart Exim and you will be using the new key.

Cleaning up Old Keys and Selectors

Once you stop using a key, you can delete it. You may want to keep a history of the keys you used and their corresponding selector records. This will allow you to validate old emails.

When you replace a selector, you should keep publishing it for at least one or two weeks. This will allow any email in transit to be validated. You can then remove the selector from bind or set the public key to an empty string. Reload your domain after making your change. Either of these changes will indicate to validation software that the selector is not valid. Any validations done after the selector is removed will fail.

Handling a Key Compromise

If your key is compromised, you will need to replace it promptly. Document as much information about the compromise as you can. Communicate the problem to senior management so that they can respond appropriately.

Setup the new key and selector as specified above. If you are willing to have some email bounce due to validation failures, you can remove the selector when you publish the new selector. DKIM implementation is not yet to the state that many sites will bounce email for DKIM validation errors. Disable the compromised key as soon as possible.

12 comments

  1. Thanks got this working ( I had to change a couple of things:

    1. you need to cd to: /etc/exim4 before you create the keys or move the key there afterwards:

    mv dkim.private.key /etc/exim4/dkim.private.rsa1

    I added the macro to the head of the exim configuration but also had to add the details to the smpt_transport section before it worked:
    ie.
    remote_smtp:
    driver = smtp
    dkim_domain = (added my domain here)
    dkim_selector = rsa1
    dkim_private_key = CONFDIR/dkim.private.rsa1
    dkim_cannon = relaxed

    I use dnsmadeeasy so the final stage was to create a text record (as you describe for bind)

    Thankyou.

  2. The post has been updated based on Julian’s comments. The remote_smtp transport documentation now uses the macros. The macro definitions have been changes to couple the selector id to the extension of the private key file. I hope you will find the revised document easier to use.

  3. Finally! I how-to that actually works! Thank you so much for your concise and ACCURATE tutorial.

    One note: I am using Bind 9.6…

    I could not make the split work with a large key. Glad this tutorial recommends the smaller key. Will save you loads of time!

    1. I can suggest three options. (I sign for multiple domains using a variation on the second option.)

      • You can sign all outgoing documents from the same domain. This is simplest. The following options require you to define selectors and keys for each sub-domain.
      • Publish the same key for all sub-domains, and sign the document using the same key and selector, but using the sub-domain as the dkim_domain.
      • Build a lookup list enabling you to lookup the key and/or selector for the sub-domain. You will need to publish the appropriate keys and selectors.
  4. The configuration entry under remote_smtp actually must be written ‘dkim_canon = …’ to be valid.

  5. While looking at implementing DKIM, I found your helpful post. Some notes:

    The o= policy specified on _domainkeys.example.com does not seem to exist anymore, it got removed before the RFC was issued. See https://tools.ietf.org/rfcdiff?url2=draft-allman-dkim-ssp-02.txt. The final RFC mentioned ADSP (http://tools.ietf.org/html/rfc5617#page-7), but its use is not recommended because it is not really used (https://en.wikipedia.org/wiki/Author_Domain_Signing_Practices). I’d suggest to drop that _domainkeys.example.com policy as it may be more confusing than helpful.

    For rotating DKIM keys, I found this document also informative: http://www.maawg.org/sites/maawg/files/news/M3AAWG_DKIM_Key_Rotation_BP-2013-12.pdf

    1. The post has been updated to reflect your comments and updated to reference newer RFCs. I never was comfortable with ADSP as specified. DMARC seems to provide a better solution. Thanks for the feedback.

  6. If you want to support multiple domains (e.g. you’re hosting several websites), then this is the settings I came up with (replacing with a random keyword or similar):

    DKIM_SELECTOR = ${hash{8}{_$DKIM_DOMAIN}}
    DKIM_PRIVATE_KEY = CONFDIR/dkim_keys/rsa.private.${DKIM_SELECTOR}

    You can get the selector for a domain by running:

    exim -be ‘${hash{8}{_}}’

    e.g. exim -be ‘${hash{8}{salty_mydomain.com}}’

    Then use that to generate the private key and DNS record.

    The salt ensures that the resulting hash won’t collide with another email provider (for when you send email from multiple services).

    One other note on the instructions, you can get the pubkey without writing a file using this command (which I found in the Exim documentation):
    openssl rsa -in /etc/exim4/dkim_keys/rsa.private.$SELECTOR -pubout -outform PEM

  7. I configured my exim4 to use a “smart host” (copying the DKIM ifdefs from conf.d/transport/0_exim4-config_remote_smtp to 0_exim4-config_remote_smtp_smarthost). I see hidden failures in the formally “passed” DMARC section of my test message report at mail-tester.com. I think this is because I rewrite my From address in exim4 with a mapping in /etc/email-addresses,

    USER: YAHOOUSER@yahoo.ca

    So even though I do see the d=MY_DOMAIN piece in the DKIM-Signature header generated by my exim4 as automatically saved in my Yahoo Sent folder, that piece appears replaced with Yahoo based on my YAHOOUSER@yahoo.ca address. Yahoo does not replace the signature, though. So DKIM forces me to either

    stop my signing and allow Yahoo to sign on its own (which appears to fail sometimes), or
    have an externally accessible mailbox in my exim4 (which would require opening my SMTP port(s)).

    ==========================================================================

    Your DMARC record is set correctly and your message passed the DMARC test

    DMARC DNS entry found for the domain _dmarc.yahoo.ca:

    “v=DMARC1; p=reject; pct=100; rua=mailto:dmarc_y_rua@yahoo.com;”

    Verification details:

    mail-tester.com; dkim=pass (2048-bit key; unprotected) header.d=yahoo.ca header.i=@yahoo.ca header.b=FJXwJU3U; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=MY_DOMAIN header.i=@MY_DOMAIN header.b=X6s0Eqai; dkim-atps=neutral
    mail-tester.com; dmarc=pass header.from=yahoo.ca
    mail-tester.com; dkim=pass (2048-bit key; unprotected) header.d=yahoo.ca header.i=@yahoo.ca header.b=FJXwJU3U; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=MY_DOMAIN header.i=@MY_DOMAIN header.b=X6s0Eqai; dkim-atps=neutral
    From Domain: yahoo.ca
    DKIM Domain: yahoo.ca

    ==========================================================================

    1. You can’t create a valid signature for Yahoo (or any other domain you don’t control) as you won’t be able to publish your key to their DNS. To use DKIM correctly you need to be sending from a domain that you have control of, or at least the support of the DNS administrator. Your public key needs to be published to the correct place in the DNS hierarchy

      The signature that is passing is the yahoo.com signature, which is correct in your situation. Your signatures will be ignored, but can be used to validate the message was not modified after leaving your server. This would only be useful for repudiating faked email and is unlikely to be check automatically by any receiving server.

      I would suggest your best option is to continue using Yahoo as your smart host when sending from a yahoo.com address and authenticate. Yahoo is signing the messsage correctly. Due to the activities of spambots, you need a fixed IP address and you own domain to reliably send email directly. If you don’t have this, use a relay. You can configure exim only to use the smarthost when the from address is yahoo.com. This would require a modified hubbed_host transport.

      Due to the growing use of DMARC, you should proxy any outgoing mail through the originating domains relay server. Use the submission port if possible. This allows your email messages to be compliant with the domain’s configuration.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Cookie Consent with Real Cookie Banner