Skip to content

Add native-tls backend for wasi-tls #11064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 27, 2025

Conversation

badeend
Copy link
Contributor

@badeend badeend commented Jun 17, 2025

During the initial wasi-tls implementation discussion, the suggestion was floated to support both rustls and native-tls. The initial/current implementation only supports rustls. This PR adds native-tls as an alternative TLS backend.

The default TLS provider remains rustls. Consumers can switch to the native-tls backend by enabling the corresponding feature flags and then use the newly created WasiTlsCtxBuilder::provider method. On the CLI, this is exposed as -S tls-provider=nativetls.

Adding this new backend immediately surfaced two bugs:

1. Improved compatibility of test_tls_sample_application

The test used to perform a "half-close" after sending the HTTP request. This is a TLS1.3+ feature, though Rustls & OpenSSL already supported it for TLS1.2 and lower. Technically, that makes them non-spec compliant, but they chose to align with the semantics of the underlying TCP connection. I suspect the TLS1.3 spec was updated to match what was already happening in reality.

Anyhow, SChannel does not support half-closed connections and so the read call after the close_output+shutdown failed. I've reordered the test to first perform the HTTP conversation and then do the TLS+TCP teardown.

2. Implement flushing on the AsyncWriteStream.

The AsyncWriteStream implementation was copied from the TCP equivalent, which doesn't need flushing. The TLS implementations do maintain an internal buffer, so the flush call needs to be hooked up. Without it, the test would hang after making the change mentioned above.

Misc: Reorganized wasi-tls folder

To make life easier for myself, I've shuffled some code around. I've isolated the rustls-specific bits from the rest of the implementation. The folder structure now looks like:

  • providers/*: The rustls & native-tls specific parts.
  • bindings.rs: The generated bindings.
  • host.rs: The host types + impls.
  • io.rs: WASI I/O conversion utility types.

CC: @jsturtevant

badeend added 5 commits June 15, 2025 22:03
… from the rest of the implementation.

- `client.rs`: The rustls parts.
- `io.rs`: WASI I/O conversion utility types.
- `host.rs`: The host types + impls.
- `bindings.rs`: The generated bindings.
It used to perform a "half-close" after sending the HTTP request. This is a TLS1.3+ feature, though Rustls & OpenSSL already supported it for TLS1.2 and lower. Technically, that makes them non-spec compliant, but they chose to align with the semantics of the underlying TCP connection. I suspect the TLS1.3 spec was updated to match what was already happening in reality.
Anyhow, SChannel does not support half-closed connections and so the `read` call after the `close_output+shutdown` failed.
I've reordered the test to first perform the HTTP conversation and _then_ do the TLS+TCP teardown.
…implementation was copied from the TCP equivalent, which doesn't need flushing. The TLS implementations _do_ maintain an internal buffer, so the `flush` call need to be hooked up.
@badeend badeend requested review from a team as code owners June 17, 2025 18:30
@badeend badeend requested review from alexcrichton and removed request for a team June 17, 2025 18:30
…nto native_tls

# Conflicts:
#	Cargo.lock
@alexcrichton
Copy link
Member

This all looks great to me, thanks again for working on this!

My only comment would be could this update CI checks to perform a build of the wasmtime-wasi-tls crate with some feature combinations? I'd be interested to see --no-default-features --features rustls and one for native-tls to ensure the crate can built with only one of the two enabled.

I'll file a separate PR to perform the vets as well.

@badeend
Copy link
Contributor Author

badeend commented Jun 19, 2025

Is this the right place to do that?

Copy link
Contributor

@jsturtevant jsturtevant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great finds in the bugs. LGTM me overall

} else if #[cfg(feature = "nativetls")] {
pub use NativeTlsProvider as DefaultProvider;
} else {
compile_error!("At least one TLS provider must be enabled.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make sense to have to set two feature flags to enable the feature or should we provide a default to enable the feature or should we provide a default?

Also a bit confused in the way to set this up. Do I need to configure both feature flags (wasi-tls and nativetls or rusttls) and then also set the provider with wasi_tls_ctx: WasiTlsCtxBuilder::new().provider(provider).build(),?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rustls is part of the default feature. So by default, DefaultProvider will point to the rustls provider and there's no need to configure anything on the WasiTlsCtxBuilder.

Similarly, if you build with --no-default-features --features nativetls, then DefaultProvider will point to the native_tls provider and there's again no need to configure anything on the WasiTlsCtxBuilder.

The only reason to call WasiTlsCtxBuilder::provider is when the crate has been built with multiple providers and you want to switch between them at run time. This is what happens in e.g. the test suite and in the CLI code.

I've added some docs to clarify this.

Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep that works 👍

@alexcrichton alexcrichton added this pull request to the merge queue Jun 20, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jun 20, 2025
@alexcrichton
Copy link
Member

Hm ok so this is actually going to be kind of tricky. We test crates in CI with --all-features but we also test cross-compilations to various architectures, and native-tls on Linux wants OpenSSL which wants a system copy by default which isn't present for cross-compiled variants.

One option might be to have a feature such as openssl-vendored on the wasmtime-wasi-tls crate which builds a vendored copy of openssl-src. That way --all-features of that would be activated an OpenSSL would be built from source?

That's not an ideal option though IMO as ideally we wouldn't test native-tls by default and we'd instead have a dedicated job for just testin native-tls, but with --all-features that's not easy :(

@badeend
Copy link
Contributor Author

badeend commented Jun 22, 2025

How about moving /crates/wasi-tls/src/providers/native_tls.rs into a dedicated wasi-tls-nativetls crate and exclude that entire crate from the default test runs?

Edit: And set up a separate CI job to test wasi-tls-nativetls in isolation on a Linux, MacOS and Windows host.

Edit2: A downside of this approach is that the wasmtime CLI may not reference the wasi-tls-nativetls package anymore either.

@alexcrichton
Copy link
Member

I think what you're proposing is probably the best option for now. You're right that it means that the CLI won't be easily buildable with native-tls, but that seems like a reasonable state of affairs for the time being and we can address that later if need be

@badeend
Copy link
Contributor Author

badeend commented Jun 26, 2025

Could you take a look at my latest changes? I've ran it with prtest:full and everything (except cargo vet) now succeeds.

@alexcrichton alexcrichton enabled auto-merge June 27, 2025 14:31
@alexcrichton alexcrichton added this pull request to the merge queue Jun 27, 2025
Merged via the queue into bytecodealliance:main with commit b85addc Jun 27, 2025
165 checks passed
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