feat(batch9): implement f3 nats server ocsp wiring
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
using System.Globalization;
|
||||
using System.Formats.Asn1;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text.Json;
|
||||
@@ -15,6 +16,8 @@ namespace ZB.MOM.NatsNet.Server;
|
||||
internal static class OcspHandler
|
||||
{
|
||||
private const string CertPemLabel = "CERTIFICATE";
|
||||
private const string TlsFeaturesOid = "1.3.6.1.5.5.7.1.24";
|
||||
private const int StatusRequestExtension = 5;
|
||||
|
||||
internal static (List<X509Certificate2>? certificates, Exception? error) ParseCertPEM(string name)
|
||||
{
|
||||
@@ -50,6 +53,43 @@ internal static class OcspHandler
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool HasOCSPStatusRequest(X509Certificate2 cert)
|
||||
{
|
||||
foreach (var extension in cert.Extensions)
|
||||
{
|
||||
if (!string.Equals(extension.Oid?.Value, TlsFeaturesOid, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var reader = new AsnReader(extension.RawData, AsnEncodingRules.DER);
|
||||
var seq = reader.ReadSequence();
|
||||
while (seq.HasData)
|
||||
{
|
||||
if (seq.ReadInteger() == StatusRequestExtension)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.HasData)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static (X509Certificate2? issuer, Exception? error) GetOCSPIssuerLocally(
|
||||
IReadOnlyList<X509Certificate2> trustedCAs,
|
||||
IReadOnlyList<X509Certificate2> certBundle)
|
||||
@@ -85,6 +125,11 @@ internal static class OcspHandler
|
||||
|
||||
if (!chain.Build(leaf) || chain.ChainElements.Count < 2)
|
||||
{
|
||||
if (string.Equals(leaf.Subject, leaf.Issuer, StringComparison.Ordinal))
|
||||
{
|
||||
return (leaf, null);
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,9 @@ internal sealed class OcspMonitor
|
||||
/// <summary>Path to the TLS certificate file being monitored.</summary>
|
||||
public string? CertFile { get; set; }
|
||||
|
||||
/// <summary>Connection kind this monitor applies to (client/router/gateway/leaf).</summary>
|
||||
public string Kind { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Path to the CA certificate file used to verify OCSP responses.</summary>
|
||||
public string? CaFile { get; set; }
|
||||
|
||||
|
||||
@@ -411,8 +411,12 @@ public sealed partial class NatsServer
|
||||
// Assign leaf options.
|
||||
s._leafNodeEnabled = opts.LeafNode.Port != 0 || opts.LeafNode.Remotes.Count > 0;
|
||||
|
||||
// OCSP (stub — session 23).
|
||||
// s.EnableOcsp() — deferred
|
||||
var ocspError = s.EnableOCSP();
|
||||
if (ocspError != null)
|
||||
{
|
||||
s._mu.ExitWriteLock();
|
||||
return (null, ocspError);
|
||||
}
|
||||
|
||||
// Gateway (stub — session 16).
|
||||
// s.NewGateway(opts) — deferred
|
||||
|
||||
@@ -139,6 +139,22 @@ public sealed partial class NatsServer
|
||||
|
||||
Noticef("Server Exiting..");
|
||||
|
||||
var monitors = GetOcspMonitors();
|
||||
foreach (var monitor in monitors)
|
||||
{
|
||||
monitor.Stop();
|
||||
}
|
||||
|
||||
_mu.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_ocsps = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mu.ExitWriteLock();
|
||||
}
|
||||
|
||||
if (_ocsprc != null) { /* stub — stop OCSP cache in session 23 */ }
|
||||
|
||||
DisposeSignalHandlers();
|
||||
|
||||
400
dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Ocsp.cs
Normal file
400
dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.Ocsp.cs
Normal file
@@ -0,0 +1,400 @@
|
||||
// Copyright 2012-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using ZB.MOM.NatsNet.Server.Auth.CertificateIdentityProvider;
|
||||
using ZB.MOM.NatsNet.Server.Auth.Ocsp;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server;
|
||||
|
||||
/// <summary>
|
||||
/// TLS configuration slot used by OCSP wiring to apply wrapped TLS settings.
|
||||
/// Mirrors Go <c>tlsConfigKind</c> shape used by <c>configureOCSP</c>.
|
||||
/// </summary>
|
||||
internal sealed class OcspTlsConfig
|
||||
{
|
||||
public required string Kind { get; init; }
|
||||
public required SslServerAuthenticationOptions TlsConfig { get; init; }
|
||||
public required TlsConfigOpts? TlsOptions { get; init; }
|
||||
public required Action<SslServerAuthenticationOptions> Apply { get; init; }
|
||||
public bool IsLeafSpoke { get; init; }
|
||||
}
|
||||
|
||||
public sealed partial class NatsServer
|
||||
{
|
||||
private const string ClientKindName = "client";
|
||||
private const string RouterKindName = "router";
|
||||
private const string GatewayKindName = "gateway";
|
||||
private const string LeafKindName = "leaf";
|
||||
private const string DefaultOcspStoreDir = "ocsp";
|
||||
|
||||
internal OcspMonitor[] GetOcspMonitors()
|
||||
{
|
||||
_mu.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _ocsps is null ? [] : [.. _ocsps];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mu.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal Exception? SetupOCSPStapleStoreDir()
|
||||
{
|
||||
var storeDir = GetOpts().StoreDir;
|
||||
if (string.IsNullOrEmpty(storeDir))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var ocspDir = Path.Combine(storeDir, DefaultOcspStoreDir);
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(ocspDir))
|
||||
{
|
||||
Directory.CreateDirectory(ocspDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
var attributes = File.GetAttributes(ocspDir);
|
||||
if ((attributes & FileAttributes.Directory) != FileAttributes.Directory)
|
||||
{
|
||||
return new InvalidOperationException("OCSP storage directory is not a directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new InvalidOperationException($"could not create OCSP storage directory - {ex.Message}", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal List<OcspTlsConfig> ConfigureOCSP()
|
||||
{
|
||||
var opts = GetOpts();
|
||||
var configs = new List<OcspTlsConfig>();
|
||||
|
||||
if (opts.TlsConfig != null)
|
||||
{
|
||||
configs.Add(new OcspTlsConfig
|
||||
{
|
||||
Kind = ClientKindName,
|
||||
TlsConfig = opts.TlsConfig,
|
||||
TlsOptions = opts.TlsConfigOpts,
|
||||
Apply = tls => opts.TlsConfig = tls,
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.Websocket.TlsConfig != null)
|
||||
{
|
||||
configs.Add(new OcspTlsConfig
|
||||
{
|
||||
Kind = ClientKindName,
|
||||
TlsConfig = opts.Websocket.TlsConfig,
|
||||
TlsOptions = opts.Websocket.TlsConfigOpts,
|
||||
Apply = tls => opts.Websocket.TlsConfig = tls,
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.Mqtt.TlsConfig != null)
|
||||
{
|
||||
configs.Add(new OcspTlsConfig
|
||||
{
|
||||
Kind = ClientKindName,
|
||||
TlsConfig = opts.Mqtt.TlsConfig,
|
||||
TlsOptions = opts.Mqtt.TlsConfigOpts,
|
||||
Apply = tls => opts.Mqtt.TlsConfig = tls,
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.Cluster.TlsConfig != null)
|
||||
{
|
||||
configs.Add(new OcspTlsConfig
|
||||
{
|
||||
Kind = RouterKindName,
|
||||
TlsConfig = opts.Cluster.TlsConfig,
|
||||
TlsOptions = opts.Cluster.TlsConfigOpts,
|
||||
Apply = tls => opts.Cluster.TlsConfig = tls,
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.LeafNode.TlsConfig != null)
|
||||
{
|
||||
configs.Add(new OcspTlsConfig
|
||||
{
|
||||
Kind = LeafKindName,
|
||||
TlsConfig = opts.LeafNode.TlsConfig,
|
||||
TlsOptions = opts.LeafNode.TlsConfigOpts,
|
||||
Apply = tls => opts.LeafNode.TlsConfig = tls,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var remote in opts.LeafNode.Remotes)
|
||||
{
|
||||
if (remote.TlsConfig == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var capturedRemote = remote;
|
||||
configs.Add(new OcspTlsConfig
|
||||
{
|
||||
Kind = LeafKindName,
|
||||
TlsConfig = remote.TlsConfig,
|
||||
TlsOptions = remote.TlsConfigOpts,
|
||||
IsLeafSpoke = true,
|
||||
Apply = tls => capturedRemote.TlsConfig = tls,
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.Gateway.TlsConfig != null)
|
||||
{
|
||||
configs.Add(new OcspTlsConfig
|
||||
{
|
||||
Kind = GatewayKindName,
|
||||
TlsConfig = opts.Gateway.TlsConfig,
|
||||
TlsOptions = opts.Gateway.TlsConfigOpts,
|
||||
Apply = tls => opts.Gateway.TlsConfig = tls,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var gateway in opts.Gateway.Gateways)
|
||||
{
|
||||
if (gateway.TlsConfig == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var capturedGateway = gateway;
|
||||
configs.Add(new OcspTlsConfig
|
||||
{
|
||||
Kind = GatewayKindName,
|
||||
TlsConfig = gateway.TlsConfig,
|
||||
TlsOptions = gateway.TlsConfigOpts,
|
||||
Apply = tls => capturedGateway.TlsConfig = tls,
|
||||
});
|
||||
}
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
internal (SslServerAuthenticationOptions? tlsConfig, OcspMonitor? monitor, Exception? error) NewOCSPMonitor(
|
||||
OcspTlsConfig config)
|
||||
{
|
||||
var opts = GetOpts();
|
||||
var ocspConfig = opts.OcspConfig;
|
||||
|
||||
var certFile = config.TlsOptions?.CertFile ?? opts.TlsCert;
|
||||
var caFile = config.TlsOptions?.CaFile ?? opts.TlsCaCert;
|
||||
|
||||
if (config.TlsConfig.ServerCertificate is not X509Certificate2 leaf)
|
||||
{
|
||||
return (null, null, new InvalidOperationException("no certificate found"));
|
||||
}
|
||||
|
||||
var shutdownOnRevoke = false;
|
||||
var mustStaple = OcspHandler.HasOCSPStatusRequest(leaf);
|
||||
if (ocspConfig != null)
|
||||
{
|
||||
switch (ocspConfig.Mode)
|
||||
{
|
||||
case OcspMode.Never:
|
||||
if (mustStaple)
|
||||
{
|
||||
Warnf("Certificate at '{0}' has MustStaple but OCSP is disabled", certFile);
|
||||
}
|
||||
return (config.TlsConfig, null, null);
|
||||
case OcspMode.Always:
|
||||
mustStaple = true;
|
||||
shutdownOnRevoke = true;
|
||||
break;
|
||||
case OcspMode.Must when mustStaple:
|
||||
shutdownOnRevoke = true;
|
||||
break;
|
||||
case OcspMode.Auto when !mustStaple:
|
||||
return (config.TlsConfig, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mustStaple)
|
||||
{
|
||||
return (config.TlsConfig, null, null);
|
||||
}
|
||||
|
||||
var setupError = SetupOCSPStapleStoreDir();
|
||||
if (setupError != null)
|
||||
{
|
||||
return (null, null, setupError);
|
||||
}
|
||||
|
||||
var chain = new List<byte[]> { leaf.RawData };
|
||||
if (config.TlsConfig.ServerCertificateContext != null)
|
||||
{
|
||||
foreach (var intermediate in config.TlsConfig.ServerCertificateContext.IntermediateCertificates)
|
||||
{
|
||||
chain.Add(intermediate.RawData);
|
||||
}
|
||||
}
|
||||
|
||||
var (issuer, issuerError) = OcspHandler.GetOCSPIssuer(caFile, chain);
|
||||
if (issuerError != null || issuer == null)
|
||||
{
|
||||
return (null, null, issuerError ?? new InvalidOperationException("no issuers found"));
|
||||
}
|
||||
|
||||
var monitor = new OcspMonitor
|
||||
{
|
||||
Kind = config.Kind,
|
||||
Server = this,
|
||||
CertFile = certFile,
|
||||
CaFile = caFile,
|
||||
Leaf = leaf,
|
||||
Issuer = issuer,
|
||||
ShutdownOnRevoke = shutdownOnRevoke,
|
||||
};
|
||||
|
||||
var (_, response, statusError) = monitor.GetStatus();
|
||||
if (statusError != null)
|
||||
{
|
||||
return (null, null,
|
||||
new InvalidOperationException($"bad OCSP status update for certificate at '{certFile}': {statusError.Message}", statusError));
|
||||
}
|
||||
|
||||
if (response != null && response.Status != OcspStatusAssertion.Good && shutdownOnRevoke)
|
||||
{
|
||||
return (null, null,
|
||||
new InvalidOperationException(
|
||||
$"found existing OCSP status for certificate at '{certFile}': {OcspHandler.OcspStatusString((int)response.Status)}"));
|
||||
}
|
||||
|
||||
return (config.TlsConfig, monitor, null);
|
||||
}
|
||||
|
||||
internal Exception? EnableOCSP()
|
||||
{
|
||||
var configs = ConfigureOCSP();
|
||||
var monitors = new List<OcspMonitor>(configs.Count);
|
||||
|
||||
foreach (var config in configs)
|
||||
{
|
||||
if (config.Kind != LeafKindName)
|
||||
{
|
||||
var (tlsConfig, monitor, error) = NewOCSPMonitor(config);
|
||||
if (error != null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
if (monitor != null && tlsConfig != null)
|
||||
{
|
||||
monitors.Add(monitor);
|
||||
config.Apply(tlsConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// OCSP peer verification hook is implemented in batch 9 F4.
|
||||
}
|
||||
|
||||
_mu.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_ocsps = monitors.Count == 0 ? null : [.. monitors];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mu.ExitWriteLock();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void StartOCSPMonitoring()
|
||||
{
|
||||
OcspMonitor[]? monitors;
|
||||
_mu.EnterReadLock();
|
||||
try
|
||||
{
|
||||
monitors = _ocsps;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mu.ExitReadLock();
|
||||
}
|
||||
|
||||
if (monitors == null || monitors.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var monitor in monitors)
|
||||
{
|
||||
Noticef("OCSP Stapling enabled for {0} connections", monitor.Kind);
|
||||
monitor.Start();
|
||||
StartGoRoutine(() => monitor.Run(_quitCts.Token).GetAwaiter().GetResult());
|
||||
}
|
||||
}
|
||||
|
||||
internal Exception? ReloadOCSP()
|
||||
{
|
||||
var setupError = SetupOCSPStapleStoreDir();
|
||||
if (setupError != null)
|
||||
{
|
||||
return setupError;
|
||||
}
|
||||
|
||||
var existingMonitors = GetOcspMonitors();
|
||||
foreach (var monitor in existingMonitors)
|
||||
{
|
||||
monitor.Stop();
|
||||
}
|
||||
|
||||
var configs = ConfigureOCSP();
|
||||
var replacement = new List<OcspMonitor>(configs.Count);
|
||||
|
||||
_mu.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_ocspPeerVerify = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mu.ExitWriteLock();
|
||||
}
|
||||
|
||||
foreach (var config in configs)
|
||||
{
|
||||
if (config.Kind != LeafKindName)
|
||||
{
|
||||
var (tlsConfig, monitor, error) = NewOCSPMonitor(config);
|
||||
if (error != null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
if (monitor != null && tlsConfig != null)
|
||||
{
|
||||
replacement.Add(monitor);
|
||||
config.Apply(tlsConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_mu.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_ocsps = replacement.Count == 0 ? null : [.. replacement];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mu.ExitWriteLock();
|
||||
}
|
||||
|
||||
StartOCSPMonitoring();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
189
dotnet/tests/ZB.MOM.NatsNet.Server.Tests/NatsServerOcspTests.cs
Normal file
189
dotnet/tests/ZB.MOM.NatsNet.Server.Tests/NatsServerOcspTests.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text.Json;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests;
|
||||
|
||||
public sealed class NatsServerOcspTests : IDisposable
|
||||
{
|
||||
private readonly List<string> _tempDirs = [];
|
||||
private readonly List<X509Certificate2> _certs = [];
|
||||
|
||||
[Fact]
|
||||
public void SetupOCSPStapleStoreDir_WithStoreDir_CreatesDirectory()
|
||||
{
|
||||
var dir = MakeTempDir();
|
||||
var server = NewServer(new ServerOptions
|
||||
{
|
||||
StoreDir = dir,
|
||||
});
|
||||
|
||||
var err = server.SetupOCSPStapleStoreDir();
|
||||
|
||||
err.ShouldBeNull();
|
||||
Directory.Exists(Path.Combine(dir, "ocsp")).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfigureOCSP_WithTlsConfig_ReturnsClientEntry()
|
||||
{
|
||||
var cert = CreateSelfSignedCertificate("CN=configure-ocsp");
|
||||
var server = NewServer(new ServerOptions
|
||||
{
|
||||
TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
|
||||
});
|
||||
|
||||
var configs = server.ConfigureOCSP();
|
||||
|
||||
configs.Count.ShouldBe(1);
|
||||
configs[0].Kind.ShouldBe("client");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewOCSPMonitor_OcspNever_ReturnsNoMonitor()
|
||||
{
|
||||
var cert = CreateSelfSignedCertificate("CN=monitor-never");
|
||||
var server = NewServer(new ServerOptions
|
||||
{
|
||||
StoreDir = MakeTempDir(),
|
||||
OcspConfig = new OcspConfig { Mode = ZB.MOM.NatsNet.Server.OcspMode.Never },
|
||||
});
|
||||
|
||||
var config = new OcspTlsConfig
|
||||
{
|
||||
Kind = "client",
|
||||
TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
|
||||
TlsOptions = null,
|
||||
Apply = _ => { },
|
||||
};
|
||||
|
||||
var (_, monitor, err) = server.NewOCSPMonitor(config);
|
||||
|
||||
err.ShouldBeNull();
|
||||
monitor.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnableOCSP_WithAlwaysMode_AddsMonitor()
|
||||
{
|
||||
var cert = CreateSelfSignedCertificate("CN=enable-ocsp");
|
||||
var storeDir = MakeTempDir();
|
||||
WriteLocalOcspStatus(storeDir, cert);
|
||||
var server = NewServer(new ServerOptions
|
||||
{
|
||||
StoreDir = storeDir,
|
||||
OcspConfig = new OcspConfig
|
||||
{
|
||||
Mode = ZB.MOM.NatsNet.Server.OcspMode.Always,
|
||||
OverrideUrls = ["https://ocsp.example.test"],
|
||||
},
|
||||
TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
|
||||
});
|
||||
|
||||
var err = server.EnableOCSP();
|
||||
|
||||
err.ShouldBeNull();
|
||||
server.GetOcspMonitors().Length.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartOCSPMonitoring_NoMonitors_DoesNotThrow()
|
||||
{
|
||||
var server = NewServer(new ServerOptions());
|
||||
|
||||
Should.NotThrow(() => server.StartOCSPMonitoring());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReloadOCSP_WithConfiguredTls_ReplacesMonitors()
|
||||
{
|
||||
var cert = CreateSelfSignedCertificate("CN=reload-ocsp");
|
||||
var storeDir = MakeTempDir();
|
||||
WriteLocalOcspStatus(storeDir, cert);
|
||||
var server = NewServer(new ServerOptions
|
||||
{
|
||||
StoreDir = storeDir,
|
||||
OcspConfig = new OcspConfig
|
||||
{
|
||||
Mode = ZB.MOM.NatsNet.Server.OcspMode.Always,
|
||||
OverrideUrls = ["https://ocsp.example.test"],
|
||||
},
|
||||
TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert },
|
||||
});
|
||||
server.EnableOCSP().ShouldBeNull();
|
||||
server.GetOcspMonitors().Length.ShouldBe(1);
|
||||
|
||||
var err = server.ReloadOCSP();
|
||||
|
||||
err.ShouldBeNull();
|
||||
server.GetOcspMonitors().Length.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasOCSPStatusRequest_CertificateWithoutExtension_ReturnsFalse()
|
||||
{
|
||||
var cert = CreateSelfSignedCertificate("CN=no-status-request");
|
||||
|
||||
OcspHandler.HasOCSPStatusRequest(cert).ShouldBeFalse();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var cert in _certs)
|
||||
{
|
||||
cert.Dispose();
|
||||
}
|
||||
|
||||
foreach (var dir in _tempDirs)
|
||||
{
|
||||
try { Directory.Delete(dir, recursive: true); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private NatsServer NewServer(ServerOptions options)
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(options);
|
||||
err.ShouldBeNull();
|
||||
return server!;
|
||||
}
|
||||
|
||||
private X509Certificate2 CreateSelfSignedCertificate(string subject)
|
||||
{
|
||||
var req = new CertificateRequest(
|
||||
subject,
|
||||
RSA.Create(2048),
|
||||
HashAlgorithmName.SHA256,
|
||||
RSASignaturePadding.Pkcs1);
|
||||
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
|
||||
req.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(req.PublicKey, false));
|
||||
|
||||
var cert = req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(90));
|
||||
_certs.Add(cert);
|
||||
return cert;
|
||||
}
|
||||
|
||||
private string MakeTempDir()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), "nats-ocsp-" + Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(path);
|
||||
_tempDirs.Add(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void WriteLocalOcspStatus(string storeDir, X509Certificate2 cert)
|
||||
{
|
||||
var key = Convert.ToHexString(SHA256.HashData(cert.RawData)).ToLowerInvariant();
|
||||
var ocspDir = Path.Combine(storeDir, "ocsp");
|
||||
Directory.CreateDirectory(ocspDir);
|
||||
var payload = JsonSerializer.SerializeToUtf8Bytes(new
|
||||
{
|
||||
Status = 0,
|
||||
ThisUpdate = DateTime.UtcNow.AddMinutes(-5),
|
||||
NextUpdate = DateTime.UtcNow.AddHours(6),
|
||||
});
|
||||
File.WriteAllBytes(Path.Combine(ocspDir, key), payload);
|
||||
}
|
||||
}
|
||||
BIN
porting.db
BIN
porting.db
Binary file not shown.
Reference in New Issue
Block a user