Skip to content
Open
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
2 changes: 2 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ component-model-async = [
"component-model",
"wasmtime-wasi?/p3",
"wasmtime-wasi-http?/p3",
"wasmtime-wasi-tls?/p3",
"dep:futures",
]
rr = ["wasmtime/rr", "component-model", "wasmtime-cli-flags/rr", "run"]
Expand Down
9 changes: 6 additions & 3 deletions ci/vendor-wit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,22 @@ get_github() {
local repo=$1
local tag=$2
local path=$3
local prefix=${4:-wit}

rm -rf "$path"
mkdir -p "$path"

cached_extracted_dir="$cache_dir/$repo-$tag"
cached_extracted_dir="$cache_dir/$prefix/$repo-$tag"

if [[ ! -d $cached_extracted_dir ]]; then
mkdir -p $cached_extracted_dir
curl --retry 5 --retry-all-errors -sLO https://github.com/WebAssembly/$repo/archive/$tag.tar.gz
tar xzf $tag.tar.gz --strip-components=1 -C $cached_extracted_dir
rm $tag.tar.gz
rm -rf $cached_extracted_dir/wit/deps*
rm -rf $cached_extracted_dir/${prefix}/deps*
fi

cp -r $cached_extracted_dir/wit/* $path
cp -r $cached_extracted_dir/${prefix}/* $path
}

p2=0.2.6
Expand Down Expand Up @@ -65,6 +66,8 @@ mkdir -p crates/wasi-tls/wit/deps
wkg get --format wit --overwrite "wasi:io@$p2" -o "crates/wasi-tls/wit/deps/io.wit"
get_github wasi-tls v0.2.0-draft+505fc98 crates/wasi-tls/wit/deps/tls

get_github wasi-tls v0.2.0-draft+6781ae2 crates/wasi-tls/src/p3/wit/deps/tls wit-0.3.0-draft

rm -rf crates/wasi-config/wit/deps
mkdir -p crates/wasi-config/wit/deps
get_github wasi-config v0.2.0-rc.1 crates/wasi-config/wit/deps/config
Expand Down
9 changes: 5 additions & 4 deletions crates/test-programs/artifacts/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,20 @@ impl Artifacts {
// generates a `foreach_*` macro below.
let kind = match test.name.as_str() {
s if s.starts_with("p1_") => "p1",
s if s.starts_with("p2_http_") => "p2_http",
s if s.starts_with("p2_cli_") => "p2_cli",
s if s.starts_with("p2_api_") => "p2_api",
s if s.starts_with("p2_cli_") => "p2_cli",
s if s.starts_with("p2_http_") => "p2_http",
s if s.starts_with("p2_tls_") => "p2_tls",
s if s.starts_with("p2_") => "p2",
s if s.starts_with("nn_") => "nn",
s if s.starts_with("piped_") => "piped",
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",
s if s.starts_with("async_") => "async",
s if s.starts_with("p3_http_") => "p3_http",
s if s.starts_with("p3_api_") => "p3_api",
s if s.starts_with("p3_http_") => "p3_http",
s if s.starts_with("p3_tls_") => "p3_tls",
s if s.starts_with("p3_") => "p3",
s if s.starts_with("fuzz_") => "fuzz",
// If you're reading this because you hit this panic, either add
Expand Down
177 changes: 177 additions & 0 deletions crates/test-programs/src/bin/p3_tls_sample_application.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use anyhow::{Context as _, Result, anyhow};
use core::future::Future;
use futures::try_join;
use test_programs::p3::wasi::sockets::ip_name_lookup::resolve_addresses;
use test_programs::p3::wasi::sockets::types::{IpAddress, IpSocketAddress, TcpSocket};
use test_programs::p3::wasi::tls::client::Connector;
use test_programs::p3::wit_stream;

struct Component;

test_programs::p3::export!(Component);

const PORT: u16 = 443;

async fn test_tls_sample_application(domain: &str, ip: IpAddress) -> Result<()> {
let request = format!(
"GET / HTTP/1.1\r\nHost: {domain}\r\nUser-Agent: wasmtime-wasi-rust\r\nConnection: close\r\n\r\n"
);

let sock = TcpSocket::create(ip.family()).unwrap();
sock.connect(IpSocketAddress::new(ip, PORT))
.await
.context("tcp connect failed")?;

let conn = Connector::new();

let (sock_rx, sock_rx_fut) = sock.receive();
let (tls_rx, tls_rx_fut) = conn.receive(sock_rx);

let (mut data_tx, data_rx) = wit_stream::new();
let (tls_tx, tls_tx_err_fut) = conn.send(data_rx);
let sock_tx_fut = sock.send(tls_tx);

try_join!(
async {
Connector::connect(conn, domain.into())
.await
.map_err(|err| {
anyhow!(err.to_debug_string()).context("failed to establish connection")
})
},
async {
let buf = data_tx.write_all(request.into()).await;
assert!(buf.is_empty());
drop(data_tx);
Ok(())
},
async {
let response = tls_rx.collect().await;
let response = String::from_utf8(response)?;
if response.contains("HTTP/1.1 200 OK") {
Ok(())
} else {
Err(anyhow!("server did not respond with 200 OK: {response}"))
}
},
async { sock_rx_fut.await.context("failed to receive ciphertext") },
async { sock_tx_fut.await.context("failed to send ciphertext") },
async {
tls_rx_fut
.await
.map_err(|err| anyhow!(err.to_debug_string()))
.context("failed to receive plaintext")
},
async {
tls_tx_err_fut
.await
.map_err(|err| anyhow!(err.to_debug_string()))
.context("failed to send plaintext")
},
)?;
Ok(())
}

/// This test sets up a TCP connection using one domain, and then attempts to
/// perform a TLS handshake using another unrelated domain. This should result
/// in a handshake error.
async fn test_tls_invalid_certificate(_domain: &str, ip: IpAddress) -> Result<()> {
const BAD_DOMAIN: &str = "wrongdomain.localhost";

let sock = TcpSocket::create(ip.family()).unwrap();
sock.connect(IpSocketAddress::new(ip, PORT))
.await
.context("tcp connect failed")?;

let conn = Connector::new();

let (sock_rx, sock_rx_fut) = sock.receive();
let (tls_rx, tls_rx_fut) = conn.receive(sock_rx);

let (_, data_rx) = wit_stream::new();
let (tls_tx, tls_tx_err_fut) = conn.send(data_rx);
let sock_tx_fut = sock.send(tls_tx);
let res = try_join!(
async {
Connector::connect(conn, BAD_DOMAIN.into())
.await
.expect("`connect` failed");
Ok(())
},
async {
let response = tls_rx.collect().await;
assert_eq!(response, []);
Ok(())
},
async {
sock_rx_fut.await.expect("failed to receive ciphertext");
Ok(())
},
async {
sock_tx_fut.await.expect("failed to send ciphertext");
Ok(())
},
async { tls_rx_fut.await },
async { tls_tx_err_fut.await },
);
match res {
Err(e) => {
let debug_string = e.to_debug_string();
// We're expecting an error regarding certificates in some form or
// another. When we add more TLS backends this naive check will
// likely need to be revisited/expanded:
if debug_string.contains("certificate") || debug_string.contains("HandshakeFailure") {
return Ok(());
}
Err(anyhow!(debug_string))
}
Ok(_) => panic!("expecting server name mismatch"),
}
}

async fn try_live_endpoints<'a, Fut>(test: impl Fn(&'a str, IpAddress) -> Fut)
where
Fut: Future<Output = Result<()>> + 'a,
{
// since this is testing remote endpoints to ensure system cert store works
// the test uses a couple different endpoints to reduce the number of flakes
const DOMAINS: &[&str] = &[
"example.com",
"api.github.com",
"docs.wasmtime.dev",
"bytecodealliance.org",
"www.rust-lang.org",
];

for &domain in DOMAINS {
let result = (|| async {
let ip = resolve_addresses(domain.into())
.await?
.first()
.map(|a| a.to_owned())
.ok_or_else(|| anyhow!("DNS lookup failed."))?;
test(domain, ip).await
})();

match result.await {
Ok(()) => return,
Err(e) => {
eprintln!("test for {domain} failed: {e:#}");
}
}
}

panic!("all tests failed");
}

impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
async fn run() -> Result<(), ()> {
println!("sample app");
try_live_endpoints(test_tls_sample_application).await;
println!("invalid cert");
try_live_endpoints(test_tls_invalid_certificate).await;
Ok(())
}
}

fn main() {}
6 changes: 5 additions & 1 deletion crates/test-programs/src/p3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ wit_bindgen::generate!({

world testp3 {
include wasi:cli/imports@0.3.0-rc-2026-02-09;
include wasi:tls/imports@0.3.0-draft;
import wasi:http/types@0.3.0-rc-2026-02-09;
import wasi:http/client@0.3.0-rc-2026-02-09;
import wasi:http/handler@0.3.0-rc-2026-02-09;

export wasi:cli/run@0.3.0-rc-2026-02-09;
}
",
path: "../wasi-http/src/p3/wit",
path: [
"../wasi-http/src/p3/wit",
"../wasi-tls/src/p3/wit",
],
world: "wasmtime:test/testp3",
default_bindings_module: "test_programs::p3",
pub_export_macro: true,
Expand Down
6 changes: 3 additions & 3 deletions crates/wasi-tls-nativetls/tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ macro_rules! assert_test_exists {
};
}

test_programs_artifacts::foreach_tls!(assert_test_exists);
test_programs_artifacts::foreach_p2_tls!(assert_test_exists);

#[tokio::test(flavor = "multi_thread")]
async fn tls_sample_application() -> Result<()> {
run_test(test_programs_artifacts::TLS_SAMPLE_APPLICATION_COMPONENT).await
async fn p2_tls_sample_application() -> Result<()> {
run_test(test_programs_artifacts::P2_TLS_SAMPLE_APPLICATION_COMPONENT).await
}
6 changes: 3 additions & 3 deletions crates/wasi-tls-openssl/tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ macro_rules! assert_test_exists {
};
}

test_programs_artifacts::foreach_tls!(assert_test_exists);
test_programs_artifacts::foreach_p2_tls!(assert_test_exists);

#[tokio::test(flavor = "multi_thread")]
async fn tls_sample_application() -> Result<()> {
run_test(test_programs_artifacts::TLS_SAMPLE_APPLICATION_COMPONENT).await
async fn p2_tls_sample_application() -> Result<()> {
run_test(test_programs_artifacts::P2_TLS_SAMPLE_APPLICATION_COMPONENT).await
}
6 changes: 6 additions & 0 deletions crates/wasi-tls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ description = "Wasmtime implementation of the wasi-tls API"
[lints]
workspace = true

[features]
default = []
p3 = ["wasmtime-wasi/p3", "wasmtime/component-model-async"]

[dependencies]
bytes = { workspace = true }
tokio = { workspace = true, features = [
Expand All @@ -19,6 +23,7 @@ tokio = { workspace = true, features = [
"time",
"io-util",
] }
tracing = { workspace = true }
wasmtime = { workspace = true, features = ["runtime", "component-model"] }
wasmtime-wasi = { workspace = true }

Expand All @@ -31,3 +36,4 @@ test-programs-artifacts = { workspace = true }
wasmtime-wasi = { workspace = true }
tokio = { workspace = true, features = ["macros"] }
futures = { workspace = true }
test-log = { workspace = true }
2 changes: 2 additions & 0 deletions crates/wasi-tls/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ use wasmtime::component::{HasData, ResourceTable};
pub mod bindings;
mod host;
mod io;
#[cfg(feature = "p3")]
pub mod p3;
mod rustls;

pub use bindings::types::LinkOptions;
Expand Down
20 changes: 20 additions & 0 deletions crates/wasi-tls/src/p3/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! Raw bindings to the `wasi:tls` package.

#[expect(missing_docs, reason = "generated code")]
mod generated {
wasmtime::component::bindgen!({
path: "src/p3/wit",
world: "wasi:tls/imports",
imports: {
"wasi:tls/client.[method]connector.receive": trappable | tracing | store,
"wasi:tls/client.[method]connector.send": trappable | tracing | store,
default: trappable | tracing
},
with: {
"wasi:tls/client.connector": crate::p3::Connector,
"wasi:tls/types.error": String,
},
});
}

pub use self::generated::wasi::*;
Loading