An Authenticated Chat Server in C

Needham-Schroeder logins and group keys the server never sees.

Contents
  1. Background: the problem Needham-Schroeder solves
  2. Two services, one server
  3. Logging in with Needham-Schroeder
    1. The known weakness
  4. Private groups with multi-party Diffie-Hellman
  5. Commands
  6. What I took from it

This is a multi-user chat server in C where users have to authenticate before they can say anything and private group conversations are encrypted with a key that only the members hold. I built it to implement a classic key distribution protocol from the paper all the way down to the sockets, instead of only ever reading the message diagrams.

Background: the problem Needham-Schroeder solves#

Imagine many users and one server, all needing to communicate securely. Giving every pair of parties its own shared key doesn’t scale. Public-key cryptography is one answer. Needham-Schroeder, published in 1978, is the other: a trusted third party called a key distribution centre (KDC).

Every user shares one long-term secret key with the KDC and nobody else. When a user wants to talk to a service, the KDC creates a fresh session key for that conversation and delivers it to both sides in a way that only they can read. Long-term keys are only ever used to talk to the KDC. Everything else uses short-lived session keys.

If that sounds familiar, it’s because Kerberos, which still handles logins on most corporate Windows networks, is a direct descendant of this protocol.

Two services, one server#

The server process runs two listeners on separate ports: the KDC and the chat server itself. Each accepts connections concurrently, so several clients can be handled at once.

Client long-term key Server process, two concurrent listeners KDC listener authentication port Chat server listener chat port authenticate present ticket
One process, two listeners: the KDC for authentication and the chat server for everything after.

Each user’s long-term key is derived ahead of time from their password with a password-based key derivation function. A KDF like this deliberately makes turning a password into a key slow, so that someone who steals the stored keys can’t cheaply try billions of password guesses against them. That long-term key is what the whole authentication protocol starts from.

Logging in with Needham-Schroeder#

Logging in takes a short exchange of messages:

  1. Client to KDC: “I’m Alice, I want to talk to the chat server and here’s a fresh random number.” That random number is called a nonce (a “number used once”).
  2. KDC to client: a reply encrypted with Alice’s long-term key. Inside are the nonce she sent, a new session key and a ticket. The ticket holds the same session key and Alice’s identity, encrypted with the chat server’s long-term key, so Alice can carry it but can’t read or change it.
  3. Client to chat server: Alice forwards the ticket. The chat server decrypts it with its own key and now both sides know the session key.
  4. A final handshake between Alice and the chat server, using the session key, confirms that each side really holds it.
Client KDC Chat server nonce, ID, target server session key + ticket + nonce encrypted under long-term key verify echoed nonce ticket client and chat server now share the session key
The Needham-Schroeder message sequence, from KDC request to an authenticated session.

The nonce is the anti-replay anchor. Alice checks that the value inside the KDC’s reply matches the one she just sent. An attacker who recorded an old KDC reply can’t replay it to her, because it would carry the wrong nonce. Once the handshake is done, Alice and the chat server share a session key and all further traffic uses it.

The known weakness#

The original protocol has a famous flaw, pointed out by Denning and Sacco in 1981. The chat server has no way to tell how old a ticket is. If an attacker ever gets hold of an old session key, they can replay the matching old ticket to the server and pose as the user who received it, long after that session ended. Kerberos fixes this by adding timestamps and expiry times to tickets.

Implementing the textbook version by hand makes it very clear why the nonces are there and exactly where the protocol still leaves a gap.

Private groups with multi-party Diffie-Hellman#

Broadcasting to everyone is easy. The interesting case is a private group, where the goal is that even the server can’t read the messages.

Diffie-Hellman lets two parties agree on a shared secret over an open channel, without ever sending the secret. Each side combines its own private value with the other’s public value and both arrive at the same result. Someone watching the exchange sees only the public values, which aren’t enough to work out the secret.

Extending that to a group takes a few steps. When a user creates a group and invites people:

  1. The initiator runs Diffie-Hellman with the first member and the two of them now share a secret.
  2. To add each further member, the current shared secret is sent to the new member encrypted under their public key. Their own contribution is then mixed in.
  3. At the end, every member holds the same group key. The server, which only ever relayed messages it couldn’t decrypt, doesn’t.
Initiator + Member A Diffie-Hellman shared secret fold in B + Member B mix contribution group key encrypt Group messages AES-256 under key each new member is folded in the same way the secret is passed encrypted under each new member's public key; the server never holds it
The group key is built up one member at a time and never passes through the server in the clear.

Group messages are encrypted with that key using AES-256, so users outside the group can’t read them and neither can the server passing them along.

Commands#

Command What it does
/who List logged-in users
/write all Broadcast to everyone
/create group Create a private group
/group invite Invite users to a group
/init group dhxchg Run the group key exchange
/write group Send an encrypted message to a group
/request file Fetch a file from another user over a TLS connection with a self-signed certificate

What I took from it#

It’s a study build. Long-term keys held by a KDC and a textbook handshake aren’t how I’d ship a real chat system: today that would mean something like the Signal protocol, with forward secrecy. But wiring up a KDC, ticket validation and a group key agreement on top of raw sockets made the whole family of authentication protocols click, including the reasons their descendants look the way they do.