INTRODUCTION ============ This document will walk you through the process of configuring and activating the OpenDKIM filter once it has been compiled and installed. In doing so you will: o Generate a private and public key pair for use when signing your mail (skipped if you will only be verifying) o Post your public key in your DNS space (also skipped if only verifying) o Choose a local socket interface between the filter and your MTA o Configure your filter o Activate your filter o Test your filter COMPILING AND INSTALLING ======================== The INSTALL document in the root of the build directory covers the compilation and software installation of opendkim and its prerequisites. You should complete that process before continuing with the next section. SOCKET SELECTION ================ Your MTA and your opendkim filter will communicate over a socket connection. You can choose to use a TCP (network) socket or a UNIX domain (filesystem) socket. They each have advantages: A UNIX domain socket can be secured using the filesystem (i.e., with user or group permissions), but cannot be reached from other machines that might want to share the service. For a UNIX domain socket, you must pick a location within your filesystem that the opendkim filter will be able to access in which the socket will be created for MTAs to connect, and set file permissions accordingly. For a TCP socket, you must select a port number not in use by other applications, and the name or IP address of the interface on which opendkim should listen for connections. The IP address can be simply "ANY" if all connections should be accepted. You will also need to ensure that any packet filters in effect on the local machine permit traffic to and from that port, and that any other kernel-level policy mechanisms (e.g., selinux) will permit traffic on that port. For selinux, the following command will need to be executed as the superuser, which declares the chosen port to be a milter application port: semanage port -a -t milter_port_t -p tcp Consult your documentation for other packet filter and policy systems. CONFIGURING OPENDKIM ==================== NOTE: If you will only be verfiying incoming mail and will not be signing, start at step (5). (1) Choose a selector name. A selector is simply a symbolic name given to a key you will be using to sign your mail. You are free to choose any name you wish. One current convention if you are using multiple mailservers is to use the hostname (hostname only, not the fully-qualified domain name) of the host that will be providing the service. Another convention is to use the current month and year. (2) Run the script "opendkim-genkey -s SELECTOR". The opendkim-genkey man page has full details of options. This will generate a private key in PEM format and output a TXT record containing the matching public key appropriate for insertion into your DNS zone file. Insert it in your zone file, increment the serial number, and reload your DNS system so the data is published. (3) Publish your public DNS key. The public key DNS record should appear as a TXT resource record at: SELECTOR._domainkey.DOMAIN SELECTOR is the name you chose in step (1) above and will use with the Selector config option or the result of the KeyTable lookup. DOMAIN is the domain name specified in the "-d" command line, the Domain option in the configuration file, or the domain that would be returned from a KeyTable lookup. For a translation of the parameters and value pairs, see RFC6376. Using "t=y" is indicates you are in "test mode", advising verifiers that they should not take any real action based on success or failure of the use of this key after verifing a message. Remove the "t=y" once you have tested the DKIM signing of your messages to your satisfaction. You might want to set a short TTL on this record during testing so changes are propagated to other nameservers more quickly. Reload your nameserver so that the record gets published. If you are running BIND 9 the command is "rndc reload"; for other nameservers, consult your vendor documentation. Check that the DNS server is returning your published DNS record. Be aware that synchronization to secondary DNS servers and negative caching of DNS resolver servers may effect the results observed. For reliable results, query your DNS server directly: dig -t txt SELECTOR._domainkey.DOMAIN NAMESERVER NAMESERVER is a nameserver for your domain. You can get a list of these using: dig -t ns DOMAIN If the key can be retreived correctly then opendkim-testkey can be used to verify that the key matches the private key. opendkim-testkey -d DOMAIN -s SELECTOR -k rsa.private BIND servers have a 256 byte limit on serving TXT records, so a 1024 bit RSA key is recommended if using BIND as your primary DNS server. See section on LARGE KEYS. (4) Store the private key in a safe place. We generally use a path like /var/db/dkim/SELECTOR.key.pem (where "SELECTOR" is the name you chose). The /var/db/dkim directory and the associated .pem file should be owned by the user that will be executing the filter (preferably not the superuser) and be mode 0700 and 0600 respectively. (5) Take a look at the opendkim.conf.simple as an example configuration file for your domain. If you wish to sign mail that comes from sources other than the localhost address (127.0.0.1), include these in CIDR notation in the confiugration file for the InternalHosts configuration option. (6) Start opendkim. You will need at least the "-p" option. The current recommended set of command line options is: -l -p SOCKETSPEC -d DOMAIN -k KEYPATH -s SELECTOR ...where SOCKETSPEC is the socket you told your MTA to use, DOMAIN is the domain or set of domains for which you want to sign mail, KEYPATH is the path to the private key file you generated, and SELECTOR is the selector name you picked. You can tack "-f" on there if you want it to run in the foreground instead of in the background as a daemon. Instead of command line options, you can also use a configuration file. In that case, you only need to tell the filter where the configuration file is by specifying a command line option of: -x CONFPATH ...where CONFPATH is the path to the configuration file you wish to use. One or more configuration example files are provided. (7) Configure your MTA: For Sendmail: (a) Choose a socket at which the MTA and the filter will rendezvous (see the documentation in libmilter for details) (b) Add a line like this example to your sendmail.mc using your desired socket specification: INPUT_MAIL_FILTER(`opendkim', `S=inet:8891@localhost') (c) Rebuild your sendmail.cf in the usual way For Postfix: (a) Choose a socket at which the MTA and the filter will rendezvous. Be careful with UNIX domain sockets as on some distributions and setups the smtpd process is running in a chroot environment. A UNIX socket will need to be visible to the chrooted smtpd process. (b) Add the following lines like this example to your postfix main.cf using your desired socket specification: smtpd_milters = inet:localhost:8891 non_smtpd_milters = inet:localhost:8891 (c) If you have a content filter in master.cf that feeds it back into a different smtpd process, you should alter the second smtpd process in master.cf to contain '-o receive_override_options=no_milters' to prevent messages being signed or verified twice. For tips on avoiding DKIM signature breakage, see: http://www.postfix.org/MILTER_README.html#workarounds (8) Restart/reload your MTA. For Sendmail: kill -1 `head -1 /var/run/sendmail.pid` For Postfix: postfix reload ...or the following if master.cf was changed: /etc/init.d/postfix restart COMPLEX SIGNING CONFIGURATIONS ============================== The KeyTable and SigningTable are used to define signing instructions to the filter where use of Domain, Selector and KeyFile together are insufficient. First, select the type of database you will use for each. They need not be the same. The "DATA SETS" portion of the opendkim(8) man page describes the possibilities and how they are formatted. Then, construct those databases. Let's suppose you want to sign for two domains, example.com and example.net. Within example.com, you want to sign for user "president" differently than everyone else. Let's say further that you want to use a flat text file. You've generated private key files for each of these and stored them in the directory /usr/local/etc/dkim/keys as files "president", "excom" and "exnet", with the obvious intents. You want to use selectors "foo", "bar" and "baz" for those, respectively. The signing domains match the senders (i.e. the signatures for example.com's stuff will be held by example.com, and example.net likewise). First, write the KeyTable. This is a list of the keys you intend to use, and you just assign arbitrary names to them. So as a flat file, the KeyTable for the above might look like this: preskey example.com:foo:/usr/local/etc/dkim/keys/president comkey example.com:bar:/usr/local/etc/dkim/keys/excom netkey example.net:baz:/usr/local/etc/dkim/keys/exnet Per the documentation, multi-field data sets that are made of flat files have the fields separated by colons, but the key and value(s) are separated by whitespace. So now we've named each key file, and specified with which selector and domain each will be used, and then given each of those groupings a name. This is your KeyTable. Let's say you put it in /usr/local/etc/dkim/keytable. Next, write the SigningTable. This maps senders (by default, taken from the From: header field of a message passing through the filter) to which keys will be used to sign their mail. Wildcards are allowed. So to do what was described above, we write it as follows: president@example.com preskey *@example.com comkey *@example.net netkey Since we want to use wildcards, we can't actually use a regular flat file. Wildcards require a regular expression file, or "refile". The above is valid format for one of those. Let's say you put this in /usr/local/etc/dkim/signingtable. Finally, tell the filter that it should use these files by adding this to your configuration file: KeyTable /usr/local/etc/dkim/keytable SigningTable refile:/usr/local/etc/dkim/signingtable You could put "file:" in front of the filename for the KeyTable just to be precise, but "file:" is assumed if the value starts with a "/". TESTING ======= To test, send a piece of e-mail through the MTA doing signing for your domain to one or more of these auto-responders: sa-test@sendmail.net check-auth@verifier.port25.com autorespond+dkim@dk.elandsys.com dktest@exhalus.net dkim-test@altn.com dktest@blackops.org It should be returned to you shortly showing your message in the body of a new message, including all of the header changes that were made in transit. An alternative is to send a message to an email service provider such as Gmail or Yahoo! that is known to verify DKIM signatures, and view the full text of the message you receive there. The message you generated should appear (either at the ESP to which you sent or in the auto-reply you receive) with a DKIM-Signature: header field added containing the signature data from your opendkim filter, and an Authentication-Results: header field that the receiving machine added after verifying the signature. The value of this header field should indicate a "pass". If it doesn't, something in between has altered your message in a way that invalidated the signature. Perhaps you have other filters running that appended to or otherwise modified a header or the message body. The reply from the autoresponder address, or an e-mail sent back to you from your ESP account, will also itself be signed, and in the header of the reply you should see its signature and another Authentication-Results: header field added by your filter, which should also read "pass". LARGE KEYS ========== If you wish to use a large key in DNS, there are some limitations of which you should be aware. A TXT record in the DNS consists of a series of strings each of which don't exceed 255 bytes. This is a result of the fact that each string is preceded by a length byte (which, of course, can't exceed 255). Furthermore, some DNS implementations don't allow packets larger than 512 bytes. Some RSA keys will exceed the 255 byte limit once encoded with base64, so some special formatting must be used to make such a record fit. Failing to do so can cause an incomplete record to be published or, worse, the nameserver to refuse to serve the record or even the entire zone. In the case of the BIND nameserver, there are two syntax rules one can use to make a large record fit within these boundaries: 1) TXT substrings Instead of a record like: recname IN TXT "foobarbazblivitalphabravocharliedelta...zulu" ...one can also do: recname IN TXT "foobar" "baz" "blivit" "alpha" ... "zulu" (The "..." is mean to indicate continuation and is not a literal set of three "." characters.) You simply have to break up the large record into smaller strings such that no string exceeds 255 bytes. DKIM implementations will reassemble TXT records broken down this way into the full original single string before processing them. 2) Line continuations It can be difficult for some to edit very long lines of text. It's therefore desirable to have a mechanism to break very long TXT records down so that they fit nicely within an editor window. In BIND, this is done by enclosing the wrapped lines within parentheses. Continuing with the example above, this: recname IN TXT "foobar" "baz" "blivit" "alpha" ... "zulu" ...can also be expressed as: recname IN TXT ( "foobar" "baz" "blivit" "alpha" "bravo" "charlie" "delta" "echo" ... "yankee" "zulu" ) So using these two techniques, a very large public key could be encoded in a DNS zone file as follows: recname IN TXT ( "v=DKIM1; g=*; k=rsa; " "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Z4F" "JEMHjJDuBmt25zvYFVejlARZGt1L8f0s1+rLxIPYkfCogQi+Y8" "oLEg9vvEKnLx9aogZzuNt6j4Sty3LgXxaIwHnMqk0LldbA/mh3" "wLZb16Wc6btXHON0o3uDipxqGK2iRLTvcgAnNDegseOS+i0aJE" "nNSl663ywRBp/QKezhUC7cnbqR/H8dz8pEOjeawNN3nexdHGsk" "+RaafYvCFvU+70CQORcsk+mxb74SwGT2CGHWxVywQA9yrV+sYk" "JpxaufZLo6xp0Z7RZmbf1eGlCAdhkEy+KYQpQkw2Cdl7iKIK4+" "17gr+XZOrfFLJ5IwpVK/a19m3BLxADf0Kh3oZwIDAQAB" ) DEBUG FEATURES ============== (a) The Canonicalized Content Two configuration file entries are provided to enable detailed debugging and forensics when tracking down failed verifications. These are "KeepTemporaryFiles" and "TemporaryDirectory". When a message is to be signed or verified, its headers and body are passed through a selected canonicalization algorithm. The output of this algorithm is stored in a pair of temporary files if "KeepTemporaryFiles" is enabled. If a verifier and a signer both produce these files, they can be compared after a failed verification to determine how the signed data and verified data differ. "TemporaryDirectory" simply selects the directory to be used to create these files, if other than the default (/tmp) is desired. Recent extensions to DKIM formalized a mechanism by which a signer can ask a verifier to send back copies of these files when verification fails. Specifically, if the failing signature contains an "r=y" tag, then the signer is requesting such feedback. If the verifier is willing to provide it, it makes a second DNS query to find the report destination and packages up the canonicalized forms into a message, which it sends to the named address. The second query goes to the domain named in the signature's "d=" tag, with "_report._domainkey." prepended to it. For example, a failed signature from "example.com" that also had an "r=y" tag would result in a query for a TXT record at "_report._domainkey.example.com". If the reply contains an "ra=" tag, it identifies the requested destination of the report within that same domain (e.g., "ra=foobar" means "send reports to foobar@example.com"). (See RFC 6651 for details.) The signer, on receiving this, can unpack the two files and compare them to the two files it generated for the same message. This comparison usually reveals the change made in transit that invalidated the signature. (b) Diagnostics There are two other features available that are less involved, but also less likely to yield a result. Signers can configure their systems using the "Diagnostics" setting which, if enabled, produces signatures that contain the original header set that was signed. These will be stored in a "z=" tag in the signature. A verifier can extract this information from the signature and, on failed signatures, compare it to the header set that was received and look for discrepancies. If a verifier sets the "DiagnosticDirectory" to name a directory, a file will be generated there whenever a signature bearing a "z=" tag fails to verify, and present the two header sets for visual comparison. Additional detail is provided if the package is compiled with libtre support, as it can identify reordered header fields that differ slightly, and present them alongside each other. REUSING DOMAINKEYS RECORDS ========================== Published DomainKeys key records are almost totally compatible with DKIM. The one area in which they differ is the use of the granularity ("g=") tag. Under DomainKeys, the default granularity is the empty string. DomainKeys interprets this as "match anyone". However, DKIM interprets this as "match none". Thus, a key record containing "g=" means one thing to DomainKeys but the opposite to DKIM, making it incompatible. Under DKIM, the default granularity is "*", which means "match anyone". Since both systems have defaults that mean "match anyone", a DomainKeys key record which doesn't have a "g=" string in it at all will work as expected with DKIM. Thus, if you want to use one key record with both systems, remove the "g=" portion of the record and reload your nameserver. UPGRADING FROM 1.x to 2.x ========================= Configuration files from v1.x.x installations of OpenDKIM are compatible with v2.x.x except for the "KeyList" feature, which has been dropped in favour of a more flexible configuration. The "KeyList" in older versions was a file of this form: pattern:domain:selector:keypath ...where: "pattern" was a pattern to be matched against the From: header field of a message being considered for signing, with the asterisk ("*") character being used as the conventional wildcard "domain" was the domain to be used when generating the signature "selector" was the selector to be used when generating the signature "keypath" was the path to the private key to be used to generate the signature In the newer configuration files, this function has been split into two different files. The first, known as the "KeyTable", maps names of keys to domains, selectors and keys, and the second, known as the "SigningTable", maps sender patterns to entries in the first. So if for example an older installation had the following KeyList: *@example.com:example.com:foo:/var/db/dkim/foo.private *@example.net:example.net:bar:/var/db/dkim/bar.private This would now be split into the two new data sets. In recent versions of OpenDKIM, a data set can be in any of a number of actual forms (SQL databases, flat files, LDAP directories, regular expression files, etc.) so these will be expressed in terms of their data set keys (the item looked up) and values (the item or items returned). So in the new setup, a KeyTable matching the above setup will contain: KEYS VALUES ---- ------ K1 example.com foo /var/db/dkim/foo.private K2 example.net bar /var/db/dkim/bar.private This defines the keys that will be used to sign. Then it is necessary to tell the filter which addresses get signed by which keys, so the SigningTable will contain: KEYS VALUES ---- ------ example.com K1 example.net K2 Consult SigningTable in opendkim.conf(5) man page for a description of the expected keys and values in the SigningTable data set. DATA SETS ========= Several OpenDKIM configuration values describe lists or sets of data, sometimes with associated values. OpenDKIM refers to these as "data sets". They can be stored in a variety of ways such as comma-separated lists, flat files, files involving pattern matching (called "refiles"), Oracle/Sleepycat Berkeley databases (hash or btree), SQL databases, Lua scripts or LDAP directories. For some data sets, merely a test for membership in the set is done. Such entries only have "keys" (items in the set), with no associated "values". `file' and `refile' data sets are nearly identical. Each is a flat file containing two columns separated by whitespace. The left column is the key and the right the value (as described above). If more than one value appears in the right column, then those values should be separated by colons, e.g.: key value1:value2:value3 For example, the difference is that the key column of a `file' SigningTable is matched exactly in the order described in opendkim.conf(5), while the key column of a `refile' SigningTable is a glob-style expression using where "*" is treated as zero or more characters. The `refile' matching syntax is the same as that of an OpenDKIM 1.x KeyList. The `csl' data set works exactly he same way as a `file' data set, but instead of specifying a file to read, it specifies a list of data separated by commas, e.g.: csl:key1 value1,key2 value2 The `bdb' data set simply reads those key/value pairs from a Berkeley-style DB file (also known as a Sleepycat DB) rather than a flat file or list. For LDAP data sets, the search filter and the attributes to be returned should be provided in the LDAP URI. Any instances of `$d' in the LDAP filter will be replaced with the domain or email address being queried for (with the search terms as described above). For instance, a simple SigningTable data set that searches for the "mail" attribute and returns a "keyName" attribute might be: ldap://localhost/dc=example,dc=com?keyName?sub?(mail=$d) The general format for using an LDAP directory in a data set is: ldap://host[:port]/basedn[?attrs[?scope[?filter[?exts]]]] The scheme ldaps:// may be used to connect via SSL, and ldapi:// may be used to connect via UNIX socket. LDAP credentials are stored in the opendkim.conf file. For ODBX (SQL database) data sets, a similar mechanism is used, but the URI must also be tagged with the prefix "dsn:". For the example above, returning columns in a PostgreSQL database instead of LDAP attributes, you might use: dsn:pgsql://username:password@localhost/dkim/table=dkimkeys?keycol=keyName?datacol=mail The general format for using a database in a data set is: dsn:://[user[:pwd]@][port+]host/dbase[/key=val[?...]] The special DSN keys "table", "keycol" and "datacol" are required to name the database from which to select records, the column in which keys will be found, and the column(s) from which their corresponding values should be extracted. A Lua script can be specified as below: lua:/usr/local/keyretrive.lua The Lua script will receive a global variable called "query" which is a string containing the query to be performed. The returned value is the return value of the script. MANUALLY CREATING DKIM KEYS =========================== To manually generate a public and private key: (1 Run this command: openssl genrsa -out rsa.private 1024 This generates a private key and writes it to the file "rsa.private". The generated key is in PEM format and is a 1024-bit key, the minimum required by the DKIM specification. (2) Run this command: openssl rsa -in rsa.private -out rsa.public -pubout -outform PEM This reads the private key generated in the previous step and extracts from it the matching public key. This is written to the file "rsa.public". (3) Add a TXT DNS record containing the base64 encoding of your public key, which is everything between the BEGIN and END lines in the rsa.public file generated above, with spaces and newlines removed. It should be in this form: "v=DKIM1; t=y; p=MFwwDQYJ...AwEAAQ==" ...using, of course, your own public key with is the base64 data from the generated rsa.public file. For a translation of the parameter and value pairs shown here, see the DKIM specification (RFC6376) Section 3.6. The specification is available in a file in the source code package called "rfc6376.txt". Basically this key record just announces an RSA public key and also declares that your site is using this key in test mode so nobody should take any real action based on success or failure of the use of this key to verify a message. NOTE: Key sizes above 2700 bits are unlikely to fit into a 512 byte DNS record. MAILING LISTS ============= Most common mailing list packages (e.g., Mailman) modify messages in different ways; for instance, they add footers to the body, change Reply-to header fields to send replies to the list by default, modify the Subject field to add the mailing list name, and so on. All these modifications are likely to break existing DKIM signatures. If you run a mailing list package, you might want to arrange that all messages generated by the list are signed by DKIM. If you have already configured OpenDKIM in your MTA, follow these steps to also sign mailing list messages: (1) If you are running the list software on a different domain/subdomain (e.g., main domain example.org, mailing lists on lists.example.org), you need to generate a key for that domain as well and add it to the KeyTable (see opendkim.conf(5)). (2) In the SigningTable (see opendkim.conf(5)), you need to add a key corresponding to the mailing list bounce address (e.g., mylist-bounces@lists.example.org) and NOT the list post address; alternatively, you can simply sign the whole domain, adding a key without the local part (e.g., lists.example.org). (3) In opendkim.conf(5), configure the option SenderHeaders to "Sender,From". Usually, OpenDKIM tries to match the contents of only the From field in the SigningTable. With this configuration, it will use the Sender field instead, if it exists. In messages generated by most mailing list software, the Sender field contains the mailing list bounce address, which is the one that needs to be checked in the SigningTable to sign the e-mail; instead, the From address is just the user of the mailing list that wrote the e-mail, which might be writing from a domain not found in the member list. If your mailing list software does not add the Sender field (and cannot be configured to), you can try using the List-Post field instead, though it is less preferable as it is less likely to show up in MUAs. With the above configuration, all bounces generated by the mailing list software should be signed by OpenDKIM. If a user in your domain sends an e-mail to a mailing list in your domain too, the e-mail will contain two DKIM signatures; the first added when the mail was first submitted by the user, and the second added to the message generated by the mailing list software. In this case, the first signature will likely be invalid, but this is perfectly normal; compliant receivers are required to treat an invalid signature as a non-existing signature, thus the message will appear as simply signed once to them. Notice that mailing list software could also be configured to strip the first signature on incoming mail, but this behaviour is considered a bad practice as the signature is totally harmless even if broken, and it can instead be useful for forensic analysis, just like a Received field. SUPPORT ======= There are two public mailing lists available for news and questions about OpenDKIM. To keep up to date on the latest developments, please subscribe to one or both of the following: opendkim-announce@lists.opendkim.org (release announcements) opendkim-users@lists.opendkim.org (general discussion) Send an email to opendkim-announce-request@lists.opendkim.org with subscribe as the subject to receive release announcements. You can find more information about our mailing lists at http://www.opendkim.org. To report bugs and feature requests, you can access the SourceForge "tracker" facilities at http://sourceforge.net/projects/opendkim.