Skip to content

Conversation

@Arkatufus
Copy link
Contributor

@Arkatufus Arkatufus commented Sep 15, 2025

Fixes #7823
Supercedes #7821

Summary

  • Surface TLS handshake failures from DotNetty in Akka.Remote.
  • Log the actual SSL/private-key error at ERROR and fail outbound associations fast with a clear exception instead of silent disassociations.
  • Adds unit test.

Changes

  • DotNetty transport
    • Handle TlsHandshakeCompletionEvent in TcpHandlers.UserEventTriggered:
      • Log failure at ERROR with the underlying exception.
      • Notify via UnderlyingTransportError when possible.
      • Close the channel.
    • Client association (AssociateInternal)
      • Wait for TLS handshake completion when SSL is enabled.
      • Throw InvalidAssociationException with the TLS failure as inner exception on error.
  • Tests
    • Added DotNettyTlsHandshakeFailureSpec to assert that server logs “TLS handshake failed …” at ERROR when cert lacks a private key and a client connects.

Behavioral impact

  • On TLS failures:
    • Server logs ERROR with the underlying exception (e.g., private key access denied).
    • Client Associate now fails fast with InvalidAssociationException containing the TLS error.
  • No public API changes. Normal operation unaffected.

Copy link
Contributor Author

@Arkatufus Arkatufus left a comment

Choose a reason for hiding this comment

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

Self review

Comment on lines +67 to +84
public override void UserEventTriggered(IChannelHandlerContext context, object evt)
{
if (evt is TlsHandshakeCompletionEvent { IsSuccessful: false } tlsEvent)
{
var ex = tlsEvent.Exception ?? new Exception("TLS handshake failed.");
Log.Error(ex, "TLS handshake failed. Channel [{0}->{1}](Id={2})",
context.Channel.LocalAddress, context.Channel.RemoteAddress, context.Channel.Id);

// Best-effort surface to higher layers if listener already registered
NotifyListener(new UnderlyingTransportError(ex,
$"TLS handshake failed on channel [{context.Channel.LocalAddress}->{context.Channel.RemoteAddress}](Id={context.Channel.Id})"));

context.CloseAsync();
return; // don't pass to next handlers
}

base.UserEventTriggered(context, evt);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Server side fix, listen for TLS handshake failure event and log/propagate it.

Comment on lines +175 to +191
public override void UserEventTriggered(IChannelHandlerContext context, object evt)
{
if (evt is TlsHandshakeCompletionEvent tlsEvent)
{
if (tlsEvent.IsSuccessful)
{
_tlsHandshakePromise.TrySetResult(true);
}
else
{
var ex = tlsEvent.Exception ?? new Exception("TLS handshake failed.");
_tlsHandshakePromise.TrySetException(ex);
}
}

base.UserEventTriggered(context, evt);
}
Copy link
Contributor Author

@Arkatufus Arkatufus Sep 15, 2025

Choose a reason for hiding this comment

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

Client side fix, listen for TLS handshake failure and log/propagate it.

@Arkatufus
Copy link
Contributor Author

Server side now logs the error appropriately:

[ERROR][09/15/2025 17:45:52.318Z][Thread 0207][TcpServerHandler (akka://ServerSystem)] TLS handshake failed. Channel [[::ffff:127.0.0.1]:52428->[::ffff:127.0.0.1]:52431](Id=091aa123)
Cause: System.AggregateException: One or more errors occurred. ---> System.NotSupportedException: The server mode SSL must use a certificate with the associated private key.
   at System.Net.Security.SslState.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslState.EndProcessAuthentication(IAsyncResult result)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
   --- End of inner exception stack trace ---
---> (Inner Exception #0) System.NotSupportedException: The server mode SSL must use a certificate with the associated private key.
   at System.Net.Security.SslState.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslState.EndProcessAuthentication(IAsyncResult result)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)<---

The client side logs the error appropriately:

[ERROR][09/15/2025 17:45:52.320Z][Thread 0218][TcpClientHandler (akka://ClientSystem)] TLS handshake failed. Channel [[::ffff:127.0.0.1]:52431->[::ffff:127.0.0.1]:52428](Id=61619740)
Cause: System.IO.IOException: Channel is closed
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
   at Akka.Remote.Transport.DotNetty.TcpTransport.<AssociateInternal>d__1.MoveNext() in D:\git\akkadotnet\akka.net\src\core\Akka.Remote\Transport\DotNetty\TcpTransport.cs:line 259
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
   at Akka.Remote.Transport.DotNetty.TcpTransport.<AssociateInternal>d__1.MoveNext() in D:\git\akkadotnet\akka.net\src\core\Akka.Remote\Transport\DotNetty\TcpTransport.cs:line 259

Copy link
Member

@Aaronontheweb Aaronontheweb left a comment

Choose a reason for hiding this comment

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

LGTM

@Aaronontheweb Aaronontheweb enabled auto-merge (squash) September 15, 2025 19:01
@Aaronontheweb Aaronontheweb merged commit f595a84 into akkadotnet:v1.5 Sep 15, 2025
9 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants