ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
Cryptographic Operations

🔐 Per-client cryptographic handshake and session encryption management More...

Files

file  crypto.c
 🔐 Server cryptography: per-client handshake, X25519 key exchange, and session encryption management
 
file  crypto.h
 Server cryptographic operations and per-client handshake management.
 

Detailed Description

🔐 Per-client cryptographic handshake and session encryption management

Cryptographic Operations

Overview

The cryptographic operations module manages per-client cryptographic handshakes, X25519 key exchange, session encryption, and client authentication for the ascii-chat server. This module embodies the security-first philosophy of ascii-chat, providing end-to-end encryption with multiple authentication modes to ensure secure multi-client video chat. The module integrates seamlessly with the client lifecycle, performing cryptographic handshakes during connection establishment and managing session encryption throughout the client's connection.

Implementation: src/server/crypto.c, src/server/crypto.h

Key Responsibilities:

  • Initialize server crypto system and validate encryption configuration
  • Perform cryptographic handshake with each connecting client
  • Manage per-client crypto contexts stored in client_info_t structures
  • Provide encryption/decryption functions for secure packet transmission
  • Support multiple authentication modes (password, SSH key, passwordless)
  • Integrate with client whitelist for authenticated access control

Cryptographic Handshake

The cryptographic handshake follows a multi-phase protocol:

Phase 0: Protocol Negotiation

Step 0a: Receive Client Protocol Version

// Receive client's protocol version
result = receive_packet(socket, &packet_type, &payload, &payload_len);
if (packet_type != PACKET_TYPE_PROTOCOL_VERSION) {
return -1; // Protocol mismatch
}
if (!client_version.supports_encryption) {
return -1; // Client doesn't support encryption
}
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:767
uint8_t supports_encryption
Encryption support flag (1=support encryption, 0=plaintext only)
Definition packet.h:716
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
Definition packet.h:283
Protocol version negotiation packet structure (Packet Type 1)
Definition packet.h:710

Step 0b: Send Server Protocol Version

// Send server's protocol version
protocol_version_packet_t server_version = {0};
server_version.protocol_version = htons(1);
server_version.supports_encryption = 1;
result = send_protocol_version_packet(socket, &server_version);
int send_protocol_version_packet(socket_t sockfd, const protocol_version_packet_t *version)
Send protocol version packet.
Definition packet.c:1016
uint16_t protocol_version
Major protocol version (must match for compatibility)
Definition packet.h:712

Step 0c: Receive Client Crypto Capabilities

// Receive client's supported algorithms
result = receive_packet(socket, &packet_type, &payload, &payload_len);
// Parse supported algorithms
uint16_t supported_kex = ntohs(client_caps.supported_kex_algorithms);
uint16_t supported_auth = ntohs(client_caps.supported_auth_algorithms);
uint16_t supported_cipher = ntohs(client_caps.supported_cipher_algorithms);
unsigned short uint16_t
Definition common.h:57
uint16_t supported_auth_algorithms
Supported authentication algorithms bitmask (AUTH_ALGO_*)
Definition packet.h:848
uint16_t supported_kex_algorithms
Supported key exchange algorithms bitmask (KEX_ALGO_*)
Definition packet.h:846
uint16_t supported_cipher_algorithms
Supported cipher algorithms bitmask (CIPHER_ALGO_*)
Definition packet.h:850
Crypto capabilities packet structure (Packet Type 14)
Definition packet.h:844

Step 0d: Select Algorithms and Send Parameters

