Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Mono.Android] ServerCertificateCustomValidationCallback & hostname (#…
…7246) Fixes: #7149 Context: 48540d6 Context: https://docs.microsoft.com/en-us/answers/questions/885827/error-hostname-not-verified-when-sending-request-o.html Context: dotnet/docs-maui#600 Context: dotnet/maui#8131 Context: dotnet/runtime#70434 Commit 48540d6 added support to `AndroidMessageHandler.ServerCertificateCustomValidationCallback` for self-signed certificates. However, it didn't account for mismatches between the server's hostname and the certificate's hostname. This can cause issues when e.g. you use 10.0.2.2 to send requests to a local server with a self-signed dev certificate in ASP.NET: var handler = new AndroidMessageHandler { // Allow any cert; DO NOT USE IN PRODUCTION ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true, // … }; var client = new HttpClient(handler); var response = await client.SendAsync(message); This could result an exception similar to: System.Net.WebException: Hostname EXAMPLE.DOMAIN not verified: certificate: sha1/EXAMPLE_CERT_SHA1 DN: CN=EXAMPLE_CN subjectAltNames: [EXAMPLE_CN] ---> Javax.Net.Ssl.SSLPeerUnverifiedException: Hostname EXAMPLE.DOMAIN not verified: certificate: sha1/EXAMPLE_CERT_SHA1 DN: CN=EXAMPLE_CN subjectAltNames: [EXAMPLE_CN] at Java.Interop.JniEnvironment.InstanceMethods.CallVoidMethod(JniObjectReference instance, JniMethodInfo method, JniArgumentValue* args) at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeAbstractVoidMethod(String encodedMember, IJavaPeerable self, JniArgumentValue* parameters) at Javax.Net.Ssl.HttpsURLConnectionInvoker.Connect() at Xamarin.Android.Net.AndroidMessageHandler.<>c__DisplayClass125_0.<ConnectAsync>b__0() at System.Threading.Tasks.Task.InnerInvoke() at System.Threading.Tasks.Task.<>c.<.cctor>b__272_0(Object obj) at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state) --- End of managed Javax.Net.Ssl.SSLPeerUnverifiedException stack trace --- javax.net.ssl.SSLPeerUnverifiedException: Hostname EXAMPLE.DOMAIN not verified: certificate: sha1/EXAMPLE_CERT_SHA1 DN: CN=EXAMPLE_CN subjectAltNames: [EXAMPLE_CN] at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:205) 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:289) at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232) 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) --- End of inner exception stack trace --- at Xamarin.Android.Net.AndroidMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) Address this problem and aligns the behavior of `AndroidMessageHandler` with other platforms, where hostname verification is part of the server certificate custom validation callback. There are two significant changes: - The trust manager that wraps the custom callback now verifies the certificate hostname - Java's separate hostname verification is skipped There is only one aspect which is a little hacky: the [`javax.net.ssl.HostnameVerifier` interface][0] requires an instance of [`javax.net.ssl.SSLSession`][1], which we don't have access to in the trust manager. The default hostname verifier instead uses just a single getter to access the chain of certificates so it seems OK to me to have a dummy class that implements the interface that holds just the peer certificates. (I tried to find access to the session from `HttpsURLConnection` but there doesn't seem to be a way to access the `SSLEngine` and its `HandshakeSesion` property.) [0]: https://developer.android.com/reference/javax/net/ssl/HostnameVerifier [1]: https://developer.android.com/reference/javax/net/ssl/SSLSession
- Loading branch information