diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Auth/AuthHandler.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Auth/AuthHandler.cs
index b8bbe96..963057e 100644
--- a/dotnet/src/ZB.MOM.NatsNet.Server/Auth/AuthHandler.cs
+++ b/dotnet/src/ZB.MOM.NatsNet.Server/Auth/AuthHandler.cs
@@ -278,16 +278,19 @@ public static partial class AuthHandler
///
public static ClosedState GetAuthErrClosedState(Exception? err)
{
- if (err == null) return ClosedState.AuthenticationTimeout;
- var msg = err.Message;
- if (msg.Contains("expired", StringComparison.OrdinalIgnoreCase)) return ClosedState.AuthenticationExpired;
- if (msg.Contains("revoked", StringComparison.OrdinalIgnoreCase)) return ClosedState.AuthRevoked;
- return ClosedState.AuthenticationViolation;
+ return err switch
+ {
+ AuthProxyNotTrustedException => ClosedState.ProxyNotTrusted,
+ AuthProxyRequiredException => ClosedState.ProxyRequired,
+ _ => ClosedState.AuthenticationViolation,
+ };
}
///
- /// Validates proxy configuration entries in options.
- /// Mirrors Go validateProxies in server/auth.go.
+ /// Validates that proxy protocol configuration is consistent.
+ /// If is set, must also be enabled.
+ /// Note: Full NKey-format validation of trusted proxy keys is deferred until proxy auth is fully implemented.
+ /// Partially mirrors Go validateProxies in server/auth.go.
///
public static Exception? ValidateProxies(ServerOptions opts)
{
diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Auth/AuthTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Auth/AuthTypes.cs
index d1ddb19..7d5716f 100644
--- a/dotnet/src/ZB.MOM.NatsNet.Server/Auth/AuthTypes.cs
+++ b/dotnet/src/ZB.MOM.NatsNet.Server/Auth/AuthTypes.cs
@@ -170,3 +170,21 @@ public class RoutePermissions
// Account stub removed — full implementation is in Accounts/Account.cs
// in the ZB.MOM.NatsNet.Server namespace.
+
+///
+/// Sentinel exception representing a proxy-auth "not trusted" error.
+/// Mirrors Go ErrAuthProxyNotTrusted in server/auth.go.
+///
+public sealed class AuthProxyNotTrustedException : InvalidOperationException
+{
+ public AuthProxyNotTrustedException() : base("proxy not trusted") { }
+}
+
+///
+/// Sentinel exception representing a proxy-auth "required" error.
+/// Mirrors Go ErrAuthProxyRequired in server/auth.go.
+///
+public sealed class AuthProxyRequiredException : InvalidOperationException
+{
+ public AuthProxyRequiredException() : base("proxy required") { }
+}
diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Auth/JwtProcessor.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Auth/JwtProcessor.cs
index d124c99..afb79e5 100644
--- a/dotnet/src/ZB.MOM.NatsNet.Server/Auth/JwtProcessor.cs
+++ b/dotnet/src/ZB.MOM.NatsNet.Server/Auth/JwtProcessor.cs
@@ -31,15 +31,6 @@ public static class JwtProcessor
///
public const string JwtPrefix = "eyJ";
- ///
- /// Wipes a byte slice by filling with 'x', for clearing nkey seed data.
- /// Mirrors Go wipeSlice.
- ///
- public static void WipeSlice(Span buf)
- {
- buf.Fill((byte)'x');
- }
-
///
/// Validates that the given IP host address is allowed by the user claims source CIDRs.
/// Returns true if the host is within any of the allowed CIDRs, or if no CIDRs are specified.
@@ -227,17 +218,9 @@ public static class JwtProcessor
if (opts.TrustedOperators == null || opts.TrustedOperators.Count == 0)
return null;
- // Each operator should be a well-formed JWT.
- foreach (var op in opts.TrustedOperators)
- {
- var jwtStr = op?.ToString() ?? string.Empty;
- var (_, err) = ReadOperatorJwtInternal(jwtStr);
- // Allow the "not implemented" case through — structure validated up to prefix check.
- if (err is FormatException fe && fe.Message.Contains("not fully implemented"))
- continue;
- if (err is ArgumentException)
- return new InvalidOperationException($"invalid trusted operator JWT: {err.Message}");
- }
+ // TODO: Full trusted operator JWT validation requires a NATS JWT library.
+ // Each operator JWT should be decoded and its signing key chain verified.
+ // For now, we accept any non-empty operator list and validate at connect time.
return null;
}
}
diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/ClientConnection.cs b/dotnet/src/ZB.MOM.NatsNet.Server/ClientConnection.cs
index 822d8a2..8918f0c 100644
--- a/dotnet/src/ZB.MOM.NatsNet.Server/ClientConnection.cs
+++ b/dotnet/src/ZB.MOM.NatsNet.Server/ClientConnection.cs
@@ -872,12 +872,10 @@ public sealed partial class ClientConnection
internal void SetPermissions(Auth.Permissions? perms)
{
- // Full permission installation deferred to later session.
- // Store in Perms for now.
lock (_mu)
{
if (perms != null)
- Perms ??= new ClientPermissions();
+ Perms = BuildPermissions(perms);
}
}
diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/ClientTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/ClientTypes.cs
index b0b5d22..06c25a1 100644
--- a/dotnet/src/ZB.MOM.NatsNet.Server/ClientTypes.cs
+++ b/dotnet/src/ZB.MOM.NatsNet.Server/ClientTypes.cs
@@ -166,7 +166,6 @@ public enum ClosedState
Kicked,
ProxyNotTrusted,
ProxyRequired,
- AuthRevoked,
}
// ============================================================================
diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Auth.cs b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Auth.cs
index 0a88c02..af78aba 100644
--- a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Auth.cs
+++ b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Auth.cs
@@ -169,9 +169,13 @@ public sealed partial class NatsServer
{
try
{
- var kp = NATS.NKeys.KeyPair.FromPublicKey(nkeyPub.AsSpan());
- // Sig is base64url-encoded; nonce is raw bytes.
- var sigBytes = Convert.FromBase64String(sig.Replace('-', '+').Replace('_', '/'));
+ var kp = NATS.NKeys.KeyPair.FromPublicKey(nkeyPub.AsSpan());
+ // Sig is raw URL-safe base64; convert to standard base64 with padding.
+ var padded = sig.Replace('-', '+').Replace('_', '/');
+ var rem = padded.Length % 4;
+ if (rem == 2) padded += "==";
+ else if (rem == 3) padded += "=";
+ var sigBytes = Convert.FromBase64String(padded);
var verified = kp.Verify(
new ReadOnlyMemory(nonce),
new ReadOnlyMemory(sigBytes));
diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Lifecycle.cs b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Lifecycle.cs
index 093d773..bc101de 100644
--- a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Lifecycle.cs
+++ b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Lifecycle.cs
@@ -141,6 +141,8 @@ public sealed partial class NatsServer
if (_ocsprc != null) { /* stub — stop OCSP cache in session 23 */ }
+ DisposeSignalHandlers();
+
_shutdownComplete.TrySetResult();
}
diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Signals.cs b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Signals.cs
index f9abe78..7449ee1 100644
--- a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Signals.cs
+++ b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Signals.cs
@@ -73,7 +73,7 @@ public sealed partial class NatsServer
});
}
- private void DisposeSignalHandlers()
+ internal void DisposeSignalHandlers()
{
_sigHup?.Dispose();
_sigTerm?.Dispose();
diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/AuthImplementationTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/AuthImplementationTests.cs
index 1c0cc7c..8b15f92 100644
--- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/AuthImplementationTests.cs
+++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/AuthImplementationTests.cs
@@ -27,23 +27,30 @@ public class AuthHandlerExtendedTests
}
[Fact]
- public void GetAuthErrClosedState_ExpiredMessage_ReturnsExpiredState()
+ public void GetAuthErrClosedState_ProxyNotTrusted_ReturnsProxyNotTrusted()
{
- var err = new InvalidOperationException("token is expired");
- AuthHandler.GetAuthErrClosedState(err).ShouldBe(ClosedState.AuthenticationExpired);
+ var err = new AuthProxyNotTrustedException();
+ AuthHandler.GetAuthErrClosedState(err).ShouldBe(ClosedState.ProxyNotTrusted);
}
[Fact]
- public void GetAuthErrClosedState_NullError_ReturnsTimeout()
+ public void GetAuthErrClosedState_ProxyRequired_ReturnsProxyRequired()
{
- AuthHandler.GetAuthErrClosedState(null).ShouldBe(ClosedState.AuthenticationTimeout);
+ var err = new AuthProxyRequiredException();
+ AuthHandler.GetAuthErrClosedState(err).ShouldBe(ClosedState.ProxyRequired);
}
[Fact]
- public void GetAuthErrClosedState_RevokedMessage_ReturnsRevoked()
+ public void GetAuthErrClosedState_OtherError_ReturnsAuthenticationViolation()
{
- var err = new InvalidOperationException("credential was revoked");
- AuthHandler.GetAuthErrClosedState(err).ShouldBe(ClosedState.AuthRevoked);
+ var err = new InvalidOperationException("bad credentials");
+ AuthHandler.GetAuthErrClosedState(err).ShouldBe(ClosedState.AuthenticationViolation);
+ }
+
+ [Fact]
+ public void GetAuthErrClosedState_NullError_ReturnsAuthenticationViolation()
+ {
+ AuthHandler.GetAuthErrClosedState(null).ShouldBe(ClosedState.AuthenticationViolation);
}
[Fact]
diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/JwtProcessorTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/JwtProcessorTests.cs
index 5ec132c..5db8321 100644
--- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/JwtProcessorTests.cs
+++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/JwtProcessorTests.cs
@@ -40,7 +40,7 @@ public class JwtProcessorTests
public void WipeSlice_FillsWithX()
{
var buf = new byte[] { 0x01, 0x02, 0x03 };
- JwtProcessor.WipeSlice(buf);
+ AuthHandler.WipeSlice(buf);
buf.ShouldAllBe(b => b == (byte)'x');
}
@@ -48,7 +48,7 @@ public class JwtProcessorTests
public void WipeSlice_EmptyBuffer_NoOp()
{
var buf = Array.Empty();
- JwtProcessor.WipeSlice(buf);
+ AuthHandler.WipeSlice(buf);
}
// =========================================================================
diff --git a/reports/current.md b/reports/current.md
index 72b4ab6..ebd499f 100644
--- a/reports/current.md
+++ b/reports/current.md
@@ -1,6 +1,6 @@
# NATS .NET Porting Status Report
-Generated: 2026-02-26 22:38:47 UTC
+Generated: 2026-02-26 22:49:14 UTC
## Modules (12 total)
diff --git a/reports/report_8c380e7.md b/reports/report_8c380e7.md
new file mode 100644
index 0000000..ebd499f
--- /dev/null
+++ b/reports/report_8c380e7.md
@@ -0,0 +1,37 @@
+# NATS .NET Porting Status Report
+
+Generated: 2026-02-26 22:49:14 UTC
+
+## Modules (12 total)
+
+| Status | Count |
+|--------|-------|
+| complete | 11 |
+| not_started | 1 |
+
+## Features (3673 total)
+
+| Status | Count |
+|--------|-------|
+| complete | 3596 |
+| n_a | 77 |
+
+## Unit Tests (3257 total)
+
+| Status | Count |
+|--------|-------|
+| complete | 319 |
+| n_a | 181 |
+| not_started | 2533 |
+| stub | 224 |
+
+## Library Mappings (36 total)
+
+| Status | Count |
+|--------|-------|
+| mapped | 36 |
+
+
+## Overall Progress
+
+**4184/6942 items complete (60.3%)**