SMTP DANE Ends at a Certificate Match, Not a DNS Record
Last edited on August 13, 2026

An SMTP DANE check is complete only when four facts agree: the recipient domain selects an MX hostname, DNSSEC validates the TLSA RRset at that hostname, the port-25 listener offers STARTTLS, and the certificate actually served matches at least one usable TLSA association. Finding a record with dig proves only that some DNS server returned data; it does not prove the trust chain or the live certificate.

This audit is for mail administrators and agencies that can run shell commands from a network allowed to reach outbound TCP/25. It assumes basic MX knowledge, then defines the DANE-specific pieces as they appear. No message is sent, no DNS record is changed, and no MTA configuration is edited.

DANE—DNS-based Authentication of Named Entities—changes the trust path for inter-domain SMTP. RFC 7672 uses DNSSEC-authenticated TLSA records to resist STARTTLS downgrade and server impersonation. That is different from an ordinary STARTTLS chain and hostname test, which asks whether a conventional PKIX client can trust the certificate and name after the upgrade. Both checks inspect TLS, but they answer different acceptance questions.

Select the MX Before the TLSA Record

SMTP delivery starts with the recipient domain’s MX set. A TLSA owner for port 25 is normally _25._tcp.<mx-host>, not _25._tcp.<address-domain>. Looking up _25._tcp.mailbox.org would therefore test the wrong name for the domain used in this reproduction.

The evidence below uses mailbox.org as a public, DNSSEC-signed test destination and selects one of its current MX hosts, mx1.mailbox.org. Public records and certificates change, so the values are a dated receipt rather than permanent documentation of another operator’s infrastructure.

Install openssl and Debian’s bind9-dnsutils package, which provides both dig and delv. Start with a collision-safe local path. If that path already exists, the block removes it only when its marker proves ownership.

set -euo pipefail
LAB=/tmp/voxfor-smtp-dane-155
MARKER=voxfor-smtp-dane-lab-155
DOMAIN=mailbox.org
MX_HOST=mx1.mailbox.org
TLSA_OWNER="_25._tcp.${MX_HOST}"

if [[ -d "$LAB" ]]; then
  [[ -f "$LAB/.marker" ]]
  [[ "$(<"$LAB/.marker")" == "$MARKER" ]]
  [[ "$LAB" == /tmp/voxfor-smtp-dane-155 ]]
  find "$LAB" -xdev -depth -delete
fi
install -d -m 0700 "$LAB"
printf '%s\n' "$MARKER" > "$LAB/.marker"
printf 'openssl=%s\n' "$(openssl version | cut -d' ' -f1-2)"
printf 'dig=%s\n' "$(dig -v 2>&1 | head -n1)"
printf 'delv=%s\n' "$(delv -v 2>&1 | head -n1)"
printf 'scope=%s target=%s:25 owner=%s\n' "$LAB" "$MX_HOST" "$TLSA_OWNER"

Now preserve the full MX set and prove that the chosen host is actually present. Hard-coding a familiar mail hostname without this step can produce a technically correct TLSA match for an endpoint the recipient domain does not use.

set -euo pipefail
LAB=/tmp/voxfor-smtp-dane-155
MARKER=voxfor-smtp-dane-lab-155
DOMAIN=mailbox.org
MX_HOST=mx1.mailbox.org
TLSA_OWNER="_25._tcp.${MX_HOST}"
[[ "$(<"$LAB/.marker")" == "$MARKER" ]]

dig @1.1.1.1 +short MX "$DOMAIN" | sort -n > "$LAB/mx.txt"
cat "$LAB/mx.txt"
awk -v host="${MX_HOST}." '$2 == host {found=1} END {exit !found}' "$LAB/mx.txt"
printf 'selected_mx=%s owner=%s\n' "$MX_HOST" "$TLSA_OWNER"

The observed set contained four preference-10 hosts and one preference-50 host. This article follows one host through the complete trust path; it does not convert that one result into a domain-wide pass.

DNSSEC Authenticity Is a Separate Gate

An AD flag means the replying recursive resolver claims the answer was authenticated. Over an untrusted, unauthenticated path to a public resolver, that bit alone is not a cryptographic proof for a security decision. The stronger check here uses delv to validate the DNSSEC signatures with a configured trust anchor, then treats Cloudflare and Quad9 as independent comparison views.

BIND’s delv must print fully validated. Each remote view must claim AD, normalize to the same usage, selector, matching type and digest, and equal the locally validated RRset. A SERVFAIL, bogus result, missing validation status or disagreement stops the audit. When that happens, trace the failing DNSSEC chain before interpreting any certificate association.

set -euo pipefail
LAB=/tmp/voxfor-smtp-dane-155
MARKER=voxfor-smtp-dane-lab-155
TLSA_OWNER=_25._tcp.mx1.mailbox.org
[[ "$(<"$LAB/.marker")" == "$MARKER" ]]

