# WebTunnel Transport Mechanism in webtor-rs: A Deep Dive into the Three-Stage Pipeline

> Explore the WebTunnel transport mechanism in webtor-rs. Understand its three-stage pipeline: outer TLS, HTTP Upgrade, and inner TLS for secure Tor traffic encapsulation.

- Repository: [igor53627/webtor-rs](https://github.com/igor53627/webtor-rs)
- Tags: deep-dive
- Published: 2026-03-04

---

**The WebTunnel transport mechanism in webtor-rs is a pluggable transport that encapsulates Tor traffic within an HTTPS-like connection using a three-stage pipeline: outer TLS, HTTP Upgrade handshake, and inner TLS.**

The **webtor-rs** repository implements a Rust-based Tor client with support for pluggable transports designed to circumvent censorship. The **WebTunnel transport mechanism** hides Tor protocol signatures by tunneling them through what appears to be a standard WebSocket-upgraded HTTPS connection. This implementation resides primarily in [`webtor/src/webtunnel.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/webtunnel.rs) and provides a critical bridge for users operating behind restrictive firewalls.

## What Is the WebTunnel Transport Mechanism?

**WebTunnel** is a *pluggable transport* that disguises Tor traffic as regular HTTPS traffic. Unlike obfs4 or Snowflake, WebTunnel leverages the fact that WebSocket connections over HTTPS are rarely blocked by corporate or national firewalls because they resemble standard web traffic.

In **webtor-rs**, the transport is implemented as an async Rust module that creates a layered stream. The architecture follows a strict three-stage pipeline where each stage adds a layer of encapsulation or encryption. The final output is a `WebTunnelStream` that implements `AsyncRead`, `AsyncWrite`, and the `tor_rtcompat` traits required by the higher-level Tor protocol stack.

## Three-Stage Pipeline Architecture

The WebTunnel transport mechanism operates through three distinct stages. Each stage serves a specific purpose in obfuscating the Tor traffic and establishing a secure channel to the bridge.

### Stage 1: Outer TLS with tokio-rustls

The first stage establishes a standard TLS connection to the WebTunnel bridge. This creates the "outer" HTTPS layer that network observers see.

The implementation uses **tokio-rustls** (`tokio_rustls::TlsConnector`) to initiate the handshake. The connector is configured with a `rustls::ClientConfig` that includes the system root store (`webpki_roots::TLS_SERVER_ROOTS`). This ensures the bridge's certificate is validated against standard WebPKI authorities, making the connection appear identical to a regular browser HTTPS connection.

This outer TLS layer terminates at the WebTunnel bridge, which then forwards traffic to the actual Tor network.

### Stage 2: HTTP Upgrade Handshake (WebSocket-Style)

After the outer TLS handshake completes, the client performs an HTTP Upgrade request that mimics a WebSocket handshake. This step is crucial for bypassing firewalls that allow HTTP upgrades but block arbitrary TCP streams.

The client sends an HTTP `GET` request with specific headers:
- `Upgrade: websocket`
- `Connection: Upgrade`
- `Sec-WebSocket-Key: <base64-encoded-random-key>`

In [`webtor/src/webtunnel.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/webtunnel.rs), the **`WebTunnelBridge::connect`** method (lines 35-51) assembles this request string. The bridge responds with `101 Switching Protocols`, indicating the upgrade succeeded. The client verifies this response by reading until `\r\n\r\n` and checking for the "101" status code.

Once this handshake completes, the connection is technically an HTTP-upgraded tunnel, though the subsequent traffic is not actually WebSocket frames but raw TLS data.

### Stage 3: Inner TLS with Custom Certificate Verification

The final stage establishes a second TLS session *inside* the tunneled channel. This is the "inner" TLS that carries the actual Tor protocol traffic to the ORPort of a Tor relay.

Unlike the outer TLS, the inner layer uses **futures-rustls** and a custom certificate verifier. The **`TorCertVerifier`** (lines 47-66 in [`webtunnel.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtunnel.rs)) implements `ServerCertVerifier` and accepts any certificate presented by the server. This is necessary because Tor relays use self-signed certificates that are not part of the WebPKI.

The inner TLS runs over the outer TLS stream, which is wrapped with `tokio_util::compat::Compat` to bridge between tokio and futures async I/O traits. The resulting `WebTunnelStream` implements the `tor_rtcompat` traits required by the Tor protocol stack, allowing the higher-level client code to treat it as a standard transport.

## Implementation in webtor/src/webtunnel.rs

The core implementation resides in **[`webtor/src/webtunnel.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/webtunnel.rs)**, which defines the configuration, bridge connection logic, and stream types.

The **`WebTunnelConfig`** struct provides a builder pattern for specifying the bridge URL, fingerprint, timeout, and server name. This configuration is consumed by **`create_webtunnel_stream`**, the main entry point that orchestrates the three-stage pipeline.

The **`WebTunnelBridge::connect`** method (lines 80-124) handles the TCP connection, outer TLS handshake, and HTTP upgrade sequence. It returns a raw stream that has completed the WebSocket-style handshake but has not yet established the inner TLS.

The **`TorCertVerifier`** struct (lines 47-66) implements the custom certificate verification logic for the inner TLS layer. It bypasses standard X.509 validation to accommodate Tor's self-signed relay certificates.

Finally, **`WebTunnelStream`** wraps the completed inner TLS connection and implements the necessary async I/O traits, including `AsyncRead`, `AsyncWrite`, and the `tor_rtcompat` abstractions required by the Tor protocol implementation.

## Code Examples

### Creating a WebTunnel Stream

The following example demonstrates how to configure and instantiate a WebTunnel transport using the high-level API:

```rust
use webtor::webtunnel::{WebTunnelConfig, create_webtunnel_stream};

#[tokio::main]
async fn main() -> webtor::error::Result<()> {
    // Configure the bridge connection
    let config = WebTunnelConfig::new(
        "https://bridge.example.com/secret-path".to_string(),
        "0123456789ABCDEF0123456789ABCDEF01234567".to_string(),
    )
    .with_timeout(std::time::Duration::from_secs(30))
    .with_server_name("bridge.example.com".to_string());

    // Establish the three-stage pipeline
    let mut stream = create_webtunnel_stream(config).await?;

    // Use the stream with standard async I/O
    use tokio::io::AsyncWriteExt;
    stream.write_all(b"hello").await?;
    Ok(())
}

```

This example illustrates the builder-style `WebTunnelConfig` and the `create_webtunnel_stream` function that encapsulates the outer TLS, HTTP upgrade, and inner TLS handshake sequence.

### Accessing Peer Certificates

After establishing the connection, you can inspect the inner TLS certificate for debugging or logging purposes:

```rust
let cert_opt = stream.get_peer_certificate()?;
if let Some(der) = cert_opt {
    println!("Tor relay certificate length: {} bytes", der.len());
}

```

The `get_peer_certificate` method forwards the request to the inner TLS session, returning the raw DER-encoded certificate presented by the Tor relay. This corresponds to the `WebTunnelStream::get_peer_certificate` implementation at lines 31-40 of [`webtunnel.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtunnel.rs).

## Integration with the Tor Client Stack

The WebTunnel transport integrates seamlessly with the higher-level Tor client implementation in [`webtor/src/client.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/client.rs). The client selects pluggable transports based on bridge configuration, and the `WebTunnelStream` satisfies the `tor_rtcompat` traits required by the `tor_proto` crate.

Because `WebTunnelStream` implements `AsyncRead` and `AsyncWrite`, the Tor channel handshake and subsequent circuit construction proceed exactly as they would over a direct TCP connection. The three-stage encapsulation is completely transparent to the protocol logic, which treats the transport as an opaque byte stream.

This architecture allows the WebTunnel transport to function as a drop-in replacement for other transports like Snowflake (implemented in [`webtor/src/websocket.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/websocket.rs)) or WebRTC (in [`webtor/src/webrtc_stream.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/webrtc_stream.rs)), providing flexibility for users facing different censorship environments.

## Summary

- **WebTunnel** is a pluggable transport in **webtor-rs** that hides Tor traffic behind an HTTPS and WebSocket-like handshake to evade deep packet inspection.
- The implementation follows a **three-stage pipeline**: outer TLS (tokio-rustls with WebPKI), HTTP Upgrade (WebSocket-style handshake), and inner TLS (futures-rustls with custom certificate verification).
- The core logic resides in **[`webtor/src/webtunnel.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/webtunnel.rs)**, specifically within `WebTunnelBridge::connect` and the `TorCertVerifier` struct.
- **`WebTunnelStream`** implements the necessary async I/O traits to integrate seamlessly with the Tor protocol stack, making the encapsulation transparent to higher-level client code.

## Frequently Asked Questions

### What makes WebTunnel different from other pluggable transports in webtor-rs?

Unlike **Snowflake** (which uses WebRTC) or **obfs4** (which uses obfuscation), WebTunnel specifically mimics a standard HTTPS WebSocket upgrade. This approach targets networks that allow secure web browsing and WebSocket connections but block known Tor or obfuscation signatures. According to the implementation in [`webtor/src/webtunnel.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/webtunnel.rs), it uses standard HTTP headers like `Upgrade: websocket` and `Connection: Upgrade` to blend in with legitimate web traffic.

### How does the HTTP Upgrade step bypass corporate firewalls?

The HTTP Upgrade step exploits the fact that most corporate firewalls and proxies allow HTTP `Upgrade` requests to establish WebSocket connections for legitimate web applications. In [`webtor/src/webtunnel.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/webtunnel.rs), the `WebTunnelBridge::connect` method sends a `GET` request with `Sec-WebSocket-Key` and other standard headers, then waits for a `101 Switching Protocols` response. To network observers, this appears identical to a browser connecting to a real-time web service, allowing the subsequent Tor traffic to flow through the upgraded tunnel undetected.

### Why does WebTunnel use two separate TLS layers?

WebTunnel uses **two TLS layers** to solve different security challenges. The **outer TLS** (implemented with tokio-rustls) authenticates the WebTunnel bridge using standard WebPKI certificates, ensuring the client connects to a legitimate bridge and hiding the traffic in a normal HTTPS envelope. The **inner TLS** (implemented with futures-rustls and the custom `TorCertVerifier`) establishes the actual Tor protocol link to the relay using self-signed certificates. This separation allows the bridge to act as a dumb pipe while the Tor protocol handles its own authentication via CERTS cells, maintaining end-to-end security without requiring the bridge to possess Tor relay private keys.

### Where is the WebTunnel configuration defined in webtor-rs?

The WebTunnel configuration is defined in the **`WebTunnelConfig`** struct located in [`webtor/src/webtunnel.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/webtunnel.rs). This struct uses a builder pattern to specify the bridge URL, fingerprint, timeout duration, and server name. The configuration is consumed by the **`create_webtunnel_stream`** function, which orchestrates the three-stage connection pipeline. Users instantiate the transport by calling `WebTunnelConfig::new()` with the bridge address and fingerprint, then optionally chaining methods like `.with_timeout()` before passing the config to the stream creation function.