Configuring DKIM and Sendmail on Fedora 13
With all the movement with DomainKeys and the updated DomainKeys Identified Mail (better known as DKIM), much has changed in the “standards” and it is difficult gathering the correct steps to do when integrating with sendmail on Fedora.
Following these simple steps, you should be able to get it configured and running with little headache. Note there are a lot of moving pieces and a misstep on one will cause failure. Check and re-check each step before moving on.
- Install the DKIM package using yum
- Generate the public and private keys
- Create the DNS records
- Public DomainKey Selector
- Author Domain Signing Practices
- Modify the configuration files
- Private keys
- Milter configuration
- KeyList
- Internal hosts
- Start the dkim-milter service
- Integrate with sendmail
- Test
- Caveats
Install DKIM using yum
From the command line, issue the following command to install the DKIM sendmail milter on your system. I am assuming sendmail is correctly configured and running at this point.
$ sudo yum install dkim-milter
This package provides the dkim-filter sendmail plugin, and a default configuration. It also creates a dkim-milter user and group. As of this post, the current 64-bit, Fedora 13 version is dkim-milter-2.8.3-5.fc13.x86_64.
Generate the public and private keys
DKIM needs a key pair to sign the message headers to guarantee the specified headers have not been altered. By making the public key available in a DNS TXT record in the sender’s domain, and assuming the account that manages the DNS records has not been compromised, this can verify the message has actually come from the stated domain (or the message’s origin).
The dkim-milter package comes with a utility to generate the key pair for you, dkim-genkey (located in /usr/sbin). This makes it exceedingly simple – just pass the domain name and selector on the command line and it will generate a 1024 bit (by default) private and public key pair.
Naming of the selector is not without significance, especially during the testing phase. The selector designates the DNS TXT record to retrieve; it will be in the form <selector>._domainkey. If changes are made to that entry, you may have to wait about an hour for the DNS system to flush. By generating a new selector name, you can bypass this, without having to wait for the time to live (TTL) period to expire.
The private key needs to be located such that it can be specified correctly in the milter configuration file, specifically the KeyList item; more on this later. To accommodate multiple domains, even if you have no plans on doing so now, I strongly suggest the following naming convention. Under a directory called keys within your dkim-milter folder, create a directory based on your domain name, for this example, it would be example.com. If you choose to keep the dkim-milter directory under your sendmail directory (/etc/mail), the newly created path is as follows:
/etc/mail/dkim-milter/keys/example.com/
Prior to generating the key pair, create the directory and cd into it to save the output from the dkim-genkey command.
There seem to be a number of different camps on how the selector should be named; in this example, I chose a date-based selector name: nov2010. Assuming the domain of example.com (you will need to substitute your own domain name), issue the following command to generate the key pair:
$ dkim-genkey -s nov2010 -d example.com
This will create two files in the current directory, one for the private key and one for the public. The private key will be named in the format <selector>.private, so in this example, it will be called nov2010.private. The public key will be named <selector>.txt, and in this example it will be called nov2010.txt. The public key file has the benefit of being formatted for your zone file (if running bind or other DNS server); otherwise, you can just add it using your DNS manager software on your ISP.
The generated public key
The public key file generated from the dkim-genkey command will contain something similar to the following, with the selector you have chosen replacing nov2010 and a new public key value replacing the one following the p= key.
nov2010._domainkey IN TXT "v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD0g8Ac4gbYhHA3cuSL4kLcgDTE1iyTmugK1mzvhckCP959pavuuSZ5TyA7wLYhv9VTx9QXVwj0WxYqNyRYEGOKXb+gDy16OIdRYy2h4bw5F9Y+rUpvayPSlUmJsrkEm3wuMFoybUGCOQesSeKGPSaxdc+6ENpM6YRvwc+pCh4FjQIDAQAB" ; ----- DKIM nov2010 for example.com
The generated private key
This file needs no modification. It will appear as a block of text between two comments lines, similar to the following:
-----BEGIN RSA PRIVATE KEY-----
uvap959CPkchvzm1gKumyTi1TEgDckL4SLuc3HAhbYg4c8Ag0QDgKBAAIBwCXIIM
vprU+9Y5Fwb4h2yRYdOI61ygD+bKXGOYEyRqNxY0WjwXV9QxVT9vhLYw7yA5TSZu
ABAQIDjQ4FhpC+cwvYR6pMEN6+cdxaPSKGesSeOQGCbUyoMFuw3mkErsmJlUPSya
ao4Z40oOODFIpuWzNnceu7KjUjmnSyI41VFXKoCraBIezgS8C5m2MpH5KNSBAoGA
934kMTe9lvYmoopK3+gDuqhR49/i9/p+JZwSV2vaQ8znYsXoqv/aRW94Vmy2Qmld
e6UlnElbe0A/EAkBAg/EA8UlANGz3LL+iAmm3DVaCRJxULCGRlvXY3FsjkK10Roa
GEyvqfjhZf0zzynr9qxb5RlZnZfznO7qoey+/tym3Ft78lim7tPPBPG61f/r3tbX
wWjDTMpn1QRdi7XHQSJpVQIBrrxNsomNcoMlwfGQnjDGr6Hj7PUBAQJw4FHcNklk
BBgTmV24Vg4YNn8F5J7Q43b7nQDCQ08kjOBrrnlIRUoem84rasnbVi7gsY9+iPTD
EAkBA3o8PR8+Yq9IkMz+jeMgNlecDcMG4HD7vKxnj+tubUWVh+7WBfQMxa+NpNwt
6OMaxm+Z23zHlbDi0T3hbE5CITvymj5YcI780F44ZtOhdovd0mZXNklFwgs8mTOy
LNabV8fk/nTnHP5WscBaBfXV1ADn9Wdvs6YbH3AOJB+QdgVSq+DKpBTB87U6KTyH
=4kz3ri/shYab326OcDPLuwLqtGMSyy0gAP+bTz2h3aLXbUZ=
-----END RSA PRIVATE KEY-----
Note this key is not a pair for the public key so do not use it; it has been mangled and is for demonstration purpose only.
Create the DNS records
Having successfully created a private/public key pair, the next step is to publish the public key, and optionally the Author Domain Signing Practice, to your DNS server. This will result in the addition of one or two TXT records to your DNS server.
Public DomainKey Selector
If you run your own DNS server, just add the public key file generated from the dkim-genkey command to your zone file. In this example, that would be the information from the nov2010.txt file. If you are updating DNS through your ISP, create a new TXT record and assign the Host value <selector>._domainkey, so in this example, it would be nov2010._domainkey. The value for the TXT record will be what is enclosed in quotes within the public key file, again in this example, nov2010.txt. The value will start with v=DKIM1 and end in the public key. This collection of key/value pairs compromises the DomainKey. The keys for v= and g= are optional; and either do not exist in the original DomainKey specification or are different. The values specified in the file are the default and will be assumed if omitted. You can choose to omit them when entering them in the DNS system; it may help make it compatible with DomainKey MTAs, although I have not tested this.
Author Domain Signing Practices
This is an optional DNS TXT record that describes the policy if emails are signed using DKIM. Not creating this record assumes a default of dkim=unknown, which signifies that the domain may sign some, none, most, or all email. During the testing phase, I would suggest not creating this record, or at least using the default of unknown. Once the server is verified to work correctly, updating this record to state all or discardable.
The host for this record is _adsp._domainkey and the value is “dkim=unknown”. Again, all and discardable may replace unknown once the system is completely functional. The zone record is as follows:
_adsp._domainkey IN TXT "dkim=unknown"
Modify the configuration files
Once the public keys are in place and the DNS TXT records configured correctly, the configuration files need updating. The private keys need to be stored safely and the milter configuration needs to be tweaked.
Private keys
Assuming you created the domain directory under /etc/mail/dkim-milter/keys, the private key file will exist but will have the extension .private. The file can be renamed to remove the .private extension such that it matches the name of the selector. This is important as the keyfile item in the milter configuration is fashioned to find the private key using the selector name as the last item in the path. If the file does not exist, it will search for the file with a .pem extension and if that doesn’t exist, will look for it with a .private extension. I prefer no extension so that it matches the selector and the keyfile record matches the full path.
This private key must be kept secure and access only given to the user needing it – dkim-milter (remember it was created as part of the yum package installation). It is important to change the owner (and optionally the group) to this user as the milter will be running under this user id. This is accomplished with the chown command; for the example it would be the following:
$ sudo chown dkim-milter:dkim-milter nov2010 # recall the file has been renamed from nov2010.private to nov2010
The public key will also exist in this directory as a result of the dkim-genkey command issued earlier. It is helpful to keep this file in case something happens to the DNS record.
Milter configuration
Installation of the yum package will create a generic configuration file that needs tweaking. The comments contained within the file are useful in determining what each option means. I have documented below only the options that are changed to get this to work. These are listed in alphabetical order, the same order as listed in the configuration file.
Note in other web pages, you will find references to a sysconfig file (/etc/sysconfig/dkim-milter). It is unnecessary to create and maintain this file for the Sendmail/DKIM integration to work.
The following is a list of options that must be set for the milter to work successfully. Much information about these options is also available in the dkim-filter.conf(5) man page.
Domain
If a KeyList is used (as it is in this example), is not necessary to be specify this option; the signing-domain entries in the KeyList file infer the domains.
If you plan on only a single domain, set the value of this option to the name of the domain and omit the KeyList parameter.
InternalHosts
This option specifies hosts (typcially on the local network) whose emails should be signed rather than verified. If an MTA is sitting behind this copy of sendmail (for example an Exchange server), that has this sendmail configured as its smart host, then those hosts need to be listed in this file. The localhost (127.0.0.1) should always be listed in this file.
Again, I recommend setting it to a file under the dkim-milter directory, such as /etc/mail/dkim-milter/internalhosts and remember to set the appropriate permissions.
KeyList
The KeyList option specifies where to find the list of private keys for all the domains whose emails are to be signed. I recommend using /etc/mail/dkim-milter/keys/keylist and setting the permissions appropriately. Refer to the following section for the contents of this file.
If only a single domain is to be used, this option can be omitted and the Domain and Selector options used in its place.
MTA
Other sites have specified setting this value to MSA; I have left it alone. This limits which emails to sign based on the specified MTA; I choose to not limit signing based on the MTA.
Selector
If a KeyList is used (as it is in this example), is not necessary to be specify this option; the keypath entries in the KeyList file infer the selector per domain.
If you plan on only a single domain, set the value of this option to the name of the selector and omit the KeyList parameter. In this example, the selector should be set to nov2010.
Socket
Many of the examples listed on various web pages specify a TCP port to use for communication. I have chosen to use a Unix domain socket that is created when the milter is started. By default the socket is created at /var/run/dkim-milter/dkim-milter.sock so the the value for this option is local:/var/run/dkim-milter/dkim-milter.sock. Again, note this dkim-filter daemon is not listening to requests on a TCP port in this example.
Syslog
Make sure to set this to yes so that you can verify it is working. You may also choose to set the LogWhy parameter to yes to see more debugging information.
SyslogSuccess
Again, set this to yes to confirm the process has performed successfully. It is very useful in determining what has occurred. The output of the messages from the daemon will b written to the tail end of the /var/log/maillog file.
UserID
Set this to the user that was created during the package installation: dkim-milter. This will ensure the dkim-filter daemon runs as that user and has limited access to other system files.
ADSP*
The following two options are used to make the MTA accountable to the ADSP TXT records. These are optional and can be left alone.
- ADSPDiscard yes
- ADSPNoSuchDomain yes
KeyList
This file specifies the domain or domains that will be signed, the selector for each domain, and the path to the private key used to sign the message. The file is in the format:
sender-pattern:signing-domain:keypath
where,
- sender-pattern is a “globbed” string matching the sender’s mail address to be signed. You may use “*” to match zero or more characters in the address for this entry part.
- signing-domain is the name of the domain that will have emails signed.
- keypath is the full path to the private key file, without an extension. The file must be the name of the selector, as the last part of the keypath must be the selector name. If the file is named with a .pem or .private extension, this should not be specified here.
To sign all emails from the domain example.com with the selector nov2010, the following entry can be specified:
*:example.com:/etc/mail/dkim-milter/keys/example.com/nov2010
The keypath matches what has been described in this post.
Internal hosts
Listed as one host per line, this file describes the hosts that send mail on behalf of this domain and need to have the email signed by this milter. It is important to also list the localhost or the computer will not be able to send any signed mail.
This file is recommended to be created at /etc/mail/dkim-milter/internalhosts with an owner and group of dkim-milter. Refer to the dkim-filter.conf(5) man page under the PeerList option for the format for this file.
Start the dkim-milter service
The package installation created a service but did not configure it to start upon boot. To do this, use the chkconfig command to enable it at run-levels 3, 4, & 5 (or whatever is appropriate for your organization). This is simply accomplished by issuing the following:
$ sudo chkconfig --levels 345 dkim-milter on
To start the service without reboot, issue:
$ sudo service dkim-milter start
If all goes well, it should output the following:
Starting DomainKeys Identified Mail Milter (dkim-filter): [ OK ]
If it says [FAILED], you must go back and double-check your configuration and access to the files and attempt to start the service again.
Any time the configuration is modified, restart the service with the following:
$ sudo service dkim-milter restart
Integrate with sendmail
Adding another milter to the sendmail configuration is simple, all that is necessary is the addition of the following line to the sendmail.mc file, rebuild the sendmail.cf file, and restart sendmail. Remembering we chose to communicate through a Unix socket, add this line near the end of the sendmail.mc file (in /etc/mail):
INPUT_MAIL_FILTER(`dkim-filter', `S=local:/var/run/dkim-milter/dkim-milter.sock')
To rebuild the sendmail configuration file, enter the following command:
$ sudo make
If all goes well and you see no output, restart sendmail to use the new milter. This is done by issuing:
$ sudo make restart
or
$ sudo server sendmail restart
If everything has gone well up to this point, the DKIM milter is successfully installed and configured to work with sendmail. You are now ready to test signed email messages.
Test
There are a number of sites on the web that can test your DKIM configuration. You can perform a DKIM test by sending an email to one of the following addresses:
Their email server will respond to the sender with a response indicating the success of the test.
In testing, I having a running tail of the mail log:
$ sudo tail -f /var/log/maillog
and watch the log as I send the message. If the setup is correct, the sendmail milter will sign the outgoing message and produce a log entry similar to:
dkim-filter[22234]: oAJEplSN026556 "DKIM-Signature" header added
For verification, the log will display messages such as:
dkim-filter[8206]: oAHIoXFJ008437 DKIM verification successful
or
dkim-filter[7882]: oAHIhIXU007949: no signature data
if the sender’s domain does not publish DKIM information.
To view the DNS records, use the dig command (host also works equally well). The following will view the public key information for the nov2010 selector of the example.com domain.
$ dig -t TXT nov2010._domainkey.example.com
And this will view the corresponding ADSP record:
$ dig -t TXT _adsp._domainkey.example.com
Caveats
I have had issues sending to an email account that has the mail forwarded back to the original senders account. For example, if I sent a test message to my gmail account, which in turn automatically forwards it back to me, the message will get lost in limbo, with the dkim-filter complaining that the key retrieval failed. I have yet to figure out the solution, but a work-around is to not sign emails going to those addresses. This is accomplished by setting a comma-separated list of addresses for the value of the DontSignMailTo option in the dkim-milter.conf file.
I have also had issues using the DNS managers for different ISPs, especially pertaining to quotes. Some of the tools strip the quotes and other keep them. Check the TXT value using the dig command to ensure it looks right. If there is a double-quote in the value escaped with a backslash, then there will be a syntax error with the record and you may seen a message similar to the following in your log.
dkim-filter[6813]: oAI5kwwB012942 ADSP query: syntax error in policy data










February 16th, 2012 at 3:17 am
You, sir, are the man. This was the best and most coherent (at 3am no less) description of how to set up DKIM, and even though I am running Debian. Finally after about 12 hours it WORKS! Thank you, thank you sir.
October 4th, 2012 at 12:31 am
I know this post is two years old, but I just want to say thank you very much! I hardly every post responses to blogs but this post deserves it. Figuring everything out through less-informative blogs would have been a nightmare.