2015-05-06 3 views
9

MSDN documentation говоритSslStream, отключить кэширование сеанса

рамочных кэширует SSL сессию, как они создаются и пытается повторно кешированный сеанс нового запроса, если это возможно. При попытке повторного использования сеанса SSL Framework использует первый элемент ClientCertificates (если таковой имеется) или пытается повторно использовать анонимные сеансы, если ClientCertificates пуст.

Как отключить кеширование?

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

Я проверил пакеты с наркоманом, разница в только одном месте только на клиенте Привет сообщения:

Первое подключение к серверу (успешного):

screenshot

Второму попытка соединения (без перезапуска программы, не удалось):

screenshot

д ifference кажется просто идентификатором сеанса.

P.S. Я бы хотел избежать использования сторонних SSL-клиентов. Есть ли разумное решение?

Это перевод this question из ru.stackoverflow

ответ

4

Кэширование обрабатывается внутри SecureChannel - внутренний класс, который оборачивает SSPI и используется SslStream. Я не вижу никаких точек внутри, которые можно использовать для отключения кеширования сеансов для клиентских подключений.

Вы можете очистить кэш между соединениями с помощью отражения:

var sslAssembly = Assembly.GetAssembly(typeof(SslStream)); 

var sslSessionCacheClass = sslAssembly.GetType("System.Net.Security.SslSessionsCache"); 

var cachedCredsInfo = sslSessionCacheClass.GetField("s_CachedCreds", BindingFlags.NonPublic | BindingFlags.Static); 
var cachedCreds = (Hashtable)cachedCredsInfo.GetValue(null); 

cachedCreds.Clear(); 

Но это очень плохая практика. Подумайте, исправить серверную сторону.

+1

Или исправить SslClient Microsoft. – Alexis

+0

@ Andrey: Насколько я понимаю исходный вопрос, исправление сервера не является вариантом: оно не под нашим контролем. – Vlad

+1

@rufanov: Ну, вы добираетесь до большой пистолета.Разумеется, рефлексия должна работать. – Vlad

2

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

Я остановился, чтобы подумать о том, есть ли способ изолировать процесс, а затем мой коллега из Android отозвал доступность AppDomains. Мы оба согласились, что прядение одного из них должно позволить запустить вызов Tcp/Ssl, изолированный от всего остального. Это позволит логике кэширования оставаться неповрежденной, не вызывая конфликтов между сеансами SSL.

В принципе, я изначально написал свой SSL-клиент, чтобы быть внутренним в отдельной библиотеке.Затем в этой библиотеке у меня был публичный сервис как посредник/посредник для этого клиента. На прикладном уровне я хотел иметь возможность переключаться между службами (службы HSM, в моем случае) на основе аппаратного типа, поэтому я завернул это в адаптер и связал его с фабрикой. Хорошо, так как это важно? Ну, это просто упростило это приложение AppDomain чисто, не заставляя это поведение никому другому потребителю публичной службы (прокси/посредник, о котором я говорил). Вам не обязательно следовать этой абстракции, я просто хочу поделиться хорошими примерами абстракции всякий раз, когда я их нахожу :)

Теперь, в адаптере, вместо прямого вызова службы, я в основном создаю домен. Вот т е р:

public VCRklServiceAdapter(
    string hostname, 
    int port, 
    IHsmLogger logger) 
{ 
    Ensure.IsNotNullOrEmpty(hostname, nameof(hostname)); 
    Ensure.IsNotDefault(port, nameof(port), failureMessage: $"It does not appear that the port number was actually set (port: {port})"); 
    Ensure.IsNotNull(logger, nameof(logger)); 

    ClientId = Guid.NewGuid(); 

    _logger = logger; 
    _hostname = hostname; 
    _port = port; 

    // configure the domain 
    _instanceDomain = AppDomain.CreateDomain(
     $"vcrypt_rkl_instance_{ClientId}", 
     null, 
     AppDomain.CurrentDomain.SetupInformation); 

    // using the configured domain, grab a command instance from which we can 
    // marshall in some data 
    _rklServiceRuntime = (IRklServiceRuntime)_instanceDomain.CreateInstanceAndUnwrap(
     typeof(VCServiceRuntime).Assembly.FullName, 
     typeof(VCServiceRuntime).FullName); 
} 

Все это делает создает именованный домен, из которого моя фактическая служба будет работать в изоляции. Теперь, большинство статей, которые я натолкнулся на то, как на самом деле выполнять в домене, упрощают работу. Примеры обычно включают вызов myDomain.DoCallback(() => ...);, который не является ошибочным, но попытка получить данные в этом домене и из него, скорее всего, станет проблематичной, поскольку сериализация, скорее всего, остановит вас на ваших треках. Проще говоря, объекты, созданные за пределами DoCallback(), не являются одинаковыми объектами при вызове изнутри DoCallback, так как они были созданы за пределами этого домена (см. Маршаллирование объектов). Таким образом, вы, вероятно, получите всевозможные ошибки сериализации. Это не проблема при запуске всей операции, ввода и вывода, и все это может произойти изнутри myDomain.DoCallback(), но это проблематично, если вам нужно использовать внешние параметры и вернуть что-то через этот AppDomain обратно в исходный домен.

