带有客户端证书的 Xamarin Forms https 抛出 WebException
Xamarin Forms https with Client Certificates throws WebException
我正在尝试在 iOS 的 Xamarin Forms 应用程序中使用客户端证书进行身份验证,但似乎没有任何效果。如果我启动请求,应用程序等待默认超时 100 秒(我尝试使用 HttpWebRequest.Timeout 降低它,但设置它似乎被忽略),之后,我得到以下异常:获取响应流 (ReadDone1) 时出错:ReceiveFailure
与 Windows 控制台应用程序相同的代码工作正常。这里有一些(为便于阅读而简化)的片段可以重现:
客户:
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 服务器 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 43=] 属性 :(
现在我完全不知道接下来要尝试什么...我发现的所有其他问题都是几年前的事了,没有提供任何有效的解决方案
在您的异常处理程序中,您似乎没有调用 Response.End():
catch (Exception ex)
{
if (!(ex is System.Threading.ThreadAbortException))
Response.Write(ex.ToString());
}
因此,如果在服务器端抛出异常,则可能永远不会将响应发送到客户端。我建议添加一些异常的服务器端日志记录,看看是否以这种方式提供了任何有用的东西。
您似乎还验证了客户端证书的某些字段,但据我所知,您并未验证客户端证书是否由受信任的根签名。您应该确保是这种情况,否则任何人都可以生成包含他们想要模拟的用户名的自签名证书并使用它来欺骗您的代码,这将是一个严重的安全漏洞。
我发现了几个指向 Mono HTTP 堆栈问题的问题。它可能有助于解决您的问题: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 使用平台原生 HTTP 客户端。
另外你在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.
如果您在没有特别添加证书的情况下通过 HTTPS 连接到服务器,会发生什么情况?
请求到达服务器了吗?如果是这样,问题可能出在加密响应的解码中。客户端和服务器上的证书是否相同?
我针对该问题拨打了高级支持电话。我不允许 post 代码,但解决方案是使用本机 iOS 类 而不是 HttpClient 或 HttpWebRequest。
所以本质上是创建一个 NSUrlSession,分配一个 INSUrlSessionDelegate 的自定义实现来处理 DidReceiveChallenge 事件(以提供客户端证书),然后调用 NSUrlSession 的 CreateDataTaskAsync 方法。
我正在尝试在 iOS 的 Xamarin Forms 应用程序中使用客户端证书进行身份验证,但似乎没有任何效果。如果我启动请求,应用程序等待默认超时 100 秒(我尝试使用 HttpWebRequest.Timeout 降低它,但设置它似乎被忽略),之后,我得到以下异常:获取响应流 (ReadDone1) 时出错:ReceiveFailure
与 Windows 控制台应用程序相同的代码工作正常。这里有一些(为便于阅读而简化)的片段可以重现:
客户:
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 服务器 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 43=] 属性 :(
现在我完全不知道接下来要尝试什么...我发现的所有其他问题都是几年前的事了,没有提供任何有效的解决方案
在您的异常处理程序中,您似乎没有调用 Response.End():
catch (Exception ex)
{
if (!(ex is System.Threading.ThreadAbortException))
Response.Write(ex.ToString());
}
因此,如果在服务器端抛出异常,则可能永远不会将响应发送到客户端。我建议添加一些异常的服务器端日志记录,看看是否以这种方式提供了任何有用的东西。
您似乎还验证了客户端证书的某些字段,但据我所知,您并未验证客户端证书是否由受信任的根签名。您应该确保是这种情况,否则任何人都可以生成包含他们想要模拟的用户名的自签名证书并使用它来欺骗您的代码,这将是一个严重的安全漏洞。
我发现了几个指向 Mono HTTP 堆栈问题的问题。它可能有助于解决您的问题: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 使用平台原生 HTTP 客户端。
另外你在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.
如果您在没有特别添加证书的情况下通过 HTTPS 连接到服务器,会发生什么情况?
请求到达服务器了吗?如果是这样,问题可能出在加密响应的解码中。客户端和服务器上的证书是否相同?
我针对该问题拨打了高级支持电话。我不允许 post 代码,但解决方案是使用本机 iOS 类 而不是 HttpClient 或 HttpWebRequest。
所以本质上是创建一个 NSUrlSession,分配一个 INSUrlSessionDelegate 的自定义实现来处理 DidReceiveChallenge 事件(以提供客户端证书),然后调用 NSUrlSession 的 CreateDataTaskAsync 方法。