Scenarij 10.2: Konfiguracija TLS klijenta

Kategorija: TLS/mTLS
Složenost: ⭐⭐⭐ (Srednja)
Preduvjeti: Trust Store, opcijski klijentski certifikat
Procijenjeno vrijeme: 15-20 minuta


Opis

Ovaj scenarij opisuje konfiguraciju TLS klijenta za sigurne veze sa serverima koji koriste Post-Quantum certifikate.

Zadaci klijenta:

  • Validacija server certifikata
  • Konfiguracija Trust Store-a
  • TLS verzija i Cipher Suites
  • Opcijski: Prezentiranje klijentskog certifikata

Tijek rada

flowchart LR TRUST[Učitavanje Trust Store-a] --> CONNECT[Uspostava veze] CONNECT --> VERIFY[Provjera server certifikata] VERIFY --> CHAIN[Validacija lanca] CHAIN --> REV[Provjera opoziva] REV --> OK{Sve OK?} OK -->|Da| DATA[Prijenos podataka] OK -->|Ne| ABORT[Prekid] style DATA fill:#e8f5e9 style ABORT fill:#ffebee


Primjer koda: HttpClient s prilagođenim Trust Store-om

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
 
using var ctx = PqCryptoContext.Initialize();
 
// Učitavanje prilagođenog Trust Store-a
var trustedCerts = new X509Certificate2Collection();
trustedCerts.Add(ctx.LoadCertificate("company-root-ca.crt.pem"));
trustedCerts.Add(ctx.LoadCertificate("company-intermediate-ca.crt.pem"));
 
// Konfiguracija HttpClientHandler-a
var handler = new HttpClientHandler
{
    // Prilagođena validacija server certifikata
    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
    {
        // Prvo provjeriti standardne greške
        if (errors == SslPolicyErrors.None)
        {
            return true;
        }
 
        // Kod UntrustedRoot: koristiti prilagođeni Trust Store
        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($"Greška lanca: {status.StatusInformation}");
                }
            }
 
            return isValid;
        }
 
        Console.WriteLine($"SSL greška: {errors}");
        return false;
    },
 
    // TLS verzija
    SslProtocols = SslProtocols.Tls13,
 
    // Provjera opoziva
    CheckCertificateRevocationList = true
};
 
using var httpClient = new HttpClient(handler)
{
    Timeout = TimeSpan.FromSeconds(30)
};
 
// Izvršavanje zahtjeva
var response = await httpClient.GetAsync("https://api.example.com/data");
Console.WriteLine($"Status: {response.StatusCode}");

Primjer koda: TLS klijent sa SocketsHttpHandler

// SocketsHttpHandler za više kontrole
var socketsHandler = new SocketsHttpHandler
{
    // Connection Pooling
    PooledConnectionLifetime = TimeSpan.FromMinutes(15),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
    MaxConnectionsPerServer = 10,
 
    // TLS konfiguracija
    SslOptions = new SslClientAuthenticationOptions
    {
        // Forsiranje TLS 1.3
        EnabledSslProtocols = SslProtocols.Tls13,
 
        // Server ime za SNI
        TargetHost = "api.example.com",
 
        // Certificate Validation Callback
        RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
        {
            if (errors == SslPolicyErrors.None) return true;
 
            // Logiranje
            Console.WriteLine($"Certifikat: {certificate?.Subject}");
            Console.WriteLine($"Greške: {errors}");
 
            return false;  // Stroga validacija
        },
 
        // Application Protocols (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. Standardna validacija
        if (errors != SslPolicyErrors.None)
        {
            Console.WriteLine($"SSL Policy greška: {errors}");
            return false;
        }
 
        // 2. Provjera pina (Leaf ili Intermediate)
        bool pinValid = false;
 
        // Provjera Leaf certifikata
        if (_pinnedCertificates.Contains(cert.Thumbprint))
        {
            pinValid = true;
        }
 
        // Pretraživanje lanca
        if (!pinValid && chain != null)
        {
            foreach (var element in chain.ChainElements)
            {
                if (_pinnedCertificates.Contains(element.Certificate.Thumbprint))
                {
                    pinValid = true;
                    break;
                }
            }
        }
 
        if (!pinValid)
        {
            Console.WriteLine($"Pin certifikata ne odgovara: {cert.Thumbprint}");
        }
 
        return pinValid;
    }
}
 
// Uporaba
var pinnedHandler = new CertificatePinningHandler(
    "A1B2C3D4E5F6...",  // Leaf Thumbprint
    "B2C3D4E5F6G7..."   // Intermediate Thumbprint (rezervni pin)
);
 
using var client = new HttpClient(pinnedHandler);

Upravljanje Trust Store-om

public class TrustStoreManager
{
    private readonly X509Certificate2Collection _trustedRoots = new();
    private readonly X509Certificate2Collection _trustedIntermediates = new();
 
    public void AddTrustedRoot(string certPath)
    {
        var cert = new X509Certificate2(certPath);
 
        // Provjera je li CA certifikat
        var basicConstraints = cert.Extensions["2.5.29.19"] as X509BasicConstraintsExtension;
        if (basicConstraints == null || !basicConstraints.CertificateAuthority)
        {
            throw new ArgumentException("Nije CA certifikat");
        }
 
        _trustedRoots.Add(cert);
        Console.WriteLine($"Dodan pouzdani Root: {cert.Subject}");
    }
 
    public void AddTrustedIntermediate(string certPath)
    {
        var cert = new X509Certificate2(certPath);
        _trustedIntermediates.Add(cert);
        Console.WriteLine($"Dodan pouzdani Intermediate: {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);
    }
}

Retry logika kod TLS grešaka

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($"TLS greška (pokušaj {attempt}/{_maxRetries}): {ex.Message}");
 
                if (attempt < _maxRetries)
                {
                    await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
                }
            }
        }
 
        throw new HttpRequestException($"Neuspješno nakon {_maxRetries} pokušaja", lastException);
    }
 
    private bool IsTlsError(HttpRequestException ex)
    {
        return ex.InnerException is AuthenticationException ||
               ex.Message.Contains("SSL") ||
               ex.Message.Contains("TLS");
    }
}

Sektorski specifični zahtjevi za klijente

Sektor Trust Store Pinning Timeout
Financije Vlastita PKI Obavezan 30s
Zdravstvo gematik TSL Preporučen 60s
IoT Ugrađen Obavezan 120s
Enterprise AD/GPO distribucija Opcijski 30s

Povezani scenariji

Odnos Scenarij Opis
Preduvjet 10.1 TLS server Konfiguracija servera
Proširenje 10.3 mTLS Deployment Dodavanje klijentskog certifikata
Povezano 5.2 Validacija lanca Provjera certifikata

« ← 10.1 TLS server | ↑ Pregled TLS-a | 10.3 mTLS Deployment → »


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

Zuletzt geändert: 30.01.2026. u 06:51