May 7th, 2019 I've long had a passive interest in DNSSEC (RFC 2535, RFC 4033, RFC 4034, RFC 4035 etc.; Cloudflare has a good summary) as it addresses a number of problems with the DNS. Unfortunately, I've never made the time to immerse myself in it and to attempt to set up my own nameserver, to manage my own keys, and all that. But the other day I happened to stumble upon an enticing button in Gandi's admin interface labeled "Enable DNSSEC": ![]() ![]() Clickety-click, and done -- and just like that, my domain netmeister.org now has DNSSEC! Gandi handles all the necessary magic of key management and RR signing etc. for me with no overhead whatsoever to me. Neat! So let's see what this looks like in practice: $ dig @9.9.9.9 +dnssec www.netmeister.org AAAA ; <<>> DiG 9.10.6 <<>> @9.9.9.9 +dnssec www.netmeister.org AAAA ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26108 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 512 ;; QUESTION SECTION: ;www.netmeister.org. IN AAAA ;; ANSWER SECTION: www.netmeister.org. 10800 IN CNAME panix.netmeister.org. panix.netmeister.org. 10800 IN AAAA 2001:470:30:84:e276:63ff:fe72:3900 www.netmeister.org. 10800 IN RRSIG CNAME 13 3 10800 20190516000000 20190425000000 31910 netmeister.org. tviGUJuVupLVoHB5pig9AC8FJZ3WmXrq439vNl2yzN2P/A4GFVA/q7br2ZGauaDb44wJsi9vk+hlSgtXRsJOqg== panix.netmeister.org. 10800 IN RRSIG AAAA 13 3 10800 20190516000000 20190425000000 31910 netmeister.org. omB7Ped2qZQC8JnFHI+4K7KN3lisYUpH280bWdNmNYKnvcDQjVClJqZ4M94uzeb1pj8oG2TUuK8Tv1S0yaAcVA== ;; Query time: 1576 msec ;; SERVER: 9.9.9.9#53(9.9.9.9) ;; WHEN: Thu May 02 21:51:03 EDT 2019 ;; MSG SIZE rcvd: 315 Note the bold ad flag in the above output: this shows that the authentic data bit was set in the query -- the results were verified. We also get the RRSIG records, the signatures of the records we asked for, displayed as well. Since we need a DNSSEC enabled resolver, in this example we used Quad9's. If your local resolver is DNSSEC enabled, then you can of course leave that out. Otherwise, running dig +dnssec www.netmeister.org AAAA will not actually verify the results. Ok, let's take a closer look at the signatures: $ dig www.netmeister.org AAAA +dnssec +short panix.netmeister.org. CNAME 13 3 10800 20190516000000 20190425000000 31910 netmeister.org. tviGUJuVupLVoHB5pig9AC8FJZ3WmXrq439vNl2yzN2P/A4GFVA/q7br2ZGauaDb44wJsi9vk+hlSgtXRsJOqg== 2001:470:30:84:e276:63ff:fe72:3900 AAAA 13 3 10800 20190516000000 20190425000000 31910 netmeister.org. omB7Ped2qZQC8JnFHI+4K7KN3lisYUpH280bWdNmNYKnvcDQjVClJqZ4M94uzeb1pj8oG2TUuK8Tv1S0yaAcVA== A few things to note: First, an RRSIG record always covers resource record sets; that is, if there were multiple AAAA records, there would still only be one RRSIG record. Secondly, you may notice that the CNAME resource record also has an RRSIG. That is both expected (every resource record must have a signature) and unexpected (per RFC 1034 / RFC 1912 etc., a CNAME should not have any other records -- a nuisance when it comes to e.g., CAA records). Either way, we follow the CNAME, retrieve the AAAA record, and get RRSIG records for all results along the way. Each RRSIG record consists of:
Our good friend Wireshark illustrates these fields nicely: ![]() The signatures are made using the zone-signing key (ZSK), the public portion of which is then included in the DNSKEY record. That record is itself signed, so a lookup of the DNSKEY record necessarily produces an RRSIG record: $ dig +nocmd netmeister.org dnskey +dnssec +noall +answer +multi netmeister.org. 10662 IN DNSKEY 256 3 13 ( W1zt0mA1d1FHzK+qkEzMb8B7IZkn7qbHxSzjwDzQr4DU CjlycXDRGdxupCDsE7iaaxDCGR2T8nZsteX0PXx7Bg== ) ; ZSK; alg = ECDSAP256SHA256 ; key id = 31910 netmeister.org. 10662 IN RRSIG DNSKEY 13 2 10800 ( 20190516000000 20190425000000 31910 netmeister.org. OIUrHowNF2kFBQ/rglfQMfUzYNge4DG4mhCj8pTRVcvt wRoPpR2BOkKt1fQj2vnFFiyCGCL2xwhmk8e9yv+18w== ) $ But wait, if the RRSIG for the DNSKEY record was made using keyid 3190, isn't that a self-signed signature? How can we trust it? Well, the answer is that the DNSKEY record itself would be signed by a key-signing key (KSK), the digest of which is stashed in the DS (Delegation Signer) record from the parent zone: $ dig +nocmd netmeister.org ds +dnssec +noall +answer +multi netmeister.org. 86266 IN DS 31910 13 2 ( 9AA11C0C17F25E9C09A8A7360C6292CC5DAA0FDEADDA 1D0EDC733949AA05B781 ) netmeister.org. 86266 IN RRSIG DS 7 2 86400 ( 20190522152847 20190501142847 16454 org. HDxDdbp11oHX8wHBiSzeEWb4JAWjkZwIOOATqh7cvSga 9Qln9lou79SxtFMQGGBWlsySWReNFfsjDfLzBGlcfMAc DrrQr+x02hKCG7T8pBaFObmijOymAvqKLe+4qewWLMLF 1IZ/kIGgwHFJBGrDgBQ44GGeOPkx9G7jHAL28VM= ) $ Note that this response comes from the NS server for .org, not the nameserver authoritative for netmeister.org! The respective RRSIG was made by the DNSKEY with the keyid 16454. In other words, this is where we establish trust from our parent zone, which in turn has its DNSKEY signed by its parent and so on, until you finally reach the (self-signed) root KSK. This graph generated via dnsviz.net and table from Verisign Labs' DNSSEC debugger better illustrate the relationships: ![]() ![]() Manually validating all these records is going to be a bit painful, so the friendly people from ISC provide the delv(1) utility (included in your bind package) to perform this task for you:
$ delv netmeister.org aaaa +multi +vtrace ;; fetch: netmeister.org/AAAA ;; validating netmeister.org/AAAA: starting ;; validating netmeister.org/AAAA: attempting positive response validation ;; fetch: netmeister.org/DNSKEY ;; validating netmeister.org/DNSKEY: starting ;; validating netmeister.org/DNSKEY: attempting positive response validation ;; fetch: netmeister.org/DS ;; validating netmeister.org/DS: starting ;; validating netmeister.org/DS: attempting positive response validation ;; fetch: org/DNSKEY ;; validating org/DNSKEY: starting ;; validating org/DNSKEY: attempting positive response validation ;; fetch: org/DS ;; validating org/DS: starting ;; validating org/DS: attempting positive response validation ;; fetch: ./DNSKEY ;; validating ./DNSKEY: starting ;; validating ./DNSKEY: attempting positive response validation ;; validating ./DNSKEY: verify rdataset (keyid=20326): success ;; validating ./DNSKEY: signed by trusted key; marking as secure ;; validating org/DS: in fetch_callback_validator ;; validating org/DS: keyset with trust secure ;; validating org/DS: resuming validate ;; validating org/DS: verify rdataset (keyid=25266): success ;; validating org/DS: marking as secure, noqname proof not needed ;; validating org/DNSKEY: in dsfetched ;; validating org/DNSKEY: dsset with trust secure ;; validating org/DNSKEY: verify rdataset (keyid=9795): success ;; validating org/DNSKEY: marking as secure (DS) ;; validating netmeister.org/DS: in fetch_callback_validator ;; validating netmeister.org/DS: keyset with trust secure ;; validating netmeister.org/DS: resuming validate ;; validating netmeister.org/DS: verify rdataset (keyid=16454): success ;; validating netmeister.org/DS: marking as secure, noqname proof not needed ;; validating netmeister.org/DNSKEY: in dsfetched ;; validating netmeister.org/DNSKEY: dsset with trust secure ;; validating netmeister.org/DNSKEY: verify rdataset (keyid=31910): success ;; validating netmeister.org/DNSKEY: marking as secure (DS) ;; validating netmeister.org/AAAA: in fetch_callback_validator ;; validating netmeister.org/AAAA: keyset with trust secure ;; validating netmeister.org/AAAA: resuming validate ;; validating netmeister.org/AAAA: verify rdataset (keyid=31910): success ;; validating netmeister.org/AAAA: marking as secure, noqname proof not needed ; fully validated netmeister.org. 3600 IN AAAA 2001:470:30:84:e276:63ff:fe72:3900 netmeister.org. 3600 IN RRSIG AAAA 13 2 3600 ( 20190516000000 20190425000000 31910 netmeister.org. DUKa+iLUk5Tv3+nlQAKMRs0DkOdVeoXJNnYkotRmNW9M ZLC/LhjDdqYX50q7LEPSrtF9i01VgmXLBCVyWfBTWw== ) $ Alternatively, for example if you are using unbound, you can use the drill(1) tool:
$ drill -S netmeister.org ;; Number of trusted keys: 1 ;; Chasing: netmeister.org. A DNSSEC Trust tree: netmeister.org. (A) |---netmeister.org. (DNSKEY keytag: 31910 alg: 13 flags: 256) |---netmeister.org. (DS keytag: 31910 digest type: 2) |---org. (DNSKEY keytag: 16454 alg: 7 flags: 256) |---org. (DNSKEY keytag: 9795 alg: 7 flags: 257) |---org. (DNSKEY keytag: 17883 alg: 7 flags: 257) |---org. (DS keytag: 9795 digest type: 2) | |---. (DNSKEY keytag: 25266 alg: 8 flags: 256) | |---. (DNSKEY keytag: 20326 alg: 8 flags: 257) |---org. (DS keytag: 9795 digest type: 1) |---. (DNSKEY keytag: 25266 alg: 8 flags: 256) |---. (DNSKEY keytag: 20326 alg: 8 flags: 257) ;; Chase successful $ Ok, we have DNSSEC. Big whoop. Now what? Well, now that we can trust DNS, we can use it to solve the Trust on First Use problem inherent in traditional SSH server keys and unknown fingerprints. For that, we use the SSHFP resource records (RFC 4255 and RFC 6594). You can trivially generate the SSHFP records using ssh-keygen(1). That is, assuming your various public keys are in /etc/ssh/, you can run: $ ssh-keygen -r panix panix IN SSHFP 1 1 53a76d5284c91e140dec9ad1a757da123b95b081 panix IN SSHFP 1 2 96c7f824b942c9225b71cf462ac3ddd4b9d7efa6325d98860ad7fd6268597295 panix IN SSHFP 2 1 5eb67b5b147548dc510d7dc9fc77382c9283264b panix IN SSHFP 2 2 c62f9c25e25e89984557916d41b85e75c44e46c3d629c014c20d7243aea14b58 panix IN SSHFP 3 1 5d98620a58f688998549949b3f29eaea68709e5e panix IN SSHFP 3 2 62475a22f1e4f09594206539aaff90a6edaabab1ba6f4a67ab3906177455cf84 panix IN SSHFP 4 1 69549b079f02a59e5814a66fdb0877f447e4c4e3 panix IN SSHFP 4 2 dd6e3f398637eb1086b9b2bf3f29c161dba0d12a39109e6cc87ef2486d28d5de $ In this example, we end up with (in order):
Alternatively, you could of course calculate the fingerprints yourself: $ for f in /etc/ssh/*.pub; do awk '{print $2}' $f | openssl base64 -d -A | openssl sha1 awk '{print $2}' $f | openssl base64 -d -A | openssl sha256 done (stdin)= 5eb67b5b147548dc510d7dc9fc77382c9283264b (stdin)= c62f9c25e25e89984557916d41b85e75c44e46c3d629c014c20d7243aea14b58 (stdin)= 5d98620a58f688998549949b3f29eaea68709e5e (stdin)= 62475a22f1e4f09594206539aaff90a6edaabab1ba6f4a67ab3906177455cf84 (stdin)= 69549b079f02a59e5814a66fdb0877f447e4c4e3 (stdin)= dd6e3f398637eb1086b9b2bf3f29c161dba0d12a39109e6cc87ef2486d28d5de (stdin)= da39a3ee5e6b4b0d3255bfef95601890afd80709 (stdin)= e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 (stdin)= 53a76d5284c91e140dec9ad1a757da123b95b081 (stdin)= 96c7f824b942c9225b71cf462ac3ddd4b9d7efa6325d98860ad7fd6268597295 $ Either way, add those to your DNS zone, and verify: $ delv panix.netmeister.org sshfp +multi ; fully validated panix.netmeister.org. 10800 IN SSHFP 4 1 ( 69549B079F02A59E5814A66FDB0877F447E4C4E3 ) panix.netmeister.org. 10800 IN SSHFP 4 2 ( DD6E3F398637EB1086B9B2BF3F29C161DBA0D12A3910 9E6CC87EF2486D28D5DE ) panix.netmeister.org. 10800 IN SSHFP 3 1 ( 5D98620A58F688998549949B3F29EAEA68709E5E ) panix.netmeister.org. 10800 IN SSHFP 2 1 ( 5EB67B5B147548DC510D7DC9FC77382C9283264B ) panix.netmeister.org. 10800 IN SSHFP 1 1 ( 53A76D5284C91E140DEC9AD1A757DA123B95B081 ) panix.netmeister.org. 10800 IN SSHFP 2 2 ( C62F9C25E25E89984557916D41B85E75C44E46C3D629 C014C20D7243AEA14B58 ) panix.netmeister.org. 10800 IN SSHFP 3 2 ( 62475A22F1E4F09594206539AAFF90A6EDAABAB1BA6F 4A67AB3906177455CF84 ) panix.netmeister.org. 10800 IN SSHFP 1 2 ( 96C7F824B942C9225B71CF462AC3DDD4B9D7EFA6325D 98860AD7FD6268597295 ) panix.netmeister.org. 10800 IN RRSIG SSHFP 13 3 10800 ( 20190516000000 20190425000000 31910 netmeister.org. Tn6HWogGLOfoyQ0lq1K9LYpn/3FUcPUlUiVwDhjF42Cl ubu+KbGXxOIPWNFF161crDrKwXc/ZdVXCaMeqIuksQ== ) $ Again, note that we have a single RRSIG record for the entire SSHFP resource record set. With these records in place and signed, and if you are using a DNSSEC enabled resolver, set VerifyHostKeyDNS=yes and then you can verify the authenticity of the server without requiring an entry in your ~/.ssh/known_hosts file: $ ssh -v -o UserKnownHostsFile=/dev/null -o VerifyHostKeyDNS=yes panix.netmeister.org [...] debug1: Server host key: ecdsa-sha2-nistp521 SHA256:YkdaIvHk8JWUIGU5qv+Qpu2qurG6b0pnqzkGF3RVz4Q debug1: found 8 secure fingerprints in DNS debug1: matching host key fingerprint found in DNS [...] panix$ (Without DNSSEC, these records could still be added, but your ssh(1) client will not accept them as sufficient to allow you to connect.) Ok, so that's pretty useful. The next time you rotate your hosts' keys, you no longer have to update all clients' known_hosts, so long as you update your SSHFP records in the DNS. Similarly, you can publish your PGP keys into the DNS (RFC 4398, RFC 7929). What else can we do with DNSSEC? DANE is great...
(This approach can be used to protect against MitM attacks and the notorious middleware boxes, sold as "SSL Proxy" solutions. If DANE was in widespread use, these systems would not work. Or, at a minimum, they'd have to also control the DNS in question and add TLSA records on the fly for the certificates they generate. It'd get ugly quickly.) TLSA records are created for the given name using _<port>._<protocol> service labels and consist of Certificate Usage, a Selector, Matching Type, and finally the associated data. To generate the record for a "Domain Issued Certificate" using the SHA-256 hash of the Subject Public Key, you could run: $ <cert.pem openssl x509 -noout -pubkey -outform DER | \ openssl rsa -pubin -pubout -outform DER 2>/dev/null | \ openssl sha256 | awk '{print toupper($NF);}' FB5CB6BC898A19916EB81523AB47DD9540D22326A28CA02EC4EBA14344731EB5 $ This value would then be added for every Subject Alternate Name (SAN) in the certificate, so the required records for the cert used on this website would be (as of 2019-05-03; the fingerprint will of course change the next time the certificate is renewed): _443._tcp.mta-sts IN TLSA 3 1 1 FB5CB6BC898A19916EB81523AB47DD9540D22326A28CA02EC4EBA14344731EB5 _443._tcp.panix IN TLSA 3 1 1 FB5CB6BC898A19916EB81523AB47DD9540D22326A28CA02EC4EBA14344731EB5 _443._tcp.www IN TLSA 3 1 1 FB5CB6BC898A19916EB81523AB47DD9540D22326A28CA02EC4EBA14344731EB5 _443._tcp IN TLSA 3 1 1 FB5CB6BC898A19916EB81523AB47DD9540D22326A28CA02EC4EBA14344731EB5 Using the Subject Public Key here instead of using the full certificate has the advantage that you can generate the TLSA records from the CSR before you even have received or deployed your certificate. If you do that, merely replace x509 with req in the above openssl(1) command. But of course now you have to actually keep your TLSA records in sync with your certificate! Thanks to Let's Encrypt, many of us are now renewing our certificates automatically and frequently, so care must be taken to at the same time update the TLSA records. Since DNS lookups may be cached, you mustn't remove the old TLSA records until your TTL has expired! In fact, it's probably a good idea to retain the old TLSA record for, say, two times your current TTL before you remove it. Similarly, you must not deploy the new certificate until the new TLSA record has propagated! (One way to reduce this risk and avoid having to generate new TLSA records every time you renew your certificate would be to instead add a TLSA record using a "trust anchor assertion" in the certificate usage field (2), thereby effectively pinning the CA's root cert. Be careful, though, as this may break for certificates issued via a cross-signed root!) Generating these records manually is a bit of a pain. I also found out that the web UI offered by my DNS provider -- Gandi -- does not allow adding of TLSA records and instead requires those records to be added using their API. Combined with the desire to integrate the record generation with the certificate provisioning via Let's Encrypt, I ended up putting together a script called gandi-tlsa-glue that processes the CSR or cert and adds the required records: $ gandi-tlsa-glue -v -v -v www.netmeister.org.pem => Processing cert 'www.netmeister.org.pem'... ==> Creating TLSA record for SAN 'www.netmeister.org'... ===> Adding TLSA record '_443._tcp.mail IN 3600 3 1 1 FB5CB6BC898A19916EB81523AB47DD9540D22326A28CA02EC4EBA143' in domain 'netmeister.org'... ==> Creating TLSA record for SAN 'mta-sts.netmeister.org'... ===> Adding TLSA record '_443._tcp.mta-sts IN 3600 3 1 1 FB5CB6BC898A19916EB81523AB47DD9540D22326A28CA02EC4EBA143' in domain 'netmeister.org'... ==> Creating TLSA record for SAN 'panix.netmeister.org'... ===> Adding TLSA record '_443._tcp.panix IN 3600 3 1 1 FB5CB6BC898A19916EB81523AB47DD9540D22326A28CA02EC4EBA143' in domain 'netmeister.org'... ==> Creating TLSA record for SAN 'netmeister.org'... ===> Adding TLSA record '_443._tcp IN 3600 3 1 1 FB5CB6BC898A19916EB81523AB47DD9540D22326A28CA02EC4EBA143' in domain 'netmeister.org'... $ Verification of your TLSA records can be done manually: $ tlsa=$(dig +short _443._tcp.www.netmeister.org tlsa | sed -e 's/^. . . //' -e 's/ //') $ cert=$(</dev/null openssl s_client -connect www.netmeister.org:443 2>/dev/null | \ openssl x509 -noout -pubkey -outform DER | \ openssl rsa -pubin -pubout -outform DER 2>/dev/null | \ openssl sha256 | awk '{print toupper($NF);}') $ [ x"${cert}" = x"${tlsa}" ] && echo match match $ There are online tools to verify your TLSA records as well, of course: ![]() Happy with your DANE authenticated HTTPS traffic, you can the move forward and add records for e.g., your mail server: $ gandi-tlsa-glue -p 25 mail.netmeister.org.pem $ delv _25._tcp.panix.netmeister.org tlsa +multi ; fully validated _25._tcp.panix.netmeister.org. 3600 IN TLSA 3 0 1 ( A4FB20F6CD96FCF78687717D8E4E15C831DB467B49F9 9777AE6336634B5A785C ) _25._tcp.panix.netmeister.org. 3600 IN RRSIG TLSA 13 5 3600 ( 20190516000000 20190425000000 31910 netmeister.org. rjLTZs7cCwNV5Quu/Sxn8wMCVaWVXTMIPU4efOM7LkeA 622OcqdUKbPms/oMeFh92weDLbA3DIfTwnPbr7jwDw== ) $ How do we know that this works? Let's break out our openssl(1) swiss army knife once more: $ dig _25._tcp.panix.netmeister.org tlsa +short 3 0 1 A4FB20F6CD96FCF78687717D8E4E15C831DB467B49F99777AE633663 4B5A785C $ openssl s_client -connect panix.netmeister.org:25 -starttls smtp \ -dane_tlsa_domain panix.netmeister.org \ -dane_tlsa_rrdata "3 0 1 A4FB20F6CD96FCF78687717D8E4E15C831DB467B49F99777AE6336634B5A785C" [...] --- SSL handshake has read 3979 bytes and written 481 bytes Verification: OK Verified peername: panix.netmeister.org DANE TLSA 3 0 1 ...49f99777ae6336634b5a785c matched EE certificate at depth 0 --- [...] $ Ok, so let's make sure our mail server actually uses DNSSEC and verifies DANE. Fortunately, postfix has simple instructions to configure opportunistic DANE support: $ postconf smtp_dns_support_level smtp_host_lookup smtp_tls_security_level smtp_dns_support_level = dnssec smtp_host_lookup = dns smtp_tls_security_level = dane $ As noted in the Postfix instructions, these settings apply to outgoing mail only; to test them, we need mail servers to which we send emails where it (a) has a valid TLSA record in a DNSSEC signed zone; (b) has a (any) TLSA record in a zone without DNSSEC; and (c) has an invalid TLSA record in a DNSSEC signed zone. Fortunately, our friends over at Have DANE? provide just such a setup, and sending mail with the correctly configured mail server yields the expected and desired result: ![]() In our mail logs, these connections then look as follows: <mail.info>May 6 22:37:28 panix postfix/smtp[14515]: certificate verification failed for wrong.havedane.net[2001:1af8:4700:a118:90::7c0]:25: untrusted issuer /C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=MyOrganizationalUnit/CN=Fort-Funston CA/name=EasyRSA/emailAddress=me@myhost.mydomain <mail.info>May 6 22:37:28 panix postfix/smtp[14515]: B33C1857A0: Server certificate not trusted <mail.info>May 6 22:37:28 panix postfix/smtp[22214]: B33C1857A0: to=<fcad73fa9ea1bc94@do.havedane.net>, relay=do.havedane.net[5.79.70.105]:25, delay=2.1, delays=0.1/0.18/1.6/0.17, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 6141CC0CDA) <mail.info>May 6 22:37:28 panix postfix/smtp[14515]: certificate verification failed for wrong.havedane.net[5.79.70.105]:25: untrusted issuer /C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=MyOrganizationalUnit/CN=Fort-Funston CA/name=EasyRSA/emailAddress=me@myhost.mydomain <mail.info>May 6 22:37:28 panix postfix/smtp[14515]: B33C1857A0: to=<fcad73fa9ea1bc94@wrong.havedane.net>, relay=wrong.havedane.net[5.79.70.105]:25, delay=2.2, delays=0.1/0.23/1.8/0, dsn=4.7.5, status=deferred (Server certificate not trusted) <mail.info>May 6 22:37:28 panix postfix/smtp[11388]: B33C1857A0: to=<fcad73fa9ea1bc94@dont.havedane.net>, relay=dont.havedane.net[5.79.70.105]:25, delay=2.3, delays=0.1/0.23/1.8/0.16, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 971A2C0CDA) Note that we see two attempts to deliver mail to wrong.havedane.net -- once via IPv4, and once via IPv6. In either case, the mail is not delivered due to a cert issued by an untrusted issuer, intentionally served by the server. The certificate public key fingerprint noted in the TLSA record in the DNS is also (intentionally) incorrect: $ dig _25._tcp.wrong.havedane.net tlsa +short 3 1 1 553ACF88F9EE18CCAAE635CA540F32CB84ACA77C47916682BCB542D5 1DAA871E 2 1 1 27B694B51D1FEF8885372ACFB39193759722B736B0426864DC1C79D0 651FEF72 $ openssl s_client -connect wrong.havedane.net:25 -starttls smtp </dev/null 2>/dev/null | \ openssl x509 -noout -pubkey -outform DER | \ openssl rsa -pubin -pubout -outform DER 2>/dev/null | \ openssl SHA256 | awk '{print toupper($NF);}' 553ACF88F9EE18CCAAE635CA540F32CB84ACA77C47916682BCB542D51DAA871F $ Mail to dont.havedane.net is delivered, even though no TLSA records are found, as our postfix configuration is still opportunistic; changing smtp_tls_security_level to dane-only would cause it to fail delivery to mail servers without DANE: $ sudo postconf smtp_tls_security_level=dane-only $ sudo /etc/rc.d/postfix restart postfix/postfix-script: stopping the Postfix mail system postfix/postfix-script: waiting for the Postfix mail system to terminate postfix/postfix-script: starting the Postfix mail system $ mail 2784c98ff7d91188@dont.havedane.net </dev/null No message, no subject; hope that's ok $ sudo grep dont.havedane.net /var/log/maillog <mail.warn>May 6 23:19:11 panix postfix/smtp[26740]: warning: TLS policy lookup for dont.havedane.net/dont.havedane.net: no TLSA records found <mail.info>May 6 23:19:11 panix postfix/smtp[26740]: 8FB79857A2: to=<2784c98ff7d91188@dont.havedane.net>, relay=none, delay=0.16, delays=0.08/0.08/0.01/0, dsn=4.7.5, status=deferred (no TLSA records found) $ Hvorfor gør du ikke DANE?Ok, so if DNSSEC / DANE is so great, can we just use this everywhere? Like... your browser? Mozilla and Google both have been going back and forth on their plans to implement DNSSEC and DANE. and even our otherwise ever reliable curl(1) does not have DNSSEC / DANE support: after over 6 years, it remains an open TODO item, although partial work was previously done. Google initially implemented DNSSEC authenticated HTTPS in Chrome, but later removed it. (There's some irony in Google arguing against DNSSEC in favor of HPKP, only to then remove HPKP from Chrome, shifting entirely towards detection of fraudulent certificates by way of CAA records (RFC 6844), even though each of these solutions appear to me to address different, albeit overlapping or intersecting problems and threats.) Mozilla similarly had plans, but the respective bugzilla tickets were either closed WONTFIX or left open. A web browser add-on used to be available from https://www.dnssec-validator.cz/, but that required an external binary application in addition to the add-on and was ultimately abandoned in October of 2018. So as it stands right now (as of 2019-05-07), the only browser add-on I was able to find ways this one; it adds a simple visual marker in the address bar when a site has a valid DNSSEC protected TLSA record:
Since a DNSSEC enabled resolver is needed to run these checks, the plugin uses either Cloudflare's 1.1.1.1 or Google's 8.8.8.8 public resolvers over DNS over HTTPS (DoH, RFC 8484). This of course implies that all your DNS queries are sent to either of these third parties with all the privacy implications therein. Allowing the user to specify a DNSSEC enabled resolver would be nice here.
Bad DANE! Sit!
This list is not complete, but by and large many of the concerns seem to me to fall into the Nirvana Fallacy category. I don't know if DNSSEC and DANE are the right solution -- perhaps instead we should pursue DNSCurve? Some parts of the industry appears to be favoring DNS over TLS (RFC 7858) and/or DNS over HTTPS (RFC 8484), but I simply don't know enough about either to judge. All I know is that I think it'd be rather useful to be able to trust the DNS; that I'm willing to improve security little by little, even if I can't defeat all attacks; and that I sure do appreciate an easy click-of-the-button method as offered by my DNS provider. Being able to toy around with these solutions is one of the reasons why I continue to run my own web- and mail server instead of any hosted solutions, and for whatever it's worth, these services are now secured via DNSSEC and DANE. May 7th, 2019 Links from this blog post
|