xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.49k stars 515 forks source link

Detecting certificate challenge in NSUrlSessionHandler #21688

Open mstefarov opened 3 days ago

mstefarov commented 3 days ago

I'm building cross-platform auth components, which include client-certificate (PKI) authentication. Currently only SocketsHttpHandler is fully functional, but I would like to support NSUrlSessionHandler as well.

The work on https://github.com/xamarin/xamarin-macios/issues/13856 got me most of the way there, but one problem remains: detecting when a server challenges my client for a client certificate. In the past the norm used to be to look for HTTP/401 or 403 replies, but now many TLS/SSLv3-secured services use error alerts instead. This happens when the connection is still negotiated so I have no HttpResponseMessage to work with — all I have is an HttpRequestException.

With SocketsHttpHandler on iOS, I have options. I can subscribe to LocalCertificateSelectionCallback and listen for events, or I can check HttpRequestException's InnerException (of type Interop+AppleCrypto+SslException) gives me a specific libsecurity error code via HResult. I can check if it matches e.g. errSSLPeerBadCert (-9825) and prompt user for a client certificate.

With vanilla NSUrlSession I can look for challenges with AuthenticationMethodClientCertificate protection space.

But with NSUrlSessionHandler, this challenge is doomed to fail unless I pre-load a correct certificate ahead of time, and I end up with a fairly generic "connection lost" HttpRequestException:

System.Net.Http.HttpRequestException: The network connection was lost.
 ---> Foundation.NSErrorException: Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={_kCFStreamErrorCodeKey=-4, NSUnderlyingError=0x600000c4cd20 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" ...}
   --- End of inner exception stack trace ---
   at System.Net.Http.NSUrlSessionHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/builder/azdo/_work/1/s/xamarin-macios/src/Foundation/NSUrlSessionHandler.cs:line 551
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)

I might be able to workaround this by e.g. falling back to SocketHttpHandler when I see that "network connection lost" error to double-check if this is a certificate challenge. But my life would be a lot easier if NSUrlSessionHandler itself could help detect a challenge, either by providing a client-certificate-selection callback, or by providing a more detailed error message.

P.S. I also made a similar request for SocketHttpHandler-on-Android in https://github.com/dotnet/runtime/issues/109532