I recently spent an unreasonable amount of time getting the Clipsal C-Bus Toolkit to talk to a remote C-Gate server. What started as a straightforward "it won't connect" turned into one of the most layered debugging sessions I've had in a while. If you're running C-Gate on Linux with Java 17 and trying to connect the Windows-based C-Bus Toolkit remotely, this post might save you a weekend.
My C-Bus installation runs a C-Gate server (v2.11.5) on a Linux box on my home network. This handles all the real-time communication with the C-Bus CNI and publishes state to Home Assistant via MQTT. Everything works a treat - lights respond, automations fire, life is good.
The Clipsal C-Bus Toolkit (v1.15.7) runs on a separate Windows machine and is used for programming and configuring C-Bus units - setting group addresses, configuring DLT screens, updating firmware, that sort of thing. It connects to C-Gate over TCP, but all connections are wrapped in SSL/TLS.
The problem: the Toolkit couldn't connect to the remote C-Gate. It would either fail the SSL handshake outright or get partway through connecting and crash with a cryptic "Could not load Unit Catalog" error.
The Onion: Layer by Layer
Layer 1: Java 17 Broke Native SSL
C-Gate is a Java application. Older versions shipped with Java 8, which had broad TLS compatibility. Running on Java 17 (which you'll likely get on a modern Linux distro) introduces a problem: Java 17's jdk.tls.disabledAlgorithms blocks the older TLS cipher suites that the Toolkit's bundled OpenSSL libraries (circa 2016) need to negotiate.
The Toolkit ships with Indy OpenSSL libraries (libeay32.dll, ssleay32.dll) that only support up to TLS 1.0 with older cipher suites like DHE-RSA-AES256-SHA. Java 17 considers these legacy and disables them by default.
Fix: Don't fight Java's TLS stack. Instead, put stunnel in front of C-Gate to handle TLS termination. stunnel can be configured to accept legacy TLS clients while proxying to C-Gate's plain-text ports.
Layer 2: C-Gate's Secure Port Conflicts
C-Gate listens on a base set of ports (20023 for commands, 20024 for events, 20025 for load-change, 20026 for config-change) and their secure equivalents offset by +100 or +200 depending on configuration. With stunnel taking over TLS termination, C-Gate's own secure ports need to be moved out of the way.
Fix: In C-GateConfig.txt, set secure.port-base to something that won't conflict (e.g., 30223), then configure stunnel to listen on all the standard secure port offsets:
# stunnel config - /etc/stunnel/cgate-proxy.conf
# Repeat this pattern for each port pair
[cgate-command]
accept = 0.0.0.0:20123
connect = 127.0.0.1:20023
cert = /etc/stunnel/cgate-server.pem
CAfile = /etc/stunnel/cgate-ca.pem
sslVersion = all
options = NO_SSLv2
options = NO_SSLv3
ciphers = ALL:!aNULL:!eNULL:@SECLEVEL=0
verify = 0
You need stunnel sections for ports 20123-20126 (base +100 offset) and 20223-20226 (base +200 offset), all proxying to the corresponding plain-text C-Gate ports 20023-20026.
Layer 3: The --add-opens JVM Fix
When the Toolkit sends dbgetxml to retrieve the project database, C-Gate uses com.sun.org.apache.xml.internal.serialize.XMLSerializer to serialize the response. Java 17's module system blocks this reflective access by default, causing C-Gate to crash with an IllegalAccessException.
Fix: Add the following to C-Gate's startup command:
java --add-opens java.xml/com.sun.org.apache.xml.internal.serialize=ALL-UNNAMED \
-Djava.library.path=. -jar cgate.jar
Layer 4: ServerMode
C-Gate has a ServerMode flag that changes how the Toolkit behaves. When ServerMode=yes, the Toolkit treats the remote C-Gate as a "server mode" instance and attempts to load the unit catalog locally rather than fetching it from the remote C-Gate. If the local C-Gate doesn't have the right project data, this fails.
Fix: Don't pass -servermode to cgate.jar on the remote server. Verify by connecting and running get cgate ServerMode - it should return ServerMode=no.
Layer 5: The Real Killer - Dual SSL Paths
This one took the longest to find and is the reason I'm writing this post.
The C-Bus Toolkit has two independent SSL implementations running side by side:
-
Delphi Indy/OpenSSL - The main application is written in Delphi and uses Indy's OpenSSL bindings for its probe/command connection. It validates server certificates against
cacert.pemin the Toolkit directory. -
.NET SharpCGateCommunicator - The Toolkit also loads a .NET DLL (
SharpCGateCommunicator.dll) via COM interop (Hydra) for data operations like loading the unit catalog. This component uses .NET'sSslStream, which validates server certificates against the Windows certificate store.
When you set up stunnel with a self-signed CA, the Delphi connection works fine because you update cacert.pem. But the .NET connection silently fails because the CA isn't in the Windows Trusted Root store. No error is logged, no connection attempt appears in the server logs - the .NET component simply can't establish SSL, and LoadFromCGate throws a generic "Could not load Unit Catalog" exception.
Fix: Import the stunnel CA certificate into the Windows Trusted Root Certification Authorities store:
certutil -addstore "Root" "C:\path\to\cgate-ca.crt"
This was the final piece. Once the .NET SslStream could validate the stunnel server certificate, the SharpCGateCommunicator successfully connected, dbgetxml was sent, the unit catalog loaded, and the Toolkit fully connected to the remote C-Gate.
The Complete Fix Checklist
For anyone trying to connect the C-Bus Toolkit to a remote C-Gate on Linux with Java 17:
-
Install stunnel on the C-Gate server and configure it to proxy all secure C-Gate ports (20123-20126, 20223-20226) to the corresponding plain-text ports (20023-20026).
-
Move C-Gate's native secure ports out of the way by setting
secure.port-base=30223inC-GateConfig.txt. -
Add
--add-opensto C-Gate's JVM arguments to fix the XML serializer access issue. -
Don't use
-servermodeunless you specifically need it. The Toolkit behaves differently whenServerMode=yes. -
Copy the stunnel CA cert to the Toolkit machine and update:
cacert.pemin the Toolkit directory (for the Delphi/OpenSSL path)- The Windows Trusted Root certificate store via
certutil(for the .NET/SslStream path)
-
Set
Portincgatesites.xmlto the stunnel command port (20123). -
Create
CBusToolkit.exe.configalongside the Toolkit executable to enable strong crypto in .NET:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<AppContextSwitchOverrides
value="Switch.System.Net.DontEnableSchUseStrongCrypto=false" />
</runtime>
</configuration>
Takeaway
The fundamental issue here is that the C-Bus Toolkit - a single application - uses two completely independent SSL/TLS implementations that trust certificates from different sources. The Delphi side uses file-based certificate validation, while the .NET side uses the Windows certificate store. When you're troubleshooting SSL issues, you need to satisfy both.
There's no error message that tells you this. The .NET connection fails silently. The only clue is that the Toolkit's probe connection succeeds (you can see it in the C-Gate logs), but dbgetxml is never sent and "Could not load Unit Catalog" is thrown. If you see that pattern - successful probe, no data commands, unit catalog crash - the Windows certificate store is almost certainly the missing piece.
Comments