YUME explained
YUME is two programs. yume runs on your device and collects traffic from your
applications. yumed runs on a server you control. The traffic travels between
them inside one long-lived connection that looks like a browser visiting a
website. The server authenticates the client, checks what it may reach, makes
the real connection and sends the answer back the same way.
What each part does
- yume
- Collects traffic from applications and sends it to the server inside one long-lived connection that looks like a browser visiting a website.
src/runtime/native_client_runtime.cpp - Front door
- Serves ordinary HTTP/2 requests from the static site and promotes at most one stream per TLS connection after a fresh admission proof verifies.
src/providers/h2_web_front_door.cppsrc/providers/ytp1_h2_admission.cpp - YTP/1 session
- Both identities are proven with Ed25519 and ML-DSA-87, and the session keys mix X25519, ML-KEM-1024, the client's access PSK and the TLS connection.
src/engine/session_engine.cppsrc/providers/openssl_security_provider.cpp - Policy check
- Each stream names a service. yumed checks that this client may use it and that every address of the destination is allowed before it opens a socket.
src/runtime/native_credentials.cppsrc/runtime/native_egress_policy.cpp - Cover website
- The operator's own static site, loaded when yumed starts. There is no built-in fallback site.
src/providers/cover_site.cpp
Text version
+-----------------------+
| Application |
| any program |
+-----------+-----------+
|
v SOCKS5, forward or TUN
+-----------+-----------+
| yume |
| on your device |
+-----------+-----------+
|
v ==YUME==> TLS 1.3 + HTTP/2
+-----------+-----------+ +----------------+
| Front door +---------------->| Cover website |
| checks the proof | cover request | static pages |
+-----------+-----------+ +----------------+
|
v valid proof
+-----------+-----------+
| YTP/1 session |
| AUTH, then records |
+-----------+-----------+
|
v OPEN a stream
+-----------+-----------+
| Policy check |
| service, destination |
+-----------+-----------+
|
v allowed
+-----------+-----------+
| Destination |
| sees yumed's address |
+-----------------------+
This page describes the default programs, which speak YTP/1 (YUME Transport Protocol 1). The glossary defines the terms, and the YTP/1 reference has the exact wire format.
Setting up
yume-setup init writes a server directory and a client bundle. Each side gets
a composite identity, an Ed25519 key paired with an ML-DSA-87 key. The server
also gets an ML-KEM-1024 key, a TLS certificate and a 32-byte admission key.
Each client gets its own access PSK. The client bundle carries the server’s
public keys, so the client knows exactly which server it must reach.
add-client issues more bundles for the same server.
How a connection opens
yumeopens TCP to the server. A server name is looked up in a small helper process, so a hung DNS lookup never blocks shutdown.- It starts TLS 1.3 with the ClientHello of a pinned Chrome release and asks for HTTP/2, the way that browser would.
- Over HTTP/2 it first requests the site’s page with that browser’s headers, then sends an extended CONNECT request carrying an admission proof. The proof is derived from the admission key and bound to this TLS connection, so it cannot be replayed on another one.
yumedchecks the proof. Requests that fail admission stay in the cover handler, which serves GET and HEAD from the static site and refuses other methods. A valid proof turns that HTTP/2 stream into the YUME carrier. One TLS connection can be promoted once.- Inside the carrier both sides run YTP/1 authentication, described next.
What each part does
- yume
- Dials the server and opens it the way the pinned Chrome release would, then proves it holds the admission key before YTP/1 starts.
src/runtime/native_client_runtime.cppsrc/providers/h2_duplex_carrier.cppsrc/stealth/h2_carrier.cpp - yumed
- Its front door serves ordinary HTTP/2 cover requests and turns at most one stream per TLS connection into a carrier after checking a fresh admission proof.
src/runtime/native_server_runtime.cppsrc/providers/h2_web_front_door.cppsrc/providers/cover_site.cpp
Text version
+---------+ +---------+
| yume | | yumed |
| client | | server |
+---------+ +---------+
| |
| TLS 1.3, Chrome ClientHello, h2 |
|------------------------------------>|
| |
| GET the cover page and its assets |
|------------------------------------>|
| |
| the static site answers |
|<------------------------------------|
| |
| CONNECT websocket with the proof |
|------------------------------------>|
| |
| .--|
| check proof, reserve its nonce | |
| '->|
| |
| 200: this stream is the carrier |
|<------------------------------------|
| |
| AUTH challenge: identity, keys |
|<==============================YUME==|
| |
| AUTH response: PSK proof, keys |
|==YUME==============================>|
| |
| AUTH_RESULT: accepted |
|<==============================YUME==|
| |
| CAPABILITIES, sealed |
|<==============================YUME==|
| |
| CAPABILITIES, sealed |
|==YUME==============================>|
| |
| OPEN a service, then DATA |
|==YUME==============================>|
| |
Keys
Authentication proves both identities with both signature algorithms. Key establishment combines X25519, ML-KEM-1024, the client’s access PSK and a secret exported from the live TLS connection. Both signatures and every key-schedule input are required. The transcript binds the suite, both identities, both service lists and the security parameters. There is one suite and no negotiation or fallback.
After authentication application data is sealed with AES-256-GCM under a key that is used once per record. Each direction has its own key epoch. At the default size, it rotates after one MiB of data, 512 records or 500 ms, whichever comes first. A rotation starts halfway to those limits. The old epoch carries data while confirmation is pending, up to its limits. Further data then waits for the acknowledgement; a missed acknowledgement deadline ends the session. Both sides may allow a larger epoch, up to 64 MiB. The session then uses the smaller of the two sizes, still with one record per 2 KiB, so either side can keep rotation at its own setting.
What each part does
- Your data
- What the application sent: bytes of a stream, or one whole packet for UDP and TUN.
src/engine/session_engine.cpp - YTP/1 frame
- A 12-byte header names the record type, the stream and the payload length. Frames are what both ends of a session exchange.
src/ytp/protocol.hpp - Sealed record
- Each data frame has a one-use AES-256-GCM key bound to direction, epoch and sequence. The REKEY_ACK control frame instead uses a candidate-root confirmation MAC inside TLS.
src/providers/openssl_security_provider.cppsrc/engine/session_engine.cpp - Carrier envelope
- Keeps each sealed record whole across partial reads and however WebSocket splits or joins messages.
src/providers/h2_duplex_carrier.cpp - WebSocket
- The carrier speaks ordinary WebSocket, with client frames masked as a browser masks them.
src/stealth/websocket_codec.cpp - HTTP/2
- One RFC 8441 CONNECT stream on a connection that has already loaded the cover site like a browser.
src/stealth/h2_carrier.cpp - TLS 1.3
- The outer encryption, opened with the ClientHello of the pinned Chrome release. An observer sees this layer and its sizes and timing.
src/providers/tls13_secure_channel.cppsrc/stealth/tls_client_profile.cpp - TCP
- One connection to the server's address, which anyone on the path can see.
src/providers/asio_tcp_byte_channel_provider.cpp
Text version
+-----------------------------------------------------------------+ | TCP to the server's port | | +-------------------------------------------------------------+ | | | TLS 1.3 Chrome ClientHello | | | | +---------------------------------------------------------+ | | | | | HTTP/2 extended CONNECT stream | | | | | | +-----------------------------------------------------+ | | | | | | | WebSocket binary messages | | | | | | | | +-------------------------------------------------+ | | | | | | | | | Carrier envelope 12-byte length header | | | | | | | | | | +---------------------------------------------+ | | | | | | | | | | | Sealed record AES-256-GCM, one-use key | | | | | | | | | | | | +-----------------------------------------+ | | | | | | | | | | | | | YTP/1 frame type, stream, length | | | | | | | | | | | | | | +-------------------------------------+ | | | | | | | | | | | | | | | Your data stream bytes or a packet | | | | | | | | | | | | | | | +-------------------------------------+ | | | | | | | | | | | | | +-----------------------------------------+ | | | | | | | | | | | +---------------------------------------------+ | | | | | | | | | +-------------------------------------------------+ | | | | | | | +-----------------------------------------------------+ | | | | | +---------------------------------------------------------+ | | | +-------------------------------------------------------------+ | +-----------------------------------------------------------------+
What each part does
- Sender
- Starts rotation at half the epoch's byte, record or 500 ms lifetime limit. Later data waits if the old epoch runs out.
src/engine/session_engine.cpp - Receiver
- Checks the old-root proof, contributes fresh hybrid material and confirms the candidate new root. A malformed or late exchange ends the session.
src/providers/openssl_security_provider.cppsrc/providers/ytp1_crypto.cpp
Text version
+--------------------+ +--------------------+
| Sender | | Receiver |
| sending direction | | receive direction |
+--------------------+ +--------------------+
| |
|--. |
| | fresh hybrid material |
|<-' |
| |
| REKEY_INIT, old-root seal |
|==YUME=========================>|
| |
| .--|
| check INIT, derive root | |
| '->|
| |
| REKEY_ACK, confirm new root |
|<=========================YUME==|
| |
|--. |
| | verify ACK, commit root |
|<-' |
| |
| first new-epoch record |
|==YUME=========================>|
| |
| .--|
| open record, wipe old root | |
| '->|
| |
Streams and policy
The client opens streams by service name, tcp or udp in a default kit, and
names a destination when the service routes to one. Stream numbers are 31-bit,
odd for the client and even for the server. Every stream and the connection as
a whole have flow credit, so a slow reader cannot make the other side buffer
without limit. UDP and TUN traffic travel as whole packets.
For each open, yumed checks that the client’s identity is granted the
service, that the destination is inside the adapter’s configured networks, and
that every address a destination name resolves to is allowed, before it opens a
socket. A refusal closes that stream only. The operator lists clients in
authorized-keys.json, can limit how many sessions each may hold, and applies
changes with a reload that ends revoked clients’ sessions.
If the connection drops, yume reconnects with backoff and its SOCKS5
listener and TUN routes stay in place.
Embedding
Applications can embed the same endpoint through the C ABI: named byte streams, packet channels and routed TCP/UDP, with the application deciding where traffic comes from.
What stays visible
| Who | Still sees |
|---|---|
| A network observer | The server’s IP address, the TLS ClientHello including the site name, and the timing, sizes, duration and volume of encrypted traffic |
| The server operator | Authenticated client identities, requested destinations, and any bytes the application did not encrypt itself |
| The hosting provider | The server’s outbound connections |
| The destination | The server’s address and the application’s own traffic |
The direct route uses one yumed that ends the tunnel. TCP circuits can use
two or three servers inside one operator’s cluster; the exit ends that route,
and the operator can correlate its hops. Across-operator transit is planned.
Use TLS inside the tunnel,
as browsers do, to keep content private from the server. The server’s address
stays visible, so blocking that address blocks its tunnel. The
threat model and stealth transport go into
detail.
Where to start reading
| Question | Start at |
|---|---|
| How do the programs start? | src/runtime/yume_main.cpp, src/runtime/native_cli.cpp |
| How does a client keep its session? | src/runtime/native_client_runtime.cpp, src/runtime/native_endpoint.cpp |
| How are streams, records and keys handled? | src/engine/session_engine.cpp, src/providers/openssl_security_provider.cpp |
| What does the server show a visitor? | src/providers/h2_web_front_door.cpp, src/providers/cover_site.cpp |
| How does the TLS look like Chrome? | src/providers/tls13_secure_channel.cpp, transport profiles |
| Which folder owns what? | Source map |
| What works today? | Implementation status |