mjl- / mox

modern full-featured open source secure mail server for low-maintenance self-hosted email
https://www.xmox.nl
MIT License
3.62k stars 107 forks source link

OCSP stapling #211

Open romner-set opened 1 month ago

romner-set commented 1 month ago

Mox currently doesn't seem to support OCSP stapling for SMTPS/IMAPS, which among other things makes it impossible to use OCSP Must-Staple certificates. Any plans to add this?

romner-set commented 1 month ago

On further thought I don't think this is actually very useful, since the whole point of DANE is that the TLS certificates don't need to be CA-issued anyway.

romner-set commented 1 month ago

Okay, apparently they do need to be CA-issued for at least the few mail clients I've tried to correctly recognize them, so this is a valid issue after all.

mjl- commented 1 month ago

Mox indeed doesn't do any OCSP stapling. I don't think Go's TLS package/library uses it (client side to verify or server side to staple). In general, I think people are moving away from OCSP. Let's encrypt appears to just move to shorter-lived TLS certificates. Also see https://letsencrypt.org/2024/07/23/replacing-ocsp-with-crls.html. Also see https://github.com/golang/go/issues/51064, about adding ocsp stapling support to Go's acme/autocert package. That references https://blog.apnic.net/2022/03/22/whats-going-on-with-certificate-revocation/.

It could still be worth implementing, ideally in acme/autocert, which we would get with an update.

I'm wondering how you're getting these must-staple certificates. Is it a requirement for a CA you're using, or is it something you opt into because you want to use it?

romner-set commented 1 month ago

I'm using ZeroSSL instead of Let's Encrypt which currently only supports OCSP, and I don't like the privacy implications of having to connect to a third party on every connection. But yeah, must-staple is an opt-in thing.

How difficult would OCSP stapling be to implement? Even though Let's Encrypt just announced their intent to remove OCSP support completely, other CAs still fully support it and some don't support CRLs at all as of yet. I don't think it'll be going away for at least a few years, and until then stapling has a lot of benefits even outside of being necessary for using Must-Staple certs.

mjl- commented 1 month ago

Indeed, ocsp done by every client seems problematic, but ocsp stapling seems much more reasonable. I had a look around the RFCs involved (6960 for OCSP, 6961 for multiple ocsp responses including for intermediate CAs, 7633 for must-staple), and existing Go code.

It seems we can serve OCSP stapled responses by setting OCSPStaple in our certificates: https://pkg.go.dev/crypto/tls#Certificate We can probably get the addresses from the ocsp server from the x509 certificate, see OCSPServer in https://pkg.go.dev/crypto/x509#Certificate Getting OCSP responses from an OCSP server can probably be gotten using https://pkg.go.dev/golang.org/x/crypto/ocsp. That package claims to be for the original RFC 2560, which was obsoleted by the new 6960. Would be good to see if anything important changed in between.

It seems like we should just always attempt ocsp-staple certificates that contain ocsp servers. If we can't get a staple, we could just use the certificate without ocsp staple. TLS clients can decide what to do then: contact the ocsp server themselves, fail the connection, continue anyway. For must-staple, it's probably mandated that clients don't ignore missing stapling, but at first glance I don't see a reason to preemptively fail such connections server-side.

I think we should not implement this in autocert. Mox can also use certificates/keys it reads from disk, managed by other software (eg certbot). Those certificate may require, or will at least benefit, from OCSP stapling. In mox-/config.go, we read the config files and create the TLS configs used during TLS connections. We should set a new GetCertificate handler in those tls.Config's, see https://pkg.go.dev/crypto/tls#Config. We should wrap our existing GetCertificate function that we set when autocert is in use, used to request certs using ACME.

We should store the last valid OCSP response per certificate somewhere, probably in a mox-global database (eg data/ocsp.db). At startup, we'll go through the certs, and get new OCSP responses when some our outdated. The OCSP response type in the golang.org/x package has time.Time fields that probably indicate how long responses are valid. We should probably keep track of outstanding OCSP requests, and queue new incoming TLS connections that need the expected response (only when currently known OCSP isn't valid anymore) for a short while (handful seconds).

Some questions I don't yet have answers too:

Seems worthwhile to implement. If you have time and would like to give a shot, that would be nice, I could give pointers. Otherwise I'll get to it at some point (but there are more todo's on the list, will take a while).

You tested with several mail clients right? Which ones were enforcing must-staple, and which didn't (if any)? Would be good to know for testing, and future questions about intereoperability.

romner-set commented 1 month ago

I've got my hands full at the moment, but I might find the time at some point so I'll let you know if that happens. As for the clients, from my testing msmtp and sendmail enforce must-staple while K-9 Mail and the builtin mail in Vivaldi don't.