Использование самозаверяющего сертификата с помощью .СЕТЕВОЙ HttpWebRequest /Ответ

StackOverflow https://stackoverflow.com/questions/526711

Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

Другие советы

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

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

Это позволит проверить все сертификаты (включая недействительные, с истекшим сроком действия или самоподписанные).

Обратите внимание, что в .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; };

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

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

Как сделать:Просмотр сертификатов с помощью оснастки MMC

Область обратного вызова проверки, используемая в Ответ Домстера может быть ограничено конкретным запросом с использованием параметра sender на 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();
}

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

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

    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 - Изменено имя хранилища.Root на StoreName.My - My будет устанавливаться в хранилище локальных пользователей вместо Root.Root в некоторых системах не будет работать, даже если вы "запускаетесь от имени администратора".

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top