Reference Design · HTTP-Free Transport
Point-to-point, mutually authenticated, over a single TCP port. CESR on the wire, ACDCs as payloads, KERI for identity, authentication, and key rotation. When the goal is to get HTTP/HTTPS out of the stack — by design, or because 80/443 is blocked at the firewall — this is how you run your own strictly-framed, cryptographically-attributable channel instead.
It is easy to oversell, and the design decisions depend on it. Blocking HTTP/S and running your own signed channel does not make payloads inspectable by the firewall — an encrypted or signed custom channel is exactly as opaque to deep-packet inspection as TLS is. You are not making threats detectable at the network. You are relocating inspection to your endpoint.
One port, known peers, default-deny — instead of the permissive HTTP attack surface. The firewall's job shrinks to a single question: is this the one port, from a known peer?
No headers, no body, no content-type. Header injection, content-type confusion, request smuggling and TLS-tunnelled exfil simply do not exist as constructs — there is nowhere to put them.
Every frame is attributable to a specific KERI AID. Anything unauthenticated or off-schema is dropped before it reaches application logic — validation moves from generic DPI to your endpoint checking typed, signed frames.
That is a strong posture. It is an endpoint-side posture, not a firewall-side one. Say so.
The channel is a single TCP stream on the one allowed port. No TLS record layer is needed — CESR is self-framing. Everything above the socket is comparatively easy bookkeeping; the framing layer is where the anti-stuffing property lives.
Pick CESR/KERI v1 or v2 and commit — the version-string format and the count-code tables differ. Do not hardcode code assignments from memory; drive your tables directly from the CESR spec tables. This is the one area where recall is not trustworthy.
Everything else sits on top of this and is easy bookkeeping. Get the parser wrong and you desync the stream; get it right and the rest follows. It must be self-framing and restartable on partial reads — TCP hands you arbitrary byte chunks, so at each frame boundary the parser must be able to say "need more bytes" and consume nothing, or a split read corrupts state.
At each frame boundary, sniff the first byte's high bits — the tritet — to decide what you hold: a serialised KERI event (JSON begins with {; CBOR and MGPK have their own leads), a CESR text primitive, or a CESR binary group. That dispatch is the cold start. Never parse until you can prove you hold a complete unit.
v field embeds the serialisation kind and the byte lengthloop:
sniff first byte (tritet) at frame boundary
read event: extract size from version string -> take exactly that many bytes
read attachment group(s): parse count code -> consume exactly that span
assemble (event + attachments) into one unit
hand the unit up to the state machine
repeat
The parser surfaces complete units; the state machine gives them meaning. Point-to-point makes the handshake an IK-equivalent — the client bakes in entityOS.cloud's AID and current key state ahead of time and pins it. Pre-rotation is what makes that safe: each rotation commits to the next key, so a pinned AID plus verified KEL history proves the counterparty with no negotiation an attacker can downgrade.
Optionally consume a leading genus-version count code (v2), then expect the peer's inception and any rotation events establishing its KEL.
Verify the peer's establishment history against the pinned AID. Reject on any mismatch before emitting anything.
Direct mode is receipt-based: issue a receipt (chit) over the peer's events and expect theirs over yours. The parser already surfaces receipt couples as an attachment group — route that unit to the receipt handler.
ACDC exchange, schema-validated against pinned SAIDs, each carrying a monotonic sequence or nonce. The replay guard lives here — after signature verification, before any application effect.
A rotation event arrives as an ordinary event unit. Verify against the pre-rotation commitment, update key state, continue on the same socket. This is why long-lived links stay secure without reconnecting through any external PKI.
Same opacity to the network. A very different attack surface, and a different place where trust is anchored.
| Property | HTTP / TLS Stack | KERI Direct Channel |
|---|---|---|
| Firewall allow | Permissive 80/443 | One port, known peers, default-deny |
| Free-text region on the wire | Headers, body, content-type | None — typed frames only |
| Header injection / smuggling | Possible | Not a construct |
| Per-message attribution | Session-level (TLS) | Every frame → a KERI AID |
| CA / cert-chain dependency | Required | None |
| Key rotation | Re-issue / reconnect | In-band, same socket |
| Payload inspectable at firewall | No (encrypted) | No — relocated to endpoint |
| Replay / ordering guard | Built into TLS records | You add it yourself |
The last two rows are the honest ones: opacity is unchanged, and one guarantee TLS gave you for free is now your responsibility.
KERI direct mode hands you attribution and rotation. It does not give you the rest. These are the pieces you build, and the first two are the ones most commonly skipped.
The client now owns TLS-equivalent duties: key management, rotation, replay protection, downgrade resistance. KERI hands you rotation; do not hand-roll the rest casually.
If you are reusing an existing KERI library's transport, the parser section does not apply to you — direct-mode transport is a smaller shim job. If you are writing the stream layer yourself, these are the ones to fuzz, bound, and order correctly.
Fuzz this first. Feed the parser the same valid stream sliced at every byte offset and assert identical output. If a one-byte-at-a-time feed does not match a whole-buffer feed, the "need more bytes" logic is wrong. Highest-value test you will write.
A signed event can be large; a peer can pipeline many before you receipt. Bound the accumulation buffer and stop reading the socket when full, or an authenticated peer can OOM you. Silent-drop at the firewall does not help — these are bytes from a peer you already trust.
A structurally valid unit is not an authenticated one. The order is always: parse → verify signature over the AID → replay check → then act. Never let a parsed-but-unverified unit reach application logic.
Pure app-layer over plain TCP works. But for a silent-drop property at the network edge, run the TCP channel inside WireGuard: the firewall opens one UDP port, and any packet without valid keys gets no response at all — scanners and stuffing attempts hit a black hole. WireGuard is Noise-based and mutually authenticated, so it composes cleanly under the KERI layer.
No witnesses, no mailboxes, no OOBIs over HTTP. If you later need fan-out or store-and-forward, that is a different design — witness/mailbox mode — and out of scope here.
Blocking 80/443 breaks package repos, OS updates, other APIs and third-party SaaS. Apply the block to the segment or service tier that talks to entityOS.cloud — not the whole network.
keripy's directing module (Director / Directant over TCP) is the closest existing implementation of this shape. Read it for receipt-handshake sequencing — most other KERI deployments assume witness/mailbox mode over HTTP and will mislead you.
Blocking HTTP and running your own signed channel does not make threats detectable at the network. It relocates inspection from a generic DPI guessing at arbitrary HTTP to your endpoint validating decrypted, typed, signed frames against a strict schema.
The entityOS.cloud application-level ACDC schemas come from the entityOS side and are pinned by SAID. The wire is yours; the meaning is theirs.