feat(dcl): OPC UA verify-endpoint probe with untrusted-cert capture (T17)

This commit is contained in:
Joseph Doherty
2026-06-18 03:00:55 -04:00
parent 90abb4b8e2
commit 733c7bf66c
5 changed files with 551 additions and 2 deletions
@@ -0,0 +1,80 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
/// <summary>
/// Command (Central UI → site DCL manager) to verify a data-connection endpoint
/// configuration without persisting it: connect, capture the server certificate if it
/// is untrusted, then disconnect. The probe is read-only and never trusts the server
/// certificate — an untrusted certificate is captured and the connect is rejected
/// (cert trust is a separate, later action). Only OPC UA is supported today; other
/// protocols return a <see cref="VerifyEndpointResult"/> with
/// <see cref="VerifyFailureKind.ServerError"/>.
/// </summary>
/// <param name="ConnectionName">Name of the data connection being verified (for logging/correlation).</param>
/// <param name="Protocol">Protocol type string (e.g. "OpcUa"); matched case-insensitively.</param>
/// <param name="ConfigJson">Serialized endpoint configuration JSON (the typed OPC UA endpoint shape).</param>
public record VerifyEndpointCommand(string ConnectionName, string Protocol, string ConfigJson);
/// <summary>
/// Classification of why an endpoint verification failed. Distinguishes the cases the
/// Central UI must present differently — most importantly
/// <see cref="UntrustedCertificate"/>, which carries a capturable server certificate the
/// operator can choose to trust in a later step.
/// </summary>
public enum VerifyFailureKind
{
/// <summary>The endpoint host could not be reached (DNS failure, connection refused, socket error).</summary>
Unreachable,
/// <summary>The server rejected the supplied user identity (anonymous/username/certificate).</summary>
AuthFailed,
/// <summary>
/// The server presented a certificate that is not trusted by this site. The
/// certificate is captured in <see cref="VerifyEndpointResult.Cert"/> so it can be
/// reviewed and trusted in a later step; the probe itself rejected it.
/// </summary>
UntrustedCertificate,
/// <summary>The verification did not complete within the allotted time budget.</summary>
Timeout,
/// <summary>Any other server-side or unexpected failure (including unsupported protocol).</summary>
ServerError
}
/// <summary>
/// Details of a server certificate captured during a verification probe. Carries the
/// fields the Central UI needs to display the certificate for an operator trust decision,
/// plus the raw DER (base64) so the certificate can be persisted to the trusted store
/// verbatim in a later step.
/// </summary>
/// <param name="Thumbprint">The certificate SHA-1 thumbprint (hex).</param>
/// <param name="Subject">The certificate subject distinguished name.</param>
/// <param name="Issuer">The certificate issuer distinguished name.</param>
/// <param name="NotBeforeUtc">The not-before validity bound (UTC).</param>
/// <param name="NotAfterUtc">The not-after validity bound (UTC).</param>
/// <param name="DerBase64">The raw DER-encoded certificate, base64-encoded.</param>
public record ServerCertInfo(
string Thumbprint,
string Subject,
string Issuer,
DateTime NotBeforeUtc,
DateTime NotAfterUtc,
string DerBase64);
/// <summary>
/// Result of a <see cref="VerifyEndpointCommand"/>. On success <see cref="FailureKind"/>,
/// <see cref="Error"/>, and <see cref="Cert"/> are all null. On failure
/// <see cref="FailureKind"/> classifies the failure and <see cref="Error"/> carries a
/// human-readable message; <see cref="Cert"/> is populated only when
/// <see cref="FailureKind"/> is <see cref="VerifyFailureKind.UntrustedCertificate"/>.
/// </summary>
/// <param name="Success">True if a session was established (the endpoint config is valid and reachable).</param>
/// <param name="FailureKind">The failure classification, or null on success.</param>
/// <param name="Error">A human-readable error message, or null on success.</param>
/// <param name="Cert">The captured untrusted server certificate, or null.</param>
public record VerifyEndpointResult(
bool Success,
VerifyFailureKind? FailureKind,
string? Error,
ServerCertInfo? Cert);