Scenarij 10.2: Konfiguracija TLS odjemalca

Kategorija: TLS/mTLS
Kompleksnost: ⭐⭐⭐ (Srednja)
Predpogoji: Shramba zaupanja, opcijski odjemalčev certifikat
Predviden čas: 15-20 minut


Opis

Ta scenarij opisuje konfiguracijo TLS odjemalca za varne povezave s strežniki s postkvantnimi certifikati.

Naloge odjemalca:


Potek dela

flowchart LR TRUST[Nalaganje shrambe zaupanja] --> CONNECT[Vzpostavitev povezave] CONNECT --> VERIFY[Preverjanje strežniškega certifikata] VERIFY --> CHAIN[Validacija verige] CHAIN --> REV[Preverjanje preklica] REV --> OK{Vse v redu?} OK -->|Da| DATA[Prenos podatkov] OK -->|Ne| ABORT[Prekinitev] style DATA fill:#e8f5e9 style ABORT fill:#ffebee


Primer kode: HttpClient s prilagojeno shrambo zaupanja

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
 
using var ctx = PqCryptoContext.Initialize();
 
// Nalaganje prilagojene shrambe zaupanja
var trustedCerts = new X509Certificate2Collection();
trustedCerts.Add(ctx.LoadCertificate("company-root-ca.crt.pem"));
trustedCerts.Add(ctx.LoadCertificate("company-intermediate-ca.crt.pem"));
 
// Konfiguracija HttpClientHandler
var handler = new HttpClientHandler
{
    // Prilagojena validacija strežniškega certifikata
    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
    {
        // Najprej preverjanje standardnih napak
        if (errors == SslPolicyErrors.None)
        {
            return true;
        }
 
        // Pri UntrustedRoot: Uporaba prilagojene shrambe zaupanja
        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($"Napaka verige: {status.StatusInformation}");
                }
            }
 
            return isValid;
        }
 
        Console.WriteLine($"SSL napaka: {errors}");
        return false;
    },
 
    // TLS različica
    SslProtocols = SslProtocols.Tls13,
 
    // Preverjanje preklica
    CheckCertificateRevocationList = true
};
 
using var httpClient = new HttpClient(handler)
{
    Timeout = TimeSpan.FromSeconds(30)
};
 
// Izvajanje zahteve
var response = await httpClient.GetAsync("https://api.example.com/data");
Console.WriteLine($"Status: {response.StatusCode}");

Primer kode: TLS odjemalec s SocketsHttpHandler

// SocketsHttpHandler za več nadzora
var socketsHandler = new SocketsHttpHandler
{
    // Združevanje povezav
    PooledConnectionLifetime = TimeSpan.FromMinutes(15),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
    MaxConnectionsPerServer = 10,
 
    // TLS konfiguracija
    SslOptions = new SslClientAuthenticationOptions
    {
        // Zahtevanje TLS 1.3
        EnabledSslProtocols = SslProtocols.Tls13,
 
        // Ime strežnika za SNI
        TargetHost = "api.example.com",
 
        // Povratni klic za validacijo certifikata
        RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
        {
            if (errors == SslPolicyErrors.None) return true;
 
            // Beleženje
            Console.WriteLine($"Certifikat: {certificate?.Subject}");
            Console.WriteLine($"Napake: {errors}");
 
            return false;  // Stroga validacija
        },
 
        // Aplikacijski protokoli (ALPN)
        ApplicationProtocols = new List<SslApplicationProtocol>
        {
            SslApplicationProtocol.Http2,
            SslApplicationProtocol.Http11
        }
    }
};
 
using var client = new HttpClient(socketsHandler);

Pripenjanje certifikata

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 napaka politike: {errors}");
            return false;
        }
 
        // 2. Preverjanje pripetja (list ali vmesni)
        bool pinValid = false;
 
        // Preverjanje list certifikata
        if (_pinnedCertificates.Contains(cert.Thumbprint))
        {
            pinValid = true;
        }
 
        // Iskanje po verigi
        if (!pinValid && chain != null)
        {
            foreach (var element in chain.ChainElements)
            {
                if (_pinnedCertificates.Contains(element.Certificate.Thumbprint))
                {
                    pinValid = true;
                    break;
                }
            }
        }
 
        if (!pinValid)
        {
            Console.WriteLine($"Neujemanje pripetja certifikata: {cert.Thumbprint}");
        }
 
        return pinValid;
    }
}
 
// Uporaba
var pinnedHandler = new CertificatePinningHandler(
    "A1B2C3D4E5F6...",  // Prstni odtis list certifikata
    "B2C3D4E5F6G7..."   // Prstni odtis vmesnega certifikata (rezervno pripetje)
);
 
using var client = new HttpClient(pinnedHandler);

Upravljanje shrambe zaupanja

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

Logika ponovnih poskusov pri TLS napakah

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

Panožne zahteve za odjemalce

Panoga Shramba zaupanja Pripenjanje Časovna omejitev
Finance Lastna PKI Obvezno 30 sekund
Zdravstvo gematik TSL Priporočeno 60 sekund
IoT Vgrajeno Obvezno 120 sekund
Podjetja AD/GPO porazdeljeno Opcijsko 30 sekund

Povezani scenariji

Povezava Scenarij Opis
Predpogoj 10.1 TLS strežnik Konfiguracija strežnika
Razširitev 10.3 Uvajanje mTLS Dodajanje odjemalčevega certifikata
Povezano 5.2 Validacija verige Preverjanje certifikata

« ← 10.1 TLS strežnik | ↑ Pregled TLS | 10.3 Uvajanje mTLS → »


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