-
Notifications
You must be signed in to change notification settings - Fork 196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document how to work with a local ASP.NET Core backend during development #600
Comments
|
In typical fashion this won't be a straight port. The old way of connecting to localhost from Android doesn't work in MAUI - see dotnet/android#6665 |
@davidbritch dotnet/android#6665 is a merged PR. Is there an active issue tracking the problem you are seeing? |
@danroth27 It's more a question of what modifications are required to the Android config in the MAUI app to make it connect to both HTTP and HTTPS localhost without errors. I'm working on it. |
I am also experiencing problems with connecting to localhost from Android in MAUI and searching for a solution to this issue. Would appreciate any update/solution that you may have. |
Hi @AndreduToit There'll be docs on doing this by the end of the week. In the meantime, let's see if I can help you. Are you trying to connecting to HTTP localhost or HTTPS? The solution is different for each scheme. |
Hello @davidbritch I am trying to connect to HTTPS with my .NET Maui App. I have added the following to my Maui App:
I re-produced kestrel.cer in dotnet with command line 'dotnet dev-certs https --clean' and 'dotnet dev-certs https --trust' and exported the certificates with certmgr.exe to the folder Android\Resources\raw in my Maui App (tried both DER encoded and Base-64 encoded formats in 'Personal' and 'Trusted Root Certificate Authorities' folders). I would very much appreciate any tips or guidance that you can give me. |
Hi @AndreduToit The network_security_config.xml approach is for HTTP. The issue on HTTPS is that Android doesn't trust self-signed certificates, and so you have to add code to ignore SSL errors for localhost. Add the following class to your project: public class HttpsClientHandlerService : IHttpsClientHandlerService
{
public HttpMessageHandler GetPlatformMessageHandler()
{
#if ANDROID
var handler = new CustomAndroidMessageHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
if (cert != null && cert.Issuer.Equals("CN=localhost"))
return true;
return errors == System.Net.Security.SslPolicyErrors.None;
};
return handler;
#elif IOS
var handler = new NSUrlSessionHandler
{
TrustOverrideForUrl = IsHttpsLocalhost
};
return handler;
#elif WINDOWS || MACCATALYST
return null;
#else
throw new PlatformNotSupportedException("Only Android, iOS, MacCatalyst, and Windows supported.");
#endif
}
#if ANDROID
internal sealed class CustomAndroidMessageHandler : Xamarin.Android.Net.AndroidMessageHandler
{
protected override Javax.Net.Ssl.IHostnameVerifier GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection connection)
=> new CustomHostnameVerifier();
private sealed class CustomHostnameVerifier : Java.Lang.Object, Javax.Net.Ssl.IHostnameVerifier
{
public bool Verify(string hostname, Javax.Net.Ssl.ISSLSession session)
{
return Javax.Net.Ssl.HttpsURLConnection.DefaultHostnameVerifier.Verify(hostname, session) ||
hostname == "10.0.2.2" && session.PeerPrincipal?.Name == "CN=localhost";
}
}
}
#elif IOS
public bool IsHttpsLocalhost(NSUrlSessionHandler sender, string url, Security.SecTrust trust)
{
if (url.StartsWith("https://localhost"))
return true;
return false;
}
#endif
} Then invoke it when you create your #if DEBUG
clientHandlerService = new HttpsClientHandlerService();
client = new HttpClient(clientHandlerService.GetPlatformMessageHandler());
#else
client = new HttpClient();
#endif Hopefully this will unblock you. |
Hi @davidbritch |
Hello @davidbritch - I am unable to locate IHttpsClientHandlerService - assmebly/namespace - nuget. |
You could delete that reference (if you want). It's just: public interface IHttpsClientHandlerService
{
HttpMessageHandler GetPlatformMessageHandler();
} |
@davidbritch Can you advise - for HTTPS, do I remove the following:
Also, let you know - I tried the same code previously as suggested by @Eilon here dotnet/maui#8131 and provided here: https://gist.github.com/Eilon/49e3c5216abfa3eba81e453d45cba2d4 Did not work before, but I try again. Will let you know. |
@davidbritch |
Glad you're unblocked! Yes, you can remove the following if your localhost service is running over HTTPS:
Similarly, you don't need the code-based approach from above if your localhost service is running over HTTP. |
@davidbritch thank you for helping me to unblock this - it was super frustrating up to here but I am happy now........ can push ahead. |
@davidbritch I'd like to thank you, too, for the clarification about the Addendum: can you please explain why Additionally, from what I read over at xamarin/xamarin-macios, the #elif IOS to #elif IOS || MACCATALYST |
' why null instead of new() is return for Windows & Mac Catalyst?'
I also found that calling instantiating as follows new HttpClient(null) throws and exception. For this reason (and because I always enable nullable in my code), I added a property for HttpClient in my helper class - which builds a HttpMessageHandler (GetPlatformMessageHandler) and then on a null test returns either new HttpClient() or new HttpClient(handler) |
Two things here:
HTTP + HTTPS localhost works out of the box on MacCatalyst, without having to modify |
Exactly what I do in the code on my Windows machine (and is the code that will make it into the docs). |
Understood. Then my approach is just different: I'll return a (new) handler instead of a prepared
But I've been talking about the "force-pass localhost certs" workaround here & as I said, looking at the aforementioned Xamarin repo's code, iOS & MacCatalyst seem to use the same class, hence overriding
And that totally confuses me now.. 😅 |
Yes, both iOS + MacCatalyst use Hence no need to set the |
I share the following for your information and in case someone else can benefit from it. The code for Verify method of the CustomHostnameVerifier calls suggested by yourself is as follows: public bool Verify(string hostname, Javax.Net.Ssl.ISSLSession session)
{
return Javax.Net.Ssl.HttpsURLConnection.DefaultHostnameVerifier.Verify(hostname, session) ||
hostname == "10.0.2.2" && session.PeerPrincipal.Name == "CN=localhost";
} This code results in some null reference warnings. public bool Verify(string? hostname, Javax.Net.Ssl.ISSLSession? session)
{
return Javax.Net.Ssl.HttpsURLConnection.DefaultHostnameVerifier?.Verify(hostname, session) ?? false ||
hostname == "10.0.2.2" && session?.PeerPrincipal?.Name == "CN=localhost";
} The following code seems top be working - without any null reference warnings: public bool Verify(string? hostname, Javax.Net.Ssl.ISSLSession? session)
{
if (string.IsNullOrEmpty(hostname) || session == null) return false;
var httpsDefaultVerified = Javax.Net.Ssl.HttpsURLConnection.DefaultHostnameVerifier?.Verify(hostname, session) ?? false;
var httpsCustomVerified = hostname == "10.0.2.2" && session.PeerPrincipal?.Name == "CN=localhost";
return httpsDefaultVerified || httpsCustomVerified;
} |
…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
Just FYI since dotnet/android#7246 was merged, the |
Port the following Xamarin docs on consuming backend APIs from a native client app during development:
We'll also need to migrate the Todo sample app to .NET MAUI as well.
This is needed so that we can update the ASP.NET Core tutorial on building an ASP.NET Core backend for a native client: dotnet/AspNetCore.Docs#26109.
The text was updated successfully, but these errors were encountered: