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