Deep dive · WebTransport

A browser talking straight to a device — no CA, no DNS.

How serverCertificateHashes lets a web page open a secure WebTransport connection directly to a device that has no certificate authority and no public hostname — by trusting a certificate by its fingerprint instead of by a chain of authorities.

The problem

A browser can't open a raw socket. To talk to something directly it has essentially one modern tool: WebTransport — QUIC (HTTP/3) exposed to JavaScript. But WebTransport is still TLS, and TLS normally means the server presents a certificate signed by a certificate authority for a public hostname.

A camera on your desk has neither. No public DNS name, no Let's Encrypt cert — just an IP on your LAN. Under the normal rules, a browser refuses to connect. So how do you let a page talk directly to a device you physically own?

The mechanism: pin the certificate by its hash

WebTransport has an escape hatch built for exactly this. When you construct the connection you can pass serverCertificateHashes — the SHA-256 of the certificate you expect. The browser then skips the certificate-authority check entirely and instead accepts the connection only if the server's certificate hashes to the value you pinned.

const wt = new WebTransport("https://10.0.0.133:4433/", {
  serverCertificateHashes: [{
    algorithm: "sha-256",
    value: /* 32-byte SHA-256 of the device's self-signed cert */,
  }],
});

Trust is transferred out of band: someone hands the browser the hash — a URL query param, a QR code, a config push. It's the WebTransport equivalent of an SSH known-host or a certificate pin. No authority in the middle vouches for anything; you pinned the exact certificate, so the identity is the fingerprint.

The rules the browser enforces

A hash-pinned certificate can't be revoked, so the spec caps its blast radius with three constraints — all of which the device's self-signed cert must satisfy:

Trust, not reachability. The hash solves "is this the right server?" without a CA. It does nothing about NAT. A browser's WebTransport can only dial a URL — it has no hole-punching — so the device still has to be reachable: same LAN, a public IP, or a forwarded port. On a LAN that's free; behind NAT you bridge instead (more below).

How viroh uses it

Run a sender with the WebTransport door open and it mints a fresh self-signed identity on startup and prints its hash:

# a camera on the LAN at 10.0.0.133
viroh-sender --wt --san 10.0.0.133 --wt-port 4433

  [wt]   certhash: Y1QU5PTQVntMiQmD82MeP82L8spyzaJd84XNYY+Ae3M=
  [wt]   player  : https://viroh.net/wt?url=https://10.0.0.133:4433&hash=Y1QU...

Open that player URL in Chrome on the same network and the page connects straight to the device, pins the hash, and plays live video. No relay, no gateway, no cloud — the pixels go device → browser and nothing else sees them.

The nice part: one-click, and the fleet is just a bulletin board

Typing hashes by hand doesn't scale. So a viroh sender can register itself with a fleet dashboard, publishing its URL and its current cert hash. The dashboard then shows a watch (wt-direct) button:

1

The device announces itself

The sender POSTs { url, hash } to the fleet every 20 seconds.

2

You click "watch (wt-direct)"

The fleet hands your browser the address + hash — and then gets out of the way.

3

Browser ↔ device, peer-to-peer

The video connection is direct. The fleet, relay, and directory never touch the pixels.

Two things fall out of this for free. Because the cert is only valid ~14 days but the sender re-registers every 20 seconds, rotation is automatic — restart the sender, it re-mints, and the fresh hash flows on the next heartbeat. And because the browser and the device share the network, the video never leaves the premises. The fleet is a rendezvous board, not a middleman.

When the device is behind NAT

WebTransport can't hole-punch, so direct only works when the browser can reach the device. For a camera behind NAT with a viewer on the open internet, viroh puts an iroh node on the device (which can traverse NAT — reachable by public key, no port forwarding) and a small public bridge that terminates the browser's WebTransport and relays over iroh. Same pixels, one hop added only where the network forces it.

For the curious: two silent gotchas

Getting live media through WebTransport surfaced two failures that both showed up as the same opaque Chrome error and had nothing to do with the certificate:

Both are written up in full in the technical note on GitHub.

Try it

viroh is open source and pure Rust. Point a sender at your LAN, open the player, and watch a device talk straight to your browser: github.com/erikherz/viroh.