feat(batch9): implement f4 ocsp peer validation hooks
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
using ZB.MOM.NatsNet.Server.Auth.CertificateIdentityProvider;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.Auth.CertificateIdentityProvider;
|
||||
@@ -36,4 +37,18 @@ public sealed class CertificateIdentityProviderTests
|
||||
var decoded = Convert.FromBase64String(unescaped);
|
||||
decoded.ShouldBe(data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseOCSPPeer_UnknownField_ReturnsError()
|
||||
{
|
||||
Dictionary<string, object?> map = new()
|
||||
{
|
||||
["unexpected"] = true,
|
||||
};
|
||||
|
||||
var (config, err) = OcspHandler.ParseOCSPPeer(map);
|
||||
|
||||
config.ShouldBeNull();
|
||||
err.ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
using ZB.MOM.NatsNet.Server.Auth.CertificateIdentityProvider;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.Auth;
|
||||
|
||||
public sealed class OcspPeerValidationTests : IDisposable
|
||||
{
|
||||
private readonly List<X509Certificate2> _certs = [];
|
||||
|
||||
[Fact]
|
||||
public void ParseOCSPPeer_ValidMap_ReturnsConfig()
|
||||
{
|
||||
Dictionary<string, object?> map = new()
|
||||
{
|
||||
["verify"] = true,
|
||||
["allowed_clockskew"] = 30.0,
|
||||
["ca_timeout"] = 5.0,
|
||||
["cache_ttl_when_next_update_unset"] = 120.0,
|
||||
["warn_only"] = true,
|
||||
["unknown_is_good"] = true,
|
||||
["allow_when_ca_unreachable"] = true,
|
||||
};
|
||||
|
||||
var (config, err) = OcspHandler.ParseOCSPPeer(map);
|
||||
|
||||
err.ShouldBeNull();
|
||||
config.ShouldNotBeNull();
|
||||
config.Verify.ShouldBeTrue();
|
||||
config.ClockSkew.ShouldBe(30.0);
|
||||
config.Timeout.ShouldBe(5.0);
|
||||
config.TTLUnsetNextUpdate.ShouldBe(120.0);
|
||||
config.WarnOnly.ShouldBeTrue();
|
||||
config.UnknownIsGood.ShouldBeTrue();
|
||||
config.AllowWhenCAUnreachable.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PeerFromVerifiedChains_EmptyChains_ReturnsNull()
|
||||
{
|
||||
OcspHandler.PeerFromVerifiedChains([]).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PeerFromVerifiedChains_NonEmptyChains_ReturnsFirstLeaf()
|
||||
{
|
||||
var cert = CreateSelfSignedCertificate("CN=peer-first");
|
||||
|
||||
var result = OcspHandler.PeerFromVerifiedChains([[cert]]);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result!.Subject.ShouldBe(cert.Subject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlugTLSOCSPPeer_ClientWithoutVerify_ReturnsError()
|
||||
{
|
||||
var server = NewServer();
|
||||
var cert = CreateSelfSignedCertificate("CN=client-no-verify");
|
||||
var config = new OcspTlsConfig
|
||||
{
|
||||
Kind = "client",
|
||||
TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
|
||||
TlsOptions = new TlsConfigOpts
|
||||
{
|
||||
Verify = false,
|
||||
OcspPeerConfig = new OcspPeerConfig { Verify = true },
|
||||
},
|
||||
Apply = _ => { },
|
||||
};
|
||||
|
||||
var (_, plugged, err) = server.PlugTLSOCSPPeer(config);
|
||||
|
||||
plugged.ShouldBeFalse();
|
||||
err.ShouldNotBeNull();
|
||||
err!.Message.ShouldContain("mTLS");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlugTLSOCSPPeer_ClientWithVerify_ReturnsPlugged()
|
||||
{
|
||||
var server = NewServer();
|
||||
var cert = CreateSelfSignedCertificate("CN=client-verify");
|
||||
var config = new OcspTlsConfig
|
||||
{
|
||||
Kind = "client",
|
||||
TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
|
||||
TlsOptions = new TlsConfigOpts
|
||||
{
|
||||
Verify = true,
|
||||
OcspPeerConfig = new OcspPeerConfig { Verify = true },
|
||||
},
|
||||
Apply = _ => { },
|
||||
};
|
||||
|
||||
var (tlsConfig, plugged, err) = server.PlugTLSOCSPPeer(config);
|
||||
|
||||
err.ShouldBeNull();
|
||||
plugged.ShouldBeTrue();
|
||||
tlsConfig.ShouldNotBeNull();
|
||||
tlsConfig!.RemoteCertificateValidationCallback.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlugTLSOCSPPeer_LeafSpoke_ReturnsPlugged()
|
||||
{
|
||||
var server = NewServer();
|
||||
var cert = CreateSelfSignedCertificate("CN=leaf-spoke");
|
||||
var config = new OcspTlsConfig
|
||||
{
|
||||
Kind = "leaf",
|
||||
IsLeafSpoke = true,
|
||||
TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
|
||||
TlsOptions = new TlsConfigOpts
|
||||
{
|
||||
Verify = true,
|
||||
OcspPeerConfig = new OcspPeerConfig { Verify = true },
|
||||
},
|
||||
Apply = _ => { },
|
||||
};
|
||||
|
||||
var (_, plugged, err) = server.PlugTLSOCSPPeer(config);
|
||||
|
||||
err.ShouldBeNull();
|
||||
plugged.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TlsServerOCSPValid_SelfSignedChain_ReturnsTrue()
|
||||
{
|
||||
var server = NewServer();
|
||||
var cert = CreateSelfSignedCertificate("CN=selfsigned");
|
||||
var opts = new OcspPeerConfig { Verify = true };
|
||||
|
||||
var valid = server.TlsServerOCSPValid([[cert]], opts);
|
||||
|
||||
valid.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TlsClientOCSPValid_EmptyChains_ReturnsFalse()
|
||||
{
|
||||
var server = NewServer();
|
||||
var opts = new OcspPeerConfig { Verify = true };
|
||||
|
||||
var valid = server.TlsClientOCSPValid([], opts);
|
||||
|
||||
valid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CertOCSPGood_EmptyChainLink_ReturnsFalse()
|
||||
{
|
||||
var server = NewServer();
|
||||
var (reason, good) = server.CertOCSPGood(new ChainLink(), new OcspPeerConfig());
|
||||
|
||||
good.ShouldBeFalse();
|
||||
reason.ShouldContain("Empty chainlink");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var cert in _certs)
|
||||
{
|
||||
cert.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private NatsServer NewServer()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
return server!;
|
||||
}
|
||||
|
||||
private X509Certificate2 CreateSelfSignedCertificate(string subject)
|
||||
{
|
||||
var request = new CertificateRequest(
|
||||
subject,
|
||||
RSA.Create(2048),
|
||||
HashAlgorithmName.SHA256,
|
||||
RSASignaturePadding.Pkcs1);
|
||||
request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
|
||||
request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
|
||||
|
||||
var cert = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30));
|
||||
_certs.Add(cert);
|
||||
return cert;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user