Why build a networking protocol from scratch when TLS exists? When HTTP/3 exists? When QUIC exists?
The answer is the same reason you write a toy operating system kernel in a computer science course: not because you need one, but because you cannot truly understand how a thing works until you have built it yourself.
The Motivation
I had been working on systems-level Rust for about a year. I had read through the TLS 1.3 RFC (RFC 8446), QUIC (RFC 9000), and parts of the HTTP/2 specification. Each time I finished a section, I had a vague sense of understanding — but when I tried to explain how HKDF key derivation actually worked, or why TLS uses a transcript hash in the Finished message, I found I couldn't.
Reading is not understanding. You understand a protocol when you can implement it from a blank file.
KSP started as a question: What is the simplest protocol I could build that has all the important security properties of TLS?
The Design Goals
I set four non-negotiable requirements before writing a single line of code:
- Binary wire format — no text parsing, no ambiguity
- Authenticated encryption — every byte of payload encrypted and authenticated
- Forward secrecy — compromise of long-term keys does not reveal past traffic
- Replay protection — a captured packet cannot be replayed
Everything else was optional. Stream multiplexing made it in because it is genuinely interesting. Session resumption made it in because I wanted to understand how TLS session tickets work.
Binary Protocol vs Text Protocol
The first decision was easy: binary framing. HTTP/1.1 is a text protocol and it has caused decades of security vulnerabilities due to ambiguous parsing rules. HTTP/2 moved to binary framing and immediately eliminated entire classes of request smuggling attacks.
KSP uses a 48-byte fixed header. Every field has a fixed offset and a fixed width. The parser is not a state machine that processes characters — it is a fixed sequence of memcpy operations.
// Pseudocode — parsing is trivial
let version = buf[0];
let packet_type = buf[1];
let flags = u16::from_be_bytes([buf[2], buf[3]]);
let payload_len = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
let session_id = &buf[8..24];
let stream_id = u32::from_be_bytes([buf[24], buf[25], buf[26], buf[27]]);
let seq = u64::from_be_bytes(buf[28..36].try_into().unwrap());
let nonce = &buf[36..48];Zero dynamic allocation in the parser. Zero string processing. The entire header parse is ~10 instructions.
Choosing AEAD
Authenticated Encryption with Associated Data (AEAD) is the right primitive for a secure channel protocol. It provides both confidentiality and integrity in a single operation.
KSP supports two cipher suites: AES-256-GCM (fast on hardware with AES-NI) and ChaCha20-Poly1305(fast in software, preferred when AES-NI is unavailable). This mirrors TLS 1.3's cipher suite design.
The key insight with AEAD is that the KSP header is included as Additional Authenticated Data (AAD). This means the header is authenticated but not encrypted — a MITM who modifies the header (changing the packet type, sequence number, or session ID) will cause AEAD authentication to fail.
The Nonce Problem
AEAD with the same nonce twice is catastrophic. For AES-GCM specifically, it allows an attacker to recover the authentication key. This is not a theoretical concern — it has happened in production systems.
KSP solves this the same way TLS 1.3 does: deterministic nonce construction. The nonce is the per-direction write IV XOR'd with the current sequence number (zero-padded to 12 bytes):
nonce = write_iv XOR (seq_number || zeros)[0..12]Since sequence numbers are monotonically increasing, nonces are guaranteed to never repeat within a session. And since we use separate keys (and IVs) for each direction, client→server and server→client nonces don't collide.
What I Learned
Building KSP taught me things that months of reading didn't. Here are the three most important:
- The Finished message transcript hash is not just a checksum — it is the mechanism that prevents downgrade attacks. Without it, a MITM can silently change the cipher suite negotiation.
- The binding signature in the Certificate step is the mechanism that prevents MITM attacks against the key exchange. Without it, an attacker can present a valid certificate while doing their own key exchange.
- Replay protection requires a sliding window, not just a counter. A pure counter cannot detect out-of-order duplicates that arrive within the window.
KSP is not production software. It is a learning artifact. But it is a documented, tested, RFC-specified learning artifact — and that makes it more useful than a toy.