dsfx/docs/internals/handshake.md

8.0 KiB
Raw Permalink Blame History

Handshake Protocol Documentation

This document describes the handshake protocol implemented in the DSFX library. The protocol establishes a secure, authenticated channel between two parties over a network connection. It uses a combination of ephemeral DiffieHellman key exchange, symmetric encryption, and digital signatures to both derive a shared secret and verify the identities of the communicating parties.

Note: The protocol relies on several subcomponents: • The key exchange mechanisms (using ECDH with the X25519 curve and HKDF for key derivation) • Encryption and decryption using AES-GCM • Digital signatures with ED25519 • Transport framing using length-prefixed buffers

Overview

The handshake protocol involves two roles:

  • Initiator (Client): Starts the handshake, sends its ephemeral public key, performs client authentication, and verifies the servers signature.
  • Acceptor (Server): Waits for the clients initiation, sends its own ephemeral public key, verifies the clients authentication, and responds with its signed authentication message.

Both parties, after successful authentication, derive an identical shared secret that is used to secure subsequent communication.

Protocol Steps

Step 1: Ephemeral Key Exchange

Purpose: Each party generates an ephemeral ECDH key pair to perform a DiffieHellman key exchange. This key exchange enables both parties to derive a shared symmetric key while providing forward secrecy.

Actions:

  • Initiator:

    • Generates an ephemeral ECDH key pair.
    • Exports its ephemeral public key into a length-prefixed buffer (using the buffer.NewLenPrefixed function).
    • Sends the ephemeral public key over the connection.
  • Acceptor:

    • Waits for the client's ephemeral public key.
    • Reads and imports the clients key from the length-prefixed payload (using buffer.ReadLenPrefixed and keyexchange.ImportPublicKey).
    • Generates its own ephemeral key pair.
    • Exports its ephemeral public key.
    • Sends its ephemeral public key (again using a length-prefixed buffer).

Step 2: Shared Secret Derivation

Purpose: Using the exchanged ephemeral keys, both parties compute a shared secret. This shared secret is derived via ECDH and then processed with HKDF (using SHA-256) to produce a symmetric session key for message encryption.

Actions:

  • Both parties call the keyexchange.ComputeDHSecret() method with their private ephemeral key and the other partys public ephemeral key.
  • An AES-GCM ciphersuite is used in the encryption.Encrypt() and encryption.Decrypt() functions to protect subsequent authentication messages.

Step 3: Authentication of Long-Term Identity

Purpose: To bind the ephemeral keys (used for the session) with each partys long-term identity (represented by an ED25519 key pair), the protocol uses digital signatures. This step prevents man-in-the-middle attacks by ensuring that both parties are in possession of their respective long-term private keys.

Actions (Client/Initiator):

  1. Export Signing Keys:

    • The client exports its long-term public signing key.
    • It also exports the servers public signing key (provided as a parameter to the handshake).
  2. Build Authentication Message:

    • A helper function (buildMessage) constructs an authentication message that binds the ephemeral keys:
      • First, both ephemeral public keys (client and server) are exported.
      • The message is built by concatenating these keys and appending a SHA-256 checksum of the concatenated key values.
      • Notably, the final authentication message is defined as: authMessage = serverEphemeralPublicKey || SHA256(clientEphemeralPublicKey || serverEphemeralPublicKey)
  3. Sign the Message:

    • The client signs the constructed authentication message with its long-term ED25519 private key using the identity.Sign() function.
  4. Encrypt and Send Authentication Data:

    • The client builds a plaintext message that contains:
      • Its exported long-term public key.
      • Its signature over the authentication message.
    • This plaintext is encrypted using the derived session key (from Step 2) via AES-GCM.
    • The encrypted block is sent using a length-prefixed transmission.

Actions (Server/Acceptor):

  1. Receive and Decrypt Client Authentication Message:

    • The server reads the clients authentication message (which was sent as a length-prefixed encrypted message).
    • It decrypts the message with the derived session key, retrieving:
      • The clients long-term public signing key.
      • The clients signature.
  2. Build the Authentication Message:

    • Similarly to the client side, the server constructs an authentication message with the exchanged ephemeral keys using the buildMessage function (note the parameter order is reversed).
  3. Verify the Clients Signature:

    • The server verifies the clients signature against the built authentication message using identity.Verify().
  4. Sign and Send the Server Authentication Response:

    • The server signs the same authentication message with its own long-term private key.
    • It then encrypts the signed message with the derived session key.
    • This encrypted signature is sent back to the client as a length-prefixed buffer.

Step 4: Finalizing the Handshake

Purpose: Both parties confirm that the handshake is complete and that the authentication was successful.

Actions:

  • Acceptor (Server): After sending its authentication response, the server waits for a final "handshake complete" message from the client.

  • Initiator (Client):

    • After verifying the servers authentication response (by decrypting and checking the servers signature), the client sends a final handshake completion message. This is a simple length-prefixed payload containing a one-byte flag (0x01).
  • Both Parties:

    • On reception of this handshake completion message, the handshake process is finalized.
    • At this point, both sides share the same derived symmetric session key which will be used for all subsequent encrypted communication.

Message Framing and Transport

All messages (ephemeral keys, authentication messages, handshake completion flag) are transmitted using a length-prefixed framing mechanism. The buffer package is responsible for:

  • Prepending a 2-byte big-endian length prefix to each message.
  • Reading and validating the length prefix before consuming the payload.

This ensures that the message boundaries are clearly established over the TCP stream.

Security Considerations

  • Ephemeral Keys: The use of ephemeral ECDH keys ensures forward secrecy; even if long-term keys are compromised later, past session keys remain secure.

  • Authentication Binding: By signing a message that binds the ephemeral keys with the long-term identities, the protocol guarantees that the established session is authenticated. Any tampering in the handshake will cause signature verification to fail.

  • Encryption: All sensitive handshake messages (including public keys and signatures) are encrypted with the derived symmetric key using AES-GCM. This mode provides both confidentiality and integrity.

  • Length Prefixing: The fixed-length (2-byte) prefix before each message fragment ensures that only complete messages are processed, reducing the risk of framing errors and injection attacks.

Summary of the Handshake Flow

  1. Initiate (Client):
    • Generate ephemeral DH key and send it.
  2. Accept (Server):
    • Receive clients ephemeral key.
    • Generate its own ephemeral key and send it.
  3. Client Authentication:
    • Client builds and signs an authentication message.
    • Encrypts with derived key and sends to the server.
  4. Server Authentication:
    • Server decrypts and verifies the clients authentication message.
    • Signs the same authentication message and sends it back.
  5. Handshake Completion:
    • Client verifies servers signature.
    • Client sends a final “handshake complete” flag.
    • Both sides now share the session key for secure communication.