【问题标题】:Best practices for using ServerCertificateValidationCallback使用 ServerCertificateValidationCallback 的最佳实践
【发布时间】:2014-01-03 23:04:00
【问题描述】:

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

一般情况下,处理方式是使用ServicePointManager.ServerCertificateValidationCallback,如:

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

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

想到的唯一解决方案是创建一个单独的AppDomain 来执行客户端 HTTP 请求。这会起作用,但实际上 - 必须这样做才能执行 HTTP 请求是很愚蠢的。开销将是惊人的。

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

【问题讨论】:

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


    【解决方案1】:

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

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

    【讨论】:

    • 为了完整起见,ServerCertificateValidationCallback 属性不适用于 .Net Framework 4.5 中的 FtpWebRequest
    • 另外,对于那些使用HttpClient 而不是HttpWebRequest 的人,ServerCertificateValidationCallback 属性在您传递给HttpClient 构造函数的WebRequestHandler 对象上结束。 (这是您已经用于设置客户端证书的对象,因此很容易添加。)
    • 我可以在生成的 wcf 服务参考上使用这种方法吗?我坐在这里,生成了 System.ServiceModel.ClientBase 的子类,但我找不到在哪里插入此行为,除非通过执行问题中提出的“全局”方式。
    【解决方案2】:

    对于不使用 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 客户端实例(它们都将接受指定的哈希字符串),但至少不会阻止其他证书。

    【讨论】:

    • 我更喜欢您的回答,因为它允许您维护白名单并允许其他人正常实施。
    【解决方案3】:

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

    【讨论】:

    • 我不明白它是如何简单的。在第三方根目录中安装任何东西都是高度安全敏感的操作。我试图避免影响这里的其他类,而您的建议会影响系统上的每个应用程序。
    • 从某种意义上说,其他应用程序也可以在没有证书警告的情况下访问您的服务器,正确。
    • 这是迄今为止更好的解决方案。它将只允许您选择的证书,并且您不会禁用标准 SSL 证书检查。
    【解决方案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
    }
    

    【讨论】:

    • 这不是一个好方法,因为它仍然会在全局范围内关闭所有请求的所有验证,直到代码转义 using 块。
    • 这是一个公平的观点。可以侵入其他线程吗?
    猜你喜欢
    • 2012-12-31
    • 2017-11-29
    • 2010-10-08
    • 2012-04-11
    • 2010-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多