SMTP MTA STS with SMTP TLS Reporting
I watched a DNS request for _mta-sts.randy7.com
get rejected and it made me wonder.
What was that?
It’s part of SMTP MTA Strict Transport Security.
It uses DNS and HTTPS to prevent downgrade attacks, so email isn’t sent as plaintext when encryption is available.
Turning off non-encrypted transport modes would also prevent a downgrade attack but at the cost of some email being undeliverable. It’s all about risks and best practices.
My current email setup is a modified version of OpenSMTPD developer Gilles Chehade’s “Setting up a mail server with OpenSMTPD, Dovecot and Rspamd” post. He didn’t cover this, so I decided to try MTA-STS and SMTP TLS Reporting on my own.
This is only for receiving email securely from mail servers that check for MTA-STS. This does not include checking for MTA-STS when delivering email.
Current Status
-
Inbound email only comes directly to my SMTP server
- TLS optional
- No MTA-STS
- No SMTP TLS Reporting
- Rejected if sent from an address that looks residential
- Rejected if no reverse DNS
- Rejected if no forward-confirmed reverse DNS
-
Outbound email starts at my SMTP server
- Email is signed with DKIM before leaving
- Relayed to another SMTP server with good reputation for delivery
- (Outbound email could start at the relay…)
-
Outbound destination mail server
- Should check DKIM
- Should check SPF
Preparation
First, I prepared for SMTP TLS Reporting by adding the _smtp._tls
TXT record.
That’s all.
Now for MTA-STS.
Next, I added A
and AAAA
records for host mta-sts
so httpd
can eventually serve this URL: https://mta-sts.randy7.com/.well-known/mta-sts.txt
.
Finally, I added the DNS entry for _mta-sts
which has an id
that must be updated each time the policy file, mta-sts.txt
, is changed.
File: /var/nsd/zones/master/randy7.com
$ORIGIN randy7.com.
[...]
_smtp._tls IN TXT "v=TLSRPTv1;rua=mailto:postmaster@randy7.com"
_mta-sts IN TXT "v=STSv1; id=2021021101;"
mta-sts IN A 71.19.146.7
mta-sts IN AAAA 2605:2700:0:3:a800:ff:fe17:f907
[...]
Because the mta-sts.txt
file must be served over TLS, I added a mta-sts.randy7.com
TLS certificate.
File: /etc/acme-client.conf
[...]
domain mta-sts.randy7.com {
domain key "/etc/ssl/private/mta-sts.randy7.com.key"
domain full chain certificate "/etc/ssl/mta-sts.randy7.com.fullchain.pem"
sign with letsencrypt
}
[...]
Let’s not forget to renew this certificate.
File: /etc/daily.local
[...]
acme-client -v mta-sts.randy7.com && rcctl reload httpd
[...]
And because the certificate doesn’t exist yet, the acme-challenge will need to go over HTTP.
Also notice the mta-sts.txt
file is only served over HTTPS.
File: /etc/httpd.conf
[...]
server "mta-sts.randy7.com" {
listen on * port 80
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location * {
block
}
}
server "mta-sts.randy7.com" {
listen on * tls port 443
tls {
certificate "/etc/ssl/mta-sts.randy7.com.fullchain.pem"
key "/etc/ssl/private/mta-sts.randy7.com.key"
}
location "/.well-known/mta-sts.txt" {
root "/mta-sts"
request strip 1
}
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location * {
block
}
}
[...]
Finally, we need a policy. No testing here, just going full enforce mode for one week.
File: /var/www/mta-sts/mta-sts.txt
version: STSv1
mode: enforce
mx: randy7.com
max_age: 604800
Activation
Now that everything is prepared, we can start activating all the peices.
- Reload
httpd
for getting TLS certificate - Reload the zone file so
mta-sts
exists - Receive TLS certificate
- Reload
httpd
after receiving TLS certificate - Check
mta-sts.txt
- Check
txt
for host_mta-sts
- Check
txt
for host_smtp._tls
- Receive a test email
$ rcctl restart httpd
httpd(ok)
httpd(ok)
$ nsd-control reload randy7.com
ok
$ acme-client -v mta-sts.randy7.com
acme-client: /etc/ssl/private/mta.randy7.com.key: generated RSA domain key
[...]
acme-client: /etc/ssl/mta.randy7.com.fullchain.pem: created
$ rcctl restart httpd
httpd(ok)
httpd(ok)
$ ftp -o - https://mta-sts.randy7.com/.well-known/mta-sts.txt 2> /dev/null
version: STSv1
mode: enforce
mx: randy7.com
max_age: 604800
$ dig +short -t txt _mta-sts.randy7.com
"v=STSv1; id=2021021101;"
$ dig +short -t txt _smtp._tls.randy7.com
"v=TLSRPTv1;rua=mailto:postmaster@randy7.com"
Then I sent an email from a Gmail account to myself.
Here are DNS requests from Google, from tshark
.
74.125.44.144 randy7.com
74.125.40.6 randy7.com
74.125.44.69 _mta-sts.randy7.com
74.125.40.76 randy7.com
74.125.191.14 _mta-sts.randy7.com
66.249.64.177 mta-sts.randy7.com
66.249.79.124 mta-sts.randy7.com
Here is the HTTP request from /var/www/logs/access.log
.
mta-sts.randy7.com 74.125.150.77 "GET /.well-known/mta-sts.txt HTTP/1.1" 200 60
And here is the message delivery from /var/log/maillog
.
smtp connected address=209.85.160.172 host=mail-qt1-f172.google.com
smtp tls ciphers=TLSv1.3:AEAD-AES256-GCM-SHA384:256
smtp message msgid=6ac45a2e size=2723 nrcpt=1 proto=ESMTP
smtp envelope evpid=6ac45a2e0f4a01ba from=<[redacted]@gmail.com> to=<test@randy7.com>
mda delivery evpid=6ac45a2e0f4a01ba from=<[redacted]@gmail.com> to=<test@randy7.com> rcpt=<test@randy7.com> user=test delay=0s result=Ok stat=Delivered
smtp disconnected reason=quit
Success! Or so I thought.
The next day Google emailed me this:
{ "organization-name": "Google Inc.",
"date-range": {
"start-datetime": "2021-02-11T00:00:00Z",
"end-datetime": "2021-02-11T23:59:59Z" },
"contact-info": "smtp-tls-reporting@google.com",
"report-id": "2021-02-11T00:00:00Z_randy7.com",
"policies": [{
"policy": {
"policy-type": "no-policy-found",
"policy-domain": "randy7.com" },
"summary": {
"total-successful-session-count": 2,
"total-failure-session-count":0 }}]}
But there was nothing wrong, so I waited another day and then Google reported a success. One email sent using the policy, and one sent without the policy. I assume the one sent without the policy was the TLS report.
{ "organization-name":"Google Inc.",
"date-range": {
"start-datetime":"2021-02-12T00:00:00Z",
"end-datetime":"2021-02-12T23:59:59Z" },
"contact-info":"smtp-tls-reporting@google.com",
"report-id":"2021-02-12T00:00:00Z_randy7.com",
"policies": [
{
"policy": {
"policy-type":"no-policy-found",
"policy-domain":"randy7.com" },
"summary": {
"total-successful-session-count":1,
"total-failure-session-count":0 }
}, {
"policy": {
"policy-type":"sts",
"policy-string": [
"version: STSv1",
"mode: enforce",
"mx: randy7.com",
"max_age: 604800"],
"policy-domain":"randy7.com" },
"summary": {
"total-successful-session-count":2,
"total-failure-session-count":0 }}]}
Much better!
Thank you Google, httpd, Let’s Encrypt, nsd, OpenBSD, and OpenSMTPD.