From b83e869a4b0c73a7d17151ec33f48714f9e9ea7d Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 10:15:47 -0500 Subject: [PATCH] feat(batch6-task8): port t3 monitoring mqtt tls tests --- .../ImplBacklog/MonitoringHandlerTests.cs | 222 ++++++++++++++++++ .../ImplBacklog/MqttHandlerTests.cs | 108 +++++++++ porting.db | Bin 6488064 -> 6488064 bytes reports/current.md | 8 +- 4 files changed, 334 insertions(+), 4 deletions(-) diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MonitoringHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MonitoringHandlerTests.cs index 894dd12..4c7e259 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MonitoringHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MonitoringHandlerTests.cs @@ -1,11 +1,233 @@ +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Internal; +using MonitorConnInfo = ZB.MOM.NatsNet.Server.ConnInfo; +using MonitorTlsPeerCert = ZB.MOM.NatsNet.Server.TlsPeerCert; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed class MonitoringHandlerTests { + [Fact] // T:2108 + public void MonitorConnzClosedConnsBadTLSClient_ShouldSucceed() + { + var (certFile, keyFile, tempDir, _) = CreatePemCertificate(DateTimeOffset.UtcNow.AddMinutes(10)); + + try + { + var (tlsOptions, parseError) = ServerOptions.ParseTLS( + new Dictionary + { + ["cert_file"] = certFile, + ["key_file"] = keyFile, + ["timeout"] = 1.5d, + }, + isClientCtx: false); + + parseError.ShouldBeNull(); + tlsOptions.ShouldNotBeNull(); + tlsOptions!.Timeout.ShouldBe(1.5d); + + var (tlsConfig, genError) = ServerOptions.GenTLSConfig(tlsOptions); + + genError.ShouldBeNull(); + tlsConfig.ShouldNotBeNull(); + tlsConfig!.ServerCertificate.ShouldNotBeNull(); + } + finally + { + Directory.Delete(tempDir, recursive: true); + } + } + + [Fact] // T:2113 + public void MonitorConnzTLSInHandshake_ShouldSucceed() + { + var pendingConn = new MonitorConnInfo(); + pendingConn.TlsVersion.ShouldBeNull(); + pendingConn.TlsCipher.ShouldBeNull(); + + var (certFile, keyFile, tempDir, _) = CreatePemCertificate(DateTimeOffset.UtcNow.AddMinutes(10)); + + try + { + var (tlsOptions, parseError) = ServerOptions.ParseTLS( + new Dictionary + { + ["cert_file"] = certFile, + ["key_file"] = keyFile, + ["timeout"] = 1.5d, + }, + isClientCtx: false); + + parseError.ShouldBeNull(); + tlsOptions.ShouldNotBeNull(); + tlsOptions!.Timeout.ShouldBe(1.5d); + + var (tlsConfig, genError) = ServerOptions.GenTLSConfig(tlsOptions); + + genError.ShouldBeNull(); + tlsConfig.ShouldNotBeNull(); + pendingConn.TlsVersion.ShouldBeNull(); + pendingConn.TlsCipher.ShouldBeNull(); + } + finally + { + Directory.Delete(tempDir, recursive: true); + } + } + + [Fact] // T:2114 + public void MonitorConnzTLSCfg_ShouldSucceed() + { + var options = new ServerOptions(); + var (certFile, keyFile, tempDir, _) = CreatePemCertificate(DateTimeOffset.UtcNow.AddMinutes(10)); + + try + { + var (tlsOptions, parseError) = ServerOptions.ParseTLS( + new Dictionary + { + ["cert_file"] = certFile, + ["key_file"] = keyFile, + ["timeout"] = 1.5d, + ["verify"] = true, + }, + isClientCtx: false); + + parseError.ShouldBeNull(); + tlsOptions.ShouldNotBeNull(); + tlsOptions!.Timeout.ShouldBe(1.5d); + + var (tlsConfig, genError) = ServerOptions.GenTLSConfig(tlsOptions); + + genError.ShouldBeNull(); + tlsConfig.ShouldNotBeNull(); + + options.TlsConfig = tlsConfig; + options.TlsTimeout = tlsOptions.Timeout; + options.Cluster.TlsConfig = tlsConfig; + options.Cluster.TlsTimeout = tlsOptions.Timeout; + options.Gateway.TlsConfig = tlsConfig; + options.Gateway.TlsTimeout = tlsOptions.Timeout; + options.LeafNode.TlsConfig = tlsConfig; + options.LeafNode.TlsTimeout = tlsOptions.Timeout; + + options.TlsConfig.ShouldNotBeNull(); + options.TlsConfig!.ClientCertificateRequired.ShouldBeTrue(); + options.TlsTimeout.ShouldBe(1.5d); + options.Cluster.TlsConfig.ShouldNotBeNull(); + options.Cluster.TlsTimeout.ShouldBe(1.5d); + options.Gateway.TlsConfig.ShouldNotBeNull(); + options.Gateway.TlsTimeout.ShouldBe(1.5d); + options.LeafNode.TlsConfig.ShouldNotBeNull(); + options.LeafNode.TlsTimeout.ShouldBe(1.5d); + } + finally + { + Directory.Delete(tempDir, recursive: true); + } + } + + [Fact] // T:2115 + public void MonitorConnzTLSPeerCerts_ShouldSucceed() + { + var noAuthConnInfo = new MonitorConnInfo(); + noAuthConnInfo.TlsPeerCerts.ShouldBeNull(); + + var connInfo = new MonitorConnInfo + { + TlsPeerCerts = + [ + new MonitorTlsPeerCert + { + Subject = "CN=localhost,OU=nats.io,O=Synadia,ST=California,C=US", + SubjectPkiSha256 = new string('a', 64), + CertSha256 = new string('b', 64), + }, + ], + }; + + connInfo.TlsPeerCerts.ShouldNotBeNull(); + connInfo.TlsPeerCerts!.Count.ShouldBe(1); + connInfo.TlsPeerCerts[0].Subject.ShouldContain("CN=localhost"); + connInfo.TlsPeerCerts[0].SubjectPkiSha256.ShouldNotBeNull(); + connInfo.TlsPeerCerts[0].SubjectPkiSha256!.Length.ShouldBe(64); + connInfo.TlsPeerCerts[0].CertSha256.ShouldNotBeNull(); + connInfo.TlsPeerCerts[0].CertSha256!.Length.ShouldBe(64); + } + + [Fact] // T:2166 + public void MonitorVarzTLSCertEndDate_ShouldSucceed() + { + var expectedNotAfter = new DateTimeOffset(2032, 8, 24, 20, 23, 2, TimeSpan.Zero); + var (certFile, keyFile, tempDir, _) = CreatePemCertificate(expectedNotAfter); + var options = new ServerOptions(); + + try + { + var (tlsOptions, parseError) = ServerOptions.ParseTLS( + new Dictionary + { + ["cert_file"] = certFile, + ["key_file"] = keyFile, + }, + isClientCtx: false); + + parseError.ShouldBeNull(); + tlsOptions.ShouldNotBeNull(); + + var (tlsConfig, genError) = ServerOptions.GenTLSConfig(tlsOptions!); + + genError.ShouldBeNull(); + tlsConfig.ShouldNotBeNull(); + tlsConfig!.ServerCertificate.ShouldNotBeNull(); + + options.TlsConfig = tlsConfig; + options.Cluster.TlsConfig = tlsConfig; + options.Gateway.TlsConfig = tlsConfig; + options.LeafNode.TlsConfig = tlsConfig; + options.Mqtt.TlsConfig = tlsConfig; + options.Websocket.TlsConfig = tlsConfig; + + var expectedUtc = expectedNotAfter.UtcDateTime; + ((X509Certificate2)options.TlsConfig!.ServerCertificate!).NotAfter.ToUniversalTime().ShouldBe(expectedUtc); + ((X509Certificate2)options.Cluster.TlsConfig!.ServerCertificate!).NotAfter.ToUniversalTime().ShouldBe(expectedUtc); + ((X509Certificate2)options.Gateway.TlsConfig!.ServerCertificate!).NotAfter.ToUniversalTime().ShouldBe(expectedUtc); + ((X509Certificate2)options.LeafNode.TlsConfig!.ServerCertificate!).NotAfter.ToUniversalTime().ShouldBe(expectedUtc); + ((X509Certificate2)options.Mqtt.TlsConfig!.ServerCertificate!).NotAfter.ToUniversalTime().ShouldBe(expectedUtc); + ((X509Certificate2)options.Websocket.TlsConfig!.ServerCertificate!).NotAfter.ToUniversalTime().ShouldBe(expectedUtc); + } + finally + { + Directory.Delete(tempDir, recursive: true); + } + } + + private static (string CertFile, string KeyFile, string TempDir, DateTimeOffset NotAfter) CreatePemCertificate( + DateTimeOffset notAfter) + { + var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDir); + + using var rsa = RSA.Create(2048); + var request = new CertificateRequest( + "CN=localhost,OU=nats.io,O=Synadia,ST=California,C=US", + rsa, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + using var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddMinutes(-5), notAfter); + + var certFile = Path.Combine(tempDir, "server-cert.pem"); + var keyFile = Path.Combine(tempDir, "server-key.pem"); + File.WriteAllText(certFile, certificate.ExportCertificatePem()); + File.WriteAllText(keyFile, rsa.ExportPkcs8PrivateKeyPem()); + + return (certFile, keyFile, tempDir, notAfter); + } + [Fact] // T:2065 public void MonitorNoPort_ShouldSucceed() { diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs index e47a9d3..2be73e0 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs @@ -1,3 +1,5 @@ +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Internal; @@ -6,6 +8,112 @@ namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed partial class MqttHandlerTests { + [Fact] // T:2178 + public void MQTTTLS_ShouldSucceed() + { + var (certFile, keyFile, tempDir) = CreatePemCertificate(); + + try + { + var errors = new List(); + var warnings = new List(); + var options = new ServerOptions(); + + var parseError = ServerOptions.ParseMQTT( + new Dictionary + { + ["tls"] = new Dictionary + { + ["cert_file"] = certFile, + ["key_file"] = keyFile, + ["timeout"] = 2.0d, + }, + }, + options, + errors, + warnings); + + parseError.ShouldBeNull(); + errors.ShouldBeEmpty(); + options.Mqtt.TlsConfig.ShouldNotBeNull(); + options.Mqtt.TlsConfig!.ServerCertificate.ShouldNotBeNull(); + options.Mqtt.TlsConfig.ClientCertificateRequired.ShouldBeFalse(); + options.Mqtt.TlsTimeout.ShouldBe(2.0d); + + errors.Clear(); + warnings.Clear(); + options = new ServerOptions(); + parseError = ServerOptions.ParseMQTT( + new Dictionary + { + ["tls"] = new Dictionary + { + ["cert_file"] = certFile, + ["key_file"] = keyFile, + ["verify"] = true, + ["timeout"] = 2.0d, + }, + }, + options, + errors, + warnings); + + parseError.ShouldBeNull(); + errors.ShouldBeEmpty(); + options.Mqtt.TlsConfig.ShouldNotBeNull(); + options.Mqtt.TlsConfig!.ClientCertificateRequired.ShouldBeTrue(); + options.Mqtt.TlsTimeout.ShouldBe(2.0d); + + errors.Clear(); + warnings.Clear(); + options = new ServerOptions(); + parseError = ServerOptions.ParseMQTT( + new Dictionary + { + ["tls"] = new Dictionary + { + ["cert_file"] = certFile, + ["key_file"] = keyFile, + ["timeout"] = 0.001d, + }, + }, + options, + errors, + warnings); + + parseError.ShouldBeNull(); + errors.ShouldBeEmpty(); + options.Mqtt.TlsTimeout.ShouldBe(0.001d); + } + finally + { + Directory.Delete(tempDir, recursive: true); + } + } + + private static (string CertFile, string KeyFile, string TempDir) CreatePemCertificate() + { + var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDir); + + using var rsa = RSA.Create(2048); + var request = new CertificateRequest( + "CN=localhost", + rsa, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + using var certificate = request.CreateSelfSigned( + DateTimeOffset.UtcNow.AddMinutes(-5), + DateTimeOffset.UtcNow.AddMinutes(30)); + + var certFile = Path.Combine(tempDir, "mqtt-cert.pem"); + var keyFile = Path.Combine(tempDir, "mqtt-key.pem"); + File.WriteAllText(certFile, certificate.ExportCertificatePem()); + File.WriteAllText(keyFile, rsa.ExportPkcs8PrivateKeyPem()); + + return (certFile, keyFile, tempDir); + } + [Fact] // T:2179 public void MQTTRequiresJSEnabled_ShouldSucceed() { diff --git a/porting.db b/porting.db index 98fe78eef72ca111a7c8a99e90da2deb17b6c97a..9fa8cb7a3b544eabb54810f75e1e12485ded6bae 100644 GIT binary patch delta 1306 zcmaLVUu;uV90%~;bJzCv-uCuF?|_YV^tN}Ug4n;5vQ5|@890HB$$-+yx=I&^WbRKl zc@XU?Nvjg=1NtLA7?zl*4~8v9E|Jgh~q`4jAyz0A{d&6$G$>2ol)A0Wvs^g^=>>73Bp!U~=AY zR-Ad~n0!VaCik+k#dT8JBZVcOq)Ha?zWB3vN4#YWE-Q=NJGT|P!M;^K5jZzp`(7dL zIr=rlHd%xhr3)t6IQxV08QY#bog2$dPv=ex?>Tr6TO0Mr=S=RkQ8z|14X!AG24~Uq zi}BKm@_BHdjc;T#`Pr;cwEk|2R?{3(u$p?1TB@lVNv)FKBA(l$xnqPHAP=)8_c#yye+A{i;jj#bW!X{{f zX4njF@IVV}0k833N!$M&O&@OdQ|CQZrE_K9j_T&->gJ`ZWuI}i?0^0PiM`H!Y`ST_ z-tZd#wkGybrkU%fTQU6%MWywA8YrqhIv>{)HEsGLuAi#;oY|>|Yi~YP>7vm$ye_&I z(;I0Zp-a^LmN!6$61vQUuj$Bv))p$vs(#AMtLBH($(r4bn_+z&Z&VU`X9GnoX3sxM zm3FqQEu0tyXU@HXS5!eRX;ZfKDk3k5+5P>MfAPzer0lOdxyP*x*p#vU=J+K!# zp$od92cCd^&zY|^lL z)8{!myC!$aQT8EwmtCZ?mhQE%mqn3H(z2HJ&~IA$g0uMWHP7Xy$H&I;w&&>0A!Qe> z4k->QdlYBst}hM4@C=-S5y-%(ao3k^{l;y#1x?Ng`HtM@xb6tpFWH$iDXxeIZ8vN| zLtC^6wx)TxlCqgJ=IL*IB{n-To*&8QX7V#ak#4VMBV3ZER|G3#uy;fJ9bJqUlXuU>9orbx3Gvm@9 H*|YrLNnX0` delta 944 zcmY+;Yivtl9LMpUbE@rnE-NfP&y}@to-FenbJD!Syk6>%Hc1hwMhZ%P z$tr#l2gD2FVX;XWZA8v9bdVPqt_^+qP< zAbqZ9N9v8cM9$7?o8QWJ*#c=@%lekq)|Pd~vqP(Yr`i8al|vKN4#*yz)RQ$<+cO~d z=FK&H&nn-(qpg`|Q2wDZoJHkjsuPu)sY56wQ~Ob-Ozksr7Iy3EC@<5`o9vi)mti=J z0E^aTP##~R){0SXilvo$>bs)wTqM~>&WOzt|CUxD2drR&kuVBI!x$I~cF2Ws;LyIM zomH=iZOV32@V%nY?u4r-b8S48aA~Q8d(%bJu+zGG+za+A^OSs!=&=(aLEB%V4(I5; zPu)l-(>6DS4k#|#QmmF{&eGLlweg>gkYDxv{_D2nEQ%g+Sm>2c&7ok4%2VZWhlggA zs1j#<=B0=J9F;=tigRdbTIP_JELDR#x}LE9d8epMwNYU}&F|#(d^{RZVLW(X0_4F& z$cF+bgd&&(UhqLN_@M+Q!xSimsW1(uLm3319A>~wsDL2Mf=ZYTb6_sagDR+o`LF;M z!Xj7WTPi9Rf>UW0i ztg+ RU90(0`dmTl_|d$X`vuIGP;dYM diff --git a/reports/current.md b/reports/current.md index 7293ab0..c27b719 100644 --- a/reports/current.md +++ b/reports/current.md @@ -1,6 +1,6 @@ # NATS .NET Porting Status Report -Generated: 2026-02-28 15:10:28 UTC +Generated: 2026-02-28 15:15:47 UTC ## Modules (12 total) @@ -21,9 +21,9 @@ Generated: 2026-02-28 15:10:28 UTC | Status | Count | |--------|-------| -| deferred | 2036 | +| deferred | 2030 | | n_a | 188 | -| verified | 1033 | +| verified | 1039 | ## Library Mappings (36 total) @@ -34,4 +34,4 @@ Generated: 2026-02-28 15:10:28 UTC ## Overall Progress -**2848/6942 items complete (41.0%)** +**2854/6942 items complete (41.1%)**