Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4ea5bbd
feat(remote): add CertificateValidationCallback delegate and Certific…
Aaronontheweb Oct 21, 2025
f99cc88
feat(remote): implement single execution path for certificate validat…
Aaronontheweb Oct 21, 2025
b2f079c
fix: use TlsValidationCallbacks for config-based validation in single…
Aaronontheweb Oct 21, 2025
9bdb66e
fix: reject missing client certificates in server-side mutual TLS val…
Aaronontheweb Oct 21, 2025
4b76704
docs: add programmatic certificate validation examples and consolidat…
Aaronontheweb Oct 21, 2025
6744feb
fix: correct compilation errors in TlsConfigurationSample documentati…
Aaronontheweb Oct 21, 2025
56e85e5
Merge branch 'dev' into feature/custom-cert-validation
Aaronontheweb Oct 22, 2025
732abae
Merge branch 'dev' into feature/custom-cert-validation
Arkatufus Oct 23, 2025
29ec21c
fix: wire CustomValidator through SslSettings and add comprehensive i…
Aaronontheweb Oct 23, 2025
fe4cb91
Add warning when both DotNettySslSetup and HOCON SSL certificate conf…
Aaronontheweb Oct 23, 2025
c56fe95
Remove unnecessary NET6_0_OR_GREATER conditional compilation directives
Aaronontheweb Oct 23, 2025
995234e
Consolidate TlsValidationCallbacks into public CertificateValidation API
Aaronontheweb Oct 23, 2025
46d6a47
cleaned up `CertificateValidation` composition code for default settings
Aaronontheweb Oct 23, 2025
07105a3
Add comprehensive test coverage for CertificateValidation helpers
Aaronontheweb Oct 23, 2025
bec4065
Remove unnecessary Combine() wrapper for single validators in tests
Aaronontheweb Oct 23, 2025
dc2e48a
added `nullability` annotations to DotNettyTransport
Aaronontheweb Oct 23, 2025
a9d362c
Remove obsolete Mono and NET471 workarounds from SSL tests
Aaronontheweb Oct 23, 2025
3ec36a7
Fix null certificate handling in SSL validation methods
Aaronontheweb Oct 23, 2025
4302a0f
Add documentation explaining why case-insensitive thumbprint comparis…
Aaronontheweb Oct 23, 2025
841a8ef
Improve certificate validation tests with EventFilter
Aaronontheweb Oct 23, 2025
2022d63
Use EventFilter to assert SSL validation errors in multi-actor system…
Aaronontheweb Oct 23, 2025
5756bca
Revert "Use EventFilter to assert SSL validation errors in multi-acto…
Aaronontheweb Oct 23, 2025
f07ff17
remove unnecessary project reference
Aaronontheweb Oct 23, 2025
a2a59a1
added API approvals
Aaronontheweb Oct 23, 2025
1e5644e
Fix incorrect bitwise AND check with SslPolicyErrors.None
Aaronontheweb Oct 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 168 additions & 109 deletions docs/articles/remoting/security.md

Copy link
Copy Markdown
Member Author

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.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -860,12 +860,34 @@ namespace Akka.Remote.Transport
}
namespace Akka.Remote.Transport.DotNetty
{
[System.Runtime.CompilerServices.NullableAttribute(0)]
public class static CertificateValidation

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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) { }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CTOR overload for adding the custom CertificationValidationCallabck to the DotNettySslSetup. This will need to be integrated into Akka.Hosting.

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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -860,12 +860,34 @@ namespace Akka.Remote.Transport
}
namespace Akka.Remote.Transport.DotNetty
{
[System.Runtime.CompilerServices.NullableAttribute(0)]
public class static CertificateValidation
{
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) { }
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; }
Expand Down
131 changes: 131 additions & 0 deletions src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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
}
}
Loading
Loading