// Select algorithms (currently only X25519 + Ed25519 + XSalsa20-Poly1305)
crypto_parameters_packet_t server_params = {0};
server_params.selected_kex = KEX_ALGO_X25519;
// Select authentication algorithm based on server configuration
}
result = send_crypto_parameters_packet(socket, &server_params);
key_type_t type
Definition key_types.h:92
@ KEY_TYPE_ED25519
Definition key_types.h:52
uint8_t selected_kex
Selected key exchange algorithm (KEX_ALGO_*)
Definition packet.h:875
#define AUTH_ALGO_ED25519
Ed25519 authentication (Edwards-curve signatures)
Definition packet.h:950
uint8_t selected_cipher
Selected cipher algorithm (CIPHER_ALGO_*)
Definition packet.h:879
uint8_t selected_auth
Selected authentication algorithm (AUTH_ALGO_*)
Definition packet.h:877
#define KEX_ALGO_X25519
X25519 key exchange (Curve25519)
Definition packet.h:949
int send_crypto_parameters_packet(socket_t sockfd, const crypto_parameters_packet_t *params)
Send crypto parameters packet.
Definition packet.c:1044
#define CIPHER_ALGO_XSALSA20_POLY1305
XSalsa20-Poly1305 authenticated encryption.
Definition packet.h:952
bool g_server_encryption_enabled
Global flag indicating if server encryption is enabled.
private_key_t g_server_private_key
Global server private key.
Crypto parameters packet structure (Packet Type 15)
Definition packet.h:873

Phase 1: Key Exchange

Step 1: Send Server's Ephemeral Public Key

// Generate ephemeral key pair for this client session
x25519_keypair_t ephemeral_keys;
x25519_generate_keypair(&ephemeral_keys);
// Store ephemeral private key in client's crypto context
client->crypto_ctx.ephemeral_private_key = ephemeral_keys.private_key;
// Send ephemeral public key to client
key_exchange_init_packet_t keyex_init = {0};
memcpy(keyex_init.public_key, ephemeral_keys.public_key, X25519_PUBLIC_KEY_SIZE);
// If server has identity key, sign the key exchange
ed25519_sign(&keyex_init.signature, keyex_init.public_key,
X25519_PUBLIC_KEY_SIZE, &g_server_private_key);
memcpy(keyex_init.identity_key, g_server_public_key, ED25519_PUBLIC_KEY_SIZE);
}
result = send_key_exchange_init_packet(socket, &keyex_init);
#define ED25519_PUBLIC_KEY_SIZE
Ed25519 public key size in bytes.

Step 2: Receive Client's Public Key and Derive Shared Secret

// Receive client's ephemeral public key
key_exchange_response_packet_t client_keyex;
result = receive_packet(socket, &packet_type, &payload, &payload_len);
// Derive shared secret using X25519 key exchange
x25519_shared_secret_t shared_secret;
x25519_derive_shared_secret(&shared_secret, client_keyex.public_key,
&client->crypto_ctx.ephemeral_private_key);
// Store shared secret in client's crypto context
memcpy(client->crypto_ctx.shared_secret, shared_secret, X25519_SHARED_SECRET_SIZE);

Phase 2: Authentication

Step 2: Send Authentication Challenge

// Receive client's public key and send auth challenge
auth_request_packet_t auth_req;
result = receive_packet(socket, &packet_type, &payload, &payload_len);
// If whitelist enabled, verify client's public key
bool client_in_whitelist = false;
for (size_t i = 0; i < g_num_whitelisted_clients; i++) {
if (memcmp(auth_req.public_key, g_client_whitelist[i],
client_in_whitelist = true;
break;
}
}
if (!client_in_whitelist) {
return -1; // Client not in whitelist
}
}
// Generate authentication challenge
auth_challenge_packet_t challenge;
random_bytes(challenge.challenge, AUTH_CHALLENGE_SIZE);
// Sign challenge with server's identity key (if available)
ed25519_sign(&challenge.signature, challenge.challenge,
}
result = send_auth_challenge_packet(socket, &challenge);
#define AUTH_CHALLENGE_SIZE
Challenge nonce size (32 bytes)
size_t g_num_whitelisted_clients
Number of whitelisted clients.
public_key_t g_client_whitelist[]
Global client public key whitelist.

Step 3: Receive Authentication Response and Complete Handshake

// Receive client's authentication response
auth_response_packet_t auth_resp;
result = receive_packet(socket, &packet_type, &payload, &payload_len);
// Verify client's signature on challenge
if (!ed25519_verify(auth_resp.signature, challenge.challenge,
AUTH_CHALLENGE_SIZE, auth_req.public_key)) {
return -1; // Authentication failed
}
// Derive session encryption keys from shared secret
derive_session_keys(&client->crypto_ctx.session_keys, &client->crypto_ctx.shared_secret);
// Mark handshake as complete
atomic_store(&client->crypto_initialized, true);

