feat(tls): add an async-tls-rustls-no-provider backend - #2
Merged
forust merged 2 commits intoSep 24, 2026
Merged
Conversation
Closes oksyd#1. Every async rustls path currently hard-selects a crypto provider in code - `build_rustls_ring_transport` passes `rustls::crypto::ring::default_provider()` into `builder_with_provider`, with a parallel pair for aws-lc-rs. Because Cargo feature unification is additive, a downstream crate cannot switch that off, so enabling rustls at all means linking a C/assembly crypto library. This adds a third rustls backend that takes the provider from the process-wide default the application installed, leaving the choice to the caller: a pure-Rust provider such as `rustls-graviola`, `aws-lc-rs` in FIPS mode, or anything else. `TlsBackend` is `#[non_exhaustive]`, so the new variant is semver-minor for downstream crates. **The provider is fetched, not assumed.** `build_rustls_no_provider_transport` calls `CryptoProvider::get_default()` and returns `Error::TlsBackendInit` with a message naming `install_default` when nothing is installed. Using `rustls::ClientConfig::builder()` would have been shorter, but it *panics* in exactly that case - a returned error is the point of the feature. Two `match`es in `src/blocking_client/transport.rs` are exhaustive over `TlsBackend` (`#[non_exhaustive]` constrains downstream crates, not this one), so they needed the new variant to keep compiling under any blocking feature. Both are handled explicitly rather than with a `_` arm - `backend_is_available` returns false and `build_sync_tls_config` returns `TlsBackendUnavailable`, since there is no `ureq` counterpart - so a future backend still gets a compile error here instead of silently falling into a catch-all. `DEFAULT_TLS_BACKEND` gains a branch for the case where this is the only feature enabled; ring keeps precedence wherever it is on. Verified on this commit: - `cargo check` passes for no-provider, ring, aws-lc-rs, native, blocking-tls-rustls-ring, and `--all-features`. - `cargo tree --features async-tls-rustls-no-provider -e normal -i ring` has nothing to print, and `-i aws-lc-rs` matches no package. The same query on the ring feature returns `ring v0.17.14`, so the negative result is the query working, not the query being broken. - `cargo fmt --check` clean; `cargo clippy -- -D warnings` clean under both the new feature and the defaults; `cargo test --no-run` builds.
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.
Closes #1.
What this does
Adds a third async rustls backend,
async-tls-rustls-no-provider, that takes itscrypto provider from the process-wide default the application installed instead
of one this crate hard-selects.
Why it needed code, not just a feature
My issue proposed this as a Cargo.toml one-liner. Reading the crate, that was
wrong — the provider is chosen in code, not only in features:
with a parallel pair for aws-lc-rs and a
#[cfg(not(...))]stub each. So ano-provider path needs its own
TlsBackendvariant, its own builder pair, adispatcher arm, and an entry in the
compile_error!guard that enforces "oneasync-tls-*feature must be enabled".TlsBackendis already#[non_exhaustive], so the new variant is semver-minorfor downstream crates.
The one design decision worth reviewing
The provider is fetched, not assumed:
rustls::ClientConfig::builder()would have been shorter, but it panics whenno provider is installed — which is precisely the situation this feature makes
reachable. A returned
TlsBackendInitnaminginstall_defaultseemed the onlydefensible behaviour for a library. Happy to change it if you disagree.
One change outside the async path
Two
matches insrc/blocking_client/transport.rsare exhaustive overTlsBackend—#[non_exhaustive]constrains downstream crates, not this one — sothey stopped compiling under any blocking feature once the variant existed.
Both are handled explicitly rather than with a
_arm:backend_is_availablereturns
false, andbuild_sync_tls_configreturnsTlsBackendUnavailable, sincethere is no
ureqcounterpart. That keeps the property that adding a futurebackend produces a compile error here instead of silently falling into a catch-all.
Verification
Run on the pushed commit, not just locally at some earlier point:
cargo check— no-provider / ring / aws-lc-rs / native / blocking-ring /--all-featurescargo fmt --checkcargo clippy -- -D warnings, new feature and defaultscargo test --no-runThe claim that actually matters, with a positive control so it cannot pass
vacuously:
-e normalis deliberate:ringis still reachable as a dev-dependency viarcgen, identically on every feature, which is unrelated to backend selection.Context
We run an image-processing service that removed every C and C++ dependency and
enforces it in CI.
s3builds onreqxandalready signs SigV4 with the pure-Rust
graviola, so this feature is the lastthing between that crate and a fully pure-Rust S3 client. There is also a FIPS
case:
aws-lc-rs's FIPS mode needs the same escape hatch.Precedent for the shape:
reqwestshipsrustls-no-provider, andureqshipsone that this crate already depends on in
blocking-tls-rustls-aws-lc-rs.Notes
The issue offered an alternative shape — one
async-tls-rustlsfeature plusseparate provider features, which would be cleaner but breaking. I built the
additive version since it is non-breaking; say the word if you would rather have
the other and I will rework it.
No CHANGELOG entry, since
git cliffgenerates it from commits at release time.