Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xamarin Forms https with Client Certificates throws WebException

I am trying to use client certificates for authentication in a Xamarin Forms App for iOS, but nothing seems to work. If I start the request the app waits to the default timeout of 100 seconds (I tried lowering it with HttpWebRequest.Timeout, but setting it seems to be ignored), after that, I get the following exception: Error getting response stream (ReadDone1): ReceiveFailure

The same code as a Windows console application works fine. Here some (simplified for readability) snippets to reproduce:

Client:

var clientCertificate = new X509Certificate2(data, pwd);
var result = await ExecuteRequest("https://server/user.aspx", clientCertificate);

public static async Task<string> ExecuteRequest(Uri uri, X509Certificate2 clientCertificate)
{
    var hwr = (HttpWebRequest)HttpWebRequest.Create(uri);
    if (clientCertificate != null)
    {
        hwr.ClientCertificates.Add(clientCertificate);
    }
    hwr.Method = "GET";
    try
    {
        var resWebResonse = await hwr.GetResponseAsync();
        var stream = resWebResonse.GetResponseStream(); 
        var sr = new StreamReader (stream);   
        return sr.ReadToEnd();
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"Error executing Webrequest to {uri}: {ex}");
    }
    return null;
}

Server: user.aspx

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Security.Cryptography.X509Certificates" %>
<%@ Import Namespace="System.Security.Principal" %>
<script runat="server">

        public void Page_Load(object sender, EventArgs e)
        {
            try
            {
                Regex userRegEx = new Regex("CN=(.*)@", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                if (Request.ClientCertificate.IsPresent)
                {
                    var cert = new X509Certificate2(Request.ClientCertificate.Certificate);
                    var identity = new GenericIdentity(cert.Subject, "ClientCertificate");
                    var principal = new GenericPrincipal(identity, null);
                    var m = userRegEx.Match (principal.Identity.Name);
                    Response.Write (m.Groups[1].Value);
                }
                else
                    Response.Write("No Client-Certificate");
                Response.End();
            }
            catch (Exception ex)
            {
                if (!(ex is System.Threading.ThreadAbortException))
                    Response.Write(ex.ToString());
            }
        }    
</script>

This is the exception I keep getting:

System.Net.WebException: Error getting response stream (ReadDone1): ReceiveFailure 
---> System.IO.IOException: Unable to read data from the transport connection: Connection reset by peer. 
---> System.Net.Sockets.SocketException: Connection reset by peer
  at System.Net.Sockets.Socket.Receive (System.Byte[] buffer, System.Int32 offset, System.Int32 size, System.Net.Sockets.SocketFlags socketFlags) [0x00017] 
in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.0.0.0/src/mono/mcs/class/referencesource/System/net/System/Net/Sockets/Socket.cs:1773 
at System.Net.Sockets.NetworkStream.Read (System.Byte[] buffer, System.Int32 offset, System.Int32 size) [0x0009b] 
in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.0.0.0/src/mono/mcs/class/referencesource/System/net/System/Net/Sockets/NetworkStream.cs:513 
--- End of inner exception stack trace 
---
  at Mono.Net.Security.MobileAuthenticatedStream.EndReadOrWrite (System.IAsyncResult asyncResult, Mono.Net.Security.AsyncProtocolRequest& nestedRequest) [0x00056] 
in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.0.0.0/src/mono/mcs/class/System/Mono.Net.Security/MobileAuthenticatedStream.cs:335 
at Mono.Net.Security.MobileAuthenticatedStream.EndRead (System.IAsyncResult asyncResult) [0x00000] 
in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.0.0.0/src/mono/mcs/class/System/Mono.Net.Security/MobileAuthenticatedStream.cs:278 
at System.Net.WebConnection.ReadDone (System.IAsyncResult result) [0x00027] 
in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.0.0.0/src/mono/mcs/class/System/System.Net/WebConnection.cs:475 
--- End of inner exception stack trace 
---
  at System.Net.HttpWebRequest.EndGetResponse (System.IAsyncResult asyncResult) [0x00059] 
in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.0.0.0/src/mono/mcs/class/System/System.Net/HttpWebRequest.cs:1031 
at System.Threading.Tasks.TaskFactory`1[TResult].FromAsyncCoreLogic (System.IAsyncResult iar, System.Func`2[T,TResult] endFunction, System.Action`1[T] endAction, System.Threading.Tasks.Task`1[TResult] promise, System.Boolean requiresSynchronization) [0x0000f] 
in <6314851f133e4e74a2e96356deaa0c6c>:0 
--- End of stack trace from previous location where exception was thrown 
---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] 
in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.0.0.0/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:151 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00037] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.0.0.0/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:187 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.0.0.0/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:156 
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.0.0.0/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:128 
at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] 
in <6314851f133e4e74a2e96356deaa0c6c>:0 
at EWOIS.iOS.WebHelper+<ExecuteRequest>d__4.MoveNext () [0x0012e] in C:\xxxx\WebHelper.cs:70 
  • Visual Studio 2017 v15.3.5
  • Xamarin 4.7.9.45
  • Xamarin iOS and Xamarin Mac SDK 11.0.0.0
  • Deployment Target iOS 9.3 (10.3 doesn´t work as well)
  • Windows Server 2008 R2 Enterprise SP1
  • IIS 7

