Skip to content

Conversation

michaeldjeffrey
Copy link
Contributor

Do it with me now. loooooooong sigh

Okay, rust depedencies are a headache, we know this.

Updating AWS deps in #1019 brought in a lot of newer stuff.
At this point, we're specifically concerned with rustls.

The default provider <0.23 was ring. 0.24 defaults to aws-lc-rs. It's faster, more compliant, all that good stuff.

But this poses a problem to us, what to use when both aws-lc-rs and ring are present in the dependency tree?
If they both try to install themselves as the global CryptoProvider, that's no good.
The state of rust deps that depend on TLS is slowly moving over to aws-lc-rs, but not quite there. So we have to make the decision in our runtime.

The option I don't like

We could manually install aws-lc-rs in the main() function if every bin we build.

#[tokio::main]
async fn main() -> anyhow::Result<()> {
  rustls::crypto::aws_lc_rs::default_provider().install_default();

  // ...
}

But this won't enable for tests, and we won't know if there's a conflicting backend installed until runtime.
And we would need to handle the case where someone installed before our main function could run.

I didn't do that.

The option I did

A new workspace was added, tls-init.

It uses ctor to run a constructor function when the crate is included anywhere.

#[ctor::ctor]
fn _install_tls_provider() {
    rustls::crypto::aws_lc_rs::default_provider()
        .install_default()
        .expect("failed to install aws-lc-rs crypto provider");
}

It will only run a single time no matter how many times it's included, so we don't need to worry about multiple levels of dependencies including tls-init.

For any crate that uses TLS we need to include it in Cargo.toml

[dependencies]
tls-init = { path = "../tls_init" }

And include the crate in lib.rs.

extern crate tls_init;

externing the crate is required so the linker doesn't optimize the dependency away.

Now, when any workspace is loaded or depended on that requires TLS, the first thing that happens is we install aws-lc-rs as the CryptoProvider backend.

Can we be sure?

I'm not sure how to be totally sure.
But tls_init also provides a test that can be included in workspaces that will hopefully catch if another dependency includes ring and it tries to be installed as the backend.

#[cfg(test)]
tls_init::include_tls_tests!();

This inlines the tests into the workspace to run alongside all of it's dependencies.

Other

backon was updated because there was a breaking change between 0.4 and 0.5. But weirdly, not >1. Also it was easy, and a dev-dependency.

If we let it update itself between 0.4 and 0.5 there is a breaking
change where a sleeper is no longer provided.

Forcing to ^1 fixes the problem.
This crate attempts to lock down which version of rustls crypto provider
we use to be only aws-lc-rs.

The ctor crate allows us to define a constructor that runs when a
package is loaded. And it ensures the constructor is only run a single
time no matter how many times it's included.

This is the alternative to littering all our main functions with
`rustls::crypto::aws_lc_rs::default_provider().install_provider()`
calls.

Because the linker will attempt to remove unused code, and there are no
direct calls to this function, we use `extern crate tls_init;` wherever
we want to ensure we have the `aws-lc-rs` backend. Basically, it tells
the compiler, this dependency is definitely used even though you might
not see it.
include this macro in crates that need to ensure tls is provided by
aws-lc-rs. Hopefully this will catch potential conflicts if a dependency
is included that enables something other than aws-lc.
@michaeldjeffrey michaeldjeffrey merged commit df18e25 into main Sep 18, 2025
119 checks passed
@michaeldjeffrey michaeldjeffrey deleted the mj/crypto-provider-fix branch September 18, 2025 02:16
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.

3 participants