【问题标题】:How to call the default certificate check when overriding ServicePointManager.ServerCertificateValidationCallback in C#?在 C# 中覆盖 ServicePointManager.ServerCertificateValidationCallback 时如何调用默认证书检查?
【发布时间】:2012-02-21 21:44:57
【问题描述】:

我需要信任应用程序中的一些自签名证书,所以我像这样覆盖验证回调:

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/所期望的,因为它已添加到受信任的人存储,并在证书回调未被覆盖时由 W​​indows 批准。 因此,我希望 Windows 采用该站点的默认验证程序。我不想亲自查看 Windows 受信任的商店并遍历所有链元素,因为它已经(并且希望正确)在 Windows 中实现。

换句话说,我需要明确信任用户批准的网站(存储在他的设置中的某处),并为所有其他网站调用默认认证检查。

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

【问题讨论】:

    标签: .net ssl certificate x509certificate


    【解决方案1】:

    从回调中走链并没有你想象的那么困难。

    看看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");
    }
    

    【讨论】:

      【解决方案2】:

      这样的事情可能会奏效。请注意,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);
      }
      

      【讨论】:

        【解决方案3】:

        @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);
        }
        

        【讨论】:

          猜你喜欢
          • 2014-09-02
          • 1970-01-01
          • 2021-11-16
          • 2021-03-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-12
          相关资源
          最近更新 更多