Skip to content

Commit 98c25d1

Browse files
feat(remote): custom certificate validation with single execution path - fixes mTLS asymmetry bug (#7915) (#7921)
* feat(remote): add CertificateValidationCallback delegate and CertificateValidation helper factory - Define public CertificateValidationCallback delegate for custom certificate validation - Add CertificateValidation factory class with 7 helper methods: * ValidateChain() - CA chain validation * ValidateHostname() - CN/SAN matching * PinnedCertificate() - Certificate pinning by thumbprint * ValidateSubject() - Subject DN matching (with wildcard support) * ValidateIssuer() - Issuer DN matching * Combine() - Compose multiple validators * ChainPlusThen() - Chain validation + custom logic - Add CustomValidator property to DotNettySslSetup with overloaded constructors - Maintain full backward compatibility with existing config-based validation Relates to #7914 * feat(remote): implement single execution path for certificate validation with hostname validation asymmetry fix - Integrate custom certificate validators into DotNettyTransport pipelines (client and server) - Implement single execution path: compose validator from config when custom not provided - Add ComposeValidatorFromSettings() to build validators from SuppressValidation and ValidateCertificateHostname settings - Add CustomValidator property to SslSettings with updated constructors for seamless integration - Fix asymmetry bug: server-side now applies hostname validation like client-side - Replace dual-path logic (custom vs config-based) with unified composition pattern - Add hostname matching helper with reflection-based SAN support for multi-framework compatibility - Eliminates need for TlsValidationCallbacks on each pipeline setup call * fix: use TlsValidationCallbacks for config-based validation in single execution path - Revert to using proven TlsValidationCallbacks logic for configuration-based validation - This maintains compatibility with existing validation behavior while enabling single execution path - CertificateValidation helpers remain available for custom user validators - Reduces test failures from 9 to 2 by using well-tested validation logic * fix: reject missing client certificates in server-side mutual TLS validation When the server requires mutual TLS authentication (RequireMutualAuthentication=true), it must reject TLS handshakes where the client fails to provide a certificate. Previously, the validation callback would pass a null certificate to the composed validator without pre-checking it. This allowed connections from clients without certificates to succeed when they should fail. Now we explicitly check if the certificate is null when mutual auth is required and immediately reject the connection with a warning log message. This fixes the failing test: Mutual_TLS_should_fail_when_client_has_no_certificate Fixes: All 329 tests now pass (324 passed, 5 skipped, 0 failed) * docs: add programmatic certificate validation examples and consolidate security documentation Adds comprehensive programmatic certificate validation examples to TlsConfigurationSample: - ProgrammaticMutualTlsSetup: Basic mutual TLS with custom validators - CertificatePinningExample: Certificate pinning by thumbprint - CustomValidationLogicExample: Chain validation + custom business logic - HostnameValidationExample: Programmatic hostname validation setup - SubjectValidationExample: Subject DN validation Consolidates security.md documentation: - Merged "Hostname Validation" and "Mutual TLS Authentication" into unified "Validation Strategies: HOCON vs Programmatic" section with decision matrix - Added examples for both P2P clusters and client-server architectures - Cross-referenced sections to reduce duplication - Clarified when to use programmatic vs HOCON configuration Follows documentation guidelines (security.md:70): - Uses !code references with #region tags for live code examples - Organizes content for discoverability - Provides decision matrix for choosing validation strategy * fix: correct compilation errors in TlsConfigurationSample documentation examples - Add Akka.Remote reference to Akka.Docs.Tests project for DotNettySslSetup types - Wrap new programmatic examples with #if NET6_0_OR_GREATER for framework compatibility - Convert example methods to void to simplify documentation-only code - Fix API usage: use params instead of arrays, correct delegate signatures - Remove BootstrapSetup complexity from examples to focus on core TLS setup patterns * fix: wire CustomValidator through SslSettings and add comprehensive integration tests CRITICAL FIXES: - Fixed DotNettySslSetup.Settings to pass CustomValidator to SslSettings constructor (Line 114 was creating SslSettings without the CustomValidator parameter) - Removed unused ValidateCertificateHostnameMatch method (489-542) (Hostname validation is already handled by TlsValidationCallbacks.Create) NEW TESTS - CustomValidator Functionality: - CustomValidator_that_accepts_should_allow_connection * Verifies CustomValidator callback is invoked during TLS handshake * Verifies acceptance allows successful connection - CustomValidator_that_rejects_should_prevent_connection * Verifies CustomValidator rejection prevents connection * Verifies callback was invoked even when rejecting - DotNettySslSetup_should_pass_CustomValidator_to_SslSettings * Unit test verifying CustomValidator is wired through to SslSettings Addresses PR review comments #7915: - CustomValidator now properly wired to SslSettings - Removed dead code (ValidateCertificateHostnameMatch) - Added real integration tests that validate CustomValidator actually works * Add warning when both DotNettySslSetup and HOCON SSL certificate config are present - Logs warning when DotNettySslSetup is used alongside explicit HOCON certificate configuration - Only warns when HOCON has actual certificate.path or certificate.thumbprint configured - Avoids false positives from default/empty config sections - Adds test verifying DotNettySslSetup precedence behavior - Addresses PR feedback: implement Option 1 from review comment * Remove unnecessary NET6_0_OR_GREATER conditional compilation directives - All types used (X509Certificate2, DotNettySslSetup, CertificateValidation) are available in .NET Standard 2.0 - Conditional directives were added during troubleshooting but are not needed - Verified compilation on both net8.0 and net48 targets * Consolidate TlsValidationCallbacks into public CertificateValidation API Removes internal TlsValidationCallbacks class and related enums (~145 lines) and refactors ComposeValidatorFromSettings() to use the public CertificateValidation helpers instead. Changes: - Remove ChainValidationMode and HostnameValidationMode enums - Remove TlsValidationCallbacks internal class - Refactor ComposeValidatorFromSettings() to handle all 4 combinations of SuppressValidation and ValidateCertificateHostname flags: * suppressChain=true, validateHostname=false → Accept all * suppressChain=true, validateHostname=true → Validate hostname only * suppressChain=false, validateHostname=true → Chain + hostname * suppressChain=false, validateHostname=false → Chain only (default) Benefits: - Eliminates code duplication between internal and public APIs - Simplifies maintenance by having a single validation implementation - Makes the public CertificateValidation API the canonical approach - All 43 DotNetty tests pass including edge case validations * cleaned up `CertificateValidation` composition code for default settings * Add comprehensive test coverage for CertificateValidation helpers Added 11 new tests to achieve 100% coverage of previously untested CertificateValidation helper methods: PinnedCertificate tests: - Accept connections with matching thumbprint - Reject connections with non-matching thumbprint ValidateSubject tests: - Accept certificates with matching subject - Reject certificates with non-matching subject - Support wildcard pattern matching (CN=Akka-Node-*) ValidateIssuer tests: - Accept certificates with matching issuer Combine/ChainPlusThen tests: - Verify composability of validators CustomValidator precedence tests: - Verify CustomValidator overrides validateCertificateHostname setting Also removed obsolete Mono checks from all new tests per maintainer guidance (Mono is no longer supported). Test results: 18/18 passing (7 existing + 11 new) * Remove unnecessary Combine() wrapper for single validators in tests Simplified two test cases that were unnecessarily wrapping single CertificateValidationCallback delegates in Combine(): - CustomValidator_that_accepts_should_allow_connection - CustomValidator_that_rejects_should_prevent_connection Changed from: var validator = CertificateValidation.Combine((cert, ...) => true); To cleaner direct delegate assignment: CertificateValidationCallback validator = (cert, ...) => true; Combine() is only needed when composing multiple validators. These tests verify single custom validators, so direct assignment is clearer. All tests still pass (4/4 CustomValidator tests verified). * added `nullability` annotations to DotNettyTransport * Remove obsolete Mono and NET471 workarounds from SSL tests - Removed #if !NET471 conditional compilation directives (10 instances) Project now targets net48, making NET471 conditionals meaningless - Removed if (IsMono) runtime checks (7 instances) Modern .NET uses CoreCLR cross-platform, not Mono - All SSL tests now run unconditionally on supported platforms - Tests verified passing: 27/27 on net8.0, 26/26 on net48 * Fix null certificate handling in SSL validation methods - Added explicit null checks to all CertificateValidation helper methods - PinnedCertificate: Check for null cert and filter empty thumbprints - ValidateSubject/ValidateIssuer: Check for null cert and empty values - ValidateHostname: Check for null cert before accessing properties - ValidateChain: Check for null cert before chain validation - Improved error messages to distinguish null cert from other failures - Added comprehensive unit test coverage for edge cases - Prevents potential NullReferenceException in TLS handshake scenarios * Add documentation explaining why case-insensitive thumbprint comparison is safe Added detailed comment explaining: - Thumbprints are hexadecimal SHA hash representations - Hex values are inherently case-insensitive (2A8B == 2a8b) - Different tools display differently (Windows vs OpenSSL) - Case-insensitive comparison improves usability without compromising security * Improve certificate validation tests with EventFilter - Replaced ExpectMsg with EventFilter for proper log assertion pattern - EventFilter is the idiomatic way to assert log messages in Akka.NET tests - Added test for rejecting non-matching thumbprint with EventFilter - Updated Combine test to clearly document short-circuit behavior - All tests now properly verify both result AND expected log messages * Use EventFilter to assert SSL validation errors in multi-actor system tests Updated SSL integration tests to use EventFilter for asserting specific validation errors instead of just checking connection failure. This provides better test precision by verifying the exact reason for connection failure. With mTLS enabled, validation errors occur on the server side (_sys2) when it validates the client certificate, since the client (Sys) has suppressValidation enabled. The EventFilter assertions are correctly targeted to the system where the validation errors occur. Changes: - Added EventFilter assertions to PinnedCertificate rejection test - Added EventFilter assertions to CustomValidator rejection test - Added EventFilter assertions to ValidateSubject rejection test - Modified custom validator to log error for EventFilter detection - Added comments explaining the mTLS validation flow * Revert "Use EventFilter to assert SSL validation errors in multi-actor system tests" This reverts commit 2022d63. * remove unnecessary project reference * added API approvals * Fix incorrect bitwise AND check with SslPolicyErrors.None The condition `(errors & SslPolicyErrors.None) != SslPolicyErrors.None` was always false because SslPolicyErrors.None equals 0, and any value bitwise AND with 0 always results in 0. Changed to simple equality check `errors != SslPolicyErrors.None` to correctly detect when SSL policy errors are present. This bug prevented the TlsErrorMessageBuilder from ever building detailed error messages when SSL validation failed, making debugging harder. --------- Co-authored-by: Gregorius Soedharmo <arkatufus@yahoo.com>
1 parent 1e8d606 commit 98c25d1

File tree

10 files changed

+1513
-267
lines changed

10 files changed

+1513
-267
lines changed

docs/articles/remoting/security.md

Lines changed: 168 additions & 109 deletions
Large diffs are not rendered by default.

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveRemote.DotNet.verified.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,12 +860,34 @@ namespace Akka.Remote.Transport
860860
}
861861
namespace Akka.Remote.Transport.DotNetty
862862
{
863+
[System.Runtime.CompilerServices.NullableAttribute(0)]
864+
public class static CertificateValidation
865+
{
866+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ChainPlusThen([System.Runtime.CompilerServices.NullableAttribute(new byte[] {
867+
1,
868+
2,
869+
2,
870+
1})] System.Func<System.Security.Cryptography.X509Certificates.X509Certificate2, System.Security.Cryptography.X509Certificates.X509Chain, string, bool> customCheck, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { }
871+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback Combine(params Akka.Remote.Transport.DotNetty.CertificateValidationCallback[] validators) { }
872+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback PinnedCertificate(params string[] allowedThumbprints) { }
873+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateChain([System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { }
874+
[return: System.Runtime.CompilerServices.NullableAttribute(1)]
875+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateHostname(string expectedHostname = null, Akka.Event.ILoggingAdapter log = null) { }
876+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateIssuer(string expectedIssuerPattern, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { }
877+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateSubject(string expectedSubjectPattern, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { }
878+
}
879+
public delegate bool CertificateValidationCallback([System.Runtime.CompilerServices.NullableAttribute(2)] System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, [System.Runtime.CompilerServices.NullableAttribute(2)] System.Security.Cryptography.X509Certificates.X509Chain chain, string remotePeer, System.Net.Security.SslPolicyErrors errors, Akka.Event.ILoggingAdapter log);
880+
[System.Runtime.CompilerServices.NullableAttribute(0)]
863881
public sealed class DotNettySslSetup : Akka.Actor.Setup.Setup
864882
{
865883
public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation) { }
866884
public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication) { }
867885
public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication, bool validateCertificateHostname) { }
886+
public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Remote.Transport.DotNetty.CertificateValidationCallback customValidator) { }
887+
public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication, bool validateCertificateHostname, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Remote.Transport.DotNetty.CertificateValidationCallback customValidator) { }
868888
public System.Security.Cryptography.X509Certificates.X509Certificate2 Certificate { get; }
889+
[System.Runtime.CompilerServices.NullableAttribute(2)]
890+
public Akka.Remote.Transport.DotNetty.CertificateValidationCallback CustomValidator { get; }
869891
public bool RequireMutualAuthentication { get; }
870892
public bool SuppressValidation { get; }
871893
public bool ValidateCertificateHostname { get; }

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveRemote.Net.verified.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,12 +860,34 @@ namespace Akka.Remote.Transport
860860
}
861861
namespace Akka.Remote.Transport.DotNetty
862862
{
863+
[System.Runtime.CompilerServices.NullableAttribute(0)]
864+
public class static CertificateValidation
865+
{
866+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ChainPlusThen([System.Runtime.CompilerServices.NullableAttribute(new byte[] {
867+
1,
868+
2,
869+
2,
870+
1})] System.Func<System.Security.Cryptography.X509Certificates.X509Certificate2, System.Security.Cryptography.X509Certificates.X509Chain, string, bool> customCheck, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { }
871+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback Combine(params Akka.Remote.Transport.DotNetty.CertificateValidationCallback[] validators) { }
872+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback PinnedCertificate(params string[] allowedThumbprints) { }
873+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateChain([System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { }
874+
[return: System.Runtime.CompilerServices.NullableAttribute(1)]
875+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateHostname(string expectedHostname = null, Akka.Event.ILoggingAdapter log = null) { }
876+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateIssuer(string expectedIssuerPattern, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { }
877+
public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateSubject(string expectedSubjectPattern, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { }
878+
}
879+
public delegate bool CertificateValidationCallback([System.Runtime.CompilerServices.NullableAttribute(2)] System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, [System.Runtime.CompilerServices.NullableAttribute(2)] System.Security.Cryptography.X509Certificates.X509Chain chain, string remotePeer, System.Net.Security.SslPolicyErrors errors, Akka.Event.ILoggingAdapter log);
880+
[System.Runtime.CompilerServices.NullableAttribute(0)]
863881
public sealed class DotNettySslSetup : Akka.Actor.Setup.Setup
864882
{
865883
public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation) { }
866884
public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication) { }
867885
public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication, bool validateCertificateHostname) { }
886+
public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Remote.Transport.DotNetty.CertificateValidationCallback customValidator) { }
887+
public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication, bool validateCertificateHostname, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Remote.Transport.DotNetty.CertificateValidationCallback customValidator) { }
868888
public System.Security.Cryptography.X509Certificates.X509Certificate2 Certificate { get; }
889+
[System.Runtime.CompilerServices.NullableAttribute(2)]
890+
public Akka.Remote.Transport.DotNetty.CertificateValidationCallback CustomValidator { get; }
869891
public bool RequireMutualAuthentication { get; }
870892
public bool SuppressValidation { get; }
871893
public bool ValidateCertificateHostname { get; }

src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
// </copyright>
66
//-----------------------------------------------------------------------
77

8+
using System.Security.Cryptography.X509Certificates;
9+
using Akka.Actor.Setup;
810
using Akka.Configuration;
11+
using Akka.Remote.Transport.DotNetty;
912

1013
namespace Akka.Docs.Tests.Configuration
1114
{
@@ -80,5 +83,133 @@ public class TlsConfigurationSample
8083
}
8184
");
8285
#endregion
86+
87+
#region ProgrammaticMutualTlsSetup
88+
/// <summary>
89+
/// Example of programmatic mutual TLS setup using DotNettySslSetup with custom validation.
90+
/// This allows full programmatic control over certificate validation logic.
91+
/// </summary>
92+
public static void ProgrammaticMutualTlsSetup()
93+
{
94+
// Load or obtain your certificate
95+
var certificate = new X509Certificate2("path/to/certificate.pfx", "password");
96+
97+
// Create custom validator combining multiple validation strategies
98+
var customValidator = CertificateValidation.Combine(
99+
// Validate the certificate chain
100+
CertificateValidation.ValidateChain(),
101+
// Also pin against known thumbprints for additional security
102+
CertificateValidation.PinnedCertificate(certificate.Thumbprint)
103+
);
104+
105+
// Setup SSL with custom validator taking precedence over HOCON config
106+
var sslSetup = new DotNettySslSetup(
107+
certificate: certificate,
108+
suppressValidation: false,
109+
requireMutualAuthentication: true,
110+
customValidator: customValidator
111+
);
112+
}
113+
#endregion
114+
115+
#region CertificatePinningExample
116+
/// <summary>
117+
/// Example of certificate pinning - only accept certificates with specific thumbprints.
118+
/// Useful for preventing man-in-the-middle attacks with compromised CAs.
119+
/// </summary>
120+
public static void CertificatePinningSetup()
121+
{
122+
var certificate = new X509Certificate2("path/to/certificate.pfx", "password");
123+
124+
// Allow only specific certificates by thumbprint
125+
var validator = CertificateValidation.PinnedCertificate(
126+
"2531c78c51e5041d02564697a88af8bc7a7ce3e3", // Production cert
127+
"abc123def456789ghi012jkl345mno678pqr901stu" // Backup cert
128+
);
129+
130+
var sslSetup = new DotNettySslSetup(
131+
certificate: certificate,
132+
suppressValidation: false,
133+
requireMutualAuthentication: true,
134+
customValidator: validator
135+
);
136+
}
137+
#endregion
138+
139+
#region CustomValidationLogicExample
140+
/// <summary>
141+
/// Example of custom certificate validation logic combined with standard validation.
142+
/// Allows complete control over what certificates are accepted.
143+
/// </summary>
144+
public static void CustomValidationLogicSetup()
145+
{
146+
var certificate = new X509Certificate2("path/to/certificate.pfx", "password");
147+
148+
// Start with standard chain validation, then add custom logic
149+
var validator = CertificateValidation.ChainPlusThen(
150+
// Custom validation - check certificate subject matches expected peer
151+
(cert, chain, peer) =>
152+
{
153+
// Accept only certificates from authorized-peer
154+
if (cert?.Subject != null && cert.Subject.Contains("CN=authorized-peer"))
155+
{
156+
return true; // Accept this certificate
157+
}
158+
return false; // Reject all others
159+
}
160+
);
161+
162+
var sslSetup = new DotNettySslSetup(
163+
certificate: certificate,
164+
suppressValidation: false,
165+
requireMutualAuthentication: true,
166+
customValidator: validator
167+
);
168+
}
169+
#endregion
170+
171+
#region HostnameValidationExample
172+
/// <summary>
173+
/// Example of enabling traditional hostname validation for client-server architectures.
174+
/// Use when all nodes share the same certificate with matching CN/SAN.
175+
/// </summary>
176+
public static void HostnameValidationSetup()
177+
{
178+
var certificate = new X509Certificate2("path/to/certificate.pfx", "password");
179+
180+
// Enable both chain validation and hostname validation
181+
var sslSetup = new DotNettySslSetup(
182+
certificate: certificate,
183+
suppressValidation: false,
184+
requireMutualAuthentication: true,
185+
validateCertificateHostname: true // Enable traditional TLS hostname validation
186+
);
187+
}
188+
#endregion
189+
190+
#region SubjectValidationExample
191+
/// <summary>
192+
/// Example of subject DN validation - only accept certificates with specific subject names.
193+
/// Useful for verifying peer identity based on certificate subject.
194+
/// Supports wildcards: "CN=Akka-Node-*" matches "CN=Akka-Node-001"
195+
/// </summary>
196+
public static void SubjectValidationSetup()
197+
{
198+
var certificate = new X509Certificate2("path/to/certificate.pfx", "password");
199+
200+
// Accept certificates matching the subject pattern
201+
// Wildcards are supported: CN=Akka-Node-* matches CN=Akka-Node-001
202+
var validator = CertificateValidation.ValidateSubject(
203+
"CN=Akka-Node-*" // Pattern to match
204+
);
205+
206+
var sslSetup = new DotNettySslSetup(
207+
certificate: certificate,
208+
suppressValidation: false,
209+
requireMutualAuthentication: true,
210+
customValidator: validator
211+
);
212+
}
213+
#endregion
83214
}
84215
}

0 commit comments

Comments
 (0)