{"id":2191,"date":"2022-10-10T07:54:01","date_gmt":"2022-10-10T06:54:01","guid":{"rendered":"https:\/\/cdblog.cdstealer.com\/?p=2191"},"modified":"2023-03-09T15:07:57","modified_gmt":"2023-03-09T15:07:57","slug":"spf-dkim-dmarc-sts","status":"publish","type":"post","link":"https:\/\/cdblog.cdstealer.com\/?p=2191","title":{"rendered":"SPF, DKIM, DMARC, MTA-STS"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Securing and reducing spam is an ongoing battle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Prerequisits:<br>* DNSSEC [usually managed by your domain provider and if you run bind]<br>* PTR [usually setup by your ISP unless you run an authoritative DNS.  Implies a static IP]<br>* Exim &gt;4.7<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Useful Links:<br><a rel=\"noreferrer noopener\" href=\"http:\/\/www.dnssec-or-not.com\/\" target=\"_blank\">http:\/\/www.dnssec-or-not.com<\/a><br><a rel=\"noreferrer noopener\" href=\"https:\/\/dnssec-analyzer.verisignlabs.com\" target=\"_blank\">https:\/\/dnssec-analyzer.verisignlabs.com<\/a><br><a rel=\"noreferrer noopener\" href=\"https:\/\/dnschecker.org\/domain-health-checker.php\" target=\"_blank\">https:\/\/dnschecker.org\/domain-health-checker.php<\/a><br><a href=\"https:\/\/en.internet.nl\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/en.internet.nl\/<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">SPF (Sender Policy Framework)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You can put the following into it's own config eg acl_check_spf or place it in the global acl (acl_check_data:).<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">    deny condition = ${if eq{$sender_helo_name}{} {1}}\n         message = Nice bots say HELO first\n\n    # reject messages from senders listed in these DNSBLs\n    deny dnslists = zen.spamhaus.org\n\n    # SPF validation\n    deny spf = fail : softfail\n            message = SPF validation failed: \\\n                    $sender_host_address is not allowed to send mail from \\\n                    ${if def:sender_address_domain \\\n                        {$sender_address_domain}{$sender_helo_name}}\n            log_message = SPF validation failed\\\n                    ${if eq{$spf_result}{softfail} { (softfail)}{}}: \\\n                    $sender_host_address is not allowed to send mail from \\\n                    ${if def:sender_address_domain \\\n                        {$sender_address_domain}{$sender_helo_name}}\n    deny spf = permerror\n            message = SPF validation failed: \\\n                    syntax error in SPF record(s) for \\\n                    ${if def:sender_address_domain \\\n                        {$sender_address_domain}{$sender_helo_name}}\n            log_message = SPF validation failed (permerror): \\\n                    syntax error in SPF record(s) for \\\n                    ${if def:sender_address_domain \\\n                        {$sender_address_domain}{$sender_helo_name}}\n    defer spf = temperror\n            message = temporary error during SPF validation; \\\n                    please try again later\n            log_message = SPF validation failed temporary; deferred\n    # Log SPF none\/neutral result\n    warn spf = none : neutral\n            log_message = SPF validation none\/neutral\n\n    # Use the lack of reverse DNS to trigger greylisting. Some people\n    # even reject for it but that would be a little excessive.\n\n    warn condition = ${if eq{$sender_host_name}{} {1}}\n         set acl_m_greylistreasons = Host $sender_host_address \\\n             lacks reverse DNS\\n$acl_m_greylistreasons\n\n    accept\n            # Add an SPF-Received header to the message\n            add_header = :at_start: $spf_received\n            logwrite = SPF validation passed<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You will also need a TXT record publishing with the registrar and\/or internal DNS.<br><br><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table style=\"width:70%\"><thead><tr><th><strong>Host name<\/strong><\/th><th><strong>Type<\/strong><\/th><th><strong>TTL<\/strong><\/th><th><strong>Data<\/strong><\/th><\/tr><\/thead><tbody><tr><td style=\"width:10%\">example.com<\/td><td style=\"width:10%\">TXT<\/td><td style=\"width:10%\">1 hour<\/td><td style=\"width:30%\">\"v=spf1 ip4:xxx.xxx.xxx.xxx ip6::1 -all\"<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the record itself, we see that the version indicator, 'v=spf1', is followed by a typical SPF policy: first a list of systems that are authorised to send mail for the domain, then '-all', which means that all other systems are not authorised. The alternative to ending the record with '-all' is to end with '~all'. That is known as a 'soft fail', meaning that messages from non-validating systems should not be blocked, but forwarded with a tag.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">DKIM (Domain Keys Identified Mail)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before the ACL Configuration, place the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">#  # DKIM macros\n#  # get the sender domain from the outgoing mail\n  SENDER_DOMAIN = ${if def:h_from:{${lc:${domain:${address:$h_from:}}}}{$qualify_domain}}\n#  # the key file name will be based on the domain name in the From header\n  DKIM_KEY_PATH = \/etc\/exim\/keys\n  DKIM_KEY_FILE = dkim_rsa.private<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Put the following under the ACL Configuration.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># This access control list is used to process DKIM status.\nacl_check_dkim:\n\n  # Skip DKIM checks for all authenticated connections (probably MUAs)\n  accept\n          authenticated = *\n\n  # Record the current timestamp, in order to delay crappy senders\n  warn\n          set acl_m0  = $tod_epoch\n\n  # Warn no DKIM\n  warn\n          dkim_status = none\n          set acl_c4  = X-DKIM-Warning: No signature found\n\n  # RFC 8301 requires 'permanently failed evaluation' for DKIM signatures signed with 'historic algorithms (currently, rsa-sha1)'\n  # @SEE: https:\/\/www.exim.org\/exim-html-current\/doc\/html\/spec_html\/ch-dkim_and_spf.html\n  warn\n          condition              = ${if !def:acl_c4 {true}{false} }\n          condition              = ${if eq {$dkim_verify_status}{pass} }\n          condition              = ${if eq {${length_3:$dkim_algo} }{rsa} }\n          condition              = ${if or { {eq {$dkim_algo}{rsa-sha1} } \\\n                                    {&lt; {$dkim_key_length}{1024} } } }\n          set acl_c4             = X-DKIM-Warning: forced DKIM failure (weak hash or short key)\n          set dkim_verify_status = fail\n          set dkim_verify_reason = hash too weak or key too short\n\n  # RFC6376 requires that verification fail if the From: header is not included in the signature\n  # @SEE: https:\/\/www.exim.org\/exim-html-current\/doc\/html\/spec_html\/ch-dkim_and_spf.html\n  warn\n          condition   = ${if !def:acl_c4 {true}{false} }\n          condition   = ${if !inlisti{from}{$dkim_headernames}{true}{false} }\n          set acl_c4  = X-DKIM-Warning: From: header not included in the \\\n                        signature, this defies the purpose of DKIM\n\n  # Warn invalid or failed signatures\n  warn\n          condition   = ${if !def:acl_c4 {true}{false} }\n          dkim_status = fail:invalid\n          set acl_c4  = X-DKIM-Warning: verifying signature of $dkim_cur_signer \\\n                        failed for $sender_address because $dkim_verify_reason\n\n  # Add a DKIM-Received: line to the message header (regardless of DKIM status)\n  warn\n          add_header  = Received-DKIM: $dkim_verify_status ${if \\\n                        def:dkim_cur_signer {($dkim_cur_signer with \\\n                        $dkim_algo for $dkim_headernames)} }\n\n  # Set up for finalisation: add header and write to log\n  warn\n          condition   = ${if def:acl_c4 {true}{false} }\n          add_header  = $acl_c4\n          logwrite    = $acl_c4\n\naccept<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Again a TXT record needs to be defined.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table style=\"width:70%\"><thead><tr><th><strong>Host name<\/strong><\/th><th><strong>Type<\/strong><\/th><th><strong>TTL<\/strong><\/th><th><strong>Data<\/strong><\/th><\/tr><\/thead><tbody><tr><td style=\"width:10%\">&lt;selector&gt;._domainkey.example.com<\/td><td style=\"width:10%\">TXT<\/td><td style=\"width:10%\">1 hour<\/td><td style=\"width:30%\">\"v=DKIM1; k=rsa; p=\"encrypted rsa key\"<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">To enable DKIM-validating mail servers to validate our digital signatures, the public key from the <a href=\"https:\/\/www.sidn.nl\/#4.5%20generating%20a%20dkim%20key%20pair\">DKIM key pair generated earlier<\/a> has to be published in the zone file of the signing domain. The first step is to generate the public key from the DKIM key file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  [root@system keys]# openssl rsa -in dkim_rsa.private -out \/dev\/stdout -pubout -outform PEM\n  writing RSA key\n  -----BEGIN PUBLIC KEY-----\n  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxMUk9Ac+aZVcqPkgSPny\n  UOkWGrIvXcMJvUHjObpWlMNix3D74hE4KZ+Z18ZvOCUlUQGftzv0MJND\/S4kXMlJ\n  xuoxNMCKGozD\/O71Rblz7RDUHxrhud2rjtSmXdmDHpH713djNiIxxZgeEeNBzfX3\n  UGdCJlRMVQJXUcEozqgI5BmUTsdYtrb2Trr99IZtgaLEI92yXVdholtIyt83gnhA\n  YLnvAzOQRV4zE\/eBB\/pfpbFrkPh1uQQxVIBi0pARj3xk9B8yXiCXUX+gyyBrw3zi\n  \/rnXFDe0ORjtDo\/3WsSrwaivJ6KjywauYgnwYAx1eNyBGnPquVR6d8OlI15YIXy+\n  1wIDAQAB\n  -----END PUBLIC KEY-----<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The public key can be inserted directly into a DKIM record as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  dkim202205615._domainkey.example.com.    3600 TXT (\n    \"v=DKIM1; p=\"\n    \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxMUk9Ac+aZVcqPkgSPny\"\n    \"UOkWGrIvXcMJvUHjObpWlMNix3D74hE4KZ+Z18ZvOCUlUQGftzv0MJND\/S4kXMlJ\"\n    \"xuoxNMCKGozD\/O71Rblz7RDUHxrhud2rjtSmXdmDHpH713djNiIxxZgeEeNBzfX3\"\n    \"UGdCJlRMVQJXUcEozqgI5BmUTsdYtrb2Trr99IZtgaLEI92yXVdholtIyt83gnhA\"\n    \"YLnvAzOQRV4zE\/eBB\/pfpbFrkPh1uQQxVIBi0pARj3xk9B8yXiCXUX+gyyBrw3zi\"\n    \"\/rnXFDe0ORjtDo\/3WsSrwaivJ6KjywauYgnwYAx1eNyBGnPquVR6d8OlI15YIXy+\"\n    \"1wIDAQAB\")<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Note the 'dkim20220615': that is the 'selector', which specifies the key pair used for signing. As you'll see shortly, the selector is also included in the 'DKIM Signature' header, so that when the receiving mail server follows the validation procedure, it knows exactly which public key to request from the DNS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">DMARC (Domain-based Message Authentication, Reporting and Conformance)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Put the following before the ACL Configuration.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># DMARC\n  dmarc_tld_file=\/usr\/share\/publicsuffix\/public_suffix_list.dat\n  dmarc_history_file=\/var\/spool\/exim\/opendmarc\/history.dat\n  dmarc_forensic_sender=postmaster@example.com<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Put the following under the ACL Configuration.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">acl_check_data:\n# DMARC\n  warn    dmarc_status = quarantine\n          !authenticated = *\n          log_message = Message from $dmarc_used_domain failed sender's DMARC policy; quarantine\n          #control = dmarc_enable_forensic\n          set acl_m_quarantine = 1\n          # this variable to use in a router\/transport\n  deny    dmarc_status = reject\n          !authenticated = *\n          message = Message from $dmarc_used_domain failed sender's DMARC policy; reject\n          #control = dmarc_enable_forensic\n  warn    add_header = :at_start: ${authresults {$primary_hostname}}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You'll also need to generate the key pair.<br>The DKIM key pair is generated as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  mkdir \/etc\/exim\/keys\/\n  cd \/etc\/exim\/keys\/\n  openssl genrsa -out dkim_rsa.private 2048<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The new file 'dkim_rsa.private' contains the private key, which has to be kept secret. It's therefore important to ensure that the key file access rights provide appropriate security:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  chmod 640 dkim_rsa.private\n  chown root:exim dkim_rsa.private<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Although generating a longer key (4096 bits, rather than 2048 bits) is an option, DKIM signatures remain valid for relatively short periods. They are, after all, used exclusively for delivering messages, which, even in the worst-case scenario, only takes a few days. Restricting the key length to 2048 bits allows DNS traffic to go via the efficient <a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/User_Datagram_Protocol\" target=\"_blank\">UDP protocol<\/a>, whereas it would be necessary to switch to the more onerous <a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/Transmission_Control_Protocol\" target=\"_blank\">TCP protocol<\/a> if longer keys were used.<br><br>As usual, you will need to submit a DMARC record to DNS:<br><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table style=\"width:70%\"><thead><tr><th><strong>Host name<\/strong><\/th><th><strong>Type<\/strong><\/th><th><strong>TTL<\/strong><\/th><th><strong>Data<\/strong><\/th><\/tr><\/thead><tbody><tr><td style=\"width:10%\">_dmarc.example.com<\/td><td style=\"width:10%\">TXT<\/td><td style=\"width:10%\">6 hours<\/td><td style=\"width:30%\">\"v=DMARC1;p=reject;rua=mailto:example@example.com;ruf=mailto:example@example.com;fo=1;aspf=r;adkim=r;\"<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">I also added the following to the Transports section of exim.conf.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">quarantine_delivery:\n  driver = appendfile\n  directory = \/home\/$local_part_data\/Maildir\/.INBOX.quarantine\n  maildir_format\n  delivery_date_add\n  envelope_to_add\n  return_path_add<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Make sure the directory exists on the mail server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I also have a cron setup to download the dat file (referenced above):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># DMARC\n03 5 * * 1 cd \/usr\/share\/publicsuffix &amp;&amp; wget -c https:\/\/publicsuffix.org\/list\/public_suffix_list.dat<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">MTA-STS (Mail Transfer Agent Strict Transport Security)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is just adding 2 TXT entries into DNS.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table style=\"width:70%\"><thead><tr><th><strong>Host name<\/strong><\/th><th><strong>Type<\/strong><\/th><th><strong>TTL<\/strong><\/th><th><strong>Data<\/strong><\/th><\/tr><\/thead><tbody><tr><td style=\"width:10%\">_mta-sts.exmaple.com<\/td><td style=\"width:10%\">TXT<\/td><td style=\"width:10%\">1 hour<\/td><td style=\"width:30%\">\"v=STSv1; id=0002\"<\/td><\/tr><tr><td style=\"width:10%\">_smtp._tls.example.com<\/td><td style=\"width:10%\">TXT<\/td><td style=\"width:10%\">1 hour<\/td><td style=\"width:30%\">\"v=TLSRPTv1;rua=mailto:example@example.com\"<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">More info can on this can be found <a rel=\"noreferrer noopener\" href=\"https:\/\/www.ncsc.gov.uk\/collection\/email-security-and-anti-spoofing\/using-mta-sts-to-protect-the-privacy-of-your-emails\" target=\"_blank\">here<\/a> (yes it's the UK gov)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Securing and reducing spam is an ongoing battle. Prerequisits:* DNSSEC [usually managed by your domain provider and if you run bind]* PTR [usually setup by your ISP unless you run an authoritative DNS. Implies a static IP]* Exim &gt;4.7 Useful Links:http:\/\/www.dnssec-or-not.comhttps:\/\/dnssec-analyzer.verisignlabs.comhttps:\/\/dnschecker.org\/domain-health-checker.phphttps:\/\/en.internet.nl\/ SPF (Sender Policy Framework) You can put the following into it's own config eg &hellip; <a href=\"https:\/\/cdblog.cdstealer.com\/?p=2191\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">SPF, DKIM, DMARC, MTA-STS<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[320,321,23,322,319],"class_list":["post-2191","post","type-post","status-publish","format-standard","hentry","category-gentoo","tag-dkim","tag-dmarc","tag-exim","tag-mta-sts","tag-spf"],"_links":{"self":[{"href":"https:\/\/cdblog.cdstealer.com\/index.php?rest_route=\/wp\/v2\/posts\/2191","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cdblog.cdstealer.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cdblog.cdstealer.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cdblog.cdstealer.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cdblog.cdstealer.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2191"}],"version-history":[{"count":11,"href":"https:\/\/cdblog.cdstealer.com\/index.php?rest_route=\/wp\/v2\/posts\/2191\/revisions"}],"predecessor-version":[{"id":2292,"href":"https:\/\/cdblog.cdstealer.com\/index.php?rest_route=\/wp\/v2\/posts\/2191\/revisions\/2292"}],"wp:attachment":[{"href":"https:\/\/cdblog.cdstealer.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2191"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cdblog.cdstealer.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2191"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cdblog.cdstealer.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2191"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}