.NET https-запросы с различными протоколами безопасности по потокам

Я поддерживаю довольно сложное приложение ASP.NET(настраиваемый NopCommerce 3.10). Он должен подключаться к сторонним серверам через HTTPS в разных сценариях. Я делаю это через класс HttpWebRequest.

Некоторые из этих серверов плохо настроены:

Один из сторонних серверов (например, Сервер A) требует тип протокола SSL3 и просто не работает соединение, если установлен другой тип протокола. Другой сервер (скажем Сервер B) предоставляет неверный сертификат, если соединение выполняется с SSL3. Точнее, он предоставляет сертификат с неправильным CN (общее имя). Однако, если я использую TLS с самого начала, сертификат в порядке.

Я решил проблему выше, используя обратный вызов ServicePointManager.ServerCertificateValidationCallback, чтобы проверить политическую ошибку SSL.

Изменение протокола безопасности выполняется с помощью ServicePointManager.SecurityProtocol, который является статическим свойством. Однако запросы, выполняемые клиентами для моего приложения, которые запускают соединения HTTPS, описанные выше, могут выполняться параллельно в разных потоках.

Если я, например: установите протокол безопасности на нужный тип, выполните запрос HTTPS, а затем верните его для Server A, я не гарантирую, что если запрос в то же время нуждается для подключения к Серверу B не изменяется ServicePointManager.SecurityProtocol на значение, отличное от значения, которое требуется Сервер A. Я считаю, что это типичная многопоточная проблема со статическими переменными.

Из моего исследования я решил, что .NET не дает возможности использовать определенный протокол SSL для каждого экземпляра WebRequest.

Я думаю о таких решениях, как:

  • очереди всех исходящих HTTPS-соединений внутри моего приложения для обеспечения надлежащего протокола SSL для каждого
  • создание отдельного домена приложения для каждого запроса HTTPS (предлагается qaru.site/info/45923/...)
  • изменить HTTPS-запрос на низкоуровневые TCP-соединения и обеспечить соблюдение различных протоколов SSL для каждого из них.
  • создание прокси-приложения asp.net, которое будет останавливать исходящие запросы

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

Однако вышеприведенные решения требуют сложного рефакторинга, учитывая архитектуру приложения или приблизительные обходные пути (третье решение)

Мой вопрос очень похож на на этот в msdn, однако, что он не получил удовлетворительных ответов.

Есть ли более прямой или эффективный способ обеспечения того, чтобы каждый запрос https использовал определенный протокол SSL?

Ответы

Ответ 1

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

http://www.superstarcoders.com/blogs/posts/executing-code-in-a-separate-application-domain-using-c-sharp.aspx

Мы используем его изолированный класс в значительной степени, как есть:

  public sealed class Isolated<T> : IDisposable where T : MarshalByRefObject
  {
    private AppDomain _domain;
    private readonly T _value;

    public Isolated()
    {
        _domain = AppDomain.CreateDomain("Isolated:" + Guid.NewGuid(), null, AppDomain.CurrentDomain.SetupInformation);

        var type = typeof(T);

        _value = (T)_domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
    }

    public T Value
    {
        get
        {
            return _value;
        }
    }

    public void Dispose()
    {
        if (_domain == null) return;

        AppDomain.Unload(_domain);

        _domain = null;
    }
}

И тогда у нас есть обертка вокруг стандартного WebClient, который позволяет установить протокол:

public class WebClient : MarshalByRefObject, IWebClient
{
    public WebClientResponse GetResponse(string address)
    {
        return GetResponse(address, null);
    }

    public WebClientResponse GetResponse(string address, string securityProtocol)
    {
        if (!string.IsNullOrWhiteSpace(securityProtocol))
            ServicePointManager.SecurityProtocol = (SecurityProtocolType)Enum.Parse(typeof(SecurityProtocolType), securityProtocol);

        var response = new WebClientResponse();

        try
        {
            using (var wc = new System.Net.WebClient())
            {
              // <do stuff>
            }
        }
        catch (Exception ex)
        {
            response.Exception = new GetResponseException(string.Format("Unable to get response from {0}", address), ex);
        }

        return response;
    }
}

[Serializable]
public class WebClientResponse
{
    public Exception Exception { get; set; }

    public string Response { get; set; }
}

 [Serializable]
public class GetResponseException : Exception
{
    public GetResponseException(string message, Exception innerException)
        : base(message, innerException)
    {
    }

    public GetResponseException(SerializationInfo info, StreamingContext context)  : base(info, context)
    {
    }
}

Связывая их вместе, у нас есть код, который определяет, нужно ли ему переопределять установленный в настоящее время протокол. Если это так, он закручивает изолированный домен приложения, если он не использует существующий WebClient:

...
        WebClientResponse webClientResponse;

        if (!string.IsNullOrWhiteSpace(ForceSecurityProtocol))
        {
            using (var isolated = new Isolated<WebClient>())
            {
                webClientResponse = isolated.Value.GetResponse(url, ForceSecurityProtocol);
            }
        }
        else
        {
            webClientResponse = _webClient.GetResponse(url);
        }
...

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

Ответ 2

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