2015-05-06 19 views
9

Il MSDN documentation diceSslStream, disabilitare la cache di sessione

Il quadro cache sessioni SSL come vengono creati e tenta di riutilizzare una sessione di cache per una nuova richiesta, se possibile. Quando si tenta di riutilizzare una sessione SSL, Framework utilizza il primo elemento di ClientCertificates (se ce n'è uno) o tenta di riutilizzare sessioni anonime se ClientCertificates è vuoto.

Come disabilitare questa memorizzazione nella cache?

Al momento si verifica un problema con una riconnessione a un server (ad esempio, la prima connessione funziona correttamente, ma al tentativo di riconnettersi i server interrompe la sessione). Il riavvio dell'applicazione aiuta (ma ovviamente solo per il primo tentativo di connessione). Presumo che il problema sia il caching.

Ho controllato i pacchetti con uno sniffer, la differenza è a soli unico luogo solo al Cliente Ciao messaggi:

prima connessione al server (buon):

screenshot

Seconda tentativo di connessione (senza riavvio del programma, non è riuscito):

screenshot

Il d l'ifferenza sembra essere solo l'identificatore di sessione.

P.S. Mi piacerebbe evitare l'utilizzo di client SSL di terze parti. C'è una soluzione ragionevole?

Questa è una definizione di this question da ru.stackoverflow

risposta

4

cache viene gestito all'interno SecureChannel - classe interna che avvolge SSPI e usate da SslStream. Non vedo alcun punto all'interno che puoi usare per disabilitare il caching di sessione per le connessioni client.

È possibile cancellare la cache tra le connessioni utilizzando la riflessione:

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

ma è molto cattiva pratica. Considera di correggere il lato server.

+1

Oppure correggere SslClient Microsoft. – Alexis

+0

@Andrey: Come ho capito la domanda originale, la riparazione del server non è un'opzione: non è sotto il nostro controllo. – Vlad

+1

@rufanov: Beh, stai cercando una grossa pistola.La riflessione deve funzionare, ovviamente. – Vlad

2

Quindi ho risolto questo problema in modo leggermente diverso. Non mi piaceva davvero l'idea di riflettere questo metodo statico privato per fare il dump della cache, perché non sai davvero in cosa ti stai cacciando; stai praticamente aggirando l'incapsulamento e questo potrebbe causare problemi imprevisti. In realtà, ero preoccupato per le condizioni di gara in cui eseguo il dump della cache e prima di inviare la richiesta, arriva un altro thread e stabilisce una nuova sessione, quindi il mio primo thread dirotta inavvertitamente quella sessione. Cattive notizie ... comunque, ecco cosa ho fatto.

Mi sono fermato a pensare se esistesse o meno un modo per isolare il processo e quindi un mio collaboratore Android ha ricordato la disponibilità di AppDomains. Entrambi abbiamo convenuto che girare uno su dovrebbe consentire alla chiamata Tcp/Ssl di funzionare, isolata da qualsiasi altra cosa. Ciò consentirebbe alla logica di memorizzazione nella cache di rimanere intatta senza causare conflitti tra le sessioni SSL.

Fondamentalmente, avevo originariamente scritto il mio client SSL per essere interno a una libreria separata.Quindi all'interno di quella biblioteca, ho avuto un servizio pubblico come proxy/mediatore per quel cliente. Nel livello dell'applicazione, volevo la possibilità di passare da un servizio all'altro (servizi HSM, nel mio caso) basato sul tipo di hardware, quindi l'ho racchiuso in un adattatore e interfacciato per essere utilizzato con una fabbrica. Ok, quindi quanto è rilevante? Bene, ha reso più semplice fare questa cosa AppDomain in modo pulito, senza forzare questo comportamento a nessun altro consumatore del servizio pubblico (il proxy/mediatore di cui parlavo). Non devi seguire questa astrazione, mi piace solo condividere buoni esempi di astrazione ogni volta che li trovo :)

Ora, nell'adattatore, invece di chiamare direttamente il servizio, in pratica creo il dominio. Ecco il ctor:

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

Tutto questo non fa altro che crea un nome di dominio da cui il mio servizio effettivo verrà eseguito in isolamento. Ora, la maggior parte degli articoli che ho trovato su come eseguire effettivamente all'interno del dominio semplificano eccessivamente il suo funzionamento. Gli esempi in genere coinvolgono chiamando myDomain.DoCallback(() => ...); che non è errato, ma il tentativo di ottenere dati dentro e fuori da quel dominio sarà probabilmente problematico in quanto la serializzazione probabilmente ti fermerà nelle tue tracce. In parole povere, gli oggetti istanziati al di fuori di DoCallback() non sono gli stessi oggetti quando vengono chiamati dall'interno di DoCallback poiché sono stati creati al di fuori di questo dominio (vedere il marshalling degli oggetti). Quindi probabilmente avrai tutti i tipi di errori di serializzazione. Questo non è un problema se si esegue l'intera operazione, input e output e tutti possono verificarsi dall'interno di myDomain.DoCallback() ma questo è problematico se è necessario utilizzare parametri esterni e restituire qualcosa attraverso questo AppDomain al dominio di origine.

Mi sono imbattuto in un modello diverso qui su SO che ha funzionato per me e risolto questo problema. Guarda _rklServiceRuntime = nel mio campione. Ciò che sta facendo in realtà è chiedere al dominio di creare un'istanza di un oggetto affinché tu possa agire come proxy da quel dominio. Questo ti permetterà di piazzare alcuni oggetti dentro e fuori di esso. Qui è la mia implemenation di 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; 
     } 
    } 
} 

Questa eredita da MarshallByRefObject che le permette di attraversare i confini AppDomain, e ha un unico metodo che accetta i parametri esterni ed esegue la logica dall'interno del dominio che un'istanza di esso.

Quindi ora di nuovo all'adattatore di servizio: tutto ciò che gli adattatori di servizio devono fare ora è chiamare _rklServiceRuntime.Run(...) e immettere i parametri serializzabili necessari. Ora, creo tutte le istanze dell'adattatore di servizio di cui ho bisogno e funzionano tutte nel proprio dominio. Questo funziona per me perché le mie chiamate SSL sono piccole e brevi e queste richieste sono fatte all'interno di un servizio web interno dove istanze come questa sono molto importanti. Ecco l'adattatore completo:

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

Si noti il ​​metodo di smaltimento. Non dimenticare di scaricare il dominio. Questo servizio implementa IRklService che implementa IDisposable, quindi quando lo uso, viene utilizzato con un'istruzione using.

Questo sembra un po 'forzato, ma in realtà non lo è e ora la logica verrà eseguita sul proprio dominio, in isolamento, e quindi la logica di memorizzazione nella cache rimane intatta ma non problematica. Molto meglio di intromettersi con SSLSessionCache!

Si prega di perdonare qualsiasi incongruenza di denominazione come stavo disinfettando i nomi attuali rapidamente dopo aver scritto il post .. Spero che questo aiuti qualcuno!

+0

Ho appena letto su questo e ho visto che potrebbe essere fonte di confusione su dove entra in gioco SslClient. VCServiceRuntime.Run(), verrà visualizzata una chiamata a VCServiceClient. Questo è il mio wrapper per SslClient che gestisce l'assemblaggio di richiesta, certificati, convalida ecc. Niente di straordinario lì dentro. Ci sono alcuni livelli di riferimento indiretto per mantenere tale servizio flessibile. La classe VCServiceRuntime è la parte importante in quanto è il wrapper che consente a tutta la logica al suo interno di funzionare con domini app. – Sinaesthetic

Problemi correlati