-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(remote): custom certificate validation with single execution path - fixes mTLS asymmetry bug #7915
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(remote): custom certificate validation with single execution path - fixes mTLS asymmetry bug #7915
Changes from all commits
4ea5bbd
f99cc88
b2f079c
9bdb66e
4b76704
6744feb
56e85e5
732abae
29ec21c
fe4cb91
c56fe95
995234e
46d6a47
07105a3
bec4065
dc2e48a
a9d362c
3ec36a7
4302a0f
841a8ef
2022d63
5756bca
f07ff17
a2a59a1
1e5644e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -860,12 +860,34 @@ namespace Akka.Remote.Transport | |
| } | ||
| namespace Akka.Remote.Transport.DotNetty | ||
| { | ||
| [System.Runtime.CompilerServices.NullableAttribute(0)] | ||
| public class static CertificateValidation | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New set of helpers for defining certificate validation callbacks |
||
| { | ||
| public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ChainPlusThen([System.Runtime.CompilerServices.NullableAttribute(new byte[] { | ||
| 1, | ||
| 2, | ||
| 2, | ||
| 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) { } | ||
| public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback Combine(params Akka.Remote.Transport.DotNetty.CertificateValidationCallback[] validators) { } | ||
| public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback PinnedCertificate(params string[] allowedThumbprints) { } | ||
| public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateChain([System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { } | ||
| [return: System.Runtime.CompilerServices.NullableAttribute(1)] | ||
| public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateHostname(string expectedHostname = null, Akka.Event.ILoggingAdapter log = null) { } | ||
| public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateIssuer(string expectedIssuerPattern, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { } | ||
| public static Akka.Remote.Transport.DotNetty.CertificateValidationCallback ValidateSubject(string expectedSubjectPattern, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Event.ILoggingAdapter log = null) { } | ||
| } | ||
| 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); | ||
| [System.Runtime.CompilerServices.NullableAttribute(0)] | ||
| public sealed class DotNettySslSetup : Akka.Actor.Setup.Setup | ||
| { | ||
| public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation) { } | ||
| public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication) { } | ||
| public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication, bool validateCertificateHostname) { } | ||
| public DotNettySslSetup(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool suppressValidation, bool requireMutualAuthentication, [System.Runtime.CompilerServices.NullableAttribute(2)] Akka.Remote.Transport.DotNetty.CertificateValidationCallback customValidator) { } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CTOR overload for adding the custom |
||
| 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) { } | ||
| public System.Security.Cryptography.X509Certificates.X509Certificate2 Certificate { get; } | ||
| [System.Runtime.CompilerServices.NullableAttribute(2)] | ||
| public Akka.Remote.Transport.DotNetty.CertificateValidationCallback CustomValidator { get; } | ||
| public bool RequireMutualAuthentication { get; } | ||
| public bool SuppressValidation { get; } | ||
| public bool ValidateCertificateHostname { get; } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,10 @@ | |
| // </copyright> | ||
| //----------------------------------------------------------------------- | ||
|
|
||
| using System.Security.Cryptography.X509Certificates; | ||
| using Akka.Actor.Setup; | ||
| using Akka.Configuration; | ||
| using Akka.Remote.Transport.DotNetty; | ||
|
|
||
| namespace Akka.Docs.Tests.Configuration | ||
| { | ||
|
|
@@ -80,5 +83,133 @@ public class TlsConfigurationSample | |
| } | ||
| "); | ||
| #endregion | ||
|
|
||
| #region ProgrammaticMutualTlsSetup | ||
| /// <summary> | ||
| /// Example of programmatic mutual TLS setup using DotNettySslSetup with custom validation. | ||
| /// This allows full programmatic control over certificate validation logic. | ||
| /// </summary> | ||
| public static void ProgrammaticMutualTlsSetup() | ||
| { | ||
| // Load or obtain your certificate | ||
| var certificate = new X509Certificate2("path/to/certificate.pfx", "password"); | ||
|
|
||
| // Create custom validator combining multiple validation strategies | ||
| var customValidator = CertificateValidation.Combine( | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you want stricter TLS validation, this is a good example of how to do it with certificate pinning. |
||
| // Validate the certificate chain | ||
| CertificateValidation.ValidateChain(), | ||
| // Also pin against known thumbprints for additional security | ||
| CertificateValidation.PinnedCertificate(certificate.Thumbprint) | ||
| ); | ||
|
|
||
| // Setup SSL with custom validator taking precedence over HOCON config | ||
| var sslSetup = new DotNettySslSetup( | ||
| certificate: certificate, | ||
| suppressValidation: false, | ||
| requireMutualAuthentication: true, | ||
| customValidator: customValidator | ||
| ); | ||
| } | ||
| #endregion | ||
|
|
||
| #region CertificatePinningExample | ||
| /// <summary> | ||
| /// Example of certificate pinning - only accept certificates with specific thumbprints. | ||
| /// Useful for preventing man-in-the-middle attacks with compromised CAs. | ||
| /// </summary> | ||
| public static void CertificatePinningSetup() | ||
| { | ||
| var certificate = new X509Certificate2("path/to/certificate.pfx", "password"); | ||
|
|
||
| // Allow only specific certificates by thumbprint | ||
| var validator = CertificateValidation.PinnedCertificate( | ||
| "2531c78c51e5041d02564697a88af8bc7a7ce3e3", // Production cert | ||
| "abc123def456789ghi012jkl345mno678pqr901stu" // Backup cert | ||
| ); | ||
|
|
||
| var sslSetup = new DotNettySslSetup( | ||
| certificate: certificate, | ||
| suppressValidation: false, | ||
| requireMutualAuthentication: true, | ||
| customValidator: validator | ||
| ); | ||
| } | ||
| #endregion | ||
|
|
||
| #region CustomValidationLogicExample | ||
| /// <summary> | ||
| /// Example of custom certificate validation logic combined with standard validation. | ||
| /// Allows complete control over what certificates are accepted. | ||
| /// </summary> | ||
| public static void CustomValidationLogicSetup() | ||
| { | ||
| var certificate = new X509Certificate2("path/to/certificate.pfx", "password"); | ||
|
|
||
| // Start with standard chain validation, then add custom logic | ||
| var validator = CertificateValidation.ChainPlusThen( | ||
| // Custom validation - check certificate subject matches expected peer | ||
| (cert, chain, peer) => | ||
| { | ||
| // Accept only certificates from authorized-peer | ||
| if (cert?.Subject != null && cert.Subject.Contains("CN=authorized-peer")) | ||
| { | ||
| return true; // Accept this certificate | ||
| } | ||
| return false; // Reject all others | ||
| } | ||
| ); | ||
|
|
||
| var sslSetup = new DotNettySslSetup( | ||
| certificate: certificate, | ||
| suppressValidation: false, | ||
| requireMutualAuthentication: true, | ||
| customValidator: validator | ||
| ); | ||
| } | ||
| #endregion | ||
|
|
||
| #region HostnameValidationExample | ||
| /// <summary> | ||
| /// Example of enabling traditional hostname validation for client-server architectures. | ||
| /// Use when all nodes share the same certificate with matching CN/SAN. | ||
| /// </summary> | ||
| public static void HostnameValidationSetup() | ||
| { | ||
| var certificate = new X509Certificate2("path/to/certificate.pfx", "password"); | ||
|
|
||
| // Enable both chain validation and hostname validation | ||
| var sslSetup = new DotNettySslSetup( | ||
| certificate: certificate, | ||
| suppressValidation: false, | ||
| requireMutualAuthentication: true, | ||
| validateCertificateHostname: true // Enable traditional TLS hostname validation | ||
| ); | ||
| } | ||
| #endregion | ||
|
|
||
| #region SubjectValidationExample | ||
| /// <summary> | ||
| /// Example of subject DN validation - only accept certificates with specific subject names. | ||
| /// Useful for verifying peer identity based on certificate subject. | ||
| /// Supports wildcards: "CN=Akka-Node-*" matches "CN=Akka-Node-001" | ||
| /// </summary> | ||
| public static void SubjectValidationSetup() | ||
| { | ||
| var certificate = new X509Certificate2("path/to/certificate.pfx", "password"); | ||
|
|
||
| // Accept certificates matching the subject pattern | ||
| // Wildcards are supported: CN=Akka-Node-* matches CN=Akka-Node-001 | ||
| var validator = CertificateValidation.ValidateSubject( | ||
| "CN=Akka-Node-*" // Pattern to match | ||
| ); | ||
|
|
||
| var sslSetup = new DotNettySslSetup( | ||
| certificate: certificate, | ||
| suppressValidation: false, | ||
| requireMutualAuthentication: true, | ||
| customValidator: validator | ||
| ); | ||
| } | ||
| #endregion | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably need to simplify and clean this up in a separate PR, but the new cert validation strategies are documented here.