Szenario 10.2: TLS-Client konfigurieren

Kategorie: TLS/mTLS
Komplexität: ⭐⭐⭐ (Mittel)
Voraussetzungen: Trust Store, optional Client-Zertifikat
Geschätzte Zeit: 15-20 Minuten


Beschreibung

Dieses Szenario beschreibt die Konfiguration eines TLS-Clients für sichere Verbindungen zu Servern mit Post-Quantum-Zertifikaten.

Aufgaben des Clients:

  • Server-Zertifikat validieren
  • Trust Store konfigurieren
  • TLS-Version und Cipher Suites
  • Optional: Client-Zertifikat präsentieren

Workflow

flowchart LR TRUST[Trust Store laden] --> CONNECT[Verbindung aufbauen] CONNECT --> VERIFY[Server-Cert prüfen] VERIFY --> CHAIN[Chain validieren] CHAIN --> REV[Revocation prüfen] REV --> OK{Alles OK?} OK -->|Ja| DATA[Daten übertragen] OK -->|Nein| ABORT[Abbrechen] style DATA fill:#e8f5e9 style ABORT fill:#ffebee


Code-Beispiel: HttpClient mit Custom Trust Store

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
 
using var ctx = PqCryptoContext.Initialize();
 
// Custom Trust Store laden
var trustedCerts = new X509Certificate2Collection();
trustedCerts.Add(ctx.LoadCertificate("company-root-ca.crt.pem"));
trustedCerts.Add(ctx.LoadCertificate("company-intermediate-ca.crt.pem"));
 
// HttpClientHandler konfigurieren
var handler = new HttpClientHandler
{
    // Custom Server-Zertifikats-Validierung
    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
    {
        // Standard-Fehler zuerst prüfen
        if (errors == SslPolicyErrors.None)
        {
            return true;
        }
 
        // Bei UntrustedRoot: Custom Trust Store verwenden
        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($"Chain error: {status.StatusInformation}");
                }
            }
 
            return isValid;
        }
 
        Console.WriteLine($"SSL Error: {errors}");
        return false;
    },
 
    // TLS-Version
    SslProtocols = SslProtocols.Tls13,
 
    // Revocation-Prüfung
    CheckCertificateRevocationList = true
};
 
using var httpClient = new HttpClient(handler)
{
    Timeout = TimeSpan.FromSeconds(30)
};
 
// Request ausführen
var response = await httpClient.GetAsync("https://api.example.com/data");
Console.WriteLine($"Status: {response.StatusCode}");

Code-Beispiel: TLS-Client mit SocketsHttpHandler

// SocketsHttpHandler für mehr Kontrolle
var socketsHandler = new SocketsHttpHandler
{
    // Connection Pooling
    PooledConnectionLifetime = TimeSpan.FromMinutes(15),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
    MaxConnectionsPerServer = 10,
 
    // TLS Konfiguration
    SslOptions = new SslClientAuthenticationOptions
    {
        // TLS 1.3 erzwingen
        EnabledSslProtocols = SslProtocols.Tls13,
 
        // Server-Name für SNI
        TargetHost = "api.example.com",
 
        // Certificate Validation Callback
        RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
        {
            if (errors == SslPolicyErrors.None) return true;
 
            // Logging
            Console.WriteLine($"Certificate: {certificate?.Subject}");
            Console.WriteLine($"Errors: {errors}");
 
            return false;  // Strenge Validierung
        },
 
        // 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. Standard-Validierung
        if (errors != SslPolicyErrors.None)
        {
            Console.WriteLine($"SSL Policy Error: {errors}");
            return false;
        }
 
        // 2. Pin prüfen (Leaf oder Intermediate)
        bool pinValid = false;
 
        // Leaf-Zertifikat prüfen
        if (_pinnedCertificates.Contains(cert.Thumbprint))
        {
            pinValid = true;
        }
 
        // Chain durchsuchen
        if (!pinValid && chain != null)
        {
            foreach (var element in chain.ChainElements)
            {
                if (_pinnedCertificates.Contains(element.Certificate.Thumbprint))
                {
                    pinValid = true;
                    break;
                }
            }
        }
 
        if (!pinValid)
        {
            Console.WriteLine($"Certificate pin mismatch: {cert.Thumbprint}");
        }
 
        return pinValid;
    }
}
 
// Verwendung
var pinnedHandler = new CertificatePinningHandler(
    "A1B2C3D4E5F6...",  // Leaf Thumbprint
    "B2C3D4E5F6G7..."   // Intermediate Thumbprint (Backup-Pin)
);
 
using var client = new HttpClient(pinnedHandler);

Trust Store Management

public class TrustStoreManager
{
    private readonly X509Certificate2Collection _trustedRoots = new();
    private readonly X509Certificate2Collection _trustedIntermediates = new();
 
    public void AddTrustedRoot(string certPath)
    {
        var cert = new X509Certificate2(certPath);
 
        // Prüfen ob es ein CA-Zertifikat ist
        var basicConstraints = cert.Extensions["2.5.29.19"] as X509BasicConstraintsExtension;
        if (basicConstraints == null || !basicConstraints.CertificateAuthority)
        {
            throw new ArgumentException("Kein CA-Zertifikat");
        }
 
        _trustedRoots.Add(cert);
        Console.WriteLine($"Trusted Root hinzugefügt: {cert.Subject}");
    }
 
    public void AddTrustedIntermediate(string certPath)
    {
        var cert = new X509Certificate2(certPath);
        _trustedIntermediates.Add(cert);
        Console.WriteLine($"Trusted Intermediate hinzugefügt: {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-Logik bei TLS-Fehlern

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

Branchenspezifische Client-Anforderungen

Branche Trust Store Pinning Timeout
Finanz Eigene PKI Pflicht 30s
Healthcare gematik TSL Empfohlen 60s
IoT Eingebettet Pflicht 120s
Enterprise AD/GPO verteilt Optional 30s

Verwandte Szenarien

Beziehung Szenario Beschreibung
Voraussetzung 10.1 TLS-Server Server konfigurieren
Erweiterung 10.3 mTLS Deployment Client-Cert hinzufügen
Verwandt 5.2 Chain Validation Cert-Prüfung

« ← 10.1 TLS-Server | ↑ TLS-Übersicht | 10.3 mTLS Deployment → »


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

Zuletzt geändert: den 29.01.2026 um 15:13