-
Notifications
You must be signed in to change notification settings - Fork 564
Description
Android application type
Classic Xamarin.Android (MonoAndroid12.0, etc.), Android for .NET (net6.0-android, etc.)
Affected platform version
Xamarin Android 13, .NET 7.0.100
Description
AndroidMessageHandler incorrectly validates SSL certificate after a redirect to another domain if and only if ServerCertificateCustomValidationCallback is set.
Consider the following code:
// Redirects to facebook.com
const string url = "https://cutt.ly/n0HotDr";
using var handler = new AndroidMessageHandler()
{
ServerCertificateCustomValidationCallback = (message, certificate, chain, errors) => errors == SslPolicyErrors.None
};
using var client = new HttpClient(handler);
await client.GetAsync(url);When I put a breakpoint inside the lambda above, I can see that I hit it twice:
- First time I see that the cutt.ly certificate is being validated and the
HttpRequestMessage.RequestUripoints to cutt.ly as well - that's expected. - Second time we validate a certificate for a domain we got redirected to (e.g. Facebook), but the
HttpRequestMessage.RequestUristill points to cutt.ly -- and theerrorsparameter hasSslPolicyErrors.RemoteCertificateNameMismatchvalue.
If I change AndroidMessageHandler to the managed handler, it works fine. Also, if I continue to use AndroidMessageHandler, but simply don't set ServerCertificateCustomValidationCallback, it works fine as well.
So it looks like the problem is somewhere inside the custom trust manager the handler uses when the validation callback is not null: https://github.com/xamarin/xamarin-android/blob/2a10e9696cf51a467db58e5c73a4fab764507482/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs#L1093 I believe the request URI here is not updated after a redirect, but it should: https://github.com/xamarin/xamarin-android/blob/2a10e9696cf51a467db58e5c73a4fab764507482/src/Mono.Android/Xamarin.Android.Net/ServerCertificateCustomValidator.cs#L85
Steps to Reproduce
- Create a Xamarin.Android app a .NET 6/7 Android app
- Use
AndroidMessageHandlerto send a request that will be redirected to anotehr domain
Expected:
No issues
Actual:
An exception with the name mismatch
Did you find any workaround?
- Using a managed handler (not fully reliable with .NET 6/7 too).
- Not assigning
ServerCertificateCustomValidationCallback. In my case I only use it in production builds to log the errors if there are any, so I was able to work around this with aDelegatingHandlerand a fewtry/catch-es. For those who use the callback to apply some additional validation this may be a bigger deal.
Relevant log output
Java.Security.Cert.CertificateException: Exception_WasThrown, Java.Security.Cert.CertificateException
at Xamarin.Android.Net.ServerCertificateCustomValidator.TrustManager.CheckServerTrusted(X509Certificate[] , String )
at Javax.Net.Ssl.IX509TrustManagerInvoker.n_CheckServerTrusted_arrayLjava_security_cert_X509Certificate_Ljava_lang_String_(IntPtr , IntPtr , IntPtr , IntPtr )
at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLL_V(_JniMarshal_PPLL_V , IntPtr , IntPtr , IntPtr , IntPtr )
--- End of managed Java.Security.Cert.CertificateException stack trace ---
java.security.cert.CertificateException: The remote certificate was rejected by the provided RemoteCertificateValidationCallback.
at xamarin.android.net.ServerCertificateCustomValidator_TrustManager.n_checkServerTrusted(Native Method)
at xamarin.android.net.ServerCertificateCustomValidator_TrustManager.checkServerTrusted(ServerCertificateCustomValidator_TrustManager.java:42)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:254)
at com.android.org.conscrypt.ConscryptEngine.verifyCertificateChain(ConscryptEngine.java:1644)
at com.android.org.conscrypt.NativeCrypto.ENGINE_SSL_read_direct(Native Method)
at com.android.org.conscrypt.NativeSsl.readDirectByteBuffer(NativeSsl.java:568)
at com.android.org.conscrypt.ConscryptEngine.readPlaintextDataDirect(ConscryptEngine.java:1095)
at com.android.org.conscrypt.ConscryptEngine.readPlaintextData(ConscryptEngine.java:1079)
at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:876)
at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:747)
at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:712)
at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:849)
at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.access$100(ConscryptEngineSocket.java:722)
at com.android.org.conscrypt.ConscryptEngineSocket.doHandshake(ConscryptEngineSocket.java:238)
at com.android.org.conscrypt.ConscryptEngineSocket.startHandshake(ConscryptEngineSocket.java:217)
at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:196)
at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:153)
at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:116)
at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:186)
at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128)
at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:302)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:245)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131)
at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:90)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:30)