Recover clients after server certificate rotation - #1949
Merged
kevinherron merged 5 commits intoSep 9, 2026
Merged
Conversation
kevinherron
force-pushed
the
t3code/client-certificate-rotation-recovery
branch
from
September 8, 2026 18:02
47584dd to
e74c439
Compare
kevinherron
force-pushed
the
t3code/client-certificate-rotation-recovery
branch
from
September 8, 2026 23:45
73dfe6a to
c7855fc
Compare
kevinherron
changed the base branch from
integration/1.2
to
fix/session-original-server-certificate
September 8, 2026 23:45
kevinherron
added this pull request to stack #1956
September 8, 2026 23:54
…nvalid An OpenSecureChannel request naming a thumbprint the server no longer holds, as happens after the server's application certificate is replaced, describes a certificate problem rather than a generic security check failure. Bad_CertificateInvalid tells the client which side is stale.
Transports that implement ChannelStateObservable now report each failed connection attempt to their listeners, and failures during SecureChannel establishment carry a SecureChannelHandshakeException in the cause chain. The marker identifies the handshake phase independently of the server's status code or whether the server sent an error at all, including timeouts and silent closure. TCP connection and Hello/Acknowledge failures do not carry it. The SDK will use this boundary to refresh discovery information without depending on particular status codes. The TCP transport also aborts an in-flight socket and handshake on disconnect() instead of waiting for the peer or a handshake timeout, since the ChannelFsm shelves Disconnect while an attempt is in progress. The reverse-connect transport preserves the phase marker when the peer closes the socket before channelInactive runs.
OpcClientTransport.sendRequestMessage awaits an in-progress reconnect internally, so a request built eagerly could sign over a dead channel's thumbprint and be sent on the channel that replaces it. Add a sendRequestMessage overload that takes a request supplier and a bounded channel wait; the TCP transport invokes the supplier only after its ChannelFsm publishes the new channel, and other transports build immediately. CreateSession and reactivation use it, so an expired wait fails with Bad_Timeout instead of creating a Session later, and a transport that throws synchronously still produces a failure event. Sessions now retain the endpoint and client certificate they were created with. Reactivation signs with those inputs and the replacement channel's certificate and thumbprint, and requires a new Session when the client's application certificate has changed (Part 4 6.7).
A client connecting to a secured endpoint pins the server certificate advertised at discovery. When the server replaces its application certificate, every reconnect fails until the application rediscovers the endpoint and builds a new client. Add an EndpointResolver that reruns discovery and endpoint selection, and an EndpointRefresh that invokes it after any SecureChannel establishment failure on a secured endpoint, classified by the transport's phase marker rather than by status code. The transport's next attempt reads the refreshed endpoint; the Session FSM reads the endpoint and discovery list together once the channel is ready so CreateSession validation uses one resolution result. Refresh runs at most one resolution at a time, within the resolver's cooldown and timeout, and resets the cooldown when a channel becomes ready. A refresh must select the same endpoint: URL, server URI, transport, security policy, mode, and user token policies. Only the server certificate may differ. Token policies are pinned because GetEndpoints is unauthenticated and a refresh is reachable by anyone able to disrupt the handshake. Replacement certificates still pass the configured validator; discovery grants no trust. URL-based client factories configure the resolver automatically, and the client example runner builds one explicitly.
Exercise the Java client against a Milo server that rotates its application certificate while connected: recovery with and without a certificate status from the server, recovery after a server restart, bounded rediscovery during a discovery outage, rejection of untrusted replacements, reactivation of retained Sessions with username and X509 credentials, unchanged renewal bindings on an established channel, and a new Session when the client's own certificate changes.
kevinherron
force-pushed
the
t3code/client-certificate-rotation-recovery
branch
from
September 9, 2026 00:05
c7855fc to
3cf936f
Compare
kevinherron
marked this pull request as ready for review
September 9, 2026 00:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1955, which carries the server half. Merge #1955 first; this PR's base will then move to integration/1.2.
Clients can recover after a server application certificate changes without replacing the client instance. An EndpointResolver repeats discovery and endpoint selection after SecureChannel establishment failures on a secured endpoint. URL-based client factories configure it automatically; directly constructed clients can supply their own resolver.
Refresh preserves the endpoint's URL, server identity, transport, security policy, security mode, and user token policies. Replacement certificates still pass the configured validator. Retained Sessions keep their original certificate inputs while reactivation uses the replacement channel's security bindings; the server side of that contract is in #1955.
Deferred requests have bounded channel waits and stay on the channel used to build their signatures. The refresh cooldown resets when a channel becomes ready, so a second rotation is not delayed by an earlier outage. The change includes rotation and recovery tests, a configuration example, and feature documentation.