[Devolutions Gateway] Validate the TLS certificate at service startup, not at first handshake

[Devolutions Gateway] Validate the TLS certificate at service startup, not at first handshake

1 vote

avatar

Summary

When the Gateway cannot use its configured TLS certificate, it currently starts anyway, opens the listener, reports itself as healthy to the operating system, and only surfaces the problem once per incoming connection — deep in the log. Everything visible from the outside says the service is fine.

We would like the Gateway to validate its certificate at load time and fail loudly there.

What we saw

Our Gateway was configured to load its certificate from the Windows certificate store. The service account had no read permission on the private key. From the outside, the service looked perfectly healthy:

PS> Get-Service DevolutionsGateway
Status   Name                DisplayName
------   ----                -----------
Running  DevolutionsGateway  Devolutions Gateway Service

PS> Get-NetTCPConnection -LocalPort 7171 -State Listen
LocalAddress   LocalPort  State
------------   ---------  -----
::             7171       Listen
0.0.0.0        7171       Listen


The startup sequence in the log was equally clean:

INFO devolutions_gateway::listener: Listening on https://[::]:7171
INFO devolutions_gateway::listener: Listening on https://0.0.0.0:7171
INFO devolutions_gateway: devolutions-gateway service started


The actual problem only appeared when a client connected, and then repeated for every single connection thereafter:

ERROR devolutions_gateway::tls::windows: Failed to resolve TLS certificate
error="no usable certificate found in the system store;
key acquisition failures: cert[0]: failed to acquire key: Error code 80090016"

ERROR devolutions_gateway::listener: handle_https_peer failed
error="TLS handshake failed: unexpected error: no server certificate chain resolved"


`0x80090016` is `NTE_BAD_KEYSET`. The message itself is excellent — it named the exact cause and we fixed it within a minute of finding it. The problem is where and when it appeared.

The request

Resolve the certificate once at service startup. If it cannot be loaded or its private key cannot be acquired, log that at startup with:

  • the certificate subject that was looked for
  • the store name and location that were searched
  • the account the service is running as
  • the underlying error


For example:

ERROR Cannot use configured TLS certificate.
      Subject: CN=gateway.example.com
      Store:   LocalMachine\My
      Service account: NT AUTHORITY\NetworkService
      failed to acquire key: 0x80090016 (NTE_BAD_KEYSET)
      -> Verify that the service account has read permission
         on the certificate's private key.


Whether the service should then refuse to start, or start and report unhealthy, is a design decision we do not have a strong opinion on. What matters is that the condition is visible before a user tries to connect, rather than being inferred from a listener that accepts connections and drops every one of them.

Why this matters

The installer has performed certificate checks since 2026.2.x, which is a good addition — but it only covers configuration made through the installer. Any change afterwards is unchecked:

  • `gateway.json` edited directly, which is the documented way to configure the certificate store option
  • a certificate replaced or renewed in the store
  • private key permissions changed by policy or by a GPO
  • the service account changed


In all of those cases the installer's validation has long since run, and the next indication that something is wrong is a user reporting that they cannot connect.

There is already a `doctor` diagnostic that performs exactly the checks needed here — SAN presence, EKU `serverAuth`, missing intermediates. Running an equivalent check at service start would close the gap between "the installer said it was fine" and "it is fine right now".

---

Related request for Devolutions Server: surface the actual reason behind "Certificate could not be verified", which is the dialog users see when this condition occurs.

All Comments (0)