Auto: opcuaclient-5 — CRL/revocation handling

Adds explicit revoked-vs-untrusted distinction to the OpcUaClient driver's
server-cert validation hook, plus three new knobs on a new
OpcUaCertificateValidationOptions sub-record:

  RejectSHA1SignedCertificates  (default true — SHA-1 is OPC UA spec-deprecated;
                                 this is a deliberately tighter default)
  RejectUnknownRevocationStatus (default false — keeps brownfield deployments
                                 without CRL infrastructure working)
  MinimumCertificateKeySize     (default 2048)

The validator hook now runs whether or not AutoAcceptCertificates is set:
revoked / issuer-revoked certs are always rejected with a distinct
"REVOKED" log line; SHA-1 + small-key certs are rejected per policy;
unknown-revocation gates on the new flag; untrusted still honours
AutoAccept.

Decision pipeline factored into a static EvaluateCertificateValidation
helper with a CertificateValidationDecision record so unit tests cover
all branches without needing to spin up an SDK CertificateValidator.

CRL files themselves: the OPC UA SDK reads them automatically from the
crl/ subdir of each cert store — no driver-side wiring needed.
Documented on the new options record.

Tests (12 new) cover defaults, every branch of the decision pipeline,
SHA-1 detection (custom X509SignatureGenerator since .NET 10's
CreateSelfSigned refuses SHA-1), and key-size detection. All 127
OpcUaClient unit tests still pass.

Closes #277

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-25 16:05:50 -04:00
parent d57e24a7fa
commit 4a3860ae92
3 changed files with 418 additions and 11 deletions

View File

@@ -141,8 +141,56 @@ public sealed class OpcUaClientDriverOptions
/// values so existing deployments see no behaviour change.
/// </summary>
public OpcUaSubscriptionDefaults Subscriptions { get; init; } = new();
/// <summary>
/// Server-certificate validation knobs applied during the
/// <c>CertificateValidator.CertificateValidation</c> callback. Surfaces explicit
/// handling for revoked certs (always rejected, never auto-accepted), unknown
/// revocation status (rejected only when <see cref="OpcUaCertificateValidationOptions.RejectUnknownRevocationStatus"/>
/// is set), SHA-1 signature rejection, and minimum RSA key size. Defaults preserve
/// existing behaviour wherever possible — the one tightening is
/// <see cref="OpcUaCertificateValidationOptions.RejectSHA1SignedCertificates"/>=true
/// since SHA-1 is spec-deprecated for OPC UA.
/// </summary>
public OpcUaCertificateValidationOptions CertificateValidation { get; init; } = new();
}
/// <summary>
/// Knobs governing the server-certificate validation callback. Plumbed onto
/// <see cref="OpcUaClientDriverOptions.CertificateValidation"/> rather than the top-level
/// options to keep cert-related config grouped together.
/// </summary>
/// <remarks>
/// <para>
/// <b>CRL discovery:</b> the OPC UA SDK reads CRL files automatically from the
/// <c>crl/</c> sub-directory of each cert store (own, trusted, issuers). Drop the
/// issuer's <c>.crl</c> in that folder and the SDK picks it up — no driver-side wiring
/// required. When the directory is absent or empty, the SDK reports
/// <c>BadCertificateRevocationUnknown</c>, which this driver gates with
/// <see cref="RejectUnknownRevocationStatus"/>.
/// </para>
/// </remarks>
/// <param name="RejectSHA1SignedCertificates">
/// Reject server certificates whose signature uses SHA-1. Default <c>true</c> — SHA-1 was
/// deprecated by the OPC UA spec and is treated as a hard fail in production. Flip to
/// <c>false</c> only for short-term interop with legacy controllers.
/// </param>
/// <param name="RejectUnknownRevocationStatus">
/// When the SDK can't determine revocation status (no CRL present, or stale CRL),
/// reject the cert if <c>true</c>; allow if <c>false</c>. Default <c>false</c> — many
/// plant deployments don't run CRL infrastructure, and a hard-fail default would break
/// them on first connection. Set <c>true</c> in environments with a managed PKI.
/// </param>
/// <param name="MinimumCertificateKeySize">
/// Minimum RSA key size (bits) accepted. Certs with shorter keys are rejected. Default
/// <c>2048</c> matches the current OPC UA spec floor; raise to 3072 or 4096 for stricter
/// deployments. Non-RSA keys (ECC) bypass this check.
/// </param>
public sealed record OpcUaCertificateValidationOptions(
bool RejectSHA1SignedCertificates = true,
bool RejectUnknownRevocationStatus = false,
int MinimumCertificateKeySize = 2048);
/// <summary>
/// Tuning surface for OPC UA subscriptions created by <see cref="OpcUaClientDriver"/>.
/// Lifted from the per-call hard-coded literals so operators can tune publish cadence,