How webtor-rs Handles TLS Connections in the Browser: Wasm-Compatible Encryption
webtor-rs delegates TLS encryption to the browser's native networking stack via the subtle-tls crate, using conditional compilation to exclude native TLS implementations when targeting wasm32.
webtor-rs is a Rust implementation of Tor networking designed to compile to WebAssembly for browser environments. When running in the browser, the library cannot rely on native TLS libraries due to WebAssembly sandbox constraints, so it leverages the subtle-tls crate to perform TLS 1.2 and 1.3 handshakes through the Web Crypto API.
Why Native TLS Fails in WebAssembly
Standard Rust TLS implementations depend on system-level cryptography libraries and std::time::Instant, which are unavailable in browser contexts. The webtor-rs codebase explicitly excludes native TLS code from WebAssembly builds using conditional compilation attributes.
In webtor/src/tls.rs, the entire native TLS implementation is gated behind #[cfg(not(target_arch = "wasm32"))]. This ensures that when compiling for wasm32-unknown-unknown, the compiler skips this file entirely, preventing linker errors and runtime incompatibilities.
The Wasm-Specific TLS Architecture
Conditional Compilation Strategy
The codebase uses Rust's conditional compilation to maintain dual TLS implementations. For native targets, webtor/src/tls.rs provides platform-specific encryption. For WebAssembly targets, the library routes all TLS operations through webtor/src/http.rs, which implements browser-compatible encryption layers.
The WasmTlsStream Abstraction
At the heart of the browser TLS implementation lies the WasmTlsStream trait, defined in webtor/src/http.rs at lines 91-98:
trait WasmTlsStream {
async fn tls_write(&mut self, buf: &[u8]) -> std::io::Result<usize>;
async fn tls_flush(&mut self) -> std::io::Result<()>;
async fn tls_read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>;
}
This trait abstracts the asynchronous read, write, and flush operations required by TLS streams, providing a uniform interface regardless of the underlying TLS version.
subtle-tls Integration
The same file provides concrete implementations of WasmTlsStream for the subtle-tls crate's stream types. For TLS 1.3 connections, lines 102-113 implement the trait for subtle_tls::TlsStream<S>:
// Implementation for subtle_tls::TlsStream<S> (TLS 1.3)
impl<S> WasmTlsStream for subtle_tls::TlsStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
async fn tls_write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write(buf).await
}
async fn tls_flush(&mut self) -> std::io::Result<()> {
self.flush().await
}
async fn tls_read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.read(buf).await
}
}
For legacy TLS 1.2 support, lines 118-130 provide an identical implementation for subtle_tls::TlsStream12<S>. Both implementations forward directly to the underlying async methods of the subtle-tls stream, which handles the cryptographic operations using the browser's Web Crypto API.
Executing HTTPS Requests in the Browser
The Request Flow
When an HTTPS request originates from a WebAssembly build, the HttpClient::request method creates a TLS connection using subtle_tls::TlsConnector::connect. The resulting stream passes to execute_http_request_wasm, defined at lines 336-378 in webtor/src/http.rs:
async fn execute_http_request_wasm<S: WasmTlsStream>(
stream: &mut S,
request: &Request,
) -> Result<Response, Error> {
// Write request headers and body
let data = format_request(request);
stream.tls_write(data.as_bytes()).await?;
stream.tls_flush().await?;
// Read response with size limits
let mut buf = vec![0u8; 8192];
let n = stream.tls_read(&mut buf).await?;
parse_response(&buf[..n])
}
For TLS 1.2 connections, the code path diverges at line 244 to execute_http_request_wasm_tls12, maintaining separate handling for legacy protocol versions while using the same WasmTlsStream abstraction.
Integration with Browser Primitives
Under the hood, subtle-tls establishes the TLS session over browser-native transports. The crate performs the handshake using the Web Crypto API for cryptographic operations, while the actual network traffic flows through the browser's fetch or WebSocket implementations. This approach avoids the std::time::Instant limitation prevalent in WebAssembly environments.
Practical Implementation Examples
Creating an HTTPS request from a wasm-compiled webtor client requires no special syntax—the library handles TLS transparently:
let client = HttpClient::new(/* Tor config */).await?;
let response = client.get("https://example.com").await?;
println!("Status: {}", response.status);
println!("Body ({} bytes)", response.body.len());
The underlying process executes these steps:
- Resolve the host and open a TCP-like connection via browser
WebSocketorfetchprimitives - Call
subtle_tls::TlsConnector::connectto establish a TLS 1.3 (or 1.2) session using browser cryptography - Wrap the resulting
subtle_tls::TlsStreamas aWasmTlsStream - Use
execute_http_request_wasmto transmit the HTTP request and read the response
For advanced use cases requiring manual stream control:
let connector = subtle_tls::TlsConnector::new().await?;
let mut tls_stream = connector
.connect("example.com".parse().unwrap(), WebSocket::new("wss://example.com").await?)
.await?;
tls_stream.write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n").await?;
tls_stream.flush().await?;
let mut buf = vec![0u8; 8192];
let n = tls_stream.read(&mut buf).await?;
println!("{}", std::str::from_utf8(&buf[..n])?);
This example mirrors the internal steps of execute_http_request_wasm while exposing the raw subtle_tls API for custom protocols.
Key Source Files and Their Roles
Understanding the browser TLS implementation requires familiarity with these specific files in the igor53627/webtor-rs repository:
webtor/src/http.rs– Defines theWasmTlsStreamtrait, implements it for subtle-tls types, and drives TLS-enabled HTTP requests in wasm environmentswebtor/src/tls.rs– Contains the native-only TLS implementation, excluded from browser builds via conditional compilationsubtle-tls/src/lib.rs– Core crate providingTlsConnector,TlsStream, and Web-Crypto-based TLS for WebAssemblysubtle-tls/tests/tls12_browser_tests.rs– Test suite confirming subtle-tls functionality in browser environmentswebtor/src/snowflake_ws.rs– Demonstrates additional subtle-tls usage for WebSocket-based Snowflake bridges
Summary
- webtor-rs uses conditional compilation to exclude native TLS code in wasm32 targets, routing browser builds through
webtor/src/http.rsinstead ofwebtor/src/tls.rs - The
WasmTlsStreamtrait abstracts TLS operations for browser environments, with implementations for both TLS 1.3 and TLS 1.2 via the subtle-tls crate - HTTPS requests execute through
execute_http_request_wasmandexecute_http_request_wasm_tls12, which handle request serialization and response parsing over encrypted streams - Cryptographic operations rely on the Web Crypto API rather than native system libraries, ensuring compatibility with WebAssembly sandbox constraints
- The subtle-tls crate performs handshakes while browser networking primitives (
fetch/WebSocket) handle the underlying transport layer
Frequently Asked Questions
What prevents webtor-rs from using rustls or native-tls in the browser?
WebAssembly running in browsers lacks access to system-level sockets and native threading primitives. Additionally, std::time::Instant is unavailable in wasm32-unknown-unknown targets, breaking most native TLS implementations. The webtor-rs codebase explicitly excludes these via #[cfg(not(target_arch = "wasm32"))] attributes in webtor/src/tls.rs.
How does subtle-tls differ from traditional Rust TLS libraries?
subtle-tls builds TLS 1.2 and 1.3 sessions on top of the browser's Web Crypto API rather than interfacing with operating system cryptographic libraries. It delegates network I/O to browser fetch and WebSocket APIs while maintaining the standard async read/write interface exposed by the WasmTlsStream trait in webtor/src/http.rs.
Can webtor-rs negotiate both TLS 1.2 and 1.3 in browser environments?
Yes. The codebase provides separate implementations of WasmTlsStream for both subtle_tls::TlsStream (TLS 1.3) at lines 102-113 and subtle_tls::TlsStream12 (TLS 1.2) at lines 118-130 of webtor/src/http.rs. The HttpClient automatically selects the appropriate version during the handshake process.
Where does the actual TLS handshake occur when using webtor-rs in a browser?
The handshake occurs within the subtle-tls crate's TlsConnector::connect method, which utilizes the Web Crypto API for cryptographic operations. However, the underlying TCP-like connection and network transport are handled by the browser's native fetch or WebSocket implementation, as seen in webtor/src/snowflake_ws.rs and the HTTP client code paths.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →