fix(historian-sidecar): cancel SocketException guard + version-reject log + TLS test (review)
This commit is contained in:
@@ -69,6 +69,7 @@ public sealed class TcpFrameServer : IDisposable
|
|||||||
try { client = await _listener!.AcceptTcpClientAsync().ConfigureAwait(false); }
|
try { client = await _listener!.AcceptTcpClientAsync().ConfigureAwait(false); }
|
||||||
catch (ObjectDisposedException) when (linked.Token.IsCancellationRequested) { throw new OperationCanceledException(linked.Token); }
|
catch (ObjectDisposedException) when (linked.Token.IsCancellationRequested) { throw new OperationCanceledException(linked.Token); }
|
||||||
catch (InvalidOperationException) when (linked.Token.IsCancellationRequested) { throw new OperationCanceledException(linked.Token); }
|
catch (InvalidOperationException) when (linked.Token.IsCancellationRequested) { throw new OperationCanceledException(linked.Token); }
|
||||||
|
catch (SocketException) when (linked.Token.IsCancellationRequested) { throw new OperationCanceledException(linked.Token); }
|
||||||
|
|
||||||
using (client)
|
using (client)
|
||||||
{
|
{
|
||||||
@@ -107,6 +108,7 @@ public sealed class TcpFrameServer : IDisposable
|
|||||||
await writer.WriteAsync(MessageKind.HelloAck,
|
await writer.WriteAsync(MessageKind.HelloAck,
|
||||||
new HelloAck { Accepted = false, RejectReason = $"major-version-mismatch-peer={hello.ProtocolMajor}-server={Hello.CurrentMajor}" },
|
new HelloAck { Accepted = false, RejectReason = $"major-version-mismatch-peer={hello.ProtocolMajor}-server={Hello.CurrentMajor}" },
|
||||||
linked.Token).ConfigureAwait(false);
|
linked.Token).ConfigureAwait(false);
|
||||||
|
_logger.Warning("Sidecar TCP Hello rejected: major mismatch peer={Peer} server={Server}", hello.ProtocolMajor, Hello.CurrentMajor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await writer.WriteAsync(MessageKind.HelloAck,
|
await writer.WriteAsync(MessageKind.HelloAck,
|
||||||
|
|||||||
+33
-1
@@ -97,7 +97,7 @@ public sealed class TcpRoundTripTests
|
|||||||
userCertificateValidationCallback: (_, cert, _, _) =>
|
userCertificateValidationCallback: (_, cert, _, _) =>
|
||||||
cert is not null &&
|
cert is not null &&
|
||||||
string.Equals(
|
string.Equals(
|
||||||
new X509Certificate2(cert).Thumbprint,
|
cert.GetCertHashString(),
|
||||||
expectedThumbprint,
|
expectedThumbprint,
|
||||||
StringComparison.OrdinalIgnoreCase));
|
StringComparison.OrdinalIgnoreCase));
|
||||||
await ssl.AuthenticateAsClientAsync("otopcua-historian-sidecar-test", clientCertificates: null,
|
await ssl.AuthenticateAsClientAsync("otopcua-historian-sidecar-test", clientCertificates: null,
|
||||||
@@ -172,6 +172,38 @@ public sealed class TcpRoundTripTests
|
|||||||
await serverTask;
|
await serverTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TLS: when the client pins a wrong thumbprint the validation callback returns false,
|
||||||
|
/// causing <see cref="SslStream.AuthenticateAsClientAsync"/> to throw
|
||||||
|
/// <see cref="AuthenticationException"/> before any Hello is exchanged.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public async Task Tls_BadThumbprint_AuthenticationFails()
|
||||||
|
{
|
||||||
|
using var cts = new CancellationTokenSource(Timeout);
|
||||||
|
using var cert = MakeSelfSignedCert();
|
||||||
|
using var server = new TcpFrameServer(IPAddress.Loopback, 0, "shh", tlsCert: cert, Quiet);
|
||||||
|
var serverTask = server.RunOneConnectionAsync(new EchoHandler(), cts.Token);
|
||||||
|
|
||||||
|
using var client = new TcpClient();
|
||||||
|
await client.ConnectAsync(IPAddress.Loopback, server.BoundPort);
|
||||||
|
|
||||||
|
// Deliberately pin the wrong thumbprint — all zeros.
|
||||||
|
const string wrongThumbprint = "0000000000000000000000000000000000000000";
|
||||||
|
var ssl = new SslStream(client.GetStream(), leaveInnerStreamOpen: false,
|
||||||
|
userCertificateValidationCallback: (_, serverCert, _, _) =>
|
||||||
|
serverCert is not null &&
|
||||||
|
string.Equals(serverCert.GetCertHashString(), wrongThumbprint, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
await Should.ThrowAsync<AuthenticationException>(async () =>
|
||||||
|
await ssl.AuthenticateAsClientAsync("otopcua-historian-sidecar-test", clientCertificates: null,
|
||||||
|
enabledSslProtocols: SslProtocols.Tls12, checkCertificateRevocation: false));
|
||||||
|
|
||||||
|
ssl.Dispose();
|
||||||
|
// Server will see the broken TLS handshake and end the connection; let it finish.
|
||||||
|
try { await serverTask; } catch { /* server may throw on the aborted TLS */ }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Bad secret: Hello is rejected with Accepted=false and the shared-secret-mismatch reason.</summary>
|
/// <summary>Bad secret: Hello is rejected with Accepted=false and the shared-secret-mismatch reason.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BadSecret_HelloRejected()
|
public async Task BadSecret_HelloRejected()
|
||||||
|
|||||||
Reference in New Issue
Block a user