review(Driver.Historian.Wonderware.Client.Contracts): redact SharedSecret in ToString (High)

First review at 7286d320. -001 (High): record ToString() leaked SharedSecret into logs ->
override ToString() that omits it. -003 (Medium): [Range(1,65535)] on Port. -004 thumbprint
doc. -002 (SHA-1 pin -> SHA-256, lives in FrameChannel) deferred cross-module.
This commit is contained in:
Joseph Doherty
2026-06-19 12:22:53 -04:00
parent 0c1d5f7f88
commit 78542ab2d2
2 changed files with 160 additions and 5 deletions
@@ -16,14 +16,14 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client;
/// </para>
/// </remarks>
/// <param name="Host">Sidecar TCP host (DNS name or IP) the client dials.</param>
/// <param name="Port">Sidecar TCP port (matches the sidecar's <c>OTOPCUA_HISTORIAN_TCP_PORT</c>).</param>
/// <param name="Port">Sidecar TCP port (matches the sidecar's <c>OTOPCUA_HISTORIAN_TCP_PORT</c>). Valid range: 165535.</param>
/// <param name="SharedSecret">Per-process shared secret the sidecar will verify in the Hello frame.</param>
/// <param name="PeerName">Diagnostic peer identifier sent in Hello — typically the OtOpcUa instance id.</param>
/// <param name="ConnectTimeout">Cap on the TCP connect + Hello round trip on each (re)connect.</param>
/// <param name="CallTimeout">Cap on a single read/write call once connected.</param>
public sealed record WonderwareHistorianClientOptions(
string Host,
int Port,
[Range(1, 65535)] int Port,
string SharedSecret,
string PeerName = "OtOpcUa",
TimeSpan? ConnectTimeout = null,
@@ -47,9 +47,25 @@ public sealed record WonderwareHistorianClientOptions(
public bool UseTls { get; init; }
/// <summary>
/// Optional SHA-1 thumbprint (hex, no spaces) the client pins the sidecar's TLS server
/// cert against. When null/empty and <see cref="UseTls"/> is true, the client validates
/// the cert chain normally (CA-issued cert).
/// Optional SHA-1 thumbprint (40 hex characters, no spaces, case-insensitive) the client
/// pins the sidecar's TLS server cert against. When null/empty and
/// <see cref="UseTls"/> is true, the client validates the cert chain normally
/// (CA-issued cert).
/// </summary>
/// <remarks>
/// The consumer matches against <c>X509Certificate.GetCertHashString()</c> (SHA-1, 40
/// hex chars). Supplying a SHA-256 thumbprint (64 hex chars, the format shown by modern
/// tooling such as <c>certutil</c> or Windows Certificate Manager) will never match and
/// will cause the TLS handshake to fail silently. Only 40-character SHA-1 hex strings
/// are accepted.
/// </remarks>
public string? ServerCertThumbprint { get; init; }
/// <inheritdoc/>
/// <remarks>
/// Redacts <see cref="SharedSecret"/> so the value cannot appear in log output when the
/// options object is passed to a structured-logging statement.
/// </remarks>
public override string ToString() =>
$"WonderwareHistorianClientOptions {{ Host={Host}, Port={Port}, PeerName={PeerName}, UseTls={UseTls}, ServerCertThumbprint={ServerCertThumbprint ?? "<null>"} }}";
}