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.
167 lines
6.4 KiB
C#
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);
|
|
}
|
|
}
|