Использование самоподписанного сертификата с .NET HttpWebRequest / Response - PullRequest
76 голосов
/ 09 февраля 2009

Я пытаюсь подключиться к API, который использует самозаверяющий сертификат SSL. Я делаю это, используя .NET-объекты HttpWebRequest и HttpWebResponse. И я получаю исключение, которое:

Базовое соединение было закрыто: не удалось установить доверительные отношения для безопасного канала SSL / TLS.

Я понимаю, что это значит. И я понимаю , почему .NET чувствует, что должен предупредить меня и закрыть соединение. Но в этом случае, я бы все равно хотел просто подключиться к API, проклятые атаки типа «человек посередине».

Итак, как мне добавить исключение для этого самозаверяющего сертификата? Или подход HttpWebRequest / Response вообще не проверять сертификат? Как бы я это сделал?

Ответы [ 9 ]

87 голосов
/ 09 февраля 2009

Оказывается, если вы просто хотите полностью отключить проверку сертификата, вы можете изменить ServerCertificateValidationCallback на ServicePointManager, например, так:

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

При этом будут проверены все сертификаты (включая недействительные, просроченные или самоподписанные).

74 голосов
/ 09 февраля 2009

@ Domster: это работает, но вы, возможно, захотите немного повысить безопасность, проверив, соответствует ли хэш сертификата тому, что вы ожидаете. Таким образом, расширенная версия выглядит примерно так (на основе некоторого живого кода, который мы используем):

static readonly byte[] apiCertHash = { 0xZZ, 0xYY, ....};

/// <summary>
/// Somewhere in your application's startup/init sequence...
/// </summary>
void InitPhase()
{
    // Override automatic validation of SSL server certificates.
    ServicePointManager.ServerCertificateValidationCallback =
           ValidateServerCertficate;
}

/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this
/// validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the
/// remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote
/// certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified
/// certificate is accepted for authentication; true to accept or false to
/// reject.</returns>
private static bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        // Good certificate.
        return true;
    }

    log.DebugFormat("SSL certificate error: {0}", sslPolicyErrors);

    bool certMatch = false; // Assume failure
    byte[] certHash = cert.GetCertHash();
    if (certHash.Length == apiCertHash.Length)
    {
        certMatch = true; // Now assume success.
        for (int idx = 0; idx < certHash.Length; idx++)
        {
            if (certHash[idx] != apiCertHash[idx])
            {
                certMatch = false; // No match
                break;
            }
        }
    }

    // Return true => allow unauthenticated server,
    //        false => disallow unauthenticated server.
    return certMatch;
}
43 голосов
/ 28 февраля 2013

Обратите внимание, что в .NET 4.5 вы можете переопределить проверку SSL для самого HttpWebRequest (а не через глобальный делегат, который влияет на все запросы):

http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.servercertificatevalidationcallback.aspx

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.ServerCertificateValidationCallback = delegate { return true; };
42 голосов
/ 08 марта 2009

Добавление самозаверяющего сертификата в доверенные корневые центры сертификации локального компьютера

Вы можете импортировать сертификат, запустив MMC от имени администратора.

Как: просмотреть сертификаты с помощью оснастки MMC

33 голосов
/ 15 августа 2012

Область обратного вызова проверки, используемая в Ответ Domster , может быть ограничен конкретным запросом с использованием параметра отправителя в делегате ServerCertificateValidationCallback. В следующем простом классе области действия этот метод используется для временного подключения обратного вызова проверки, который выполняется только для данного объекта запроса.

public class ServerCertificateValidationScope : IDisposable
{
    private readonly RemoteCertificateValidationCallback _callback;

    public ServerCertificateValidationScope(object request,
        RemoteCertificateValidationCallback callback)
    {
        var previous = ServicePointManager.ServerCertificateValidationCallback;
        _callback = (sender, certificate, chain, errors) =>
            {
                if (sender == request)
                {
                    return callback(sender, certificate, chain, errors);
                }
                if (previous != null)
                {
                    return previous(sender, certificate, chain, errors);
                }
                return errors == SslPolicyErrors.None;
            };
        ServicePointManager.ServerCertificateValidationCallback += _callback;
    }

    public void Dispose()
    {
        ServicePointManager.ServerCertificateValidationCallback -= _callback;
    }
}

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

var request = WebRequest.Create(uri);
using (new ServerCertificateValidationScope(request, delegate { return true; }))
{
    request.GetResponse();
}
3 голосов
/ 03 сентября 2015

Чтобы добавить в качестве возможной помощи кому-то еще ... Если вы хотите, чтобы он предложил пользователю установить самоподписанный сертификат, вы можете использовать этот код (измененный сверху).

