Let’s Encrypt is soon going to do something that I personally call “root-cross-signing”. This has definetly impact on some TLS clients, which is why I decided to give it some attention.
But first, let’s explain what the potential problem is: For a certificate to be trusted, it needs to be signed by a trusted Certificate Authority (CA) – the shift-of-trust model I already mentioned in earlier posts. Traditionally, the trust anchor – the point where the trust starts – used to be something called a root certificate1. Those are not signed by anyone (well, they technically sign themselves, but that doesn’t really do that much) and are shipped with your TLS client, or operating system (or similar). We call those certificates that are shipped with your client the trust store.
A root certificate usually signs at least one intermediate certificate (which in turn can sign other – lower – intermediates) and the lowest intermediate certificate signs the leaf certificate. That is the certificate of the actual service you’re connecting to, e.g this blog. This collection of certificates is what we call a certificate chain2. The server sends this chain to the client and the client then verifies this chain, checking whether all signatures are correct and whether the client trusts at least one (or the highest) of those certificates (remember, the clients has a trust store that contains root certificates)3. At least in theory. In practice this is much more difficult, as for example modern browsers to things like caching trust anchors + intermediates or auto-fetching intermediates, there is more than one chain, there is more than one root, chains are send with incorrect ordering and much more.
The actual issue
So, what has all of this to do with Let’s Encrypt?
As they tell us in their blog, they’re soon going to use a chain that looks like this: leaf -> R3 -> ISRG Root X1 -> DST Root CA X3. They’re going to use this chain for years to come. But DST Root CA X3 expires later this year! What happens then?
What will happen is heavily client specific, and depends on how your client verifies certificate chains. Some clients will not have a problem, others will completly break.
Let’s Encrypt tried to downplay the issue a bit, stating that Android compability is important and only “very old” other clients will have issues. I believe this isn’t the entire truth and I want to have a closer look at compatibility across clients, hence this blog post.
Why are we even doing this? Why not just go with ISRG Root X1?
As Let’s Encrypt explains, this is due to the fact that so many devices, especially Android, have older trust stores that do not get updates. Those devices have DST Root CA X3 in their trust store, but not ISRG Root X1. Hence the magic dance to try to support those old devices. But sadly, this will break compatibility with newer devices. Lets have a closer look at compatibility.
Compatibility across clients
Android
The new chain was specifically created for Android devices (>= 4.0) and thus no compatibility issues are expected on those Androids.
OpenSSL
OpenSSL has really bad chain validation in older versions and will thus try to validate up to the highest certificate in the chain, even if it should know that a lower certificate is a root and could just stop validating there. This means that older versions of OpenSSL will break with Let’s Encrypts new default chain, even if the trust store is up to date.
Affected is OpenSSL version less than 1.1 4. Yes, OpenSSL 1.1.0 was technically released 4 years ago, but many, many computers I know still ship something older. Take for example Ubuntu 16.04 which is supported until 2024 – that distro still uses OpenSSL 1.0.2g and thus needs the manual fix (see footnote above/below) in order to connect with Let’s Encrypt subscriber servers starting late 2021. Many people who need FIPS certification still use 1.0, because the FIPS module isn’t available in OpenSSL 1.1. I don’t have statistics about the used versions in the wild, but I expect millions of OpenSSL versions that aren’t yet running 1.1+ out here. Most of these will have trouble if no one does anything.
GnuTLS
A library I often see when I work with Debian/Ubuntu related things, or anything else that doesn’t like OpenSSL for some reason. Those products often tend to use GnuTLS, as its FOSS.
GnuTLS will break with the new Let’s Encrypt chain starting late 2021, unless you’re running a version newer or equal to 3.6.14 (released 2020). Again, lets have a look at what the distributions ship:
- Debian 10 Buster currently ships GnuTLS 3.6.7, which is < 3.6.14, so it’s probably affected. Note that this is the latest Debian release, as of now there is nothing newer (but Debian Bullseye will be released this summer, which will have a fixed version). However, many people will run buster – or even older versions, you still see many stretch installations in the wild – for years to come. The GnuTLS versions here will probably all be affected. I’m not aware of a workaround for GnuTLS.
Update: Numerous distributions have started to ship backports/workarounds to affected GnuTLS versions. - For Ubuntu, apparently everything older than Ubuntu 20.10 (Groovy Gorilla) is affected. This means that even the current LTS release Ubuntu 20.04 LTS “Focal Fossa”, supported until 2025+, is most likely affected (they apparently ship GnuTLS 3.6.13, one version below the fix).
Update: Ubuntu recently backported patches to supported versions.
In summary, many distributions that don’t have a very fast version cycle will ship slightly older GnuTLS versions, and everything that is older than a year will likely break.
LibreSSL
Another library I sometimes see in the wild, especially when OpenBSD is involved. I’m not that familiar with it, so I’m going to make it short.
Everything less than 3.2.0 is affected, which was released in June 2020. So you need some version that is less than a year old to be not affected.
Windows, macOS and browsers native TLS libraries
Let’s Encrypt says those are not affected – at least someone who is fine.
Conclusion
I believe that Let’s Encrypts decision to use an Android-compatible chain by default will break setups for many people. That doesn’t mean that it was the wrong call, but it means that people need to be aware that their scripts, API integrations or whatever non-browser-non-android they have will most likely break.
What can I do if I don’t want all of this?
You can manually configure the alternate chain Let’s Encrypt will offer later this year. This means that you’re breaking Android compatiblity with Android < 7.1, but will preserve compatibility with all others, if their trust store contains ISRG Root X1 (which is a hidden requirement for all non-Android devices starting in late 2021, this applies for both chains).
Further reading
I highly recommend Ryan Sleevies Implementation Showdown, it explains in a much greater detail about how implementations did it wrong and it also gives you an idea what clients may be affected (it covers many more clients than those mentioned in this post).
Sources
Details about affected version numbers were taken from here:
- https://community.letsencrypt.org/t/openssl-client-compatibility-changes-for-let-s-encrypt-certificates/143816
- https://medium.com/@sleevi_/path-building-vs-path-verifying-implementation-showdown-39a9272b2820
- Package versions included in certain distributions were taken from the distributions package search.
- The trust anchor does not need to be the root certificate though, which many clients do not understand.
- Note that the root certificate itself is usually not part of the chain, as your browser already knows it. Sending it would be useless bytes on the wire.
- I recognize that this section may either introduce too much terminology, or is not easily understandable for some readers, or is not detailed enough. Maybe this post is better than mine in explaining the details.
- I’ve heard you can fix OpenSSL 1.0.2+ by manually setting the OpenSSL flag X509_V_FLAG_TRUSTED_FIRST, this flag is set by default since OpenSSL 1.1.0, hence why we’re listing 1.1 as fixed.