Files
natsdotnet/tests/NATS.Server.Transport.Tests/TlsOcspParityBatch2Tests.cs
Joseph Doherty d2c04fcca5 refactor: extract NATS.Server.Transport.Tests project
Move TLS, OCSP, WebSocket, Networking, and IO test files from
NATS.Server.Tests into a dedicated NATS.Server.Transport.Tests
project. Update namespaces, replace private GetFreePort/ReadUntilAsync
with shared TestUtilities helpers, extract TestCertHelper to
TestUtilities, and replace Task.Delay polling loops with
PollHelper.WaitUntilAsync/YieldForAsync for proper synchronization.
2026-03-12 14:57:35 -04:00

167 lines
6.4 KiB
C#

using System.Formats.Asn1;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using NATS.Server.TestUtilities;
using NATS.Server.Tls;
namespace NATS.Server.Transport.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, _) = TestCertHelper.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);
}
}