2015-05-06 14 views
9

Các MSDN documentation nóiSslStream, vô hiệu hóa bộ nhớ đệm phiên

Khung lưu trữ phiên SSL khi chúng được tạo ra và cố gắng để tái sử dụng một phiên cache cho một yêu cầu mới, nếu có thể. Khi cố gắng sử dụng lại phiên SSL, Framework sử dụng phần tử đầu tiên là ClientCertificates (nếu có) hoặc cố gắng sử dụng lại phiên ẩn danh nếu ClientCertificates trống.

Tôi làm cách nào để tắt bộ đệm ẩn này?

Hiện tại, tôi đang gặp phải sự cố khi kết nối lại với máy chủ (nghĩa là kết nối đầu tiên hoạt động tốt nhưng cố gắng kết nối lại máy chủ sẽ phá vỡ phiên). Khởi động lại ứng dụng sẽ giúp (nhưng tất nhiên chỉ dành cho lần thử kết nối đầu tiên). Tôi cho rằng vấn đề gốc là bộ nhớ đệm.

Tôi đã kiểm tra các gói dữ liệu với một sniffer, sự khác biệt là ở chỗ chỉ đơn chỉ khách hàng Xin chào thông điệp:

kết nối đầu tiên đến máy chủ (thành công):

screenshot

Second nỗ lực kết nối (không có chương trình khởi động lại, thất bại):

screenshot

Các d ifference dường như chỉ là định danh phiên.

P.S. Tôi muốn tránh sử dụng ứng dụng khách SSL của bên thứ ba. Có một giải pháp hợp lý?

Đây là bản dịch của this question từ ru.stackoverflow

Trả lời

4

Caching được xử lý bên trong SecureChannel - lớp nội bộ mà kết thúc tốt đẹp SSPI và được sử dụng bởi SslStream. Tôi không thấy bất kỳ điểm bên trong mà bạn có thể sử dụng để vô hiệu hóa bộ nhớ đệm phiên cho các kết nối khách hàng.

Bạn có thể xóa bộ nhớ cache giữa các kết nối sử dụng phản ánh:

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

Nhưng đó là thực tế rất xấu. Xem xét để sửa chữa phía máy chủ.

+1

Hoặc khắc phục SslClient Microsoft. – Alexis

+0

@Andrey: Khi tôi hiểu câu hỏi ban đầu, việc sửa chữa máy chủ không phải là một lựa chọn: nó không nằm trong tầm kiểm soát của chúng tôi. – Vlad

+1

@rufanov: Vâng, bạn đang vươn tới một khẩu súng lớn.Dĩ nhiên, sự phản chiếu phải làm việc. – Vlad

2

Vì vậy, tôi đã giải quyết vấn đề này hơi khác một chút. Tôi thực sự không thích ý tưởng phản ánh phương thức tĩnh riêng tư này để kết xuất bộ nhớ cache vì bạn không thực sự biết những gì bạn đang thực hiện bằng cách làm như vậy; về cơ bản bạn đang phá vỡ đóng gói và có thể gây ra các vấn đề không lường trước được. Nhưng thực sự, tôi đã lo lắng về điều kiện chủng tộc nơi tôi đổ bộ nhớ cache và trước khi tôi gửi yêu cầu, một số chủ đề khác đi vào và thiết lập một phiên mới để sau đó luồng đầu tiên của tôi vô tình chiếm đoạt phiên đó. Tin xấu ... dù sao, đây là những gì tôi đã làm.

Tôi dừng lại để suy nghĩ về việc có hay không có cách nào để phân loại cô lập quy trình và sau đó một đồng nghiệp Android của tôi nhớ lại tính sẵn có của AppDomains. Cả hai chúng tôi đồng ý rằng việc quay vòng một lần sẽ cho phép cuộc gọi Tcp/Ssl chạy, bị cô lập khỏi mọi thứ khác. Điều này sẽ cho phép logic bộ nhớ đệm duy trì nguyên vẹn mà không gây xung đột giữa các phiên SSL.

Về cơ bản, ban đầu tôi đã viết ứng dụng khách SSL của mình vào nội bộ thư viện riêng.Sau đó, trong thư viện đó, tôi đã có một hành động dịch vụ công cộng như một proxy/hòa giải cho khách hàng đó. Trong lớp ứng dụng, tôi muốn khả năng chuyển đổi giữa các dịch vụ (các dịch vụ HSM, trong trường hợp của tôi) dựa trên loại phần cứng, vì vậy tôi đã bọc nó vào bộ điều hợp và giao tiếp với một nhà máy. Ok, vậy điều đó có liên quan như thế nào? Vâng nó chỉ làm cho nó dễ dàng hơn để làm điều này AppDomain sạch, mà không cần buộc hành vi này bất kỳ người tiêu dùng khác của dịch vụ công cộng (các proxy/trung gian tôi nói). Bạn không phải tuân theo sự trừu tượng này, tôi chỉ muốn chia sẻ những ví dụ trừu tượng tốt bất cứ khi nào tôi tìm thấy chúng :)

