I use a HttpClient
to communicate with my server as shown below:
private static readonly HttpClientHandler Handler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
AllowAutoRedirect = true,
};
private static readonly HttpClient Client = new HttpClient(Handler, false);
The problem is that I get an System.AggregateException
exception on Android. The exception details can be seen in the screenshot below:
App is working perfectly fine on iOS though.
Server is using Let's Encrypt
certificate and the problem has only surfaced since a week ago after renewing the certificate.
I have already checked anything regarding certificate validity (Expiry, DNS names, etc.) and all seems to be OK.
CodePudding user response:
The red chain is what used to happen: the IdenTrust DST Root CA X3 is an old root certificate which is trusted pretty much everywhere, including on Android devices from 2.3.6 onwards. This is what LetsEncrypt used to use as their root, and it meant that everyone trusted them. However, this IdenTrust DST Root CA X3 recently expired, which means that a bunch of devices won't trust anything signed by it. LetsEncrypt needed to move to their own root certificate.
The blue chain is the ideal new one -- the ISRG Root X1 is LetsEncrypt's own root certificate, which is included on Android 7.1.1 . Android devices >= 7.1.1 will trust certificates which have been signed by this ISRG Root X1.
However, the problem is that old pre-7.1.1 Android devices don't know about ISRG Root X1, and don't trust this.
The workaround which LetsEncrypt is using is that old Android devices don't check whether the root certificate has expired. They therefore by default serve a chain which includes LetsEncrypt's root ISRG Root X1 certificate (which up-to-date devices trust), but also include a signature from that now-expired IdenTrust DST Root CA X3. This means that old Android devices trust the chain (as they trust the IdenTrust DST Root CA X3, and don't check whether it's expired), and newer devices also trust the chain (as they're able to work out that even though the root of the chain has expired, they still trust that middle ISRG Root X1 certificate as a valid root in its own right, and therefore trust it).
This is the green path, the one which LetsEncrypt currently serves by default.
However, the BoringSSL library used by xamarin-android isn't Android's SSL library. It 1) Doesn't trust the IdenTrust DST Root CA X3 because it's expired, and 2) Isn't smart enough to figure out that it does trust the ISRG Root X1 which is also in the chain. So if you serve the green chain in the image above, it doesn't trust it. Gack.
The options therefore are:
- Don't use BoringSSL and do use Android's SSL library. This means that xamarin-android behaves the same as other Android apps, and trusts the expired root. This is done by using
AndroidClientHandler
as described previously. This should fix Android >= 2.3.6. - Do use BoringSSL but remove the expired IdenTrust DST Root CA X3 from Android's trust store ("Digital Signature Trust Co. - DST Root CA X3" in the settings). This tricks BoringSSL into stopping its chain at the ISRG Root X1, which it trusts (on Android 7.1.1 ). However this will only work on Android devices which trust the ISRG Root X1, which is 7.1.1 .
- Do use BoringSSL, and change your server to serve a chain which roots in the ISRG Root X1, rather than the expired IdenTrust DST Root CA X3 (the blue chain in the image above), using
--preferred-chain "ISRG Root X1"
. This means that BoringSSL ignores the IdenTrust DST Root CA X3 entirely, and roots to the ISRG Root X1. This again will only work on Android devices which trust the ISRG Root X1, i.e. 7.1.1 . - Do the same as 3, but by manually editing fullchain.pem.
- Use another CA such as ZeroSSL, which uses a root which is trusted back to Android 2.2, and which won't expire until 2038.