Skip to content

Add TLSConfig.verification_server_name to decouple certificate verification from SNI#1651

Open
kollektiv wants to merge 1 commit into
temporalio:mainfrom
kollektiv:verification-server-name
Open

Add TLSConfig.verification_server_name to decouple certificate verification from SNI#1651
kollektiv wants to merge 1 commit into
temporalio:mainfrom
kollektiv:verification-server-name

Conversation

@kollektiv

Copy link
Copy Markdown

What was changed

Adds an optional TLSConfig.verification_server_name. When set, the server certificate is verified against this fixed name instead of the connection's server name, while the TLS SNI and HTTP/2 :authority values keep following the connected host (or domain, if set).

Today the only way to verify a certificate that carries a different name than the one being dialed is TLSConfig.domain, but domain couples three things: the name the certificate is checked against, the SNI, and the :authority header. So verifying against name X forces SNI to X too.

This came up connecting through an SNI-inspecting egress proxy that re-resolves the SNI hostname: the server's certificate carries an internal-only name, so setting domain to that name made the connection unresolvable at the proxy, and leaving domain unset failed certificate verification. There's an earlier ask in the same family in #463.

Implementation

The bundled sdk-core client crate already exposes TlsOptions.server_cert_verifier; the Python bridge just hardcoded it to None. This wires it up: when verification_server_name is set, the bridge builds a rustls ServerCertVerifier that delegates to the standard WebPkiServerVerifier (full chain, validity, and configured revocation checks) but substitutes the fixed name for the connection's server name. Root loading and crypto-provider selection mirror tonic's default (non-verifier) client path, so trust and negotiation match an ordinary connection.

Because a custom verifier can't be combined with connection roots, the CA bundle from server_root_ca_cert is consumed by the verifier's own root store; server_root_ca_cert is therefore required when verification_server_name is set (validated up front with a clear error). Existing behavior when the field is unset is byte-for-byte unchanged.

Deliberately not touched: envconfig TOML profiles, since that schema mirrors the cross-SDK envconfig spec and a Python-only key would fork it. Happy to follow up if you'd rather that be added.

Testing

tests/test_tls.py runs an in-process TLS server whose certificate is valid only for pinned.test while the client dials localhost, and asserts on both the handshake outcome and the SNI the server observed:

  • default verification rejects the certificate (baseline);
  • with verification_server_name="pinned.test" the handshake succeeds and the observed SNI is localhost, not the pinned name;
  • a pinned name the certificate does not carry is still rejected, so the option redirects verification rather than disabling it;
  • the missing-CA case raises before any dial.

Also probed the bridge with malformed/truncated/empty PEM and invalid names; all surface as ValueError, no panics.

Open design question for reviewers: this could instead live in sdk-core's TlsOptions so every language bridge gets it. Kept it in the bridge here to stay self-contained, but glad to move it upstream to core if that's preferred.

…cation from SNI

When set, the server certificate is verified against this fixed name
(using server_root_ca_cert) via a custom rustls ServerCertVerifier built
in the bridge, while SNI and the HTTP/2 authority keep following the
connected host (or domain, if set). This supports servers whose
certificate does not carry the dialed name without overriding SNI, e.g.
when the connection traverses an SNI-inspecting proxy that must be able
to resolve the SNI value.
@CLAassistant

CLAassistant commented Jul 15, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@kollektiv kollektiv marked this pull request as ready for review July 15, 2026 23:50
@kollektiv kollektiv requested a review from a team as a code owner July 15, 2026 23:50
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