使用ServerCertificateValidationCallback的最佳实践

时间:2014-01-03 23:04:00

标签: c# .net web-services ssl webclient

我正在开发一个在两个后端服务器之间使用HTTP通信的项目。服务器使用X509证书进行身份验证。不用说,当服务器A(客户端)建立与服务器B(服务器)的连接时,存在SSL / TLS验证错误,因为使用的证书不是来自受信任的第三方权限。

通常,处理它的方法是使用ServicePointManager.ServerCertificateValidationCallback,例如:

ServicePointManager.ServerCertificateValidationCallback += 
        (sender, cert, chain, error) =>
{
    return cert.GetCertHashString() == "xxxxxxxxxxxxxxxx";
};

这种方法有效,除非它不理想。它本质上做的是覆盖应用程序完成的每个http请求的验证过程。因此,如果另一个类将尝试运行HTTP请求,它将失败。另外,如果另一个类为了自己的目的而覆盖ServicePointManager.ServerCertificateValidationCallback,那么我的通信就会突然失败。

唯一可以想到的解决方案是创建一个单独的AppDomain来执行客户端HTTP请求。这可行,但实际上 - 只有这样才能执行HTTP请求是愚蠢的。开销将是惊人的。

考虑到这一点,是否有人研究过.NET中是否有更好的实践,这将允许访问Web服务,同时处理客户端SSL / TLS验证而不影响其他Web客户端?

4 个答案:

答案 0 :(得分:31)

在.NET 4.5+中使用的可接受(安全)方法是使用HttpWebRequest.ServerCertificateValidationCallback。在特定的请求实例上分配该回调将仅针对请求更改验证逻辑,而不会影响其他请求。

var request = (HttpWebRequest)WebRequest.Create("https://...");
request.ServerCertificateValidationCallback += 
        (sender, cert, chain, error) =>
{
    return cert.GetCertHashString() == "xxxxxxxxxxxxxxxx";
};

答案 1 :(得分:12)

不使用HttpWebRequest的代码的替代方案,以及无法在证书存储区中安装可信证书的环境:检查回调的错误参数,该参数将包含之前检测到的任何错误回调。这样,您可以忽略特定哈希字符串的错误,但仍然接受通过验证的其他证书。

ServicePointManager.ServerCertificateValidationCallback += 
    (sender, cert, chain, error) =>
{
    if (cert.GetCertHashString() == "xxxxxxxxxxxxxxxx")
    {
        return true;
    }
    else
    {
       return error == SslPolicyErrors.None;
    }
};

参考: https://msdn.microsoft.com/en-us/library/system.net.security.remotecertificatevalidationcallback(v=vs.110).aspx

请注意,这仍将影响同一appdomain中的其他Web客户端实例(它们都将接受指定的哈希字符串),但至少它不会阻止其他证书。

答案 2 :(得分:6)

此方案的直接方法应该是在客户端计算机上的受信任根存储中安装两个自生成的证书。执行此操作时,您将收到安全警告,因为无法使用Thawte或类似程序对证书进行身份验证,但在此之后应进行常规安全通信。 IIRC,您需要在受信任的根目录中安装完整的(公钥和私钥)版本才能实现。

答案 3 :(得分:4)

我知道晚点了,但是另一种选择是使用一个继承IDisposable的类,该类可以放在您代码的using(){}块中:

public class ServicePointManagerX509Helper : IDisposable
{
    private readonly SecurityProtocolType _originalProtocol;

    public ServicePointManagerX509Helper()
    {
        _originalProtocol = ServicePointManager.SecurityProtocol;
        ServicePointManager.ServerCertificateValidationCallback += TrustingCallBack;
    }

    public void Dispose()
    {
        ServicePointManager.SecurityProtocol = _originalProtocol;
        ServicePointManager.ServerCertificateValidationCallback -= TrustingCallBack;
    }

    private bool TrustingCallBack(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        // The logic for acceptance of your certificates here
        return true;
    }
}

以这种方式使用:

using (new ServicePointManagerX509Helper())
{
    // Your code here
}