Skip to content

feat(tls): accept a caller-supplied rustls ClientConfig - #463

Open
tommihip wants to merge 1 commit into
tiberius-rs:mainfrom
tommihip:feat/rustls-client-config
Open

tommihip wants to merge 1 commit into
tiberius-rs:mainfrom
tommihip:feat/rustls-client-config

Conversation

@tommihip

Copy link
Copy Markdown

Summary

Adds Config::rustls_client_config(Arc<rustls::ClientConfig>) (and the ConfigBuilder mirror), behind the existing rustls feature. When set, the TLS handshake uses the caller's configuration instead of one built from the trust settings.

Use case: applications that already manage rustls themselves (a custom ServerCertVerifier, a root store shared with their other database clients, a specific CryptoProvider, client authentication through a ResolvesClientCert, session resumption or key logging) and want SQL Server verified the same way as everything else, without mapping their policy onto trust_cert_ca / trust_webpki_roots.

Behaviour

  • The supplied configuration is used as-is: its verifier, roots, client authentication and crypto provider. The trust settings (trust_cert, trust_cert_ca, trust_cert_ca_bundle, trust_webpki_roots, and their connection-string equivalents) and client_certificate are not consulted. A trust_cert() from a connection string therefore cannot weaken a caller's verifier (there is a test for this).
  • SNI and name verification still use hostname_in_certificate (or host).
  • For EncryptionLevel::Strict, tds/8.0 is added to the ALPN list of a copy if it is not already there. The caller's Arc<ClientConfig> is never modified. Without strict encryption the same Arc is used.
  • Handshake errors from a caller-supplied configuration pass through without the trust_cert_ca / trust_cert hints from map_handshake_error, since those settings don't apply.
  • Without the setter nothing changes. The built-in path was moved into a client_config(&Config) helper so it can be unit-tested; its logic is the same.

Relation to #456 / #457

This complements #456 (rustls-no-provider) and doesn't overlap with it. With a caller-supplied configuration the provider comes from the ClientConfig, so an application on rustls-no-provider doesn't need a process-wide default provider for tiberius. If #456 merges first, the only change here is the gate: feature = "rustls" becomes feature = "rustls-no-provider" in the three new cfgs (the test helpers use aws-lc-rs and would need the same gating as #456's tests). I'm happy to rebase onto whichever lands first.

Tests

  • Unit (rustls_tls_stream): the caller's config is used unchanged (Arc::ptr_eq); strict adds tds/8.0 exactly once, keeps the caller's provider and doesn't mutate the caller's config; trust settings aren't read (an unreadable CA file fails the built-in path but not this one).
  • In-memory handshakes against docker/certs/server.crt over tokio::io::duplex, with the verification time pinned inside the fixture's validity window: a strict handshake completes and the server negotiates tds/8.0; with trust_cert() also set, a caller config that doesn't trust customCA still rejects the server (UnknownIssuer).
  • Unit (config): default, setter and builder.
  • Integration (tests/custom-cert.rs): connect_to_custom_cert_instance_rustls_client_config connects to the custom-cert instance with a ClientConfig that trusts only customCA.

Run locally:

cargo test --no-default-features --features rustls,chrono,time,tds80 --lib          # 1027 passed
cargo test --no-default-features --features rustls-webpki-roots,chrono,time,tds73 --lib
cargo test --no-default-features --features rustls,chrono,time,tds80 --doc
cargo test --lib                                                                    # default (native-tls)
cargo test --no-default-features --features rustls,chrono,time,tds80 --test custom-cert   # against docker/test-server.sh (mssql-2022)
cargo clippy --no-default-features --features rustls,chrono,time,tds80 --all-targets -- -D warnings
cargo clippy --features all --all-targets
cargo fmt --check

Changelog

Added under a new ## Unreleased heading.

🤖 Generated with Claude Code

Add `Config::rustls_client_config(Arc<rustls::ClientConfig>)` and its
`ConfigBuilder` mirror (rustls only). When set, the TLS handshake uses the
caller's configuration as-is (verifier, roots, client authentication and
crypto provider) instead of one built from the trust settings, which are
then not consulted. For `EncryptionLevel::Strict` the `tds/8.0` ALPN
protocol is added to a copy if missing; the caller's configuration is never
modified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant