Skip to content

android: Cannot detect when HTTPS server needs a client certificate with SocketsHttpHandler #109532

Description

@mstefarov

Description

I'm building a cross-platform auth library and I am stumped trying to detect a challenge for a client certificate (for PKI-secured HTTPS services). In the past the norm used to be to look for 401/Unauthorized replies, but these days 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.

On most platforms, I'm able to detect this. For example:

  • With SocketsHttpHandler on iOS, an InnerException (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 AndroidMessageHandler, an InnerException (Javax.Net.Ssl.SSLException) gives me a detailed message from OpenSSL that includes easily testable strings like "SSLV3_ALERT_BAD_CERTIFICATE".
  • Old Xamarin "Managed" handler on Android used to give me an InnerException with the string "CERTIFICATE_VERIFY_FAILED"
  • Old Xamarin "Native" handler on Android used to give me an Javax.Net.Ssl.SSLHandshakeException with a detailed inner Java.Security.Cert.CertificateException

But with SocketsHttpHandler on Android, the inner exception (Interop+AndroidCrypto+SslException) the HResult is always just SecurityStatusPalErrorCode.InternalError (14). I bet there is more information available to the interop but there does not seem to be any way for me to access it.

Using LocalCertificateSelectionCallback also doesn't help because it just fires once for all HTTPS connections, with no way to know whether a client certificate is required or not.

Reproduction Steps

Use an HttpClient backed by SocketsHttpHandler to make an HTTPS request to a server that requires client certificate authentication:

var client = new HttpClient(new SocketsHttpHandler());
try
{
    await client.GetAsync("https://your-pki-secured-server-here.com/");
}
catch (Exception ex)
{
    // try to figure out if we were asked for client certificate authentication here
}

Unfortunately I do not have a specific public-facing service to share, but it should be possible to set one up for testing.

Expected behavior

SocketsHttpHandler throws an informative exception, from which we can determine that the server needs a client certificate. Perhaps an HttpRequestException with HttpRequestError.UserAuthenticationError would work.

Actual behavior

SocketsHttpHandler throws a generic HttpRequestException with "Unknown" HttpRequestError.

System.Net.Http.HttpRequestException: An error occurred while sending the request.
---> System.IO.IOException: The read operation failed, see inner exception.
---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
---> Interop+AndroidCrypto+SslException: Exception of type 'Interop+AndroidCrypto+SslException' was thrown.

The inner-most exception has a generic HResult of 14 ("internal error").

Regression?

Regression only if you compare the behavior to the old "managed" monodroid HttpClient.

Known Workarounds

Using AndroidMessageHandler instead of SocketsHttpHandler.

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions