With the ever increasing volume of SPAM being transmitted, many email services look toward more elaborate ways to authenticate email. Beyond the obvious – content – two commonly used methods (perhaps with some variations), include sender policy framework (SPF), and DomainKeys Identified Mail (DKIM). Both of these require the use of DNS records to function. With SPF, a list of servers allowed to send mail for a domain is entered into the DNS. The focus of this article will be DKIM, and the older DomainKeys (mostly used by Yahoo!, although they do now check DKIM as well).
DKIM is a process for generating a cryptographic hash from an email (the body as well as select headers). This serves two main functions: firstly, the signature provides credibility, in that the private key is needed to generate the signature. Since the private key is not publicized, signing with it provides a degree of certainty that the server possessing the private key has approved the message (usually meaning that an authenticated user has sent the message). Secondly, since the cryptographic hash is dependent on the content of the email, the signature verifies that the email was not altered between the sending and receiving servers.
There are a number of ways of implementing DKIM and DomainKeys – one common way uses a milter for each algorithm. Another approach is that of DKIMproxy, with a signing module listening on a specified port, and selected emails proxied through the signing module. DKIMproxy implements both DomainKeys and DKIM specifications, with easy customizations of specifics on a per-domain or even per address basis.
Specifics (e.g. paths) of this article pertain to Amazon’s Linux distribution (RHEL/CentOS derived).
DKIMproxy has a number of Perl dependencies. These can be installed either via yum or CPAN.
yum --enablerepo rpmforge install perl-Mail-DKIM perl-Net-Server
(launch CPAN via:
perl -MCPAN -e shell
Install CPAN via
yum install perl-CPAN)
(Dependencies include: Crypt::OpenSSL::RSA, Digest::SHA, Mail::Address, MIME::Base64, Net::DNS)
install Net::Server install Error (might not be required)
The CPAN method is preferable as it typically provides the most up-to-date packages.
After the dependencies are resolved, DKIMproxy should install – first, however, we need to download it (the current version, at the time of writing, is 1.4.1).
cd /usr/local/src wget http://downloads.sourceforge.net/dkimproxy/dkimproxy-1.4.1.tar.gz tar -xzvf dkimproxy-1.4.1.tar.gz cd dkim* ./configure --prefix=/usr/local/dkimproxy make install
The above commands download the software, and installs it to the path specified by the ‘prefix’ parameter.
We now create a user (dkim) for DKIMproxy to run as. In this case, we don’t need a user directory, and don’t want a shell assigned to the user either, for a bit of added security, we also lock the password (ideally, we don’t permit password logins to being with).
useradd -M -s /bin/false dkim passwd -l dkim
To delete the password altogether (if one was set), run:
passwd -d dkim
You can also change the shell after the user is created by running:
usermod -s /sbin/nologin dkim
You can verify the user and group by running:
Setup the Init-script
We next want to setup the init script. DKIMproxy comes with a sample init script, but you might need to modify some paths. The following copies the init script, makes it executable, and adds it to the start-up.
cp /usr/loca/src/ dkimproxy-1.4.1/sample-dkim-init-script.sh /etc/init.d/dkimproxy cd /etc/init.d chmod +x dkimproxy chkconfig --add dkimproxy
The values, from the init-script, which might need modification are:
DKIMPROXYUSER(set to the user you created above)
DKIMPROXYGROUP(set to the group of the user created above)
PIDDIR(should start with the install path, does not exist until script is run)
DKIMPROXY_IN_CFG(path to dkimproxy_in.conf, need not exist if not using in/verifier script)
DKIMPROXY_OUT_CFG(path to dkimproxy_out.conf, need not exist if not using out/signer script)
There are no options in the init-script to specify whether you are using the in, out, or both scripts. It is determined automatically based on which config files exist. The script will only start the filter(s) with existing config files. As such, do not create/rename config files for a filter you don’t want.
Generate the RSA Keys
A common location for the keys is in the dkimproxy folder:
We start by generating a private key. If you will be using a different key for each domain (probably a good idea), you may want to include the domain in the file name. You also need to specify the length of the key (in bits) – common values include 512, 768, and 1024. Since some registrars impose a 256 character limit on TXT DNS records, a private key exceeding 1024 bits may result in the public key exceeding that limit. UDP typically has a 512 byte limit which would limit keys above 4096 bits. Additionally, longer keys require more processing per email.
You can verify an existing RSA key by running:
openssl rsa -in domain.priv.key -check
You can find information (such as key length) from an existing RSA key by running:
openssl rsa -in domain.priv.key -text -noout
If you aren’t using a pre-existing key, generate a 1024 bit private RSA key:
openssl genrsa -out domain.priv.key 1024
We now generate a public key from the private key – specifying the private key as the input, and the desired filename as the output.
openssl rsa -in domain.priv.key -pubout -out domain.pub.key
Set permissions and ownership on the keys:
chown dkim:dkim *.key chmod 400 *.key
In order to easily configure multiple domains, we will be availing of the sender_map parameter. Due to this, we can remove all domain/signature specific entries (domain, signature, keyfile, selector) from the config file, but must add the path to the sender_map file.
Start by copying the example file
cp /usr/local/dkimproxy/etc/dkimproxy_out.conf.example /usr/local/dkimproxy/etc/dkimproxy_out.conf
Remove (or comment out)
signature (may be two),
From the default file, this will leave only
Create file ‘/usr/local/dkimproxy/etc/senders’:
The format of the sender_map file is:
- one domain per line;
- signature types (dkim/domainkeys) separated by commas;
- signature details (e.g. c, a, s, key) in brackets, beside signature type, each separated by a comma.
General Signature Parameters
|s||Selector (without domain name)|
|d||domain to sign for (default is domain matched)|
|i||Identity (default is omitted)|
|key||Full path to private key|
DKIM Specific Parameters
|c||Canonicalization (simple/relaxed) (default is simple)|
|a||Hash algorithm (rsa-sha1/rsa-sha256)|
DomainKeys Specific Parameters
|c||Canonicalization (simple/nofws) (default is simple)|
|a||Hash algorithm (rsa-sha1)|
Note: DomainKeys only supports the SHA-1 hash, while DKIM also supports SHA-256. If you set a=rsa-sha256 for DomainKeys, DKIMproxy will fail to start (throwing a ‘signing error: Can’t call method “new” on an undefined value at Signer.pm’).
domain.com dkim(c=relaxed, a=rsa-sha256,s=mail,key=/usr/local/dkimproxy/domain.priv.key), domainkeys(c=nofws,a=rsa-sha1,s=mail,key=/usr/local/dkimproxy/domain.priv.key)
Setup the DNS Records
In order for your DKIM signature to be verified, you need to add a TXT record to your domain’s DNS. In this case a single DNS record is being setup for both DKIM and DomainKeys – as such, certain optional parameters are omitted.
It is typical to add 2 records – one for ‘policy’ and one containing the public key.
The policy record
|Contents||o=~; t=y||o: outbound policy: ~ (some emails signed); – ( all emails signed); ! (all signed, no 3rd party signatures); . (does not send emails)
t: flags: n (not testing); s (no subdomains); y (test mode)
n: notes (human readable)
r: reporting email address (where to report invalid results)
Change the ‘contents’ once you are sure everything is setup right.
The selector record
|Name||selector._domainkey||Replace ‘selector’ with the selector (e.g. ‘mail’) used|
|Contents||p=[base64 key]||k: key type; h: hash algorithm; v: version (must come first); g: granularity; p: public key; s: service type; n: notes; t: flags|
You can publish both the policy and public key in a single record if desired (or you may wish to include some policy records in the selector record for services which do not look for a policy record).
Note: remember to remove all line breaks from the base64 encoded public key.
You can use
host to read your DNS records. (Note, both enclose the ‘contents’ in double quotes, and escape semicolons)
For the policy record:
dig +short -ttxt _domainkey.domain.com
For the selector record:
dig +short -ttxt selector._domainkey.domain.com
Edit Postfix configuration
The basis of the DKIMproxy has mail received by Postfix, on a specific port (typically, the submission port – 587), relayed through DKIMproxy (which will sign the email) and then sent back to Postfix on port 25 (the typical SMTP port) from where it is dispatched. Any mail arriving on port 25 is not signed.
To modify a working copy of Postfix, we need only edit the master.cf file.
Firstly setup the submission listener service – there is probably already one existing, but it is likely commented out:
submission inet n - n - - smtpd -o smtpd_etrn_restrictions=reject -o smtpd_sasl_auth_enable=yes -o content_filter=dksign:[127.0.0.1]:10027 -o receive_override_options=no_address_mappings -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
Of key importance is specifying the filter (
dksign) and its port (10027) as well as limiting access (we only want authenticated users to have their email signed – other emails (e.g. sent by PHP
mail() command) will still work, but will not be signed.
We need to add two more blocks (at the end of the file) to complete the configuration. The first block is named to match that specified above, and transports messages to the filter:
dksign unix - - n - 4 smtp -o smtp_send_xforward_command=yes -o smtp_discard_ehlo_keywords=8bitmime,starttls
The second block sets up an smtp listener to receive the messages that have been signed:
127.0.0.1:10028 inet n - n - 10 smtpd -o content_filter= -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks -o smtpd_helo_restrictions= -o smtpd_client_restrictions= -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o mynetworks=127.0.0.0/8 -o smtpd_authorized_xforward_hosts=127.0.0.0/8
To finish the setup, we start DKIMproxy and we reload Postfix:
service dkimproxy start service postfix reload
Existing applications may continue to send mail without interruption on port 25 – this mail will not be signed. To have mail from an application signed, you must change the port to 587. If you do not accept external mail, you may want to keep port 587 firewalled.
DKIMproxy logs to
You can increase the verbosity of the Postfix logs by adding ‘
-v’ to the command on any line of master.cf (e.g.
To change the port, modify
You do not need to specify
$rcmail_config['smtp_pass'] (but may want to); you may need to specify
$rcmail_config['smtp_server'] = 'localhost';.
Some FastCGI setups of RoundCube might need the install path explicitly specified:
In program/include/iniset.php, replace (around line 45):
Checking your setup
The following are some useful resources to validate various parts of your setup:
- Check signatures and published key: email firstname.lastname@example.org (response, containing report, will be sent to the address in the mail_from header)
- Check your selector record: http://dkimcore.org/tools/
- Check your selector record: http://domainkeys.sourceforge.net/selectorcheck.html
- Check your selector record: http://www.sendmail.org/dkim/checker
- Check your policy record: http://domainkeys.sourceforge.net/policycheck.html
DKIMproxy home page: http://dkimproxy.sourceforge.net/
DKIM specifications: http://www.ietf.org/rfc/rfc4871.txt