ubuntu:bind:domain_keys_identified_mail_dkim
Differences
This shows you the differences between two versions of the page.
ubuntu:bind:domain_keys_identified_mail_dkim [2019/11/26 21:01] – created peter | ubuntu:bind:domain_keys_identified_mail_dkim [2020/05/12 14:08] (current) – removed peter | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Ubuntu - Bind - Domain Keys Identified Mail (DKIM) ====== | ||
- | |||
- | Domain Keys Identified Mail (DKIM) helps you protect your company from email spamming and phishing attempts. | ||
- | |||
- | * A public key for each DKIM-protected domain is stored in DNS. | ||
- | * The private key for each DKIM-protected domain is stored on your mailserver. | ||
- | |||
- | When a mail is sent a number of fields are hashed together, and that hash is then signed with the private key. | ||
- | |||
- | To validate an incoming message the receiver can see the from-address, | ||
- | |||
- | The intention is that only somebody who has control of the DNS for a domain can send mail from it - because all others will be missing the private key, and unable to sign the message. | ||
- | |||
- | A DKIM signature is recorded as an RFC2822 header field for the signed message. | ||
- | |||
- | For example: | ||
- | |||
- | <code bash> | ||
- | DKIM-Signature a=rsa-sha1; q=dns; | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | | ||
- | </ | ||
- | |||
- | and configured as | ||
- | |||
- | <code bash> | ||
- | selector._domainkey.example.com IN TXT " | ||
- | </ | ||
- | |||
- | Where the following parts are defined: | ||
- | |||
- | * `v=DKIM1`: This optional fragment identifies the record as a DKIM version 1 public key. | ||
- | * `p=MIG…QIDAQAB`: | ||
- | |||
- | |||
- | The process of setting up DKIM involves several tasks: | ||
- | |||
- | * Create a selector. This is a simple, user-defined text string that you will associate with a public key in a later step. | ||
- | * Generate a public/ | ||
- | * Publish the selector and public key by creating a DNS TXT record. | ||
- | * Attach the token to each outgoing email. | ||
- | |||
- | ---- | ||
- | |||
- | ===== Create a selector ===== | ||
- | |||
- | Before storing the public key in DNS you need to pick a " | ||
- | |||
- | To make it obvious when a key was generated I recommend you use a date, e.g. 20161117. | ||
- | |||
- | Once you have chosen your selector you the need to publish the key in DNS. If you're protecting the domain example.com you need to publish a TXT record with the name: | ||
- | |||
- | <code bash> | ||
- | 20161117._domainkey.example.com | ||
- | </ | ||
- | |||
- | |||
- | ===== Generate public and private DKIM keys ===== | ||
- | |||
- | <code bash> | ||
- | cd /etc/exim4 | ||
- | openssl genrsa -out example.com.private.pem 1024 | ||
- | openssl rsa -in example.com.private.pem -out example.com.public.pem -pubout -outform PEM | ||
- | </ | ||
- | |||
- | or | ||
- | |||
- | <code bash> | ||
- | openssl genrsa -out example.com.private.pem 1024 -outform PEM | ||
- | openssl rsa -in example.com.private.pem -out example.com.public.pem -pubout -outform PEM | ||
- | </ | ||
- | |||
- | <WRAP important> | ||
- | **IMPORTANT**: | ||
- | |||
- | [[https:// | ||
- | |||
- | * Verifiers MUST be able to validate signatures with keys ranging from **512 bits to 2048 bits**, and | ||
- | * They **MAY** be able to validate signatures with larger keys. | ||
- | * Verifier policies may use the length of the signing key as one metric for determining whether a signature is acceptable. | ||
- | |||
- | The practical constraint is that large (e.g., 4096 bit) keys may not fit within a 512-byte DNS UDP response packet. | ||
- | |||
- | Records greater than the UDP 512-byte limit will still be handled as DNS will use TCP. This should be transparent to the user, but sometimes buggy firewall appliances (such as Cisco PIX/ASA) will filter/ | ||
- | |||
- | Since short RSA keys more easily succumb to off-line attacks, signers MUST use RSA keys of at least 1024 bits for long-lived keys. | ||
- | |||
- | Factors that should influence the key size choice include the following: | ||
- | |||
- | * The security constraint that keys smaller than 1024 bits are subject to off-line attacks. | ||
- | * Larger keys impose higher CPU costs to verify and sign email. | ||
- | * Keys can be replaced on a regular basis, thus their lifetime can be relatively short. | ||
- | * The security goals of this specification are modest compared to typical goals of other systems that employ digital signatures | ||
- | </ | ||
- | |||
- | |||
- | This will result in two files: | ||
- | |||
- | ^File^Description^ | ||
- | |example.com.private.pem|The private file you need to keep secure, and which exim will read to sign messages.| | ||
- | |example.com.public.pem|The public key you'll publish in DNS.| | ||
- | |||
- | ---- | ||
- | |||
- | ===== Create DKIM DNS record ===== | ||
- | |||
- | The public key is in the / | ||
- | |||
- | <code bash> | ||
- | cat / | ||
- | </ | ||
- | |||
- | Result | ||
- | |||
- | <code bash> | ||
- | -----BEGIN PUBLIC KEY----- | ||
- | abCdefGhijKLm | ||
- | noPQRsTuvWxyZ | ||
- | -----END PUBLIC KEY----- | ||
- | </ | ||
- | |||
- | The DKIM DNS record is a TXT record containing the public DKIM key without line breaks. | ||
- | |||
- | 20161117._domainkey.example.com TXT IN " | ||
- | |||
- | <WRAP tip> | ||
- | **TIP**: Long DKIM strings can be split into separate text fields to make them easier to maintain, however note that there is overhead for each split. | ||
- | |||
- | Due to the 255 character limit on the DNS UDP packets, the text fields should be split into parts of 255 characters or less. | ||
- | |||
- | There are two formats for long fields. | ||
- | |||
- | * TXT "part one" \ "part two" | ||
- | * TXT ( "part one" "part two" ) | ||
- | |||
- | Both of which will combine as "part onepart two" | ||
- | |||
- | For example, if the public key file contains:. | ||
- | |||
- | <code bash> | ||
- | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD78Ki2d0zmOlmjYNDC7eLG3af12KrjmPDeYRr3 | ||
- | q9MGquKRkRFlY+Alq4vMxnp5pZ7lDaAXXwLYjN91YY7ARbCEpqapA9Asl854BCHMA7L+nvk9kgC0 | ||
- | ovLlGvg+hhqIPqwLNI97VSRedE60eS+CwcShamHTMOXalq2pOUw7anuenQIDAQAB | ||
- | </ | ||
- | |||
- | the DNS zone file entry could appear as follows: | ||
- | |||
- | <code bash> | ||
- | 20161117._domainkey | ||
- | " | ||
- | " | ||
- | " | ||
- | </ | ||
- | |||
- | Notice how each line is wrapped in quotation marks. | ||
- | </ | ||
- | |||
- | ---- | ||
- | |||
- | ===== DKIM Tags ===== | ||
- | |||
- | Example DKIM header: | ||
- | |||
- | <code bash> | ||
- | DKIM-Signature: | ||
- | v=1; | ||
- | a=rsa-sha256; | ||
- | c=relaxed/ | ||
- | d=sharewiz.net; | ||
- | s=sel42; | ||
- | t=1399817581; | ||
- | bh=Pl25…dcMqN+E=; | ||
- | h=Message-ID: | ||
- | </ | ||
- | |||
- | * **v=1**: This indicates the DKIM version in use. For the time being this must be " | ||
- | * **a=rsa-sha256**: | ||
- | * **c=relaxed/ | ||
- | * **d=sharewiz.net**: | ||
- | * **s=sel42**: | ||
- | * **t=1399817581**: | ||
- | * **bh=Pl25…dcMqN+E=**: | ||
- | * **h=Message-ID: | ||
- | * **b=Xp/ | ||
- | |||
- | [[http:// | ||
- | |||
- | ---- | ||
- | |||
- | ===== Test DNS ===== | ||
- | |||
- | <code bash> | ||
- | dig 20161117._domainkey.example.com txt | ||
- | </ | ||
- | |||
- | ---- | ||
- | |||
- | ===== Exim4 Config ===== | ||
- | |||
- | With that entry stored in DNS we can now configure exim to sign the mail. | ||
- | |||
- | After line CONFDIR = /etc/exim4, add: | ||
- | |||
- | <WRAP info> | ||
- | For split config, create the file / | ||
- | </ | ||
- | |||
- | <file bash / | ||
- | DKIM_CANON = relaxed | ||
- | DKIM_SELECTOR = 20161117 | ||
- | DKIM_DOMAIN = example.com | ||
- | DKIM_PRIVATE_KEY = / | ||
- | </ | ||
- | |||
- | or | ||
- | |||
- | <file bash / | ||
- | DKIM_CANON = relaxed | ||
- | DKIM_SELECTOR = 20161117 | ||
- | DKIM_DOMAIN = ${sender_address_domain} | ||
- | DKIM_PRIVATE_KEY = CONFDIR/ | ||
- | </ | ||
- | |||
- | or | ||
- | |||
- | <file bash / | ||
- | # DKIM | ||
- | DKIM_SELECTOR = 20161117 | ||
- | # Get the domain from the outgoing mail. | ||
- | DKIM_DOMAIN = ${lc: | ||
- | # If key exists then use it, if not don't. | ||
- | DKIM_PRIVATE_KEY=${if exists{/ | ||
- | </ | ||
- | |||
- | <WRAP important> | ||
- | Ensure the private key in placed into the named path, and has sufficient permissions such that exim4 can read it. | ||
- | </ | ||
- | |||
- | ---- | ||
- | |||
- | Once you've updated your configuration template you need to rebuild the real configuration, | ||
- | |||
- | <code bash> | ||
- | update-exim4.conf | ||
- | service exim4 restart | ||
- | </ | ||
- | |||
- | ---- | ||
- | |||
- | ===== Handling Multiple Domains ===== | ||
- | |||
- | The previous example used a single domain, example.com, | ||
- | |||
- | <code bash> | ||
- | DKIM_CANON = relaxed | ||
- | DKIM_SELECTOR = 20150726 | ||
- | |||
- | # Get the domain from the outgoing mail. | ||
- | DKIM_DOMAIN = ${sg{${lc: | ||
- | |||
- | # The file is based on the outgoing domain-name in the from-header. | ||
- | DKIM_FILE = / | ||
- | |||
- | # If key exists then use it, if not don't. | ||
- | DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}} | ||
- | </ | ||
- | |||
- | <WRAP todo> | ||
- | **NOTE**: The **DKIM_FILE** statement may need to be set as | ||
- | |||
- | <code bash> | ||
- | DKIM_FILE= / | ||
- | </ | ||
- | |||
- | The {DKIM_DOMAIN} doesn' | ||
- | |||
- | <WRAP todo> | ||
- | Check if DKIM_FILE should actually be DKIM_PRIVATE_KEY? | ||
- | </ | ||
- | |||
- | or | ||
- | |||
- | <code bash> | ||
- | # DKIM | ||
- | DKIM_SELECTOR = 20161117 | ||
- | |||
- | # Get the domain from the outgoing mail. | ||
- | DKIM_DOMAIN = ${lc: | ||
- | |||
- | # If key exists then use it, if not don't. | ||
- | DKIM_PRIVATE_KEY=${if exists{/ | ||
- | </ | ||
- | </ | ||
- | |||
- | |||
- | The net result of this is that if your mailserver sends mail from " | ||
- | |||
- | <WRAP box 80% center grey> | ||
- | **The Exim4 magic** | ||
- | The lookup uses the native facilities exim4 has for working with strings, etc, and to understand how it works you need to bear in mind that things work from the inside-out. The final result contains expressions that can can be written individually, | ||
- | |||
- | The initial selection is made via reading the " | ||
- | |||
- | <code bash> | ||
- | ${domain: | ||
- | </ | ||
- | |||
- | The next part uses ensures that is lower-cased: | ||
- | |||
- | <code bash> | ||
- | ${lc:XXXX} - The string " | ||
- | </ | ||
- | |||
- | So we can see that this will be the outgoing domain, in lower-case: | ||
- | |||
- | < | ||
- | ${lc: | ||
- | </ | ||
- | |||
- | Once that is present we can then wrap it up using the sg operation which allows search & replacement. In our case we remove any leading www. prefix from the value. This probably isn't required, but a lot of systems have hostnames setup with a www.prefix, and so this will let them sign with the domain " | ||
- | |||
- | In conclusion this is probably a useful way of looking up the correct domain on a per-email basis: | ||
- | |||
- | <code bash> | ||
- | DKIM_DOMAIN = ${sg{${lc: | ||
- | </ | ||
- | |||
- | The next part merely uses the domain to find the file on-disk, if it exists. If it does: great. If not then the mail won't be signed: | ||
- | |||
- | <code bash> | ||
- | DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}} | ||
- | </ | ||
- | |||
- | With this setup present you can generate keys for multiple domains, store the public-part in DNS, the private parts in a common-directory, | ||
- | |||
- | The only commonality here is that all the domains will need the same selector, but that could be worked around if you wished. | ||
- | </ | ||
- | |||
- | ---- | ||
- | |||
- | ===== Use DMARC ===== | ||
- | |||
- | The preceeding work will allow your outgoing emails to be signed via DKIM, and will allow the mailservers which receive the mails to validate that they are not spoofed, or were altered in-transit. | ||
- | |||
- | However they do not prevent a remote attacker from spoofing mail and merely avoiding the use of DKIM. For example: | ||
- | |||
- | * You might be sending out DKIM-signed messages. | ||
- | * An attacker would just send out messages without a DKIM-signature. | ||
- | |||
- | To prevent this we need to look at a second system called DMARC. | ||
- | |||
- | By mandating the use of DKIM you essentially kill spoofed mails for your domain(s), especially in combination with SPF. | ||
- | |||
- | |||
- | ===== Check that Exim supports DKIM ===== | ||
- | |||
- | <code bash> | ||
- | exim4 -bV | ||
- | </ | ||
- | |||
- | result: | ||
- | |||
- | <code bash> | ||
- | Exim version 4.80 #3 built 24-Jul-2014 03:28:10 | ||
- | Copyright (c) University of Cambridge, 1995 - 2012 | ||
- | (c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2012 | ||
- | Berkeley DB: Berkeley DB 5.1.29: (October 25, 2011) | ||
- | Support for: crypteq iconv() IPv6 GnuTLS move_frozen_messages DKIM | ||
- | Lookups (built-in): lsearch wildlsearch nwildlsearch iplsearch cdb dbm dbmjz dbmnz dnsdb dsearch nis nis0 passwd | ||
- | Authenticators: | ||
- | Routers: accept dnslookup ipliteral manualroute queryprogram redirect | ||
- | Transports: appendfile/ | ||
- | Fixed never_users: | ||
- | Size of off_t: 8 | ||
- | Configuration file is / | ||
- | </ | ||
- | |||
- | See the line starting with **" | ||
- | |||
- | ---- | ||
- | |||
- | ===== Key Rotation Schedule ===== | ||
- | |||
- | The first step towards publishing DKIM Public Keys is deciding on your key rotation schedule. | ||
- | |||
- | Common practice involves rotating the keys at varying intervals. | ||
- | |||
- | DKIM Key Rotation is actually very simple thanks to the introduction of the key selectors (the `s=` fragment in the DKIM Signature header). | ||
- | |||
- | |||
- | ---- | ||
- | |||
- | ===== Reference ===== | ||
- | |||
- | <WRAP todo> | ||
- | |||
- | https:// | ||
- | |||
- | </ | ||
ubuntu/bind/domain_keys_identified_mail_dkim.1574802074.txt.gz · Last modified: 2020/07/15 09:30 (external edit)