使用ServerCertificateValidationCallback的最佳实践

49

我正在处理一个项目,其中涉及两个后端服务器之间的一些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中是否有更好的实践方式,可以在处理客户端SSL/TLS验证时,不影响其他Web客户端的访问Web服务?

4个回答

53

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

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

4
仅为完整性而言,在 .Net Framework 4.5 中,FtpWebRequest 不支持 ServerCertificateValidationCallback 属性。 - Storm
11
对于那些使用 HttpClient 而不是 HttpWebRequest 的人,需要注意的是,ServerCertificateValidationCallback 属性位于你传入 HttpClient 构造函数的 WebRequestHandler 对象上。这个对象已经被用来设置客户端证书,所以这是一个简单的补充。 - Granger
3
我能在生成的WCF服务引用上使用这种方法吗?我现在手头有一个System.ServiceModel.ClientBase的生成子类,但是除了按照问题中提出的“全局”方式之外,我找不到插入此行为的位置。 - Mattias Nordqvist

22

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

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

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

请注意,这仍然会影响同一应用程序域中的其他网络客户端实例(它们将全部接受指定的哈希字符串),但至少不会阻止其他证书。


2
我更喜欢你的答案,因为它允许你维护一个白名单并为其他人提供正常实现。 - Osa E

7

可能来晚了,但另一种选择是使用继承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
}

2
这不是一个好的处理方式,因为它仍然会全局关闭所有请求的验证,直到代码从 using 块中跳出。 - user3032348
那是一个很好的观点。但可能会干扰其他线程吗? - Matt Sach

6

对于这种情况,直接的方法应该是在客户机的受信任根证书存储中安装两个自生成的证书。这样做时会收到安全警告,因为证书无法通过 Thawte 或类似实体进行认证,但之后普通的安全通信应该可以正常工作。我记得,你需要在受信任根目录下安装完整的(包括公钥和私钥)版本才能使其生效。


1
我并不认为这是多么简单明了的事情。在第三方根目录中安装任何东西都是高度安全敏感的操作。我正在尝试避免影响其他类,而你的建议会影响系统上的每个应用程序。 - galets
1
从其他应用程序可以访问您的服务器而无需证书警告的意义上来说,是正确的。 - 500 - Internal Server Error
1
这绝对是更好的解决方案。它只允许您选择的证书,并且不会禁用标准的SSL证书检查。 - Soeren L. Nielsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接