Как вызвать проверку сертификата по умолчанию при переопределении ServicePointManager.ServerCertificateValidationCallback в C#?

мне нужно доверять некоторым самозаверяющим сертификатам в приложении, поэтому я переопределяю обратный вызов проверки следующим образом:

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 Windwos 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.

иными словами, Мне нужно явно доверия к сайтам approvied пользователем (которые хранятся где-то в его настройках), и проверка аттестация по умолчанию для всех остальных.

значение по умолчанию для ServicePointManager.ServerCertificateValidationCallback имеет значение null, поэтому для меня нет обратного вызова по умолчанию. Как мне назвать этот обработчик сертификатов "по умолчанию"?

спасибо

3 ответов


что-то вроде этого может сработать. Примечание. X509CertificateValidator позволяет выбрать, следует ли включать хранилище доверенных лиц в проверку.

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

это менее сложно, чем вы думаете, чтобы пройти цепь из вашего обратного вызова.

посмотрите на 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;
}

кроме того, проверить the 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");
}

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