~~NOTOC~~
====== Scenario 10.2: TLS Client Configuration ======
**Category:** [[.:start|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.Http2,
SslApplicationProtocol.Http11
}
}
};
using var client = new HttpClient(socketsHandler);
----
===== Certificate Pinning =====
public class CertificatePinningHandler : HttpClientHandler
{
private readonly HashSet _pinnedCertificates;
public CertificatePinningHandler(params string[] pinnedThumbprints)
{
_pinnedCertificates = new HashSet(
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 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 |
----
===== Related Scenarios =====
^ Relationship ^ Scenario ^ Description ^
| **Prerequisite** | [[.:server_setup|10.1 TLS Server]] | Configure server |
| **Extension** | [[.:mtls_deployment|10.3 mTLS Deployment]] | Add client cert |
| **Related** | [[en:int:pqcrypt:szenarien:validierung:chain_validation|5.2 Chain Validation]] | Cert verification |
----
<< [[.:server_setup|← 10.1 TLS Server]] | [[.:start|↑ TLS Overview]] | [[.:mtls_deployment|10.3 mTLS Deployment →]] >>
{{tag>scenario tls client httpclient trust-store pinning}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//