Не требует прав администратора, устанавливает локальным пользователям доверенные профили:

    private static bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
        {
            // Good certificate.
            return true;
        }

        Common.Helpers.Logger.Log.Error(string.Format("SSL certificate error: {0}", sslPolicyErrors));
        try
        {
            using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
            {
                store.Open(OpenFlags.ReadWrite);
                store.Add(new X509Certificate2(cert));
                store.Close();
            }
            return true;
        }
        catch (Exception ex)
        {
            Common.Helpers.Logger.Log.Error(string.Format("SSL certificate add Error: {0}", ex.Message));
        }

        return false;
    }

Кажется, это хорошо работает для нашего приложения, и если пользователь нажимает нет, связь не будет работать.

Обновление: 2015-12-11 - Изменен StoreName.Root на StoreName.My - My будет установлен в локальном хранилище пользователей вместо Root. Root на некоторых системах не будет работать, даже если вы «запускаете от имени администратора»

2 голосов
/ 14 декабря 2012

Просто опираясь на ответ от devstuff , чтобы включить тему и издателя ... комментарии приветствуются ...

public class SelfSignedCertificateValidator
{
    private class CertificateAttributes
    {
        public string Subject { get; private set; }
        public string Issuer { get; private set; }
        public string Thumbprint { get; private set; }

        public CertificateAttributes(string subject, string issuer, string thumbprint)
        {
            Subject = subject;
            Issuer = issuer;                
            Thumbprint = thumbprint.Trim(
                new char[] { '\u200e', '\u200f' } // strip any lrt and rlt markers from copy/paste
                ); 
        }

        public bool IsMatch(X509Certificate cert)
        {
            bool subjectMatches = Subject.Replace(" ", "").Equals(cert.Subject.Replace(" ", ""), StringComparison.InvariantCulture);
            bool issuerMatches = Issuer.Replace(" ", "").Equals(cert.Issuer.Replace(" ", ""), StringComparison.InvariantCulture);
            bool thumbprintMatches = Thumbprint == String.Join(" ", cert.GetCertHash().Select(h => h.ToString("x2")));
            return subjectMatches && issuerMatches && thumbprintMatches; 
        }
    }

    private readonly List<CertificateAttributes> __knownSelfSignedCertificates = new List<CertificateAttributes> {
        new CertificateAttributes(  // can paste values from "view cert" dialog
            "CN = subject.company.int", 
            "CN = issuer.company.int", 
            "f6 23 16 3d 5a d8 e5 1e 13 58 85 0a 34 9f d6 d3 c8 23 a8 f4") 
    };       

    private static bool __createdSingleton = false;

    public SelfSignedCertificateValidator()
    {
        lock (this)
        {
            if (__createdSingleton)
                throw new Exception("Only a single instance can be instanciated.");

            // Hook in validation of SSL server certificates.  
            ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertficate;

            __createdSingleton = true;
        }
    }

    /// <summary>
    /// Validates the SSL server certificate.
    /// </summary>
    /// <param name="sender">An object that contains state information for this
    /// validation.</param>
    /// <param name="cert">The certificate used to authenticate the remote party.</param>
    /// <param name="chain">The chain of certificate authorities associated with the
    /// remote certificate.</param>
    /// <param name="sslPolicyErrors">One or more errors associated with the remote
    /// certificate.</param>
    /// <returns>Returns a boolean value that determines whether the specified
    /// certificate is accepted for authentication; true to accept or false to
    /// reject.</returns>
    private bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;   // Good certificate.

        Dbg.WriteLine("SSL certificate error: {0}", sslPolicyErrors);
        return __knownSelfSignedCertificates.Any(c => c.IsMatch(cert));            
    }
}
1 голос
/ 15 августа 2016

Я столкнулся с той же проблемой, что и OP, когда веб-запрос выдает именно это исключение. У меня все настроено правильно, я думал, что сертификат был установлен, я мог найти его в хранилище компьютеров и подключить его к веб-запросу, и я отключил проверку сертификатов в контексте запроса.

Оказалось, что я работал под своей учетной записью и что сертификат был установлен в хранилище компьютеров. Это заставило веб-запрос выдать это исключение. Чтобы решить эту проблему, мне нужно было либо работать от имени администратора, либо установить сертификат в хранилище пользователей и прочитать его оттуда.

Может показаться, что C # может найти сертификат в хранилище компьютеров, даже если его нельзя использовать с веб-запросом, и это приводит к тому, что исключение OP генерируется после отправки веб-запроса. *

1 голос
/ 06 ноября 2013

Следует иметь в виду, что наличие ServicePointManager.ServerCertificateValidationCallback, по-видимому, не означает, что проверка CRL и проверка имени сервера не выполняются, а только предоставляет возможность переопределить их результат. Таким образом, вашему сервису все еще может потребоваться некоторое время, чтобы получить CRL, после этого вы будете знать только, что он не прошел некоторые проверки.

...