A missing blog post image

Introduction

This blog post isn’t about explaining MTA-STS (there are already plenty of resources on the Internet to read about this), but more about its integration with custom domains when e-mail is handled by ProtonMail.

@Wonderfall wrote some years ago a very interesting blog post detailing how ProtonMail lack of MTA-STS support could be circumvented using two DNS entries and a statically served Web page.

However, one month and half ago, someone on Reddit has come up with an elegant solution, presented as “zero maintenance”, leveraging a CloudFlare worker (source, inspired by CloudFlare own documentation) :

export default {
    async fetch(request, env, ctx) {
        return fetch('https://mta-sts.protonmail.ch/.well-known/mta-sts.txt');
    },
};

So we will properly detail how to implement this solution, but more importantly how we can self-host a CloudFlare worker alternative, so as not to depend on (another) third-party service provider.

How to setup MTA-STS

Below configuration and code snippets use fake example.org domain name.

Create a new mta-sts subdomain

Before anything else, we have to create a mta-sts.example.org subdomain. So you can create an A or AAAA record which targets a Web server you administrate. A record type is currently safer, just in case the MTA on the verge of connecting to ProtonMail servers lack of IPv6 support.

Create a new Apache site : TLS, reverse proxy and cache

Once it’s done and DNS propagation took place, you have to setup TLS for this domain on the Web server, as you would do for any other site.

As MTA-STS policies aren’t really known to change over time, we enable caching to prevent systematic connections to ProtonMail servers.

For instance, your Apache VHOST configurations could look like (/etc/apache2/sites-available/mta-sts.example.org.conf) :

<VirtualHost *:443>
	ServerName mta-sts.example.org
	ServerAdmin webmaster@example.org

	# Allow proxy engine to connect to an HTTPS server
	SSLProxyEngine On

	# Setup caching to make it honor ACL and cache the response for 1 day
	CacheQuickHandler Off
	CacheSocache shmcb
	CacheSocacheMaxTime 86400

	# Ignore ProtonMail "Cache-Control" and "Expires" headers
	Header unset Cache-Control
	Header unset Expires
	# Ignore lack of "Last Modified" header in ProtonMail response
	CacheIgnoreNoLastMod On

	# Transparently serve Proton's MTA-STS policy
	<Location /.well-known/mta-sts.txt>
		CacheEnable socache

		# Result will be cached, always close TCP session afterwards
		ProxyPass https://mta-sts.protonmail.ch/.well-known/mta-sts.txt disablereuse=on
		ProxyPassReverse https://mta-sts.protonmail.ch/.well-known/mta-sts.txt
	</Location>

	ErrorLog ${APACHE_LOG_DIR}/mta-sts.example.org_error.log
	CustomLog ${APACHE_LOG_DIR}/mta-sts.example.org_access.log combined

	SSLEngine On
	SSLCertificateFile /path/to/mta-sts.example.org/fullchain.pem
	SSLCertificateKeyFile /path/to/mta-sts.example.org/privkey.pem

	Header always set Strict-Transport-Security: "max-age=63072000"
</VirtualHost>

<VirtualHost *:80>
	ServerName mta-sts.example.org
	ServerAdmin webmaster@example.org

	RewriteEngine On
	RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

You now only have to enable some Apache modules as well as above new site, and reload Apache configuration (Debian procedure) :

a2enmod cache_socache proxy ssl
a2ensite mta-sts.example.org.conf
systemctl reload apache2.service

# If you opt for disk-based caching (mod_cache_disk), don't forget to enable Apache2 cleaning systemd service
systemctl enable --now apache-htclean.service

You can now check MTA-STS policy is properly enforced for your domain by downloading it :

curl https://mta-sts.example.org/.well-known/mta-sts.txt

version: STSv1
mode: enforce
mx: mail.protonmail.ch
mx: mailsec.protonmail.ch
max_age: 604800

Setup MTA-STS discovery through _mta-sts subdomain

Now your MTA-STS policy is available to the world, you have to allow its “discovery”, through an additional TXT DNS record.

So the idea here, instead of copying ProtonMail own TXT record value (dig +noall +answer TXT _mta-sts.protonmail.ch) and adding it ourselves as _mta-sts.example.org, is rather to simply create a CNAME for _mta-sts.example.org, pointing to _mta-sts.protonmail.ch.

This way, when MTA will try to probe SMTP server for MTA-STS support, they will hit ProtonMail own beacon :

dig +noall +answer TXT _mta-sts.example.org

_mta-sts.example.org.	1799	IN	CNAME	_mta-sts.protonmail.ch.
_mta-sts.protonmail.ch.	1200	IN	TXT		"v=STSv1; id=190906205100Z;"

That’s it ! MTA-STS policy should be enforced for your domain :innocent:

How to setup TLS-RPT

Wait ! Don’t close this tab yet ! Let’s take the opportunity to have access to our DNS administration console to also setup TLS-RPT.

TLS-RPT (“TLS Reporting”) allows you to receive reports about e-mail delivering issues, which are related to TLS connection. As we just have enforced a strict TLS policy, this is important to at least be notified in case of errors.

So first, let’s query ProtonMail’s TLS-RPT configuration :

dig +noall +answer TXT _smtp._tls.protonmail.ch

_smtp._tls.protonmail.ch. 1200	IN	TXT	"v=TLSRPTv1; rua=https://reports.proton.me/reports/smtptls"

So ProtonMail asks MTA to POST TLS-RPT reports to their own API endpoint.

However, as TLS-RPT allows us to specify more than one RUA (which stands for “Reporting URI for Aggregated reports”) :

The record supports the ability to declare more than one rua, and if there exists more than one, the reporter MAY attempt to deliver to each of the supported rua destinations.

So let’s leverage this specification detail to send report to ourselves, as well as ProtonMail (hoping they actually do something about them).

You only have to create a new TXT record for _smtp._tls.example.org which contains the value v=TLSRPTv1; rua=mailto:security@example.org,https://reports.proton.me/reports/smtptls. Don’t forget to adapt the e-mail address which should receive those reports !

Check your security policy

If you’re happy with your setup, you can then test it using (for instance, and I’ve absolutely nothing to do with them) EasyDMARC. A TLS-RPT checker is also available.

Conclusion

I hope this post has helped you ! As always, credits go to their respective owners and comments are appreciated :pray:


> Post header image was generated using ChatGPT (please, never again)