Skip to content

DrvCnlMqtt: add CA pinning, mutual TLS, and revocation control - #126

Open
xtim8719xCopilot wants to merge 1 commit into
RapidScada:developfrom
xtim8719xCopilot:feat/mqtt-tls-ca-pinning-mtls
Open

xtim8719xCopilot wants to merge 1 commit into
RapidScada:developfrom
xtim8719xCopilot:feat/mqtt-tls-ca-pinning-mtls

Conversation

@xtim8719xCopilot

Copy link
Copy Markdown

Summary

The MQTT channel driver's UseTls option only ever called MqttClientTlsOptionsBuilder.UseTls() with no further configuration. That's fine against a broker with a publicly-trusted certificate, but there was no way to:

  • trust a private or self-signed CA
  • present a client certificate for mutual TLS
  • control revocation-check behavior

MQTTnet itself already supports all of this (WithCertificateValidationHandler, WithClientCertificates, WithIgnoreCertificateRevocationErrors, etc.) — it just wasn't exposed through MqttConnectionOptions or the options dialog.

What's added

Four new connection options on MqttConnectionOptions, all optional and off by default (fully backward compatible — verified the sibling DrvMqttClient/DrvMqttPublisher drivers that also reference DrvMqtt.Common still build unchanged):

  • CaCertFile — pins the broker's certificate chain to a specific CA file via a custom X509Chain (X509ChainTrustMode.CustomRootTrust), instead of trusting whatever happens to be in the OS trust store. This performs real chain validation against the given CA — it is not a bypass.
  • ClientCertFile / ClientCertPassword — presents a client certificate (PFX) for mutual TLS authentication.
  • AllowUntrustedCertificates — explicit opt-in bypass, only applied when no CaCertFile is given.
  • IgnoreCertificateRevocationErrors — skips CRL/OCSP revocation checking. Useful because ScadaComm often runs as a Windows service account (e.g. LocalSystem), which can fail to complete an online revocation check — and that failure surfaces as an opaque NotSupportedException ("Specified method is not supported") with nothing indicating revocation checking was the cause.

Also improved MqttClientHelper's connection error logging: it previously logged only the wrapping AggregateException's generic message ("One or more errors occurred."), discarding the real inner exception and stack trace. It now unwraps and logs the actual cause — which is what made the revocation-check failure above diagnosable in the first place, instead of a dead-end guess.

The options dialog (FrmMqttClientChannelOptions) gains matching fields: CA certificate file / client certificate file (with browse buttons), client certificate password, and the two checkboxes. English and Russian language files updated.

Test plan

  • Built DrvCnlMqtt.View.csproj and DrvMqtt.Common.csproj in Release against a clean checkout of develop, per HowToBuild.txt.
  • Verified DrvCnlMqtt.Logic, DrvMqttClient.Logic, and DrvMqttPublisher.Logic (all consumers of DrvMqtt.Common) still build unchanged against the extended options class.
  • Deployed to a real ScadaComm6 instance connecting to EMQX Cloud over TLS with a pinned CA cert (CaCertFile) — confirmed MqttClientHelper.Connect() succeeds and the line shows "MQTT client, connected".
  • Verified the CA-pinning path rejects a connection when the presented cert doesn't chain to the configured CA (not just a blanket accept).

Note: this PR is independent of #125 (protocol version dropdown fix) — they don't depend on each other and can be reviewed/merged separately.

@2mik

2mik commented Jul 28, 2026

Copy link
Copy Markdown
Member

Hello,
Thank you for the pull request.
Could you add screenshots of the updated Windows forms?

@xtim8719xCopilot

Copy link
Copy Markdown
Author
image

The MQTT channel driver's UseTls option only ever called
MqttClientTlsOptionsBuilder.UseTls() with no further configuration -
no way to trust a private or self-signed CA, no client certificate
support for mutual TLS, and no control over revocation checking.

Adds four new connection options, all optional and off by default:
- CaCertFile: pins the broker's certificate chain to a specific CA
  file via a custom X509Chain (CustomRootTrust), instead of trusting
  whatever the OS trust store happens to contain. This validates the
  chain properly against the given CA - it is not a bypass.
- ClientCertFile / ClientCertPassword: presents a client certificate
  (PFX) for mutual TLS authentication.
- AllowUntrustedCertificates: explicit opt-in bypass, only used when
  no CaCertFile is given.
- IgnoreCertificateRevocationErrors: skips CRL/OCSP revocation
  checking. Useful because ScadaComm often runs as a Windows service
  account (e.g. LocalSystem) that can fail to complete an online
  revocation check, which otherwise surfaces as an opaque
  "Specified method is not supported" error at connect time with no
  indication that revocation checking was the cause.

Also improves MqttClientHelper's connection error logging: it
previously logged only the AggregateException's generic message
("One or more errors occurred."), discarding the real inner exception
and its stack trace. Now unwraps and logs the actual cause, which is
what made the revocation-check failure above diagnosable at all.

The options dialog (FrmMqttClientChannelOptions) gains matching
fields: CA certificate file / client certificate file (with browse
buttons), client certificate password, and the two checkboxes.
@xtim8719xCopilot
xtim8719xCopilot force-pushed the feat/mqtt-tls-ca-pinning-mtls branch from 4080b38 to b3d8c12 Compare August 6, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants