The KSP handshake is the most security-critical part of the entire protocol. A bug in the handshake can undermine every security property the protocol claims to have. This article walks through every step of the seven-message sequence and explains the cryptographic purpose of each one.
Step 1: ClientHello
The client sends a ClientHello frame. At this point, the session ID is all zeros (no session established yet). The payload contains:
- A list of supported protocol versions (ordered by preference)
- A 32-bit capabilities bitfield (cipher suites, features)
- 32 bytes of client_random (cryptographically secure randomness)
- 32 bytes of client ephemeral public key (X25519)
The ephemeral key pair is generated fresh for every handshake. The private key never leaves the client and is zeroed after the shared secret is computed.
Step 2: ServerHello
The server selects the highest mutually supported version and the negotiated cipher suite. It responds with its own 32-byte server_random and its ephemeral X25519 public key. It also generates and includes the session ID — a UUID v4 that will identify this session for the rest of its lifetime.
Step 3: Certificate + Binding Signature
This is the most important step for security. The server sends its KSP certificate (containing its Ed25519 public key). But crucially, it also computes a binding signature:
binding_signature = Ed25519_Sign(
server_private_key,
client_random || server_random || client_ephemeral_pub || server_ephemeral_pub
)This signature ties the server's identity to this specific key exchange. A MITM who intercepts the TCP connection, performs their own key exchange with the client, and forwards the server's certificate cannot compute this signature — they don't have the server's private key. If they try to modify the ephemeral keys, the signature check fails.
Steps 4–5: Encrypted Authentication
After verifying the certificate and binding signature, both sides compute the shared secret and derive session keys via HKDF-SHA256. From this point, all messages are encrypted. The client sends its credentials in an AuthRequest — because the channel is now encrypted, credentials are never exposed in plaintext.
Steps 6–7: HandshakeFinish (Transcript Verification)
Both sides compute an HMAC-SHA256 over a hash of the entire handshake transcript (all messages from ClientHello to AuthResponse). Each side sends this verify_data and checks the other side's value. If they match, both sides know they negotiated with the same parameters, derived the same keys, and no message was tampered with.
This is the downgrade protection mechanism. A MITM who silently changed the cipher suite in ClientHello or ServerHello would have changed the transcript, causing the HMAC to not match.