Scenario 10.2: TLS Client Configuration

Category: TLS/mTLS
Complexity: ⭐⭐⭐ (Medium)
Prerequisites: Trust store, optional client certificate
Estimated Time: 15-20 Minutes


Description

This scenario describes the configuration of a TLS client for secure connections to servers with Post-Quantum certificates.

Client Tasks:

  • Validate server certificate
  • Configure trust store
  • TLS version and cipher suites
  • Optional: Present client certificate

Workflow

flowchart LR TRUST[Load Trust Store] --> CONNECT[Establish Connection] CONNECT --> VERIFY[Verify Server Cert] VERIFY --> CHAIN[Validate Chain] CHAIN --> REV[Check Revocation] REV --> OK{All OK?} OK -->|Yes| DATA[Transfer Data] OK -->|No| ABORT[Abort] style DATA fill:#e8f5e9 style ABORT fill:#ffebee


Code Example: HttpClient with Custom Trust Store

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
 
using var ctx = PqCryptoContext.Initialize();
 
// Load custom trust store
var trustedCerts = new X509Certificate2Collection();
trustedCerts.Add(ctx.LoadCertificate("company-root-ca.crt.pem"));
trustedCerts.Add(ctx.LoadCertificate("company-intermediate-ca.crt.pem"));
 
// Configure HttpClientHandler
var handler = new HttpClientHandler
{
    // Custom server certificate validation
    ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
    {
        // Check standard errors first
        if (errors == SslPolicyErrors.None)
        {
            return true;
        }
 
        // For UntrustedRoot: Use custom 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($"Chain error: {status.StatusInformation}");
                }
            }
 
            return isValid;
        }
 
        Console.WriteLine($"SSL Error: {errors}");
        return false;
    },
 
    // TLS version
    SslProtocols = SslProtocols.Tls13,
 
    // Revocation checking
    CheckCertificateRevocationList = true
};
 
using var httpClient = new HttpClient(handler)
{
    Timeout = TimeSpan.FromSeconds(30)
};
 
// Execute request
var response = await httpClient.GetAsync("https://api.example.com/data");
Console.WriteLine($"Status: {response.StatusCode}");

Code Example: TLS Client with SocketsHttpHandler

// SocketsHttpHandler for more control
var socketsHandler = new SocketsHttpHandler
{
    // Connection pooling
    PooledConnectionLifetime = TimeSpan.FromMinutes(15),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
    MaxConnectionsPerServer = 10,
 
    // TLS configuration
    SslOptions = new SslClientAuthenticationOptions
    {
        // Enforce TLS 1.3
        EnabledSslProtocols = SslProtocols.Tls13,
 
        // Server name for 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;  // Strict validation
        },
 
        // 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 validation
        if (errors != SslPolicyErrors.None)
        {
            Console.WriteLine($"SSL Policy Error: {errors}");
            return false;
        }
 
        // 2. Check pin (leaf or intermediate)
        bool pinValid = false;
 
        // Check leaf certificate
        if (_pinnedCertificates.Contains(cert.Thumbprint))
        {
            pinValid = true;
        }
 
        // Search chain
        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;
    }
}
 
// Usage
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);
 
        // Check if it's a CA certificate
        var basicConstraints = cert.Extensions["2.5.29.19"] as X509BasicConstraintsExtension;
        if (basicConstraints == null || !basicConstraints.CertificateAuthority)
        {
            throw new ArgumentException("Not a CA certificate");
        }
 
        _trustedRoots.Add(cert);
        Console.WriteLine($"Trusted root added: {cert.Subject}");
    }
 
    public void AddTrustedIntermediate(string certPath)
    {
        var cert = new X509Certificate2(certPath);
        _trustedIntermediates.Add(cert);
        Console.WriteLine($"Trusted intermediate added: {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 Logic for TLS Errors

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

Industry-Specific Client Requirements

Industry Trust Store Pinning Timeout
Financial Own PKI Required 30s
Healthcare gematik TSL Recommended 60s
IoT Embedded Required 120s
Enterprise AD/GPO distributed Optional 30s

Relationship Scenario Description
Prerequisite 10.1 TLS Server Configure server
Extension 10.3 mTLS Deployment Add client cert
Related 5.2 Chain Validation Cert verification

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


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

Zuletzt geändert: on 2026/01/30 at 06:50 AM