Я столкнулся с другим шаблоном здесь, на котором я работал, и решил эту проблему. Посмотрите на _rklServiceRuntime = в моем примере ctor. То, что это делает, - это попросить домен создать экземпляр объекта, чтобы вы выступали в качестве прокси-сервера из этого домена. Это позволит вам маршировать некоторые объекты в и из него. Вот мой IRklServiceRuntime реализация внешний:

public interface IRklServiceRuntime 
{  
    RklResponse Run(RklRequest request, string hostname, int port, Guid clientId, IHsmLogger logger); 
} 

public class VCServiceRuntime : MarshalByRefObject, IRklServiceRuntime 
{ 
    public RklResponse Run(
     RklRequest request, 
     string hostname, 
     int port, 
     Guid clientId, 
     IHsmLogger logger) 
    { 
     Ensure.IsNotNull(request, nameof(request)); 
     Ensure.IsNotNullOrEmpty(hostname, nameof(hostname)); 
     Ensure.IsNotDefault(port, nameof(port), failureMessage: $"It does not appear that the port number was actually set (port: {port})"); 
     Ensure.IsNotNull(logger, nameof(logger)); 

     // these are set here instead of passed in because they are not 
     // serializable 
     var clientCert = ApplicationValues.VCClientCertificate; 
     var clientCerts = new X509Certificate2Collection(clientCert); 

     using (var client = new VCServiceClient(hostname, port, clientCerts, clientId, logger)) 
     { 
      var response = client.RetrieveDeviceKeys(request); 
      return response; 
     } 
    } 
} 

Это наследует от MarshallByRefObject, которая позволяет ему пересекать границы AppDomain, и имеет один метод, который принимает ваши внешние параметры и выполняет свою логику внутри домена, который конкретизируется его.

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

public class VCRklServiceAdapter : IRklService 
{ 
    private readonly string _hostname; 
    private readonly int _port; 
    private readonly IHsmLogger _logger; 
    private readonly AppDomain _instanceDomain; 
    private readonly IRklServiceRuntime _rklServiceRuntime; 

    public Guid ClientId { get; } 

    public VCRklServiceAdapter(
     string hostname, 
     int port, 
     IHsmLogger logger) 
    { 
     Ensure.IsNotNullOrEmpty(hostname, nameof(hostname)); 
     Ensure.IsNotDefault(port, nameof(port), failureMessage: $"It does not appear that the port number was actually set (port: {port})"); 
     Ensure.IsNotNull(logger, nameof(logger)); 

     ClientId = Guid.NewGuid(); 

     _logger = logger; 
     _hostname = hostname; 
     _port = port; 

     // configure the domain 
     _instanceDomain = AppDomain.CreateDomain(
      $"vc_rkl_instance_{ClientId}", 
      null, 
      AppDomain.CurrentDomain.SetupInformation); 

     // using the configured domain, grab a command instance from which we can 
     // marshall in some data 
     _rklServiceRuntime = (IRklServiceRuntime)_instanceDomain.CreateInstanceAndUnwrap(
      typeof(VCServiceRuntime).Assembly.FullName, 
      typeof(VCServiceRuntime).FullName); 
    } 

    public RklResponse GetKeys(RklRequest rklRequest) 
    { 
     Ensure.IsNotNull(rklRequest, nameof(rklRequest)); 

     var response = _rklServiceRuntime.Run(
      rklRequest, 
      _hostname, 
      _port, 
      ClientId, 
      _logger); 

     return response; 
    } 

    /// <summary> 
    /// Releases unmanaged and - optionally - managed resources. 
    /// </summary> 
    public void Dispose() 
    { 
     AppDomain.Unload(_instanceDomain); 
    } 
} 

Обратите внимание на способ утилизации. Не забывайте выгружать домен. Эта служба реализует IRklService, которая реализует IDisposable, поэтому, когда я ее использую, она используется с оператором using.

Это кажется немного надуманным, но на самом деле это не так, и теперь логика будет выполняться в собственном домене изолированно, и поэтому логика кэширования остается неповрежденной, но не проблематичной. Гораздо лучше, чем вмешаться в SSLSessionCache!

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

+0

Я только что прочитал это и увидел, что это может сбивать с толку, где SslClient входит в игру. VCServiceRuntime.Run() вы увидите вызов VCServiceClient. Это моя оболочка для SslClient, которая обрабатывает сборку запроса, сертификатов, проверку и т. Д. Ничего примечательного там. Там есть несколько уровней косвенности, чтобы поддерживать эту услугу гибкой. Класс VCServiceRuntime является важной частью, так как это оболочка, которая позволяет всей логике внутри нее работать с доменами приложений. – Sinaesthetic

Смежные вопросы