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

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

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'
}

Но когда есть некоторые ошибки политики, и сайт, с которым я подключаюсь, не одобрен приложением, исключается Exception. Проблема здесь в том, что она отличается от стандартного поведения Windows.

Рассмотрим этот сайт: https://www.dscoduc.com/

У этого сертификата есть неизвестный эмитент, и поэтому он не доверен. Я добавил его с MMC к доверенным людям Local Copmuter (это 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' из моего обратного вызова. Но я не хочу возвращать "ложный" ни один, или я получу исключение: "Удаленный сертификат недействителен в соответствии с процедурой проверки", что явно не ожидается для https://www.dscoduc.com/, потому что он добавлен в хранилище доверенных лиц и одобрен Windows, когда обратный вызов сертификата не переоценивается. Поэтому я хочу, чтобы Windows приняла процедуру проверки по умолчанию для этого сайта. Я не хочу заглядывать в хранилища Windows Trusted и просматривать все элементы цепочки, потому что он уже (и, надеюсь, правильно) реализован в Windows.

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

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

Спасибо

Ответы

Ответ 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 позволяет вам выбрать, включать ли хранилище доверенных лиц в валидацию.

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