【问题标题】:Xamarin Forms https with Client Certificates throws WebException带有客户端证书的 Xamarin 表单 https 引发 WebException
【发布时间】:2018-03-15 16:11:32
【问题描述】:

我正在尝试在适用于 iOS 的 Xamarin Forms App 中使用客户端证书进行身份验证,但似乎没有任何效果。如果我启动请求,应用程序等待默认超时 100 秒(我尝试使用 HttpWebRequest.Timeout 降低它,但设置它似乎被忽略),之后,我得到以下异常:Error getting response stream (ReadDone1): ReceiveFailure

与 Windows 控制台应用程序相同的代码可以正常工作。这里有一些(为便于阅读而简化)sn-ps 来重现:

客户:

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;
}

服务器: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>

这是我不断遇到的异常:

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 和 Xamarin Mac SDK 11.0.0.0
  • 部署目标 iOS 9.3(10.3 也不行)
  • Windows Server 2008 R2 企业版 SP1
  • IIS 7

我在 AppDelegate.cs 中添加了对服务器证书(由我们的域 CA 签名)的检查,它报告 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)
}

如果我禁用 Web 服务器上的证书要求,则请求有效,因此连接正常。

我什至尝试过 System.Net.HttpClient,但在访问 ClientCertificate 属性时会抛出 NotImplementedException :(

现在我完全不知道下一步该尝试什么......我发现的所有其他问题都是几年前的问题,并且没有提供任何可行的解决方案

【问题讨论】:

    标签: c# ssl https xamarin.ios ssl-certificate


    【解决方案1】:

    我针对该问题发起了高级支持电话。我不允许发布代码,但解决方案是使用原生 iOS 类而不是 HttpClient 或 HttpWebRequest。

    所以本质上是创建一个 NSUrlSession,分配一个 INSUrlSessionDelegate 的自定义实现来处理 DidReceiveChallenge 事件(以提供客户端证书),然后调用 NSUrlSession 的 CreateDataTaskAsync 方法。

    【讨论】:

      【解决方案2】:

      如果您通过 HTTPS 连接到服务器而没有专门添加证书,会发生什么情况?

      请求可以到达服务器吗?如果是这样,问题可能在于加密响应的解码。客户端和服务器端的证书是一样的吗?

      【讨论】:

      • 有趣,如果我在没有证书的情况下调用 servername,它会起作用,如果我调用 servername/test/test.aspx 它不会(即使在 ssl 设置中停用“需要客户端证书”后)...我会测试更多寻找模式
      【解决方案3】:

      我发现了几个指向 Mono HTTP 堆栈问题的问题。它可能有助于解决您的问题:How to validate SSL certificates when using HttpWebRequestUrgent help needed: "error getting response stream (ReadDone1): ReceiveFailure"。一种可能的方法是使用使用平台原生 HTTP 客户端的ModernHttpClient

      您在ServicePointManager.ServerCertificateValidationCallback property 中的回调也有错误。 RemoteCertificateValidationCallback delegate 返回一个布尔值,用于确定是否接受指定的证书进行身份验证。在 MSDN 示例中查看更多详细信息。也尝试阅读问题How to validate SSL certificates when using HttpWebRequest

      【讨论】:

      • 我发现了几个关于 ModernHttpClient 的线程,问题是它所做的只是设置原生的东西并在下面使用普通的 HttpClient。如问题中所述,HttpClient.ClientCertificate 引发 NotImplementedException。我不小心删除了代码清理中的回调返回值以进行发布,没有它就无法编译。我尝试了返回 true(出于安全原因不推荐)和返回 sslPolicyErrors == SslPolicyError.None,它们都不起作用。我会相应地编辑问题
      • 在这种情况下,请尝试在服务器端和客户端检查您的证书的正确性。确保您有权访问包括私钥在内的完整证书。证书应位于 Web 服务器的受信任的根证书颁发机构证书存储中。我想this question 可以帮助你。
      • ClientCertificate.IsValid 和 HasPrivateKey 都是真的。根 CA 存在于设备(设置 -> 常规 -> 配置文件 ->(我们的 MDM 名称)-> 更多详细信息 -> 我们的根证书)和网络服务器上的受信任根中。可以在控制台应用程序中使用相同的证书文件,因此它应该可以工作。
      【解决方案4】:

      在您的异常处理程序中,您似乎没有调用 Response.End():

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

      因此,如果在服务器端引发异常,则响应可能永远不会发送到客户端。我建议添加一些异常的服务器端日志记录,看看是否提供了任何有用的信息。

      您似乎还验证了客户端证书的某些字段,但据我所知,您并未验证客户端证书是否由受信任的根签名。您应该确保是这种情况,否则任何人都可以生成包含他们想要冒充的用户名的自签名证书并使用它来欺骗您的代码,这将是一个严重的安全漏洞。

      【讨论】:

      • 响应结束实际上不需要 Response.End,我只是希望服务器不向响应添加任何 html。服务器不应该是问题,因为客户端代码作为控制台应用程序工作,但不是在 Xamarin iOS 中。我仍然会在星期一进行测试,我无法在家访问我的工作。缺少证书检查,感谢您的建议
      • 我在 catch-block 中添加了另一个 Response.End,没有改变任何东西。我检查了 IIS-Logs,似乎请求甚至没有到达服务器,看起来更像是客户端问题...
      猜你喜欢
      • 2017-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-02
      • 1970-01-01
      • 1970-01-01
      • 2010-12-12
      • 1970-01-01
      相关资源
      最近更新 更多