I added a check for the server certificate (which is signed by our domain-CA) in AppDelegate.cs, it reports SslPolicyErrors.None

System.Net.ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) =>
{
    if (cert != null)
        System.Diagnostics.Debug.WriteLine($"Servercertificate: {cert.Subject}, SSL-Policy-Errors: {sslPolicyErrors}");
    return sslPolicyErrors == SslPolicyErrors.None; // return true does not work either (and is not recommended for security reasons)
}

The request works if I disable the certificate requirement on the web server, so the connection is working.

I even tried System.Net.HttpClient, but it throws a NotImplementedException when accessing the ClientCertificate Property :(

Now I am completely clueless what to try next... all other questions I found were a few years old and don´t offer any working solutions

like image 485
Brandtware Avatar asked Oct 04 '17 09:10

Brandtware


Video Answer


3 Answers

In your exception handler, it looks like you are not calling Response.End():

        catch (Exception ex)
        {
            if (!(ex is System.Threading.ThreadAbortException))
                Response.Write(ex.ToString());
        }

So if an exception is being thrown on the server side, it's possible that the response never gets sent to the client. I would recommend adding some server-side logging of the exception and see if anything useful is provided that way.

Also it looks like you verify certain fields of the client certificate, but as far as I can tell you don't verify that the client certificate is signed by a trusted root. You should make sure that's the case, or anyone can generate a self-signed certificate containing the username they want to impersonate and use that to trick your code, which would be a serious security vulnerability.

like image 134
jsha Avatar answered Nov 16 '22 02:11

jsha


I found several quesitons that point to problem with the Mono HTTP stack. It may help for resolving your problem: How to validate SSL certificates when using HttpWebRequest, Urgent help needed: "error getting response stream (ReadDone1): ReceiveFailure". One of the possible ways is to use ModernHttpClient that use platform-native HTTP client.

Also you have a mistake in the callback in ServicePointManager.ServerCertificateValidationCallback property. RemoteCertificateValidationCallback delegate returns a boolean value that determines whether the specified certificate is accepted for authentication. See more details in MSDN examples. Also try to read the question How to validate SSL certificates when using HttpWebRequest.

like image 44
Didgeridoo Avatar answered Nov 16 '22 01:11

Didgeridoo


What happens if you connect to the server over HTTPS without specifically adding the certificate?

Does the request get to the server alright? If so, the problem is probably in the decoding of the encrypted response. Are the certificates on the client and server the same?

like image 29
Leo Muller Avatar answered Nov 16 '22 00:11

Leo Muller