Module 3 · Lesson 4
Implementing DANE for Enhanced Security
⏱ 40 min
TLSA records, what DANE actually proves, the DNSSEC dependency, and where DANE gets real deployment today — spoiler: email, not browsers.
Implementing DANE for Enhanced Security
TLS certificate validation has a well-known structural problem: any of the hundreds of trusted Certificate Authorities can issue a certificate for any domain. In 2011, DigiNotar was compromised and issued fraudulent certificates for Google domains. In 2012, Trustwave admitted issuing a subordinate CA certificate used for corporate traffic interception. The CA system is a web of trust where any node can betray everyone.
DANE — DNS-Based Authentication of Named Entities — offers a different model. Instead of trusting any CA, you publish the specific certificate (or CA) you authorize, in your DNS zone, protected by DNSSEC. Clients check DNS first; if the TLSA record doesn't match the certificate presented, the connection fails. You're in control of what certificate is valid for your domain.
TLSA Records
The record type is TLSA. The name format for HTTPS on port 443:
_443._tcp.example.com. 3600 IN TLSA usage selector matching-type certificate-data
Three parameters define what you're pinning:
Usage (0-3):
0— PKIX-TA: The cert must chain to this specified CA, and the CA must be in the public trust store1— PKIX-EE: The cert must be exactly this end-entity certificate, and it must chain to a public trust CA2— DANE-TA: The cert must chain to this specified CA (trust anchor), regardless of public trust store3— DANE-EE: The cert must be exactly this end-entity certificate; no CA validation needed
For self-hosted infrastructure where you control the CA, usage 2 (DANE-TA) is typical. For servers where you want to pin a Let's Encrypt certificate, usage 1 (PKIX-EE) works. Usage 3 is for scenarios where you're entirely outside the public CA system.
Selector (0-1):
0— Full certificate (the entire DER-encoded cert)1— SubjectPublicKeyInfo (just the public key — survives certificate renewals if you keep the same key)
Matching-type (0-2):
0— Exact match (raw bytes)1— SHA-256 hash2— SHA-512 hash
In practice, 1 1 1 (PKIX-EE, public key, SHA-256) is the most common combination for HTTPS. It pins the public key rather than the full certificate, so you can renew with Let's Encrypt without updating DNS — as long as you reuse the private key.
Generating TLSA Records
# Generate a TLSA record from an existing certificate
# Usage 3 1 1 = DANE-EE, SubjectPublicKeyInfo, SHA-256
openssl x509 -in /etc/ssl/certs/example.com.crt -noout -pubkey | \
openssl pkey -pubin -outform DER | \
openssl dgst -sha256 -binary | \
xxd -p -c 256
# Or use the tlsa tool from hash-slinger / ldns
ldns-dane create example.com 443
# Or use the OpenSSL one-liner that does it all
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | \
openssl x509 -outform DER | \
openssl dgst -sha256 | \
awk '{print "3 1 1 " $2}'
The hash goes in the TLSA record:
_443._tcp.example.com. 3600 IN TLSA 3 1 1 a5e3... (64 hex chars)
Verifying TLSA Records
# Query TLSA records
dig _443._tcp.example.com TLSA
# Verify that a server's certificate matches its TLSA record
ldns-dane verify example.com 443
# Check with DNSSEC validation
dig +dnssec _443._tcp.example.com TLSA
# Look for the AD (Authenticated Data) flag in the response
In Python, verification using dnspython:
import dns.resolver
import dns.dnssec
import ssl
import socket
import hashlib
from cryptography import x509
from cryptography.hazmat.primitives import serialization
def get_server_pubkey_hash(hostname: str, port: int) -> str:
"""Get SHA-256 hash of server's SubjectPublicKeyInfo."""
context = ssl.create_default_context()
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert_der = ssock.getpeercert(binary_form=True)
cert = x509.load_der_x509_certificate(cert_der)
pubkey_der = cert.public_key().public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
return hashlib.sha256(pubkey_der).hexdigest()
def check_tlsa(hostname: str, port: int = 443) -> bool:
"""
Basic DANE-EE (3 1 1) verification.
In production, also verify DNSSEC on the TLSA query.
"""
resolver = dns.resolver.Resolver()
tlsa_name = f"_{port}._tcp.{hostname}"
try:
answer = resolver.resolve(tlsa_name, 'TLSA')
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
print(f"No TLSA records for {tlsa_name}")
return False
server_hash = get_server_pubkey_hash(hostname, port)
for rdata in answer:
if rdata.usage == 3 and rdata.selector == 1 and rdata.mtype == 1:
# DANE-EE, SubjectPublicKeyInfo, SHA-256
tlsa_hash = rdata.cert.hex()
if tlsa_hash == server_hash:
print(f"DANE validation passed: {hostname}:{port}")
return True
print(f"DANE validation failed: no matching TLSA record")
return False
# check_tlsa("example.com", 443)
The DNSSEC Dependency
DANE without DNSSEC is useless. If an attacker can forge DNS responses (which they can, without DNSSEC), they can inject a TLSA record for their own certificate and bypass DANE entirely.
DNSSEC is what makes DANE's trust chain hold. The TLSA record must come back with the AD (Authenticated Data) flag set, meaning a validating resolver confirmed it was DNSSEC-signed and valid. If there's no DNSSEC on the zone, DANE provides no security.
This is also why DANE has very limited deployment in browsers. DNSSEC deployment on end-user resolvers is inconsistent. Many ISP resolvers don't validate DNSSEC. Until DNSSEC is ubiquitous (or until DNS-over-HTTPS with a validating resolver is universal), browsers can't rely on DANE for HTTPS validation.
Email: Where DANE Actually Deploys
DANE's real deployment is in email — specifically SMTP between mail servers. The use case:
mail.example.compublishes a TLSA record for port 25- A sending mail server queries
_25._tcp.mail.example.comfor TLSA records - It validates DNSSEC on the response
- It connects to
mail.example.com:25and verifies the certificate against the TLSA record - If verification fails, it refuses to deliver the mail rather than fall back to unencrypted or unauthenticated SMTP
This solves a real problem: without DANE, SMTP opportunistic TLS can be downgraded by an attacker who intercepts the connection and responds "no TLS" — the sender then delivers in plaintext. DANE makes TLS mandatory and authenticated.
MTA-STS vs DANE for email: MTA-STS (RFC 8461) achieves similar goals for SMTP without requiring DNSSEC. It publishes a policy via HTTPS (https://mta-sts.example.com/.well-known/mta-sts.txt) that says "require TLS, here are the valid hostnames." MTA-STS has broader deployment because it doesn't depend on DNSSEC. DANE is more secure if DNSSEC is in place; MTA-STS is more widely supported.
The recommendation for email: implement both. DANE for servers that support it, MTA-STS as the fallback that works everywhere.
# Check if a mail server has DANE
dig _25._tcp.mail.example.com TLSA +dnssec
# Check MTA-STS policy
curl https://mta-sts.example.com/.well-known/mta-sts.txt
# Verify with a mail testing tool
mta-sts-daemon --test example.com
Browser Support: The Honest Answer
As of 2025, no major browser implements DANE for HTTPS validation. Firefox had experimental support that was removed in 2016. Chrome never had it.
The projects that do implement DANE client-side:
- DNSSEC/TLSA Validator browser extensions (maintained by CZ.NIC)
- Postfix with DANE support (email)
- Exim with DANE support (email)
- OpenSSL has hooks but nothing in the default verify chain
Where DANE has real deployment today: academic/research networks, European government infrastructure, and any organization serious enough about email security to run DNSSEC on their zones. NL (Netherlands) and SE (Sweden) have notable DANE adoption in their government domains.
Key Takeaways
- DANE uses TLSA records to bind TLS certificates to DNS records, bypassing the public CA system
- Usage
3 1 1(DANE-EE, SubjectPublicKeyInfo, SHA-256) is the most common combination for HTTPS - DANE without DNSSEC is no security at all — you need the AD flag on TLSA responses
- Browser support is essentially zero; DANE's real deployment is email (SMTP) where mail servers implement it
- For email: implement both DANE and MTA-STS for maximum coverage
- DANE is worth implementing if your zone has DNSSEC and you want cryptographic binding of certificates independent of CAs
Further Reading
- RFC 6698 — The DNS-Based Authentication of Named Entities (DANE) Protocol
- RFC 7671 — The DANE Protocol: Updates and Operational Guidance
- RFC 8461 — SMTP MTA Strict Transport Security (MTA-STS)
- DANE validator tool
Up Next
Lesson 05 covers DNS in containers and microservices — Docker's internal DNS resolver at 127.0.0.11, Kubernetes CoreDNS, the ndots:5 problem, and how to debug DNS inside pods with real kubectl commands.