DANE і TLSA для поштового сервера: прив'язуємо сертифікат до DNS і робимо SMTP-шифрування обов'язковим
STARTTLS між серверами ламається одним вирізаним рядком, а самопідписаний сертифікат MTA приймає мовчки. DANE закриває цю діру: відбиток сертифіката публікується в DNS, а DNSSEC гарантує, що його не підмінили. Розбираємо байти TLSA-запису, налаштування Postfix і головну операційну пастку — ротацію сертифіката, яка кладе домен у чорну діру.
EvilMail Team13 липня 2026 р.13 хв читання
О третій ночі падає алерт: домен клієнта відбиває пошту з 450 4.7.5 TLSA lookup failed. За півгодини до цього certbot тихо оновив сертифікат на MX-хості. Новий ключ — старий відбиток у DNS більше не збігається — і кожен сервер, який валідує DANE, чесно виконує контракт: «сертифікат не той, лист не прийму». Це не абстрактна теорія безпеки. Це той самий механізм, який захищає вашу пошту від перехоплення, і той самий, який кладе домен у чорну діру, якщо переплутати порядок дій на один крок.
Розберемо, чому міжсерверне SMTP-шифрування «на чесне слово» не працює, як саме DANE це лагодить і як інструментувати все так, щоб не ловити той самий bounce о третій ночі.
Чому STARTTLS без DANE — це шифрування «на чесне слово»
TLS між поштовими серверами принципово відрізняється від TLS у браузері. Коли ви відкриваєте https://, браузер вимагає валідний сертифікат від довіреного CA і звіряє ім'я хоста. Якщо щось не так — червоний екран і відмова. MTA поводиться навпаки. За RFC 3207 STARTTLS на порту 25 опортуністичний: сервер-відправник намагається підняти TLS, і якщо не вийшло — відправляє лист відкритим текстом. За замовчуванням він не перевіряє ні CN/SAN, ні центр сертифікації. Самопідписаний сертифікат приймається мовчки. Прострочений — теж.
DANE і TLSA-записи для поштового сервера: захист SMTP через DNSSEC — EvilMail Blog
Звідси два класи атак для того, хто сидить на шляху трафіку:
Downgrade / STARTTLS-stripping. Атакуючий перехоплює відповідь на EHLO і просто вирізає з неї рядок 250-STARTTLS. Сервер-відправник вважає, що приймаюча сторона не вміє TLS, і чесно шле plaintext. Ніхто не бачить помилки — з'єднання «успішне».
Підміна сертифіката (MITM). Навіть якщо TLS піднявся, атакуючий підставляє власний сертифікат. MTA його приймає, бо не звіряє ні ім'я, ні ключ. Трафік розшифровується посередині.
«А публічні CA?» — не рятують. У сховищі довіри сотні CA, і будь-який з них технічно може випустити валідний сертифікат на ваш MX. Немає жодної прив'язки «саме цей ключ належить саме цьому серверу». А оскільки MTA все одно не звіряє ім'я, навіть ця слабка гарантія не використовується.
DANE (DNS-based Authentication of Named Entities, RFC 6698, для пошти — RFC 7672) перевертає модель довіри. Замість «вір будь-якому з сотні CA» ви кажете: «ось відбиток мого сертифіката, він лежить у DNS, і DNSSEC доводить, що його не підмінили — вір саме йому». Заразом це робить TLS обов'язковим: якщо для MX є TLSA-запис, відправник зобов'язаний підняти TLS і звірити відбиток. Вирізати 250-STARTTLS вже не спрацює — відсутність TLS там, де DANE його вимагає, це помилка, а не тихий fallback.
Варто одразу зняти хибну дилему «DANE чи MTA-STS». MTA-STS (RFC 8461) вирішує ту саму задачу, але через довіру до WebPKI і політику, яку відправник тягне по HTTPS і кешує (TOFU — trust on first use). DANE спирається на DNSSEC і не має вікна TOFU. Це не конкуренти: Gmail і Outlook валідують MTA-STS і не роблять DANE, тоді як більшість європейських провайдерів роблять DANE. Тримайте обидва.
DNSSEC — фундамент, без якого DANE не існує
TLSA-запис має сенс тільки тоді, коли приймаюча сторона може криптографічно довести, що його не підмінили. Це роль DNSSEC. Без суцільного підписаного ланцюга від кореневої зони до вашої TLSA лишається просто рядком у DNS, який той самий MITM підмінить на що завгодно.
Три речі мають збігтися:
Ваша зона підписана (є RRSIG на записах).
У батьківській зоні (у реєстратора домену) лежить коректний DS-запис — хеш вашого ключа підпису.
Валідуючий резолвер повертає відповідь із виставленим AD-бітом (Authenticated Data).
Найкласичніша пастка: адміністратор підписав зону, TLSA опублікований, все виглядає правильно — але DS-запис не завантажено до реєстратора. Ланцюг обірваний. Резолвер віддає TLSA без AD-біта, приймаюча сторона трактує його як insecure і мовчки ігнорує DANE. Пошта ходить, але захисту немає, і ви про це не дізнаєтесь, поки хтось не перевірить руками.
bash
# Чи підписаний MX-ланцюг і чи є AD-біт
dig +dnssec MX evilmail.pro
delv @1.1.1.1 _25._tcp.mail.evilmail.pro TLSA
# delv у виводі має показати "fully validated" — це і є доказ AD
# Чи є DS у батьківській зоні
dig DS evilmail.pro +short
Якщо delv каже ; fully validated — ланцюг цілий. Якщо ; unsigned answer — DANE у вас декоративний.
Анатомія TLSA-запису: три байти, які все вирішують
Ім'я TLSA-запису будується за схемою _<port>._<proto>.<hostname>. Для SMTP це _25._tcp. перед іменем MX-хоста — не самого домену. Тобто якщо evilmail.pro має MX 10 mail.evilmail.pro, запис живе на _25._tcp.mail.evilmail.pro. Кілька MX — кілька наборів TLSA, по одному на кожен хост.
Далі йдуть три числові поля, які визначають усе:
Usage — на що дивимось. 0 = PKIX-TA, 1 = PKIX-EE (обидва вимагають валідного публічного CA — для SMTP практично не застосовують), 2 = DANE-TA (прив'язка до вашого CA/проміжного сертифіката), 3 = DANE-EE (прив'язка до кінцевого сертифіката, самопідписаний ОК). RFC 7672 рекомендує для пошти саме 2 і 3.
Selector — що саме хешуємо. 0 = весь сертифікат, 1 = тільки SubjectPublicKeyInfo (SPKI, публічний ключ). 1 майже завжди правильний: доки ключ не змінюється, оновлення сертифіката не ламає TLSA.
Matching — як. 0 = повне порівняння, 1 = SHA-256, 2 = SHA-512. На практиці 1.
Найпоширеніша робоча комбінація — `3 1 1`: DANE-EE + SPKI + SHA-256. Друга за популярністю — `2 1 1`: прив'язка до проміжного CA (наприклад, до Let's Encrypt E5/E6), зручна, якщо ключ листка часто змінюється, а проміжний — рідко.
Генеруємо і публікуємо TLSA
Відбиток 3 1 1 — це SHA-256 від SubjectPublicKeyInfo у DER. Його можна взяти або з сертифіката, або напряму з приватного ключа (результат ідентичний, бо публічний ключ той самий):
bash
# 3 1 1 з живого сертифіката
openssl x509 -in /etc/letsencrypt/live/mail.evilmail.pro/cert.pem \
-noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -hex | awk '{print $NF}'
# те саме, але з приватного ключа (зручно згенерувати відбиток НАСТУПНОГО серта заздалегідь)
openssl pkey -in privkey.pem -pubout -outform DER \
| openssl dgst -sha256 -hex | awk '{print $NF}'
На виході — 64 hex-символи. Повний запис виглядає так:
dns
_25._tcp.mail.evilmail.pro. 3600 IN TLSA 3 1 1 a1b2c3d4...ff00
Хто не любить возитися з openssl, може взяти hash-slinger:
Для 2 1 1 беремо не листовий сертифікат, а проміжний із chain.pem. У нас зонами керує PowerDNS, тож запис публікується через API (/api/v1/servers/localhost/zones/... з rrset типу TLSA), а не правкою файлу зони руками. Принцип той самий у BIND — просто додати рядок і перепідписати зону.
Вмикаємо DANE на відправку в Postfix
Postfix перевіряє DANE тільки коли має валідуючий DNS. Два рядки в /etc/postfix/main.cf:
ini
# без цього рядка DANE мовчки ігнорується — резолвер має віддавати AD-біт
smtp_dns_support_level = dnssec
# dane = опортуністично-посилений: є TLSA → звіряємо; немає → звичайний opportunistic
# dane-only = жорсткий: TLSA є, але не збігся → лист відкладається (bounce), plaintext НЕ йде
smtp_tls_security_level = dane
Різниця критична. dane — розумний дефолт для загального вихідного потоку: там, де є TLSA, Postfix посилює перевірку, де немає — не ламає доставку. dane-only — для конкретних чутливих напрямків, коли ви радше відкладете лист, ніж дозволите downgrade. У логах успіх виглядає так:
Verified TLS connection established to mail.posteo.de[...]:25:
TLSv1.3 ... Matched fingerprint=SHA256:...
А провал збігу — так:
Server certificate not trusted
... TLSA lookup error for mail.example.com:25
Важливо розуміти симетрію: TLSA-запис на вашому MX захищає не вас, а тих, хто вам пише — це публічний контракт, який кожен відправник із DANE зобов'язаний виконати. А smtp_tls_security_level = dane у вашому Postfix захищає вашу вихідну пошту до чужих серверів.
Ось той самий сценарій з початку статті. certbot без спеціальних опцій генерує новий ключ при кожному оновленні. Новий ключ → новий SPKI → старий 3 1 1 у DNS більше не збігається. Кожен валідатор DANE (Deutsche Telekom, GMX, web.de, mail.ru, Yandex, Posteo, mailbox.org) починає відбивати вашу пошту з TLSA mismatch. Gmail і Outlook — ні, бо вони на MTA-STS; саме тому проблему помічають не одразу, а по частині трафіку.
Причина завжди одна: сертифікат замінили раніше, ніж опублікували новий TLSA. Правильний порядок — рівно навпаки:
Тобто: опублікувати другий TLSA поряд зі старим → дочекатися 2×TTL → розгорнути новий сертифікат (обидва відбитки зараз валідні) → через 1-2 TTL видалити старий запис. TLSA-набір може містити кілька записів одночасно — валідатор приймає збіг із будь-яким.
Але найдешевший спосіб — взагалі не рухати TLSA. Зафіксуйте ключ, і SPKI (а отже 3 1 1) ніколи не зміниться:
З --reuse-key оновлення сертифіката залишає той самий публічний ключ, TLSA-запис лишається чинним роками, і вся драма з порядком дій зникає. Якщо ж ключ ротуєте свідомо (це теж захист), тоді потрібен --deploy-hook, який публікує новий TLSA на етапі підготовки, а не після встановлення. Публікація TLSA має завжди випереджати деплой ключа — це не рекомендація, а єдиний коректний порядок.
Моніторинг: частина рецепта, а не додаток
DANE ламається тихо. Тому перевірку збігу треба автоматизувати так, щоб алерт приходив до проблеми, а не з першим bounce.
[ ] За потреби піднято до dane-only на чутливих напрямках.
[ ] Cron-моніторинг збігу живого SPKI з TLSA + контроль строку RRSIG.
[ ] Фінальний тест через internet.nl і posttls-finger.
DANE безкоштовний, працює в мільйонах доменів і закриває саме ту діру, яку STARTTLS залишає відкритою. На всіх MX evilmail.pro стоїть 3 1 1 із фіксованим ключем — рівно тому, що ротаційну пастку дешевше усунути один раз, ніж ловити о третій ночі.