When one mail server delivers to another, encryption is negotiated
inside the connection it is meant to protect. The receiving server
advertises STARTTLS; the sending server upgrades if it saw the offer.
If it did not - because the receiver has no TLS, or because someone in a
position to edit the conversation deleted the offer in transit - the
mail is delivered anyway, in plaintext, with no error raised on either
side. That is not a bug. It is what opportunistic encryption means, and
it is standards-compliant behaviour.
So the entire protection collapses under exactly the adversary encryption exists for: anyone who can modify traffic can switch the encryption off, silently, per connection. The failure mode of opportunistic TLS is silence.
The previous essay on this domain ended by admitting that three cheap items sat undone: MTA-STS, TLS-RPT, DNSSEC. They are done now - plus DANE, which belongs in the same list and is the reason the list has an order. This essay is what the deployment actually involved, including the parts where the afternoon estimate was wrong.
Two promises, two anchors
The fix for a downgrade attack cannot live inside the connection being downgraded. It has to be a promise published somewhere the attacker cannot edit, saying: mail to this domain must use TLS, verified, no fallback. There are two such promises, anchored in different places on purpose.
MTA-STS publishes the promise at an HTTPS URL on your domain, backed
by an ordinary public certificate. A sending server fetches the policy,
caches it for the stated max_age, and afterwards refuses to deliver to
your MX without verified TLS. Its trust anchor is the CA system plus the
cache: the first fetch is the leap of faith, every later connection is
held to it. Gmail and Outlook check MTA-STS.
DANE publishes the promise in DNS itself: a TLSA record carrying a
fingerprint of the exact key your mail server presents, protected by
DNSSEC signatures. No certificate authority is consulted at all - if
the key does not match the fingerprint, delivery is refused. Postfix
installations and a fair share of European providers check DANE; Gmail
does not, and Microsoft has spent the last few years rolling it out
across Exchange Online.
The overlap is deliberate. One promise trusts the CA system, the other
trusts the DNS chain; a sender that checks either is covered, and an
attacker has to defeat both. But DANE's anchor creates the ordering:
a TLSA record on an unsigned zone is not merely useless, it is
strippable by exactly the attacker it exists to stop. DNSSEC first.
The half of DNSSEC your DNS host cannot do
A reasonable question, asked mid-deployment: isn't DNSSEC just a
setting at the DNS host? Half of it is. The host signs the zone - one
toggle, done in seconds. But a signed zone proves nothing by itself,
because a resolver has no reason to believe the signing key is yours:
if a zone could vouch for itself, an attacker impersonating your
nameservers would simply vouch for their own key. The endorsement has
to come from one level up. Someone must place a DS record - a
fingerprint of your signing key - into the TLD's zone, and the only
party with write access to the TLD registry is your registrar.
If your DNS host and your registrar are the same company, this is one button and the distinction never surfaces. If they are different companies, the deployment is a relay handoff between two parties who have never heard of each other, and the order matters because the failure modes are wildly asymmetric:
- Signed zone, no DS yet: harmless. Validating resolvers treat the zone as unsigned, which is what it was yesterday.
- DS published, wrong or missing key: the domain stops resolving entirely for every validating resolver - no website, no mail - and the failures are cached after you fix them.
So: sign, verify the signatures are actually being served, and only then hand the fingerprint to the registrar. Never the reverse, and never change nameservers while a DS is live upstream.
The propagation is its own small lesson in not panicking. The registry
database acknowledged the DS instantly; the TLD's zone served it a few
minutes later; and for the next hour, the public resolvers disagreed
about whether the domain validated - the same resolver answering yes,
then no, then yes, seconds apart. That is not breakage. The big public
resolvers are anycast fleets of independent caches, each deciding on
its own schedule, and a cache still holding yesterday's answer simply
treats the zone as unsigned. The distinction that matters: a broken
chain does not look like a missing validation flag, it looks like
SERVFAIL. Sample several resolvers several times; treat one query as
a coin flip, because it is one.1
MTA-STS: reporting before enforcement, again
MTA-STS deployment is mostly the discipline DMARC already teaches. The
policy file has a mode: testing, in which senders fetch and evaluate
the policy, report failures, and deliver anyway - and a companion
record, TLS-RPT, that tells them where to send those reports. Publish
both, watch the reports until they run clean, then switch to
mode: enforce. Reporting before enforcement, in that order, for the
same reason rua= comes before p=reject.
Two properties deserve more respect than the RFC's dry phrasing gives them:
max_ageis the blast radius. Senders cache the policy and hold you to it. A broken policy in enforce mode keeps bouncing your mail for up tomax_ageafter you fix it, because the fix is invisible until the cache expires.- The
idfield is the cache key. Editing the policy file without changing theidchanges nothing, formax_age, silently.
As of writing this domain is at mode: testing with reports arriving.
Enforce comes when they have run clean for two weeks - writing the
stronger claim before then would repeat exactly the mistake the
previous essay was about.
DANE: the record is easy, the promise is not
A TLSA record for modern DANE is a hash of your mail server's own
public key. Publishing it took one API call. It is everything after
that call that earns the section.
Certificate automation is built on the assumption that certificates are
disposable - and with the common tooling defaults, each renewal mints a
fresh private key. The TLSA record pins the key. Left alone, the
record is correct for about sixty days, and then every DANE-checking
sender on the internet starts refusing your mail - while Gmail, which
does not check, delivers happily. Nothing in your logs
complains, because nothing on your server is wrong. It is the same
shape of failure as the stripped STARTTLS offer: partial, silent, and
structural, except this time you built it yourself.
So DANE for a domain with automated certificates is not a record, it is an invariant with machinery attached:
The published fingerprints must always include the key the server is currently presenting.
Which forces a rollover order around every renewal:
| Step | Why it cannot move |
|---|---|
| Publish the new key's hash alongside the old | the old key is still being served |
| Wait out the DNS TTL | resolvers must be able to see the new hash |
| Only then let the mail server present the new key | senders validate against what they can see |
| Only then remove the old hash | confirmed unused, and never before |
The tooling that maintains this holds one further rule: when it cannot verify what the server is presenting, it refuses to touch DNS at all. A failed probe is not evidence a record is unused, and in this failure mode, deleting the wrong record is strictly worse than doing nothing.2
What this buys
The closing paragraph of the DMARC essay said this domain published none of these, which was the honest reason they sat at the end of that essay rather than the middle. The paragraph is now false and has been corrected - the pleasant way an essay about unfinished work gets to age.
The audit argument from that essay carries over unchanged, and is
stronger here. Every control in this one - the signed zone, the DS at
the registry, the policy file, the TLSA record - is externally
verifiable by anyone in seconds, from outside the network, with no
access granted. Under ISO 27001 that is what separates evidence from
intention. A Statement of Applicability that points at records the
auditor can check from their own laptop reads differently from one
that points at a paragraph of policy.
And the honest ledger, since the previous essay called these items
"cost an afternoon": the records were an afternoon. The DNSSEC handoff
was a form and a wait. What was not an afternoon is the standing
promise - the TLSA record that must track every future renewal, the
policy id that must change with every edit, the enforce switch that
must wait for the reports. Transport security for mail turned out to be
less a task than a small set of obligations, each cheap, none optional,
all of which fail silently when neglected. Which is to say: it is
exactly like the rest of email.
Footnotes
-
The deployment script originally asked one resolver once and aborted on a miss. It passed, then failed twenty minutes later on the identical, healthy zone. It now asks three resolvers three times, fails hard on any
SERVFAIL, and accepts partial validation for what it is: caches filling. ↩ -
Found during installation, because the machinery ran eighty milliseconds before its own bootstrap: a systemd timer with a relative start time counts from boot, so on a server booted weeks ago, "enable, then wait" is actually "fire immediately." The duplicate it raced into was harmless - the desired record already existed - but the installer treated the API's refusal as fatal and stopped. Both halves were wrong: the timer should be armed after the bootstrap, and "the record already exists" is a success wearing an error code. ↩