Authentication Modes

Password Authentication

How It Works:

  • Uses Argon2id key derivation from shared password
  • Both server and client derive same key from password
  • No identity keys required (password-only mode)
  • Suitable for trusted networks or simple deployments

Configuration:

// Set password via --password command-line option
// Password is used for key derivation, not transmitted
// Both server and client must use same password

SSH Key Authentication

How It Works:

  • Server uses Ed25519 private key for identity verification
  • Client provides Ed25519 public key for authentication
  • Identity verification via known_hosts and whitelist
  • Strong authentication with cryptographic signatures

Configuration:

// Server loads SSH key via --key option
// Supports SSH Ed25519 key files
// Supports gpg:keyid format for GPG keys (if GPG support enabled)
// Client provides public key during handshake

Key Loading:

  • Validates SSH key file format
  • Native encrypted key support (bcrypt_pbkdf + BearSSL AES-256-CTR)
  • Validates key type (Ed25519 required)
  • Loads private key for signing operations

Passwordless Mode

How It Works:

  • Ephemeral keys only (no long-term identity)
  • Key exchange provides confidentiality but not authentication
  • Suitable for trusted networks or testing
  • No identity verification performed

Configuration:

// No --key or --password provided
// Server runs in passwordless mode
// Key exchange provides encryption but no authentication

Per-Client Crypto Contexts

Each client has an independent crypto context stored in client_info_t:

typedef struct {
// Key exchange state
x25519_private_key_t ephemeral_private_key;
x25519_shared_secret_t shared_secret;
// Session encryption keys
session_keys_t session_keys;
// Authentication state
ed25519_public_key_t client_public_key;
bool authenticated;
// Handshake state
handshake_state_t state;
} crypto_handshake_ctx_t;
bool initialized
Definition mmap.c:36

Context Lifecycle:

  • Created during connection establishment
  • Initialized during cryptographic handshake
  • Used throughout client connection for encryption/decryption
  • Cleaned up on client disconnect

Thread Safety:

  • Each client has independent crypto context (no shared state)
  • Socket access protected by client_state_mutex
  • Per-client encryption/decryption operations are isolated
  • Global server crypto state (g_server_private_key) read-only after init

Encryption and Decryption Operations

Packet Encryption

After handshake completion, all packets are encrypted before transmission:

const void *plaintext, size_t plaintext_len,
void *ciphertext, size_t *ciphertext_len)
{
// Get client's crypto context
client_info_t *client = get_client_by_id(client_id);
// Check if encryption is enabled
if (!client->crypto_initialized) {
// Encryption not initialized - passthrough
memcpy(ciphertext, plaintext, plaintext_len);
*ciphertext_len = plaintext_len;
return 0;
}
// Encrypt packet using XSalsa20-Poly1305
int result = xsalsa20poly1305_encrypt(ciphertext, ciphertext_len,
plaintext, plaintext_len,
NULL, 0, // No additional data
client->crypto_ctx.session_keys.encryption_key);
return result;
}
unsigned int uint32_t
Definition common.h:58
int crypto_server_encrypt_packet(uint32_t client_id, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_len)
Per-client state structure for server-side client management.

Packet Decryption

All received packets are decrypted before processing:

const void *ciphertext, size_t ciphertext_len,
void *plaintext, size_t *plaintext_len)
{
// Get client's crypto context
client_info_t *client = get_client_by_id(client_id);
// Check if encryption is enabled
if (!client->crypto_initialized) {
// Encryption not initialized - passthrough
memcpy(plaintext, ciphertext, ciphertext_len);
*plaintext_len = ciphertext_len;
return 0;
}
// Decrypt packet using XSalsa20-Poly1305
int result = xsalsa20poly1305_decrypt(plaintext, plaintext_len,
ciphertext, ciphertext_len,
NULL, 0, // No additional data
client->crypto_ctx.session_keys.decryption_key);
return result;
}
int crypto_server_decrypt_packet(uint32_t client_id, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_len)