Bây giờ, trong bộ điều hợp, thay vì gọi trực tiếp dịch vụ, về cơ bản tôi tạo miền. Đây là 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); 
} 

Tất cả điều này tạo ra tên miền mà dịch vụ thực tế của tôi sẽ chạy riêng. Bây giờ, hầu hết các bài viết mà tôi đã xem xét về cách thực sự thực thi trong phạm vi tên miền đơn giản hóa cách thức hoạt động của nó. Các ví dụ thường liên quan đến việc gọi số myDomain.DoCallback(() => ...); không sai, nhưng cố gắng lấy dữ liệu vào và ra khỏi miền đó có thể sẽ trở thành vấn đề vì việc tuần tự hóa có thể sẽ khiến bạn chết trong các tuyến đường của mình. Nói một cách đơn giản, các đối tượng được khởi tạo bên ngoài DoCallback() không phải là các đối tượng giống nhau khi được gọi từ bên trong của DoCallback vì chúng được tạo ra bên ngoài miền này (xem đối tượng trộn). Vì vậy, bạn có thể sẽ nhận được tất cả các loại lỗi serialization. Đây không phải là vấn đề nếu chạy toàn bộ hoạt động, đầu vào và đầu ra và tất cả có thể xảy ra từ bên trong myDomain.DoCallback() nhưng đây là vấn đề nếu bạn cần sử dụng các tham số bên ngoài và trả về một cái gì đó trên AppDomain này về miền gốc.

Tôi đã xem qua một mô hình khác ở đây trên SO đã làm việc cho tôi và giải quyết vấn đề này. Hãy xem _rklServiceRuntime = trong ctor mẫu của tôi. Việc này đang thực sự yêu cầu miền khởi tạo một đối tượng để bạn hoạt động như một proxy từ miền đó. Điều này sẽ cho phép bạn marshall một số đối tượng trong và ngoài của nó. Dưới đây là implemenation của tôi về 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; 
     } 
    } 
} 

này kế thừa từ MarshallByRefObject cho phép nó vượt qua ranh giới AppDomain, và có một phương pháp duy nhất mà sẽ đưa các thông số bên ngoài của bạn và thực hiện logic của bạn từ bên trong lĩnh vực mà khởi tạo nó.

Vì vậy, bây giờ trở lại bộ điều hợp dịch vụ: Tất cả các bộ điều hợp dịch vụ phải làm bây giờ là gọi _rklServiceRuntime.Run(...) và nạp thông số cần thiết, nối tiếp. Bây giờ, tôi chỉ tạo ra nhiều phiên bản của bộ điều hợp dịch vụ như tôi cần và tất cả chúng đều chạy trong miền riêng của chúng. Điều này làm việc cho tôi bởi vì các cuộc gọi SSL của tôi nhỏ và ngắn gọn và những yêu cầu này được thực hiện bên trong một dịch vụ web nội bộ, nơi yêu cầu instancing như thế này là rất quan trọng. Dưới đây là bộ điều hợp hoàn chỉnh:

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

Lưu ý phương pháp vứt bỏ. Đừng quên dỡ bỏ tên miền. Dịch vụ này triển khai IRklService thực hiện IDisposable, vì vậy khi tôi sử dụng nó, nó được sử dụng với câu lệnh using.

Điều này có vẻ hơi khó, nhưng nó thực sự không và bây giờ logic sẽ được chạy trên miền riêng của nó, trong sự cô lập, và do đó logic bộ nhớ đệm vẫn còn nguyên vẹn nhưng không có vấn đề. Tốt hơn nhiều so với việc can thiệp vào SSLSessionCache!

Hãy tha thứ cho bất kỳ sự mâu thuẫn nào khi đặt tên thật nhanh chóng sau khi viết bài. Tôi hy vọng điều này sẽ giúp một người!

+0

Tôi chỉ đọc qua điều này và thấy rằng nó có thể gây nhầm lẫn về nơi SslClient phát huy tác dụng. VCServiceRuntime.Run(), bạn sẽ thấy một cuộc gọi đến VCServiceClient.Đó là wrapper của tôi cho SslClient mà xử lý việc lắp ráp các yêu cầu, certs, xác nhận vv Không có gì đáng chú ý trong đó. Có một vài lớp của indirection có để giữ cho rằng dịch vụ linh hoạt. Lớp VCServiceRuntime là phần quan trọng vì đó là trình bao bọc cho phép tất cả các logic bên trong nó hoạt động với các miền ứng dụng. – Sinaesthetic

Các vấn đề liên quan