在C#中覆盖ServicePointManager.ServerCertificateValidationCallback时如何调用默认证书检查?

20

我需要在应用程序中信任一些自签名证书,因此我像这样覆盖了验证回调:

ServicePointManager.ServerCertificateValidationCallback = MyRemoteCertificateValidationCallback;
...

public static bool MyRemoteCertificateValidationCallback(
            Object sender,
            X509Certificate certificate,
            X509Chain chain,
            SslPolicyErrors sslPolicyErrors)
{

    if (sslPolicyErrors == SslPolicyErrors.None)
        return true;

    if (IsAprrovedByMyApplication(sender, certificate))  // <-- no matter what the check here is
       return true;
    else 
       return false;  // <-- here I'd like to call the default Windows handler rather than returning 'false'
}

但是当出现某些策略错误并且我连接的站点未经应用程序批准时,会抛出异常。问题在于它与标准的 Windows 行为不同。
考虑这个网站:https://www.dscoduc.com/。它的证书具有未知颁发者,因此不受信任。我已经使用 MMC 将其添加到本地计算机的受信任人员 (它是 Windows 7) 中。
如果我在没有覆盖证书验证回调的情况下运行此代码:
HttpWebRequest http = (HttpWebRequest)HttpWebRequest.Create("https://www.dscoduc.com/");
using (WebResponse resp = http.GetResponse())
{
    using (StreamReader sr = new StreamReader(resp.GetResponseStream()))
    {
        string htmlpage = sr.ReadToEnd();
    }
}

它成功连接。 这意味着Windows默认验证器决定信任此证书。

但是一旦我覆盖了ServerCertificateValidationCallback,我的回调函数就会收到SslPolicyErrors.RemoteCertificateChainErrors, 并且链中包含一个状态为X509ChainStatusFlags.PartialChain的元素(实际上,我希望在这里不收到任何错误,因为当前证书应该是受信任的)

此站点未包含在我的受信任列表中,也不想从我的回调函数中返回“true”。 但我也不想返回“false”,否则我会收到异常:“根据验证过程,远程证书无效”,这显然对于https://www.dscoduc.com/来说是不符合预期的,因为它已添加到受信任人员存储,并且在未覆盖证书回调时被Windows批准。 因此,我希望Windows对此站点采取默认的验证程序。我不想自己查看Windows受信任存储并浏览所有链元素,因为它已经(并且希望正确地)在Windows中实现。

换句话说,我需要显式信任由用户批准的站点(存储在其设置的某个位置),并对所有其他站点调用默认的认证检查。

ServicePointManager.ServerCertificateValidationCallback 的默认值为 null,因此没有“默认”回调供我稍后调用。我该如何调用此“默认”证书处理程序?
3个回答

7

这样的做法可能有效。请注意,X509CertificateValidator 允许您选择是否在验证中包含 Trusted People 存储。

private static bool CertificateValidationCallBack(
    object sender,
    X509Certificate certificate,
    X509Chain chain,
    SslPolicyErrors sslPolicyErrors)
{
    // Your custom check here...
    if (isYourSpecialCase)
    {
        return true;
    }

    // If it is not your special case then revert to default checks...

    // Convert the certificate to a X509Certificate2
    var certificate2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate);

    try
    {
        // Choose the type of certificate validation you want
        X509CertificateValidator.PeerOrChainTrust.Validate(certificate2);
        //X509CertificateValidator.ChainTrust.Validate(certificate2);
    }
    catch
    {
        return false;
    }

    // Sender is always either a WebReqest or a hostname string
    var request = sender as WebRequest;
    string requestHostname = request != null ? request.RequestUri.Host : (string)sender;

    // Get the hostname from the certificate
    string certHostname = certificate2.GetNameInfo(X509NameType.DnsName, false);

    return requestHostname.Equals(certHostname, StringComparison.InvariantCultureIgnoreCase);
}

6

从回调函数中访问证书链并不像你想的那么困难。

请参考http://msdn.microsoft.com/en-us/library/dd633677(v=exchg.80).aspx

示例代码检查证书链以确定证书是否是自签名,如果是,则信任它。您可以将其改为接受PartialChain,或同时接受两种类型的链。您需要做的类似于以下内容:

if (status.Status == X509ChainStatusFlags.PartialChain ||
    (certificate.Subject == certificate.Issuer &&
     status.Status == X509ChainStatusFlags.UntrustedRoot)
{
    // Certificates with a broken chain and
    // self-signed certificates with an untrusted root are valid. 
    continue;
}
else if (status.Status != X509ChainStatusFlags.NoError)
{
    // If there are any other errors in the certificate chain,
    // the certificate is invalid, so the method returns false.
    return false;
}

或者检查Subject属性:

private static bool CertificateValidationCallBack(
    object sender,
    System.Security.Cryptography.X509Certificates.X509Certificate certificate,
    System.Security.Cryptography.X509Certificates.X509Chain chain,
    System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
    return certificate.Subject.Contains(".dsoduc.com");
}

0

@pete.c的解决方案似乎可以正确地工作(检查了不同的情况)

但是,如果仍然不确定X509CertificateValidator是否以相同的方式进行验证,则可以通过反射运行默认回调:

private static object s_defaultCallback;
private static MethodInfo s_defaultCallbackInvoker;

...
// Get the original callback using reflection 
PropertyInfo[] pis = typeof (ServicePointManager).GetProperties(BindingFlags.Static | BindingFlags.NonPublic);

foreach (var pi in pis)
{
    if (pi.Name == "CertPolicyValidationCallback")
    {
        s_defaultCallback = pi.GetValue(null, null);
        s_defaultCallbackInvoker = s_defaultCallback.GetType().GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        break;
    }
}
...

private static bool CertificateValidationCallBack(
        object sender,
        X509Certificate certificate,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
{
    // Your custom check here...
    if (isYourSpecialCase)
    {
        return true;
    }

    // Default Windows behavior
    WebRequest req = sender as WebRequest;
    if (req == null)
        return false;

    ServicePoint sp = ServicePointManager.FindServicePoint(req.RequestUri);
    string host = req.RequestUri.Host;
    object [] parameters = new object[]
                               {
                                   host,
                                   sp,
                                   certificate,
                                   req,
                                   chain,
                                   sslPolicyErrors
                               };

    return (bool)s_defaultCallbackInvoker.Invoke(s_defaultCallback, parameters);
}

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