DEV Community

gosunuts
gosunuts

Posted on

End-to-end TLS for public-domain tunnels, without trusting the relay

TL;DR

I built portal-tunnel, expose a local server with a public HTTPS URL, without trusting the relay.

It combines SNI passthrough, relay-backed keyless signing, and a built‑in TLS self‑probe that detects relay‑side TLS termination.

Overview

On the web, it is surprisingly hard to get both public-domain convenience and strong end-to-end security at the same time. This is because serving a public domain requires someone to terminate TLS for that domain, which typically pulls that party into your trust boundary.

This trade-off becomes more significant in a permissionless relay network, where relays are run by untrusted parties. Portal is designed around a different goal: to preserve the familiar “open a URL in a browser” experience, while avoiding giving the relay full visibility into tenant traffic.

Problem

Most tunnel systems solve this trade-off in one of three ways.

1. Terminate TLS at the relay ( Ngrok, Cloudflare Tunnel )

This is the most common design, used by many hosted tunnel and edge platforms. It gives you clean public domains, simple setup, and convenient Layer 7 features such as caching, WAF, and HTTP-aware routing. But the cost is that the relay can see plaintext traffic, controls certificate private keys, and must be trusted as an active part of the security model.

2. Use a tunnel protocol ( Tailscale, Openziti )

Protocols such as SSH or WireGuard preserve strong end-to-end encryption, but they change the access model in practice, this usually means requiring a custom client, losing direct browser access, or making domain-based HTTPS exposure awkward. Keep strong encryption, but move away from the standard web model.

3. Use a QUIC-native design ( Webtransport based )

QUIC provides built-in encryption and modern transport behavior, but it does not solve the relay trust problem by itself. SNI is still exposed during the handshake, browser integration is narrower than plain HTTPS, UDP may be blocked in some environments, and operational complexity increases. QUIC can improve transport, but it does not automatically remove the relay from the trust boundary.

In practice, you either trust the relay or give up the standard web model.

How Portal approaches differently

Portal moves tenant TLS termination back to the client side, using:

  • SNI passthrough – relay routes by SNI only, forwards raw ciphertext.
  • Keyless signing – relay signs certificates without holding private keys in data path.
  • Built‑in self‑mitm-probe – detects if relay actually terminates TLS.

These pieces are designed to work together. SNI passthrough enables routing without TLS termination, keyless signing enables public domains without exposing private keys, and the self-mitm-probe verifies that this behavior is actually preserved in practice.

In the normal data path, the flow looks like this:​

  1. The relay accepts a public TLS connection and inspects only the ClientHello information needed for SNI-based routing.
  2. The SNI value is used to select the correct reverse session.
  3. The relay forwards the encrypted connection as raw bytes over that reverse session.
  4. The SDK or tunnel endpoint acts as the TLS server and terminates tenant TLS locally.
  5. For relay-backed domains, the SDK requests certificate signatures through /v1/sign, using the relay as a keyless signing oracle.
  6. TLS session keys are derived only on the SDK side.
  7. After the handshake, the relay continues forwarding ciphertext without access to tenant plaintext or session keys.

The result is a model that aims to preserve several properties at once:

  • no tenant plaintext at the relay
  • no tenant session keys at the relay
  • direct browser access over standard HTTPS
  • public-domain routing without full relay-side TLS termination

Detecting relay‑side TLS termination (MITM)

Of course, this design only works if the relay actually preserves passthrough.

That is why Portal does not simply assume relay honesty. In addition to the normal forwarding path, the SDK performs a built-in self-probe to detect and ban suspected relay-side TLS termination.
The probe works like this:

  1. The client connects to its own public URL through the relay.
  2. On the public-facing client side, it extracts TLS exporter material from the connection.
  3. On the SDK side, where tenant TLS terminates locally, it reads the corresponding exporter material again.
  4. If the two values match, the connection was observed as TLS passthrough.
  5. If they differ, Portal treats that as suspected relay-side TLS termination. This shifts the model from implicit trust to observable guarantees about the data path.

Limitations and future work

This doesn’t eliminate trust completely:

  • Relay still signs certificates (not a pure packet forwarder).
  • Self‑probe cannot prove all connections were passthrough.
  • Selective/adaptive MITM remains the hardest problem.

So the point of Portal is not to claim that trust disappears. The goal is narrower and more practical: reduce unnecessary trust in the normal path, make violations more detectable, and create room for stronger relay verification models over time.

That is also why Portal’s long-term direction is not “trust one relay forever,” but to move toward a model with multiple relays, discovery, and stronger verification (such as witness-based checks), enabling better trust assumptions in a permissionless network.

Feedback, especially on the MITM detection model and its limits, is very welcome.

GitHub: https://github.com/gosuda/portal

Demo domain: https://portal.thumbgo.kr

Top comments (0)