Как вызвать проверку сертификата по умолчанию при переопределении 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 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 (на самом деле я не ожидал получить никаких ошибок) здесь, потому что текущему сертификату следует доверять)

Этот сайт не включен в мой список доверенных, и он не хочет возвращать «истина» из моего обратного вызова. Но я тоже не хочу возвращать false, иначе я получу исключение: удаленный сертификат недействителен в соответствии с процедурой проверки, что, очевидно, не ожидается для https://www.dscoduc.com/, потому что он добавлен в хранилище доверенных лиц и одобрен Windows, если обратный вызов сертификата не отменен. Поэтому я хочу, чтобы Windows использовала процедуру проверки по умолчанию для этого сайта. Я не хочу сам заглядывать в доверенные хранилища Windows и перебирать все элементы цепочки, потому что это уже (и, надеюсь, правильно) реализовано в Windows.

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

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


person mistika    schedule 30.01.2012    source источник


Ответы (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);
}
person pete.c    schedule 17.09.2014

Обойти цепочку из вашего обратного вызова проще, чем вы думаете.

Взгляните на 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");
}
person Chris Adams    schedule 07.06.2012

Решение @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);
}
person mistika    schedule 28.08.2018