delv TLSA "$TLSA_OWNER" > "$LAB/delv.txt"
grep -q '^; fully validated$' "$LAB/delv.txt"
awk '$4=="TLSA" {digest=""; for(i=8;i<=NF;i++) digest=digest $i; print $5" "$6" "$7" "toupper(digest)}' \
  "$LAB/delv.txt" | sort -u > "$LAB/tlsa-validated.txt"
[[ -s "$LAB/tlsa-validated.txt" ]]
printf 'local_dnssec_validation=fully_validated rrset_count=%s\n' \
  "$(wc -l < "$LAB/tlsa-validated.txt")"

for resolver in 1.1.1.1 9.9.9.9; do
  safe_name=${resolver//./-}
  dig "@${resolver}" +dnssec TLSA "$TLSA_OWNER" > "$LAB/dig-${safe_name}.txt"
  grep -Eq '^;; flags:.* ad[ ;]' "$LAB/dig-${safe_name}.txt"
  dig "@${resolver}" +short TLSA "$TLSA_OWNER" \
    | awk '{digest=""; for(i=4;i<=NF;i++) digest=digest $i; print $1" "$2" "$3" "toupper(digest)}' \
    | sort -u > "$LAB/tlsa-${safe_name}.txt"
  cmp "$LAB/tlsa-validated.txt" "$LAB/tlsa-${safe_name}.txt"
  printf 'resolver=%s ad_claimed=yes rrset_agrees_with_local_validation=yes count=%s\n' \
    "$resolver" "$(wc -l < "$LAB/tlsa-${safe_name}.txt")"
done

cmp "$LAB/tlsa-1-1-1-1.txt" "$LAB/tlsa-9-9-9-9.txt"
cp "$LAB/tlsa-validated.txt" "$LAB/tlsa.txt"
cat "$LAB/tlsa.txt"

Two agreeing public resolvers improve change diagnosis, but the local validation result remains the trust gate. A production MTA should use its own security-aware resolver path and record its secure, insecure, bogus or indeterminate state rather than copying this resolver list blindly.

Read What 3 1 1 Actually Binds

RFC 6698 defines four TLSA fields. Their values form one instruction, so a matching digest cannot be interpreted without the three numbers before it.

Field Reproduced value Reader decision
certificate usage 3 DANE-EE: associate directly with the end-entity certificate
selector 1 hash its SubjectPublicKeyInfo, or SPKI, rather than the complete certificate
matching type 1 compare a SHA-256 digest
association data 64 hexadecimal characters expected SHA-256 result for the selected certificate material

SPKI contains the public key plus its algorithm identifier. With selector 1, renewing a certificate while retaining the same key can leave the association unchanged; changing the key changes the digest. That convenience is also an ownership risk: certificate deployment and DNS data must remain coordinated.

For SMTP DANE, usages 2 and 3 are the usable DANE modes described by RFC 7672; usages 0 and 1 do not become safe merely because OpenSSL accepts their syntax. The reproduced RRset uses the common 3 1 1 form, so the next step hashes the served leaf certificate’s SPKI.

Compare the Published RRset With the Served Certificate

DNS and certificate stores are separate deployment surfaces. Reading a local PEM file proves what exists on disk, not what the port-25 listener presents after SNI, proxying, reload state or node selection. Fetch the leaf certificate through the same STARTTLS path a sending MTA reaches, then hash its SPKI independently.

set -euo pipefail
LAB=/tmp/voxfor-smtp-dane-155
MARKER=voxfor-smtp-dane-lab-155
MX_HOST=mx1.mailbox.org
[[ "$(<"$LAB/.marker")" == "$MARKER" ]]

timeout 20 openssl s_client -4 -starttls smtp \
  -connect "${MX_HOST}:25" -servername "$MX_HOST" -showcerts \
  </dev/null > "$LAB/starttls-full.txt" 2> "$LAB/starttls-stderr.txt"
awk '/-----BEGIN CERTIFICATE-----/{capture=1} capture{print} /-----END CERTIFICATE-----/{exit}' \
  "$LAB/starttls-full.txt" > "$LAB/leaf.pem"
openssl x509 -in "$LAB/leaf.pem" -noout -subject -issuer -dates

SPKI_SHA256=$(openssl x509 -in "$LAB/leaf.pem" -pubkey -noout \
  | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256 -binary \
  | od -An -vtx1 | tr -d ' \n' | tr '[:lower:]' '[:upper:]')
printf 'served_spki_sha256=%s\n' "$SPKI_SHA256"
awk -v hash="$SPKI_SHA256" \
  '$1==3 && $2==1 && $3==1 && $4==hash {found=1} END {exit !found}' \
  "$LAB/tlsa.txt"
printf 'served_spki_present_in_3_1_1_rrset=yes\n'

This independent digest localizes a common deployment split. If the DNS association matches a newly issued certificate file but not the listener, prove which certificate is live before editing DANE data again. A stale process, load balancer or alternate node can own the mismatch.

OpenSSL can also apply DANE’s certificate-verification rules. Supply every record in the authenticated RRset: multiple valid associations may exist for different certificate algorithms, nodes, or a controlled transition. The current openssl s_client reference documents repeatable -dane_tlsa_rrdata and -dane_tlsa_domain; it does not fetch the TLSA RRset for this command.

set -euo pipefail
LAB=/tmp/voxfor-smtp-dane-155
MARKER=voxfor-smtp-dane-lab-155
MX_HOST=mx1.mailbox.org
[[ "$(<"$LAB/.marker")" == "$MARKER" ]]

DANE_ARGS=()
while read -r usage selector matching digest; do
  DANE_ARGS+=( -dane_tlsa_rrdata "$usage $selector $matching $digest" )
done < "$LAB/tlsa.txt"

timeout 20 openssl s_client -4 -brief -verify_return_error -starttls smtp \
  -connect "${MX_HOST}:25" -servername "$MX_HOST" \
  -dane_tlsa_domain "$MX_HOST" "${DANE_ARGS[@]}" \
  </dev/null > "$LAB/dane-positive.txt" 2>&1
grep -E 'Verification: OK|DANE TLSA .* matched|^250 ' "$LAB/dane-positive.txt"
grep -q 'DANE TLSA .* matched' "$LAB/dane-positive.txt"

Verification: OK is helpful, but the decisive line names the DANE TLSA match. The final 250 CHUNKING also shows that SMTP resumed after TLS; this check authenticates a transport endpoint without transmitting a message.

A Stale Association Must Stop Authentication

A positive result alone cannot prove that the command truly depends on the TLSA data. The safe negative control changes one nibble in a copied digest and supplies that wrong association as the only RRset. Nothing is published and the real server is unchanged; the control models the state in which DNS contains no association for the certificate being served.

set -euo pipefail
LAB=/tmp/voxfor-smtp-dane-155
MARKER=voxfor-smtp-dane-lab-155
MX_HOST=mx1.mailbox.org
[[ "$(<"$LAB/.marker")" == "$MARKER" ]]

read -r usage selector matching digest \
  < <(awk '$1==3 && $2==1 && $3==1 {print; exit}' "$LAB/tlsa.txt")
first=${digest:0:1}
replacement=0
[[ "$first" == 0 ]] && replacement=1
BAD_DIGEST="${replacement}${digest:1}"

set +e
timeout 20 openssl s_client -4 -brief -verify_return_error -starttls smtp \
  -connect "${MX_HOST}:25" -servername "$MX_HOST" \
  -dane_tlsa_domain "$MX_HOST" \
  -dane_tlsa_rrdata "$usage $selector $matching $BAD_DIGEST" \
  </dev/null > "$LAB/dane-negative.txt" 2>&1
negative_rc=$?
set -e

[[ "$negative_rc" -ne 0 ]]
grep -qi 'no matching DANE TLSA records' "$LAB/dane-negative.txt"
printf 'negative_control=stale_only_rrset rejected=yes rc=%s\n' "$negative_rc"
grep -i 'no matching DANE TLSA records' "$LAB/dane-negative.txt" | head -n1

A DANE-aware sender should not reinterpret that mismatch as permission to send unauthenticated mail. Store-and-forward SMTP normally defers a potentially temporary failure and retries within its configured lifetime; Microsoft describes this behavior for Exchange Online in its current SMTP DANE flow. During a real incident, preserve the policy, certificate, timestamps and MTA error before changing anything. If Postfix already has delayed mail, preserve the Postfix queue evidence instead of flushing or repeatedly forcing delivery.

One MX Receipt Is Not a Domain Receipt

The reproduced result proves one hostname from one network path at one time. A production acceptance run must repeat MX discovery and DANE verification for every effective MX target. Where DNS returns several addresses or the service can select RSA/ECDSA certificates, exercise every reachable certificate path the sending population can negotiate.

Port 25 may be blocked on office networks, residential links and some clouds. A timeout from such a source is not a DANE mismatch; rerun from the outbound MTA network or another authorized path with equivalent reachability. Conversely, a successful direct test does not prove that every sending MTA uses a validating resolver or DANE-capable TLS library.

Operational changes need an overlap window when the served key will change. Publish the additional valid association, allow relevant DNS caches to observe it, deploy the new certificate, verify every MX path, observe DANE-aware logs or TLS reporting, and remove the old association only after the old path has left service and its TTL exposure has passed. Do not replace the only valid record and certificate simultaneously.

Ownership matters more than the tool choice. One team or provider must coordinate the MX set, DNSSEC health, TLSA data, certificate deployment, MTA logs and continuous checks. When that responsibility crosses an external boundary, document the exact server, DNS and mail-operation handoff; the linked service explicitly covers system/DNS management, mail-server configuration and monitoring, but the agreement still needs to name DANE validation and certificate-change acceptance rather than assume them.

What should the operator monitor?

Track DNSSEC validation failures, changes to MX and TLSA RRsets, certificate associations on every listener, STARTTLS availability, DANE match failures, queue delay and TLS-RPT reports. RFC 8460 defines SMTP TLS Reporting, which can surface DANE-related policy failures from capable senders. A generic TLS uptime probe is useful availability evidence, but it is not a substitute for DNSSEC plus TLSA matching.

Mail Operators’ SMTP DANE Questions

Does a TLSA answer without AD prove SMTP DANE?

No. A TLSA record carries DANE trust only when DNSSEC validation authenticates it. Even an AD claim should come through a trusted resolver path or be checked locally; this workflow requires delv to validate the signatures and uses two public resolver views only as comparison evidence.

Why is the TLSA owner based on the MX hostname?

SMTP DANE first resolves the recipient domain’s MX set, then looks for port-25 TLSA records at _25._tcp.MX-HOST. Querying the address domain directly can inspect a record that no sending MTA uses for that route.

What does a 3 1 1 TLSA record match?

3 1 1 means DANE-EE usage, SPKI selector and SHA-256 matching. The association data is the SHA-256 digest of the end-entity certificate’s SubjectPublicKeyInfo, not the certificate file’s text or its ordinary fingerprint display.

Why should OpenSSL receive every TLSA record in the RRset?

Any usable association may match the negotiated certificate. Multiple records can cover different keys, certificate algorithms, nodes or a controlled transition, so testing one chosen record can create a false failure when another current record is the intended match.

Does one successful MX check validate the whole email domain?

No. It validates only the selected hostname, reached address, negotiated certificate and observation time. Repeat the complete path for every effective MX host and every certificate path that real senders can encounter.

What should happen when the certificate matches no usable TLSA record?

A DANE-aware sender should reject that authenticated-delivery path and defer according to its mail-lifetime policy rather than silently downgrade. Preserve the mismatch evidence, restore a known-good certificate/RRset relationship and verify it before releasing queued mail.

Does this port-25 test cover submission on ports 587 or 465?

No. RFC 7672’s inter-domain SMTP DANE discovery uses the MX target and port 25. User submission on 587 and implicit TLS on 465 follow separately configured client identities and security policy; do not infer their acceptance from this receipt.

Keep the Acceptance Receipt Time-Bounded

The checked MX host is acceptable at the recorded time when its recipient domain still selects it, delv fully validates the TLSA RRset, independent resolver views equal that validated set, the served SPKI matches a usable association, OpenSSL reports the DANE match, post-TLS SMTP responds, and a stale-only association is rejected. Any missing state leaves the result incomplete rather than partially passed.

Run the guarded cleanup after saving only the secret-free output your change record needs:

set -euo pipefail
LAB=/tmp/voxfor-smtp-dane-155
MARKER=voxfor-smtp-dane-lab-155
[[ "$LAB" == /tmp/voxfor-smtp-dane-155 ]]
[[ -f "$LAB/.marker" ]]
[[ "$(<"$LAB/.marker")" == "$MARKER" ]]
find "$LAB" -xdev -depth -delete
[[ ! -e "$LAB" ]]
printf 'cleanup_scope=%s absent=yes\n' "$LAB"
local_dnssec_validation=fully_validated rrset_count=1
resolver=1.1.1.1 ad_claimed=yes rrset_agrees_with_local_validation=yes count=1
resolver=9.9.9.9 ad_claimed=yes rrset_agrees_with_local_validation=yes count=1
served_spki_sha256=996AD31D65E03F038B8EC950F6F26611529DA03E3A283E4400CBA2EDD04B8A88
served_spki_present_in_3_1_1_rrset=yes
Verification: OK
DANE TLSA 3 1 1 ...3a283e4400cba2edd04b8a88 matched the EE certificate at depth 0
negative_control=stale_only_rrset rejected=yes rc=1
verify error:num=65:no matching DANE TLSA records
cleanup_scope=/tmp/voxfor-smtp-dane-155 absent=yes

Local rollback removes only /tmp/voxfor-smtp-dane-155 after its exact marker is verified. During a real deployment mismatch, restore the last known-good served certificate and authenticated TLSA relationship through the owning DNS and MTA change records; do not disable DANE, delete unrelated DNS data, clear mail queues or replace trust stores as a shortcut. The receipt expires when MX, DNSSEC, TLSA, certificate or listener state changes, so rerun it after every material transport change.

Share this Post

Leave a Reply

Your email address will not be published. Required fields are marked *