STARTTLS is a suggestion

2026-08-02

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_age is the blast radius. Senders cache the policy and hold you to it. A broken policy in enforce mode keeps bouncing your mail for up to max_age after you fix it, because the fix is invisible until the cache expires.
  • The id field is the cache key. Editing the policy file without changing the id changes nothing, for max_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

  1. 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.

  2. 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.