Fix E2E test gaps and add comprehensive E2E + parity test suites
- Fix pull consumer fetch: send original stream subject in HMSG (not inbox) so NATS client distinguishes data messages from control messages - Fix MaxAge expiry: add background timer in StreamManager for periodic pruning - Fix JetStream wire format: Go-compatible anonymous objects with string enums, proper offset-based pagination for stream/consumer list APIs - Add 42 E2E black-box tests (core messaging, auth, TLS, accounts, JetStream) - Add ~1000 parity tests across all subsystems (gaps closure) - Update gap inventory docs to reflect implementation status
This commit is contained in:
165
tests/NATS.Server.Tests/TlsOcspParityBatch2Tests.cs
Normal file
165
tests/NATS.Server.Tests/TlsOcspParityBatch2Tests.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System.Formats.Asn1;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using NATS.Server.Tls;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
|
||||
public class TlsOcspParityBatch2Tests
|
||||
{
|
||||
[Fact]
|
||||
public void CertOCSPEligible_returns_true_and_populates_endpoints_for_http_ocsp_aia()
|
||||
{
|
||||
using var cert = CreateLeafWithOcspAia("http://ocsp.example.test");
|
||||
var link = new ChainLink { Leaf = cert };
|
||||
|
||||
var eligible = TlsHelper.CertOCSPEligible(link);
|
||||
|
||||
eligible.ShouldBeTrue();
|
||||
link.OCSPWebEndpoints.ShouldNotBeNull();
|
||||
link.OCSPWebEndpoints!.Count.ShouldBe(1);
|
||||
link.OCSPWebEndpoints[0].ToString().ShouldBe("http://ocsp.example.test/");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CertOCSPEligible_returns_false_when_leaf_has_no_ocsp_servers()
|
||||
{
|
||||
var (leaf, _) = TlsHelperTests.GenerateTestCert();
|
||||
var link = new ChainLink { Leaf = leaf };
|
||||
|
||||
TlsHelper.CertOCSPEligible(link).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetLeafIssuerCert_returns_positional_issuer_or_null()
|
||||
{
|
||||
using var root = CreateRootCertificate();
|
||||
using var leaf = CreateLeafSignedBy(root);
|
||||
|
||||
var chain = new[] { leaf, root };
|
||||
TlsHelper.GetLeafIssuerCert(chain, 0).ShouldBe(root);
|
||||
TlsHelper.GetLeafIssuerCert(chain, 1).ShouldBeNull();
|
||||
TlsHelper.GetLeafIssuerCert(chain, -1).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetLeafIssuer_returns_verified_issuer_from_chain()
|
||||
{
|
||||
using var root = CreateRootCertificate();
|
||||
using var leaf = CreateLeafSignedBy(root);
|
||||
|
||||
using var issuer = TlsHelper.GetLeafIssuer(leaf, root);
|
||||
|
||||
issuer.ShouldNotBeNull();
|
||||
issuer!.Thumbprint.ShouldBe(root.Thumbprint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OcspResponseCurrent_applies_skew_and_ttl_rules()
|
||||
{
|
||||
var opts = OCSPPeerConfig.NewOCSPPeerConfig();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
TlsHelper.OcspResponseCurrent(new OcspResponseInfo
|
||||
{
|
||||
ThisUpdate = now.AddMinutes(-1),
|
||||
NextUpdate = now.AddMinutes(5),
|
||||
}, opts).ShouldBeTrue();
|
||||
|
||||
TlsHelper.OcspResponseCurrent(new OcspResponseInfo
|
||||
{
|
||||
ThisUpdate = now.AddHours(-2),
|
||||
NextUpdate = null,
|
||||
}, opts).ShouldBeFalse();
|
||||
|
||||
TlsHelper.OcspResponseCurrent(new OcspResponseInfo
|
||||
{
|
||||
ThisUpdate = now.AddMinutes(2),
|
||||
NextUpdate = now.AddHours(1),
|
||||
}, opts).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidDelegationCheck_accepts_direct_and_ocsp_signing_delegate()
|
||||
{
|
||||
using var issuer = CreateRootCertificate();
|
||||
using var delegateCert = CreateOcspSigningDelegate(issuer);
|
||||
|
||||
TlsHelper.ValidDelegationCheck(issuer, null).ShouldBeTrue();
|
||||
TlsHelper.ValidDelegationCheck(issuer, issuer).ShouldBeTrue();
|
||||
TlsHelper.ValidDelegationCheck(issuer, delegateCert).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OcspPeerMessages_exposes_error_and_debug_constants()
|
||||
{
|
||||
OcspPeerMessages.ErrIllegalPeerOptsConfig.ShouldContain("expected map to define OCSP peer options");
|
||||
OcspPeerMessages.ErrNoAvailOCSPServers.ShouldBe("no available OCSP servers");
|
||||
OcspPeerMessages.DbgPlugTLSForKind.ShouldBe("Plugging TLS OCSP peer for [%s]");
|
||||
OcspPeerMessages.DbgCacheSaved.ShouldBe("Saved OCSP peer cache successfully (%d bytes)");
|
||||
OcspPeerMessages.MsgFailedOCSPResponseFetch.ShouldBe("Failed OCSP response fetch");
|
||||
}
|
||||
|
||||
private static X509Certificate2 CreateLeafWithOcspAia(string ocspUri)
|
||||
{
|
||||
using var key = RSA.Create(2048);
|
||||
var req = new CertificateRequest("CN=leaf-with-ocsp", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
|
||||
req.CertificateExtensions.Add(CreateOcspAiaExtension(ocspUri));
|
||||
return req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30));
|
||||
}
|
||||
|
||||
private static X509Extension CreateOcspAiaExtension(string ocspUri)
|
||||
{
|
||||
var writer = new AsnWriter(AsnEncodingRules.DER);
|
||||
writer.PushSequence();
|
||||
writer.PushSequence();
|
||||
writer.WriteObjectIdentifier("1.3.6.1.5.5.7.48.1");
|
||||
writer.WriteCharacterString(UniversalTagNumber.IA5String, ocspUri, new Asn1Tag(TagClass.ContextSpecific, 6));
|
||||
writer.PopSequence();
|
||||
writer.PopSequence();
|
||||
return new X509Extension("1.3.6.1.5.5.7.1.1", writer.Encode(), false);
|
||||
}
|
||||
|
||||
private static X509Certificate2 CreateRootCertificate()
|
||||
{
|
||||
using var rootKey = RSA.Create(2048);
|
||||
var req = new CertificateRequest("CN=Root", rootKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
|
||||
req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign, true));
|
||||
return req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddYears(5));
|
||||
}
|
||||
|
||||
private static X509Certificate2 CreateLeafSignedBy(X509Certificate2 issuer)
|
||||
{
|
||||
using var leafKey = RSA.Create(2048);
|
||||
var req = new CertificateRequest("CN=Leaf", leafKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
|
||||
req.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(req.PublicKey, false));
|
||||
|
||||
var cert = req.Create(
|
||||
issuer,
|
||||
DateTimeOffset.UtcNow.AddDays(-1),
|
||||
DateTimeOffset.UtcNow.AddYears(1),
|
||||
Guid.NewGuid().ToByteArray());
|
||||
|
||||
return cert.CopyWithPrivateKey(leafKey);
|
||||
}
|
||||
|
||||
private static X509Certificate2 CreateOcspSigningDelegate(X509Certificate2 issuer)
|
||||
{
|
||||
using var key = RSA.Create(2048);
|
||||
var req = new CertificateRequest("CN=OCSP Delegate", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
|
||||
req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(
|
||||
[new Oid("1.3.6.1.5.5.7.3.9")], true));
|
||||
|
||||
var cert = req.Create(
|
||||
issuer,
|
||||
DateTimeOffset.UtcNow.AddDays(-1),
|
||||
DateTimeOffset.UtcNow.AddYears(1),
|
||||
Guid.NewGuid().ToByteArray());
|
||||
|
||||
return cert.CopyWithPrivateKey(key);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user