# How webtor-rs Uses Browser SubtleCrypto APIs for TLS: A Deep Dive into SubtleTLS

> Discover how webtor-rs leverages browser SubtleCrypto APIs with SubtleTLS to achieve TLS 1.3 in WebAssembly, eliminating native dependencies.

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

---

**webtor-rs delegates all cryptographic operations to the browser's native SubtleCrypto API through SubtleTLS, enabling TLS 1.3 in WebAssembly without native dependencies.**

webtor-rs is a full Tor client implementation that runs both natively and in the browser via WebAssembly. When targeting `wasm32-unknown-unknown`, the project replaces the standard `rustls` dependency with **SubtleTLS**, a custom TLS implementation that uses browser SubtleCrypto APIs for TLS to perform every cryptographic operation. This design allows the Tor client to establish secure HTTPS connections in browsers without requiring native libraries like `ring` or BoringSSL.

## Compile-Time Transport Selection

The integration begins in [`webtor/src/http.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/http.rs), where conditional compilation selects the appropriate TLS backend. For native targets, the code uses `rustls`, but the `#[cfg(target_arch = "wasm32")]` block instantiates a `TlsConnector` from the `subtle-tls` crate.

In [`webtor/src/http.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/http.rs) lines 199-210, the WASM-specific branch configures the connector:

```rust
#[cfg(target_arch = "wasm32")]
{
    let connector = subtle_tls::TlsConnector::with_config(
        subtle_tls::TlsConfig {
            skip_verification: false,
            alpn_protocols: vec!["http/1.1".into()],
            version: subtle_tls::TlsVersion::Tls13,
        }
    );
    // Handshake and stream creation follow...
}

```

This compile-time switch ensures that the same high-level HTTP client API works across platforms while using browser-native cryptography in WASM builds.

## SubtleTLS Architecture and SubtleCrypto Integration

SubtleTLS is structured as a layered TLS implementation where the protocol state machine runs in Rust, but every cryptographic primitive is delegated to the browser's `window.crypto.subtle` object.

### Public API and Configuration

The main entry points are defined in [`subtle-tls/src/lib.rs`](https://github.com/igor53627/webtor-rs/blob/main/subtle-tls/src/lib.rs) lines 52-71. The `TlsConnector` struct accepts a `TlsConfig` that specifies the TLS version, ALPN protocols, and verification settings. When `TlsConnector::connect` is called (lines 62-70), it creates a `TlsStream` that will drive the handshake.

### Cryptographic Primitive Delegation

The bridge to the browser's SubtleCrypto API lives in [`subtle-tls/src/crypto.rs`](https://github.com/igor53627/webtor-rs/blob/main/subtle-tls/src/crypto.rs) lines 21-90. This module provides Rust wrappers around JavaScript cryptographic operations:

- **`get_subtle_crypto()`** obtains the `SubtleCrypto` object from `globalThis.crypto.subtle`
- **`EcdhKeyPair::generate()`** (lines 65-88) uses `subtle.generateKey` to create P-256 ECDH key pairs
- **`derive_shared_secret`** calls `subtle.deriveBits` to compute the ECDH shared secret
- **AES-GCM operations** use `subtle.encrypt` and `subtle.decrypt` for record layer protection

### Handshake State Machine

The TLS 1.3 handshake logic resides in [`subtle-tls/src/stream.rs`](https://github.com/igor53627/webtor-rs/blob/main/subtle-tls/src/stream.rs) lines 52-78. This code constructs the ClientHello, processes the ServerHello, and handles EncryptedExtensions and Certificate messages. Throughout this process, it calls into the crypto module for ECDH key generation and shared secret derivation.

### Certificate Verification

X.509 certificate parsing and signature verification occur in [`subtle-tls/src/cert.rs`](https://github.com/igor53627/webtor-rs/blob/main/subtle-tls/src/cert.rs) lines 4-15. The code uses the `x509-parser` crate to parse certificates, then verifies signatures by calling `subtle.verify` through the crypto wrapper, ensuring browser-compatible certificate validation without native crypto libraries.

## The TLS Handshake Workflow with SubtleCrypto

When a WASM-compiled webtor client makes an HTTPS request, the following sequence occurs:

1. **Transport Selection**: `HttpClient::request` detects HTTPS and enters the `#[cfg(target_arch = "wasm32")]` branch in [`webtor/src/http.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/http.rs), creating a `TlsConnector` with `TlsVersion::Tls13`.

2. **Key Generation**: The `TlsStream::connect` method generates an ECDH key pair by calling `EcdhKeyPair::generate()`, which invokes `subtle.generateKey` in the browser.

3. **Shared Secret Derivation**: Upon receiving the server's key share, the client calls `derive_shared_secret`, which uses `subtle.deriveBits` to compute the ECDH premaster secret.

4. **Key Schedule**: The handshake code derives TLS 1.3 traffic keys using HKDF-SHA256 implemented in Rust, feeding in the SubtleCrypto-derived shared secret.

5. **Record Encryption**: The `RecordLayer` encrypts application data using AES-GCM via `subtle.encrypt`, and decrypts server responses via `subtle.decrypt`.

6. **Certificate Validation**: Server certificates are parsed with `x509-parser` and signatures verified using `subtle.verify` before the handshake completes.

This workflow allows the Tor client to establish secure connections using only browser-native cryptographic primitives, eliminating the need for `ring` or other native libraries in WASM builds.

## Practical Code Examples

### Example 1: HTTPS Request via webtor Client

The following example demonstrates making an HTTPS request from a WASM-compiled webtor client. The SubtleCrypto integration happens automatically when targeting `wasm32-unknown-unknown`.

```rust
// In a WASM environment (e.g., Vite demo)
use webtor::client::TorClient;
use webtor::http::{HttpClient, HttpRequest};

#[wasm_bindgen(start)]
pub async fn start() -> Result<(), JsValue> {
    // Build a Tor client (default configuration)
    let tor = TorClient::new().await?;
    let http = HttpClient::new(&tor)?;

    // Perform an HTTPS GET – SubtleCrypto is used automatically
    let resp = http.get("https://example.com/").await?;
    web_sys::console::log_1(&format!("Status: {}", resp.status).into());

    Ok(())
}

```

The `http.get` call resolves to the WASM TLS branch in [`webtor/src/http.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/http.rs) lines 199-210, which instantiates the SubtleTLS connector.

### Example 2: Direct SubtleTLS Usage with Custom Transport

For applications requiring direct TLS over a custom stream (such as a WebSocket), SubtleTLS can be used independently of the high-level HTTP client.

```rust
use subtle_tls::{TlsConnector, TlsConfig, TlsVersion};
use futures::io::{AsyncReadExt, AsyncWriteExt};

// `stream` could be a WebSocket or any async transport that works in WASM
async fn tls_over_stream<S>(mut stream: S, host: &str) -> Result<(), Box<dyn std::error::Error>>
where
    S: futures::io::AsyncRead + futures::io::AsyncWrite + Unpin,
{
    // Configure the connector (TLS 1.3, ALPN set to HTTP/1.1)
    let config = TlsConfig {
        skip_verification: false,
        alpn_protocols: vec!["http/1.1".into()],
        version: TlsVersion::Tls13,
    };
    let connector = TlsConnector::with_config(config);

    // Perform the handshake – all crypto goes through SubtleCrypto
    let mut tls = connector.connect(stream, host).await?;

    // Send a simple HTTP request over the encrypted channel
    tls.write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
        .await?;
    tls.flush().await?;

    // Read the response
    let mut buf = vec![0u8; 4096];
    let n = tls.read(&mut buf).await?;
    web_sys::console::log_1(&format!("Received {} bytes", n).into());

    Ok(())
}

```

The `TlsConnector::connect` method creates a `TlsStream` that internally delegates to SubtleCrypto, as implemented in [`subtle-tls/src/lib.rs`](https://github.com/igor53627/webtor-rs/blob/main/subtle-tls/src/lib.rs) lines 62-70.

### Example 3: Low-Level ECDH Key Generation

The following example shows the direct use of the cryptographic wrapper to generate an ECDH key pair using the browser's SubtleCrypto API.

```rust
use subtle_tls::crypto::EcdhKeyPair;

async fn demo_ecdh() -> Result<(), Box<dyn std::error::Error>> {
    // This function runs entirely inside the browser's SubtleCrypto layer
    let keypair = EcdhKeyPair::generate().await?;
    web_sys::console::log_1(
        &format!("Public key (hex): {}", hex::encode(&keypair.public_key_bytes)).into(),
    );
    Ok(())
}

```

The generation logic is implemented in [`subtle-tls/src/crypto.rs`](https://github.com/igor53627/webtor-rs/blob/main/subtle-tls/src/crypto.rs) lines 65-88, which wraps `subtle.generateKey` and handles the JavaScript interop.

## Summary

- **webtor-rs** uses conditional compilation in [`webtor/src/http.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/http.rs) to select SubtleTLS for WASM targets while retaining `rustls` for native builds.
- **SubtleTLS** implements a full TLS 1.3 client stack in Rust, but delegates every cryptographic primitive to the browser's **SubtleCrypto API**.
- Key operations including ECDH key generation, shared secret derivation, AES-GCM encryption, and X.509 signature verification are performed via `window.crypto.subtle`.
- This architecture allows the Tor client to run in any modern browser without native dependencies, leveraging FIPS-compliant browser cryptography for secure connections.

## Frequently Asked Questions

### How does webtor-rs handle TLS when compiled to WebAssembly?

When targeting `wasm32-unknown-unknown`, webtor-rs replaces the native `rustls` crate with SubtleTLS. In [`webtor/src/http.rs`](https://github.com/igor53627/webtor-rs/blob/main/webtor/src/http.rs) lines 199-210, the code detects the WASM architecture and instantiates a `TlsConnector` from the `subtle-tls` crate, which uses the browser's SubtleCrypto API for all cryptographic operations.

### What specific SubtleCrypto operations does SubtleTLS use?

SubtleTLS delegates the following operations to `window.crypto.subtle`: ECDH key pair generation via `generateKey`, shared secret derivation via `deriveBits`, AES-GCM encryption/decryption via `encrypt` and `decrypt`, and X.509 signature verification via `verify`. These are implemented in [`subtle-tls/src/crypto.rs`](https://github.com/igor53627/webtor-rs/blob/main/subtle-tls/src/crypto.rs) lines 21-90.

### Is SubtleTLS limited to TLS 1.3?

No, SubtleTLS supports both TLS 1.2 and TLS 1.3. The version is configurable via `TlsConfig` when creating the `TlsConnector`. However, the implementation in [`subtle-tls/src/stream.rs`](https://github.com/igor53627/webtor-rs/blob/main/subtle-tls/src/stream.rs) primarily focuses on the TLS 1.3 handshake state machine for modern browser compatibility.

### Can SubtleTLS be used independently of the Tor client?

Yes, SubtleTLS is a standalone crate within the webtor-rs repository. You can use `TlsConnector` and `TlsStream` directly with any async transport that implements `AsyncRead` and `AsyncWrite`, as demonstrated in the direct usage example above. This makes it suitable for any Rust project targeting WebAssembly that requires TLS connections.