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
result =
receive_packet(socket, &packet_type, &payload, &payload_len);
return -1;
}
return -1;
}
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
uint8_t supports_encryption
Encryption support flag (1=support encryption, 0=plaintext only)
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
Protocol version negotiation packet structure (Packet Type 1)
Step 0b: Send Server Protocol Version
int send_protocol_version_packet(socket_t sockfd, const protocol_version_packet_t *version)
Send protocol version packet.
uint16_t protocol_version
Major protocol version (must match for compatibility)
Step 0c: Receive Client Crypto Capabilities
result =
receive_packet(socket, &packet_type, &payload, &payload_len);
uint16_t supported_auth_algorithms
Supported authentication algorithms bitmask (AUTH_ALGO_*)
uint16_t supported_kex_algorithms
Supported key exchange algorithms bitmask (KEX_ALGO_*)
uint16_t supported_cipher_algorithms
Supported cipher algorithms bitmask (CIPHER_ALGO_*)
Crypto capabilities packet structure (Packet Type 14)
Step 0d: Select Algorithms and Send Parameters
}
uint8_t selected_kex
Selected key exchange algorithm (KEX_ALGO_*)
#define AUTH_ALGO_ED25519
Ed25519 authentication (Edwards-curve signatures)
uint8_t selected_cipher
Selected cipher algorithm (CIPHER_ALGO_*)
uint8_t selected_auth
Selected authentication algorithm (AUTH_ALGO_*)
#define KEX_ALGO_X25519
X25519 key exchange (Curve25519)
int send_crypto_parameters_packet(socket_t sockfd, const crypto_parameters_packet_t *params)
Send crypto parameters packet.
#define CIPHER_ALGO_XSALSA20_POLY1305
XSalsa20-Poly1305 authenticated encryption.
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)
Phase 1: Key Exchange
Step 1: Send Server's Ephemeral Public Key
x25519_keypair_t ephemeral_keys;
x25519_generate_keypair(&ephemeral_keys);
client->crypto_ctx.ephemeral_private_key = ephemeral_keys.private_key;
key_exchange_init_packet_t keyex_init = {0};
memcpy(keyex_init.public_key, ephemeral_keys.public_key, X25519_PUBLIC_KEY_SIZE);
ed25519_sign(&keyex_init.signature, keyex_init.public_key,
}
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
key_exchange_response_packet_t client_keyex;
result =
receive_packet(socket, &packet_type, &payload, &payload_len);
x25519_shared_secret_t shared_secret;
x25519_derive_shared_secret(&shared_secret, client_keyex.public_key,
&client->crypto_ctx.ephemeral_private_key);
memcpy(client->crypto_ctx.shared_secret, shared_secret, X25519_SHARED_SECRET_SIZE);
Phase 2: Authentication
Step 2: Send Authentication Challenge
auth_request_packet_t auth_req;
result =
receive_packet(socket, &packet_type, &payload, &payload_len);
bool client_in_whitelist = false;
client_in_whitelist = true;
break;
}
}
if (!client_in_whitelist) {
return -1;
}
}
auth_challenge_packet_t challenge;
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
auth_response_packet_t auth_resp;
result =
receive_packet(socket, &packet_type, &payload, &payload_len);
if (!ed25519_verify(auth_resp.signature, challenge.challenge,
return -1;
}
derive_session_keys(&client->crypto_ctx.session_keys, &client->crypto_ctx.shared_secret);
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:
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:
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:
Per-Client Crypto Contexts
Each client has an independent crypto context stored in client_info_t:
typedef struct {
x25519_private_key_t ephemeral_private_key;
x25519_shared_secret_t shared_secret;
session_keys_t session_keys;
ed25519_public_key_t client_public_key;
bool authenticated;
handshake_state_t state;
} crypto_handshake_ctx_t;
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)
{
memcpy(ciphertext, plaintext, plaintext_len);
*ciphertext_len = plaintext_len;
return 0;
}
int result = xsalsa20poly1305_encrypt(ciphertext, ciphertext_len,
plaintext, plaintext_len,
NULL, 0,
client->crypto_ctx.session_keys.encryption_key);
return result;
}
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)
{
memcpy(plaintext, ciphertext, ciphertext_len);
*plaintext_len = ciphertext_len;
return 0;
}
int result = xsalsa20poly1305_decrypt(plaintext, plaintext_len,
ciphertext, ciphertext_len,
NULL, 0,
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:
return -1;
}
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)
#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:
bool client_in_whitelist = false;
client_in_whitelist = true;
break;
}
}
if (!client_in_whitelist) {
log_warn(
"Client public key not in whitelist - rejecting connection");
return -1;
}
}
#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