Automatic Passthrough:

  • When encryption is disabled (–no-encrypt), packets pass through unchanged
  • No performance overhead when encryption disabled
  • Seamless integration with non-encrypted mode

Client Whitelist Integration

Whitelist Loading

Client whitelist is loaded during server initialization:

// Load client whitelist if provided
if (strlen(opt_client_keys) > 0) {
if (parse_client_keys(opt_client_keys, g_client_whitelist,
return -1; // Whitelist loading failed
}
log_info("Server will only accept %zu whitelisted clients",
}
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:23
#define log_info(...)
Log an INFO message.
ASCIICHAT_API char opt_client_keys[OPTIONS_BUFF_SIZE]

Whitelist Format:

  • Supports multiple key formats (SSH public keys, raw hex, etc.)
  • Multiple keys can be provided (comma-separated)
  • Keys are validated during loading
  • Only Ed25519 keys are supported

Whitelist Verification

During handshake, client's public key is verified against whitelist:

// Verify client's public key against whitelist
bool client_in_whitelist = false;
for (size_t i = 0; i < g_num_whitelisted_clients; i++) {
if (memcmp(client_public_key, g_client_whitelist[i],
client_in_whitelist = true;
break;
}
}
if (!client_in_whitelist) {
log_warn("Client public key not in whitelist - rejecting connection");
return -1; // Authentication failed
}
}
#define log_warn(...)
Log a WARN message.

Verification Behavior:

  • If whitelist enabled, only whitelisted clients can connect
  • Clients not in whitelist are rejected during handshake
  • Detailed error logging for troubleshooting
  • Clean disconnect on whitelist rejection

Error Handling

Handshake Errors:

  • Client disconnection during handshake: Logged and return error
  • Protocol mismatch: Detailed error logging and disconnect
  • Authentication failure: Logged and disconnect (whitelist rejection)
  • Network errors: Detected and handled gracefully
  • Invalid packets: Validated before processing

Encryption/Decryption Errors:

  • Decryption failures: Logged and packet dropped
  • Encryption failures: Logged and connection marked lost
  • Key derivation failures: Handled gracefully
  • Session key errors: Clean disconnect

Algorithm Support

Current Algorithms:

  • Key Exchange: X25519 (Elliptic Curve Diffie-Hellman)
  • Cipher: XSalsa20-Poly1305 (Authenticated Encryption)
  • Authentication: Ed25519 (when server has identity key)
  • Key Derivation: Argon2id (for password-based authentication)
  • HMAC: HMAC-SHA256 (for additional integrity protection)

Future Algorithm Support:

  • Additional key exchange algorithms (ECDH-P256, etc.)
  • Additional cipher algorithms (ChaCha20-Poly1305, etc.)
  • Additional authentication algorithms (ECDSA, etc.)

Integration with Other Modules

Integration with client.c

Called By:

  • add_client(): Performs cryptographic handshake during connection
  • Handshake integrated into client connection flow

Provides To:

  • Per-client crypto contexts
  • Encryption/decryption functions
  • Authentication verification

Integration with protocol.c

Used By:

  • Protocol handlers decrypt received packets
  • Packet encryption before transmission
  • Seamless integration with packet processing

Provides To:

  • Packet encryption/decryption functions
  • Crypto context access

Integration with main.c

Called By:

  • init_server_crypto(): Initializes server crypto system
  • Server key loading and validation
  • Whitelist loading and validation

Provides To:

  • Global server crypto state
  • Server identity key
  • Client whitelist

Best Practices

DO:

  • Always validate packets before processing
  • Use per-client crypto contexts (no shared state)
  • Check crypto_initialized before encryption/decryption
  • Handle errors gracefully without disconnecting clients unnecessarily
  • Log detailed errors for troubleshooting

DON'T:

  • Don't share crypto contexts between clients
  • Don't skip packet validation
  • Don't ignore encryption/decryption errors
  • Don't expose private keys in logs
  • Don't skip whitelist verification when enabled
See also
src/server/crypto.c
src/server/crypto.h
Server Overview
Client Management
topic_crypto
topic_handshake
topic_keys