Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ wasi-common = { workspace = true, default-features = true, features = ["exit", "
wasmtime-wasi = { workspace = true, default-features = true, optional = true }
wasmtime-wasi-nn = { workspace = true, optional = true }
wasmtime-wasi-config = { workspace = true, optional = true }
wasmtime-wasi-tls = { workspace = true, optional = true }
wasmtime-wasi-keyvalue = { workspace = true, optional = true }
wasmtime-wasi-threads = { workspace = true, optional = true }
wasmtime-wasi-http = { workspace = true, optional = true }
Expand Down Expand Up @@ -245,6 +246,7 @@ wasmtime-component-macro = { path = "crates/component-macro", version = "=32.0.0
wasmtime-asm-macros = { path = "crates/asm-macros", version = "=32.0.0" }
wasmtime-versioned-export-macros = { path = "crates/versioned-export-macros", version = "=32.0.0" }
wasmtime-slab = { path = "crates/slab", version = "=32.0.0" }
wasmtime-wasi-tls = { path = "crates/wasi-tls", version = "32.0.0" }
component-test-util = { path = "crates/misc/component-test-util" }
component-fuzz-util = { path = "crates/misc/component-fuzz-util" }
wiggle = { path = "crates/wiggle", version = "=32.0.0", default-features = false }
Expand Down Expand Up @@ -377,6 +379,9 @@ libtest-mimic = "0.7.0"
semver = { version = "1.0.17", default-features = false }
ittapi = "0.4.0"
libm = "0.2.7"
tokio-rustls = "0.25.0"
rustls = "0.22.0"
webpki-roots = "0.26.0"

# =============================================================================
#
Expand Down Expand Up @@ -409,6 +414,7 @@ default = [
"wasi-http",
"wasi-config",
"wasi-keyvalue",
"wasi-tls",

# Most features of Wasmtime are enabled by default.
"wat",
Expand Down Expand Up @@ -459,6 +465,7 @@ disable-logging = ["log/max_level_off", "tracing/max_level_off"]
# These features are all included in the `default` set above and this is
# the internal mapping for what they enable in Wasmtime itself.
wasi-nn = ["dep:wasmtime-wasi-nn"]
wasi-tls = ["dep:wasmtime-wasi-tls"]
wasi-threads = ["dep:wasmtime-wasi-threads", "threads"]
wasi-http = ["component-model", "dep:wasmtime-wasi-http", "dep:tokio", "dep:hyper"]
wasi-config = ["dep:wasmtime-wasi-config"]
Expand Down Expand Up @@ -567,3 +574,4 @@ opt-level = 's'
inherits = "release"
codegen-units = 1
lto = true

2 changes: 2 additions & 0 deletions crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,8 @@ wasmtime_option_group! {
/// Grant access to the given TCP listen socket
#[serde(default)]
pub tcplisten: Vec<String>,
/// Enable support for WASI TLS (Transport Layer Security) imports (experimental)
pub tls: Option<bool>,
/// Implement WASI Preview1 using new Preview2 implementation (true, default) or legacy
/// implementation (false)
pub preview2: Option<bool>,
Expand Down
1 change: 1 addition & 0 deletions crates/test-programs/artifacts/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ fn build_and_generate_tests() {
s if s.starts_with("dwarf_") => "dwarf",
s if s.starts_with("config_") => "config",
s if s.starts_with("keyvalue_") => "keyvalue",
s if s.starts_with("tls_") => "tls",
// If you're reading this because you hit this panic, either add it
// to a test suite above or add a new "suite". The purpose of the
// categorization above is to have a static assertion that tests
Expand Down
48 changes: 48 additions & 0 deletions crates/test-programs/src/bin/tls_sample_application.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use core::str;

use test_programs::wasi::sockets::network::{IpSocketAddress, Network};
use test_programs::wasi::sockets::tcp::{ShutdownType, TcpSocket};
use test_programs::wasi::tls::types::ClientHandshake;

fn test_tls_sample_application() {
const PORT: u16 = 443;
const DOMAIN: &'static str = "example.com";

let request = format!("GET / HTTP/1.1\r\nHost: {DOMAIN}\r\n\r\n");

let net = Network::default();

let Some(ip) = net
.permissive_blocking_resolve_addresses(DOMAIN)
.unwrap()
.first()
.map(|a| a.to_owned())
else {
eprintln!("DNS lookup failed.");
return;
};

let socket = TcpSocket::new(ip.family()).unwrap();
let (tcp_input, tcp_output) = socket
.blocking_connect(&net, IpSocketAddress::new(ip, PORT))
.unwrap();

let (client_connection, tls_input, tls_output) =
ClientHandshake::new(DOMAIN, tcp_input, tcp_output)
.blocking_finish()
.unwrap();

tls_output.blocking_write_util(request.as_bytes()).unwrap();
client_connection
.blocking_close_output(&tls_output)
.unwrap();
socket.shutdown(ShutdownType::Send).unwrap();
let response = tls_input.blocking_read_to_end().unwrap();
let response = String::from_utf8(response).unwrap();

assert!(response.contains("HTTP/1.1 200 OK"));
}

fn main() {
test_tls_sample_application();
}
5 changes: 4 additions & 1 deletion crates/test-programs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod http;
pub mod nn;
pub mod preview1;
pub mod sockets;
pub mod tls;

wit_bindgen::generate!({
inline: "
Expand All @@ -12,15 +13,17 @@ wit_bindgen::generate!({
include wasi:http/[email protected];
include wasi:config/[email protected];
include wasi:keyvalue/[email protected];
include wasi:tls/[email protected];
}
",
path: [
"../wasi-http/wit",
"../wasi-config/wit",
"../wasi-keyvalue/wit",
"../wasi-tls/wit/world.wit",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason for this being the wit file directly is bytecodealliance/wasm-tools#1996 (comment)

],
world: "wasmtime:test/test",
features: ["cli-exit-with-code"],
features: ["cli-exit-with-code", "tls"],
generate_all,
});

Expand Down
45 changes: 45 additions & 0 deletions crates/test-programs/src/tls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use crate::wasi::clocks::monotonic_clock;
use crate::wasi::io::streams::StreamError;
use crate::wasi::tls::types::{ClientConnection, ClientHandshake, InputStream, OutputStream};

const TIMEOUT_NS: u64 = 1_000_000_000;

impl ClientHandshake {
pub fn blocking_finish(self) -> Result<(ClientConnection, InputStream, OutputStream), ()> {
let future = ClientHandshake::finish(self);
let timeout = monotonic_clock::subscribe_duration(TIMEOUT_NS * 200);
let pollable = future.subscribe();

loop {
match future.get() {
None => pollable.block_until(&timeout).expect("timed out"),
Some(Ok(r)) => return r,
Some(Err(e)) => {
eprintln!("{e:?}");
unimplemented!()
}
}
}
}
}

impl ClientConnection {
pub fn blocking_close_output(
&self,
output: &OutputStream,
) -> Result<(), crate::wasi::io::error::Error> {
let timeout = monotonic_clock::subscribe_duration(TIMEOUT_NS);
let pollable = output.subscribe();

self.close_output();

loop {
match output.check_write() {
Ok(0) => pollable.block_until(&timeout).expect("timed out"),
Ok(_) => unreachable!("After calling close_output, the output stream should never accept new writes again."),
Err(StreamError::Closed) => return Ok(()),
Err(StreamError::LastOperationFailed(e)) => return Err(e),
}
}
}
}
9 changes: 3 additions & 6 deletions crates/wasi-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,9 @@ http-body-util = { workspace = true }
tracing = { workspace = true }
wasmtime-wasi = { workspace = true }
wasmtime = { workspace = true, features = ['component-model'] }

# The `ring` crate, used to implement TLS, does not build on riscv64 or s390x
[target.'cfg(not(any(target_arch = "riscv64", target_arch = "s390x")))'.dependencies]
tokio-rustls = { version = "0.25.0" }
rustls = { version = "0.22.0" }
webpki-roots = { version = "0.26.0" }
tokio-rustls = { workspace = true }
rustls = { workspace = true }
webpki-roots = { workspace = true }

[dev-dependencies]
test-programs-artifacts = { workspace = true }
Expand Down
90 changes: 40 additions & 50 deletions crates/wasi-http/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,58 +373,48 @@ pub async fn default_send_request_handler(
})?;

let (mut sender, worker) = if use_tls {
#[cfg(any(target_arch = "riscv64", target_arch = "s390x"))]
{
return Err(crate::bindings::http::types::ErrorCode::InternalError(
Some("unsupported architecture for SSL".to_string()),
));
}
use rustls::pki_types::ServerName;

#[cfg(not(any(target_arch = "riscv64", target_arch = "s390x")))]
{
use rustls::pki_types::ServerName;

// derived from https://github.com/rustls/rustls/blob/main/examples/src/bin/simpleclient.rs
let root_cert_store = rustls::RootCertStore {
roots: webpki_roots::TLS_SERVER_ROOTS.into(),
};
let config = rustls::ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config));
let mut parts = authority.split(":");
let host = parts.next().unwrap_or(&authority);
let domain = ServerName::try_from(host)
.map_err(|e| {
tracing::warn!("dns lookup error: {e:?}");
dns_error("invalid dns name".to_string(), 0)
})?
.to_owned();
let stream = connector.connect(domain, tcp_stream).await.map_err(|e| {
tracing::warn!("tls protocol error: {e:?}");
types::ErrorCode::TlsProtocolError
})?;
let stream = TokioIo::new(stream);

let (sender, conn) = timeout(
connect_timeout,
hyper::client::conn::http1::handshake(stream),
)
.await
.map_err(|_| types::ErrorCode::ConnectionTimeout)?
.map_err(hyper_request_error)?;

let worker = wasmtime_wasi::runtime::spawn(async move {
match conn.await {
Ok(()) => {}
// TODO: shouldn't throw away this error and ideally should
// surface somewhere.
Err(e) => tracing::warn!("dropping error {e}"),
}
});
// derived from https://github.com/rustls/rustls/blob/main/examples/src/bin/simpleclient.rs
let root_cert_store = rustls::RootCertStore {
roots: webpki_roots::TLS_SERVER_ROOTS.into(),
};
let config = rustls::ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config));
let mut parts = authority.split(":");
let host = parts.next().unwrap_or(&authority);
let domain = ServerName::try_from(host)
.map_err(|e| {
tracing::warn!("dns lookup error: {e:?}");
dns_error("invalid dns name".to_string(), 0)
})?
.to_owned();
let stream = connector.connect(domain, tcp_stream).await.map_err(|e| {
tracing::warn!("tls protocol error: {e:?}");
types::ErrorCode::TlsProtocolError
})?;
let stream = TokioIo::new(stream);

(sender, worker)
}
let (sender, conn) = timeout(
connect_timeout,
hyper::client::conn::http1::handshake(stream),
)
.await
.map_err(|_| types::ErrorCode::ConnectionTimeout)?
.map_err(hyper_request_error)?;

let worker = wasmtime_wasi::runtime::spawn(async move {
match conn.await {
Ok(()) => {}
// TODO: shouldn't throw away this error and ideally should
// surface somewhere.
Err(e) => tracing::warn!("dropping error {e}"),
}
});

(sender, worker)
} else {
let tcp_stream = TokioIo::new(tcp_stream);
let (sender, conn) = timeout(
Expand Down
2 changes: 0 additions & 2 deletions crates/wasi-http/tests/all/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,8 +528,6 @@ async fn do_wasi_http_echo(uri: &str, url_header: Option<&str>) -> Result<()> {
}

#[test_log::test(tokio::test)]
// test uses TLS but riscv/s390x don't support that yet
#[cfg_attr(any(target_arch = "riscv64", target_arch = "s390x"), ignore)]
async fn wasi_http_without_port() -> Result<()> {
let req = hyper::Request::builder()
.method(http::Method::GET)
Expand Down
33 changes: 33 additions & 0 deletions crates/wasi-tls/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "wasmtime-wasi-tls"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
repository = "https://github.com/bytecodealliance/wasmtime"
license = "Apache-2.0 WITH LLVM-exception"
description = "Wasmtime implementation of the wasi-tls API"

[lints]
workspace = true

[dependencies]
anyhow = { workspace = true }
bytes = { workspace = true }
tokio = { workspace = true, features = [
"net",
"rt-multi-thread",
"time",
] }
wasmtime = { workspace = true, features = ["runtime", "component-model"] }
wasmtime-wasi = { workspace = true }
tokio-rustls = { workspace = true }
rustls = { workspace = true }
webpki-roots = { workspace = true }


[dev-dependencies]
test-programs-artifacts = { workspace = true }
wasmtime-wasi = { workspace = true }
tokio = { workspace = true, features = ["macros"] }
futures = { workspace = true }
Loading
Loading