using System.Collections.Concurrent; using System.Security.Cryptography.X509Certificates; using Csob.Configuration; namespace Csob.Client; /// /// Provides instances configured for mutual TLS with a per-request eIDAS /// client certificate. ČSOB requires the client certificate at the transport layer, so a single /// shared client cannot be used across tenants. /// /// Clients are cached by certificate thumbprint and reused for connection pooling. In practice the /// certificate identifies the TPP application (not the PSU), so the number of distinct certificates /// is small and bounded by the set of calling tenants. Certificates and clients live in memory only /// and never touch disk. /// public sealed class CsobHttpClientProvider : IDisposable { private readonly CsobSettings _settings; private readonly ConcurrentDictionary _clients = new(); private readonly object _buildLock = new(); public CsobHttpClientProvider(CsobSettings settings) => _settings = settings; /// /// Returns a (cached) mutual-TLS presenting . /// A fresh certificate instance is built per request; if an equivalent one (same thumbprint) is /// already cached, the redundant instance is disposed so it does not leak. /// public HttpClient GetClient(X509Certificate2 certificate) { var thumbprint = certificate.Thumbprint; if (_clients.TryGetValue(thumbprint, out var existing)) { certificate.Dispose(); return existing; } lock (_buildLock) { if (_clients.TryGetValue(thumbprint, out existing)) { certificate.Dispose(); return existing; } var client = BuildClient(certificate); _clients[thumbprint] = client; return client; } } private HttpClient BuildClient(X509Certificate2 certificate) { var handler = new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(10), }; handler.SslOptions.ClientCertificates = new X509CertificateCollection { certificate }; return new HttpClient(handler, disposeHandler: true) { Timeout = TimeSpan.FromSeconds(_settings.RequestTimeoutSeconds), }; } public void Dispose() { foreach (var client in _clients.Values) { client.Dispose(); } _clients.Clear(); } }