Scenario 10.2: Configurare client TLS

Categoria: TLS/mTLS
Complessità: Media
Prerequisiti: Trust Store, certificato client opzionale
Tempo stimato: 15-20 minuti


Descrizione

Questo scenario descrive la configurazione di un client TLS per connessioni sicure a server con certificati Post-Quantum.

Compiti del client:


Workflow

flowchart LR TRUST[Caricare Trust Store] --> CONNECT[Stabilire connessione] CONNECT --> VERIFY[Verificare cert server] VERIFY --> CHAIN[Validare chain] CHAIN --> REV[Verificare revoca] REV --> OK{Tutto OK?} OK -->|Si| DATA[Trasferire dati] OK -->|No| ABORT[Interrompere] style DATA fill:#e8f5e9 style ABORT fill:#ffebee


Esempio codice: HttpClient con Trust Store personalizzato

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
 
using var ctx = PqCryptoContext.Initialize();
 
// Caricare Trust Store personalizzato
var trustedCerts = new X509Certificate2Collection();
trustedCerts.Add(ctx.LoadCertificate("company-root-ca.crt.pem"));
trustedCerts.Add(ctx.LoadCertificate("company-intermediate-ca.crt.pem"));
 
// Configurare HttpClientHandler
var handler = new HttpClientHandler
{
    // Validazione personalizzata certificato server
    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
    {
        // Verificare prima gli errori standard
        if (errors == SslPolicyErrors.None)
        {
            return true;
        }
 
        // Per UntrustedRoot: usare Trust Store personalizzato
        if (errors == SslPolicyErrors.RemoteCertificateChainErrors)
        {
            var customChain = new X509Chain();
            customChain.ChainPolicy.CustomTrustStore.AddRange(trustedCerts);
            customChain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
            customChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
            customChain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
 
            bool isValid = customChain.Build(cert);
 
            if (!isValid)
            {
                foreach (var status in customChain.ChainStatus)
                {
                    Console.WriteLine($"Errore chain: {status.StatusInformation}");
                }
            }
 
            return isValid;
        }
 
        Console.WriteLine($"Errore SSL: {errors}");
        return false;
    },
 
    // Versione TLS
    SslProtocols = SslProtocols.Tls13,
 
    // Verifica revoca
    CheckCertificateRevocationList = true
};
 
using var httpClient = new HttpClient(handler)
{
    Timeout = TimeSpan.FromSeconds(30)
};
 
// Eseguire richiesta
var response = await httpClient.GetAsync("https://api.example.com/data");
Console.WriteLine($"Stato: {response.StatusCode}");

Esempio codice: Client TLS con SocketsHttpHandler

// SocketsHttpHandler per maggiore controllo
var socketsHandler = new SocketsHttpHandler
{
    // Connection Pooling
    PooledConnectionLifetime = TimeSpan.FromMinutes(15),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
    MaxConnectionsPerServer = 10,
 
    // Configurazione TLS
    SslOptions = new SslClientAuthenticationOptions
    {
        // Forzare TLS 1.3
        EnabledSslProtocols = SslProtocols.Tls13,
 
        // Nome server per SNI
        TargetHost = "api.example.com",
 
        // Callback validazione certificato
        RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
        {
            if (errors == SslPolicyErrors.None) return true;
 
            // Logging
            Console.WriteLine($"Certificato: {certificate?.Subject}");
            Console.WriteLine($"Errori: {errors}");
 
            return false;  // Validazione rigorosa
        },
 
        // Protocolli applicazione (ALPN)
        ApplicationProtocols = new List<SslApplicationProtocol>
        {
            SslApplicationProtocol.Http2,
            SslApplicationProtocol.Http11
        }
    }
};
 
using var client = new HttpClient(socketsHandler);

Certificate Pinning

public class CertificatePinningHandler : HttpClientHandler
{
    private readonly HashSet<string> _pinnedCertificates;
 
    public CertificatePinningHandler(params string[] pinnedThumbprints)
    {
        _pinnedCertificates = new HashSet<string>(
            pinnedThumbprints,
            StringComparer.OrdinalIgnoreCase
        );
 
        ServerCertificateCustomValidationCallback = ValidateServerCertificate;
    }
 
