feat: add OCSP peer reject and chain validation events (Gap 10.10)

Add OcspChainValidationEvent DTO, OcspStatus enum, and OcspEventBuilder
helper with BuildPeerReject, BuildChainValidation, and ParseStatus methods.
Register OcspChainValidationEvent in EventJsonContext source-gen context.
Add OcspPeerReject and OcspChainValidation subject constants to EventSubjects.
10 new tests in OcspEventTests cover all DTOs, builder methods, status
parsing, and JSON round-trip.
This commit is contained in:
Joseph Doherty
2026-02-25 13:15:44 -05:00
parent b314e3f510
commit 4ba87c4175
4 changed files with 385 additions and 0 deletions

View File

@@ -11,4 +11,5 @@ namespace NATS.Server.Events;
[JsonSerializable(typeof(LameDuckEventMsg))]
[JsonSerializable(typeof(AuthErrorEventMsg))]
[JsonSerializable(typeof(OcspPeerRejectEventMsg))]
[JsonSerializable(typeof(OcspChainValidationEvent))]
internal partial class EventJsonContext : JsonSerializerContext;

View File

@@ -40,6 +40,11 @@ public static class EventSubjects
// Inbox for responses
public const string InboxResponse = "$SYS._INBOX_.{0}";
// OCSP advisory events
// Go reference: ocsp.go — OCSP peer reject and chain validation subjects.
public const string OcspPeerReject = "$SYS.SERVER.{0}.OCSP.PEER.REJECT";
public const string OcspChainValidation = "$SYS.SERVER.{0}.OCSP.CHAIN.VALIDATION";
// JetStream advisory events
// Go reference: jetstream_api.go advisory subjects
public const string JsAdvisoryStreamCreated = "$JS.EVENT.ADVISORY.STREAM.CREATED.{0}";

View File

@@ -540,6 +540,119 @@ public sealed class OcspPeerRejectEventMsg
public string Reason { get; set; } = string.Empty;
}
/// <summary>
/// OCSP chain validation advisory, published when a certificate's OCSP status
/// is checked during TLS handshake.
/// Go reference: ocsp.go — OCSP peer verification advisory.
/// </summary>
public sealed class OcspChainValidationEvent
{
public const string EventType = "io.nats.server.advisory.v1.ocsp_chain_validation";
[JsonPropertyName("type")]
public string Type { get; init; } = EventType;
[JsonPropertyName("id")]
public string Id { get; init; } = Guid.NewGuid().ToString("N");
[JsonPropertyName("time")]
public string Time { get; init; } = DateTime.UtcNow.ToString("O");
[JsonPropertyName("server")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public EventServerInfo? Server { get; set; }
[JsonPropertyName("cert_subject")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? CertSubject { get; set; }
[JsonPropertyName("cert_issuer")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? CertIssuer { get; set; }
[JsonPropertyName("serial_number")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? SerialNumber { get; set; }
/// <summary>OCSP status string: "good", "revoked", or "unknown".</summary>
[JsonPropertyName("ocsp_status")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? OcspStatus { get; set; }
[JsonPropertyName("checked_at")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public DateTime? CheckedAt { get; set; }
[JsonPropertyName("error")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Error { get; set; }
}
/// <summary>
/// OCSP status values matching the Go OCSP response status strings.
/// Go reference: ocsp.go — ocspStatusGood, ocspStatusRevoked, ocspStatusUnknown.
/// </summary>
public enum OcspStatus
{
Unknown,
Good,
Revoked,
}
/// <summary>
/// Factory helpers for constructing OCSP advisory event messages.
/// Go reference: ocsp.go — OCSP peer reject and chain validation advisory publishing.
/// </summary>
public static class OcspEventBuilder
{
/// <summary>
/// Build an OcspPeerRejectEventMsg for a rejected peer certificate.
/// Go reference: ocsp.go — postOCSPPeerRejectEvent.
/// </summary>
public static OcspPeerRejectEventMsg BuildPeerReject(
string serverId, string serverName,
string kind, string reason) =>
new()
{
Id = EventBuilder.GenerateEventId(),
Time = DateTime.UtcNow,
Server = new EventServerInfo { Id = serverId, Name = serverName },
Kind = kind,
Reason = reason,
};
/// <summary>
/// Build an OcspChainValidationEvent for a certificate OCSP check.
/// Go reference: ocsp.go — OCSP chain validation advisory.
/// </summary>
public static OcspChainValidationEvent BuildChainValidation(
string serverId, string serverName,
string certSubject, string certIssuer, string serialNumber,
string ocspStatus, string? error = null) =>
new()
{
Server = new EventServerInfo { Id = serverId, Name = serverName },
CertSubject = certSubject,
CertIssuer = certIssuer,
SerialNumber = serialNumber,
OcspStatus = ocspStatus,
CheckedAt = DateTime.UtcNow,
Error = error,
};
/// <summary>
/// Parse an OCSP status string into the <see cref="OcspStatus"/> enum.
/// Go reference: ocsp.go — ocspStatusGood / ocspStatusRevoked string constants.
/// </summary>
public static OcspStatus ParseStatus(string? status) =>
status?.ToLowerInvariant() switch
{
"good" => OcspStatus.Good,
"revoked" => OcspStatus.Revoked,
_ => OcspStatus.Unknown,
};
}
/// <summary>
/// Remote server shutdown advisory.
/// Go reference: events.go — remote server lifecycle.