    private bool ValidateServerCertificate(
        HttpRequestMessage message,
        X509Certificate2? cert,
        X509Chain? chain,
        SslPolicyErrors errors)
    {
        if (cert == null) return false;
 
        // 1. Validazione standard
        if (errors != SslPolicyErrors.None)
        {
            Console.WriteLine($"Errore SSL Policy: {errors}");
            return false;
        }
 
        // 2. Verificare pin (Leaf o Intermediate)
        bool pinValid = false;
 
        // Verificare certificato leaf
        if (_pinnedCertificates.Contains(cert.Thumbprint))
        {
            pinValid = true;
        }
 
        // Cercare nella chain
        if (!pinValid && chain != null)
        {
            foreach (var element in chain.ChainElements)
            {
                if (_pinnedCertificates.Contains(element.Certificate.Thumbprint))
                {
                    pinValid = true;
                    break;
                }
            }
        }
 
        if (!pinValid)
        {
            Console.WriteLine($"Pin certificato non corrispondente: {cert.Thumbprint}");
        }
 
        return pinValid;
    }
}
 
// Utilizzo
var pinnedHandler = new CertificatePinningHandler(
    "A1B2C3D4E5F6...",  // Thumbprint Leaf
    "B2C3D4E5F6G7..."   // Thumbprint Intermediate (pin di backup)
);
 
using var client = new HttpClient(pinnedHandler);

Gestione Trust Store

public class TrustStoreManager
{
    private readonly X509Certificate2Collection _trustedRoots = new();
    private readonly X509Certificate2Collection _trustedIntermediates = new();
 
    public void AddTrustedRoot(string certPath)
    {
        var cert = new X509Certificate2(certPath);
 
        // Verificare se è un certificato CA
        var basicConstraints = cert.Extensions["2.5.29.19"] as X509BasicConstraintsExtension;
        if (basicConstraints == null || !basicConstraints.CertificateAuthority)
        {
            throw new ArgumentException("Non è un certificato CA");
        }
 
        _trustedRoots.Add(cert);
        Console.WriteLine($"Trusted Root aggiunto: {cert.Subject}");
    }
 
    public void AddTrustedIntermediate(string certPath)
    {
        var cert = new X509Certificate2(certPath);
        _trustedIntermediates.Add(cert);
        Console.WriteLine($"Trusted Intermediate aggiunto: {cert.Subject}");
    }
 
    public X509Chain CreateChain()
    {
        var chain = new X509Chain();
 
        chain.ChainPolicy.CustomTrustStore.AddRange(_trustedRoots);
        chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
 
        chain.ChainPolicy.ExtraStore.AddRange(_trustedIntermediates);
 
        chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
        chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
        chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromSeconds(15);
 
        return chain;
    }
 
    public bool ValidateCertificate(X509Certificate2 cert)
    {
        using var chain = CreateChain();
        return chain.Build(cert);
    }
}

Logica di retry per errori TLS

public class ResilientTlsClient
{
    private readonly HttpClient _client;
    private readonly int _maxRetries;
 
    public ResilientTlsClient(HttpClient client, int maxRetries = 3)
    {
        _client = client;
        _maxRetries = maxRetries;
    }
 
    public async Task<HttpResponseMessage> GetWithRetry(string url)
    {
        Exception? lastException = null;
 
        for (int attempt = 1; attempt <= _maxRetries; attempt++)
        {
            try
            {
                return await _client.GetAsync(url);
            }
            catch (HttpRequestException ex) when (IsTlsError(ex))
            {
                lastException = ex;
                Console.WriteLine($"Errore TLS (tentativo {attempt}/{_maxRetries}): {ex.Message}");
 
                if (attempt < _maxRetries)
                {
                    await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
                }
            }
        }
 
        throw new HttpRequestException($"Fallito dopo {_maxRetries} tentativi", lastException);
    }
 
    private bool IsTlsError(HttpRequestException ex)
    {
        return ex.InnerException is AuthenticationException ||
               ex.Message.Contains("SSL") ||
               ex.Message.Contains("TLS");
    }
}

Requisiti client specifici per settore

Settore Trust Store Pinning Timeout
Finanza PKI propria Obbligatorio 30s
Sanità gematik TSL Raccomandato 60s
IoT Integrato Obbligatorio 120s
Enterprise Distribuito AD/GPO Opzionale 30s

Scenari correlati

Relazione Scenario Descrizione
Prerequisito 10.1 Server TLS Configurare server
Estensione 10.3 Deployment mTLS Aggiungere cert client
Correlato 5.2 Validazione Chain Verifica certificati

« ← 10.1 Server TLS | ↑ Panoramica TLS | 10.3 Deployment mTLS → »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional