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

Server cryptographic operations and per-client handshake management. More...

Go to the source code of this file.

Functions

int server_crypto_init (void)
 
int server_crypto_handshake (client_info_t *client)
 
bool crypto_server_is_ready (uint32_t client_id)
 
const crypto_context_t * crypto_server_get_context (uint32_t client_id)
 
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)
 
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)
 
void crypto_server_cleanup_client (uint32_t client_id)
 

Detailed Description

Server cryptographic operations and per-client handshake management.

Definition in file server/crypto.h.

Function Documentation

◆ crypto_server_cleanup_client()

void crypto_server_cleanup_client ( uint32_t  client_id)

Cleanup crypto resources for a specific client

Parameters
client_idClient ID to cleanup crypto for

Definition at line 749 of file src/server/crypto.c.

749 {
750 client_info_t *client = find_client_by_id(client_id);
751 if (!client) {
752 return;
753 }
754
755 if (client->crypto_initialized) {
756 crypto_handshake_destroy(&client->crypto_handshake_ctx);
757 client->crypto_initialized = false;
758 log_debug("Crypto handshake cleaned up for client %u", client_id);
759 }
760}
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
client_info_t * find_client_by_id(uint32_t client_id)
Fast O(1) client lookup by ID using hash table.

References crypto_handshake_destroy(), and find_client_by_id().

◆ crypto_server_decrypt_packet()

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 
)

Decrypt a received packet from a specific client

Parameters
client_idClient ID that sent the packet
ciphertextEncrypted data to decrypt
ciphertext_lenLength of encrypted data
plaintextOutput buffer for decrypted data
plaintext_sizeSize of output buffer
plaintext_lenOutput length of decrypted data
Returns
0 on success, -1 on failure

Definition at line 733 of file src/server/crypto.c.

734 {
735 client_info_t *client = find_client_by_id(client_id);
736 if (!client) {
737 return -1;
738 }
739
740 return crypto_decrypt_packet_or_passthrough(&client->crypto_handshake_ctx, crypto_server_is_ready(client_id),
741 ciphertext, ciphertext_len, plaintext, plaintext_size, plaintext_len);
742}
asciichat_error_t crypto_decrypt_packet_or_passthrough(const crypto_handshake_context_t *ctx, bool crypto_ready, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_len)
bool crypto_server_is_ready(uint32_t client_id)

References crypto_decrypt_packet_or_passthrough(), crypto_server_is_ready(), and find_client_by_id().

Referenced by process_encrypted_packet().

◆ crypto_server_encrypt_packet()

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 
)

Encrypt a packet for transmission to a specific client

Parameters
client_idClient ID to encrypt for
plaintextPlaintext data to encrypt
plaintext_lenLength of plaintext data
ciphertextOutput buffer for encrypted data
ciphertext_sizeSize of output buffer
ciphertext_lenOutput length of encrypted data
Returns
0 on success, -1 on failure

Definition at line 711 of file src/server/crypto.c.

712 {
713 client_info_t *client = find_client_by_id(client_id);
714 if (!client) {
715 return -1;
716 }
717
718 return crypto_encrypt_packet_or_passthrough(&client->crypto_handshake_ctx, crypto_server_is_ready(client_id),
719 plaintext, plaintext_len, ciphertext, ciphertext_size, ciphertext_len);
720}
asciichat_error_t crypto_encrypt_packet_or_passthrough(const crypto_handshake_context_t *ctx, bool crypto_ready, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_len)

References crypto_encrypt_packet_or_passthrough(), crypto_server_is_ready(), and find_client_by_id().

◆ crypto_server_get_context()

const crypto_context_t * crypto_server_get_context ( uint32_t  client_id)

Get crypto context for encryption/decryption for a specific client

Parameters
client_idClient ID to get context for
Returns
crypto context or NULL if not ready

Definition at line 687 of file src/server/crypto.c.

687 {
688 if (!crypto_server_is_ready(client_id)) {
689 return NULL;
690 }
691
692 client_info_t *client = find_client_by_id(client_id);
693 if (!client) {
694 return NULL;
695 }
696
697 return crypto_handshake_get_context(&client->crypto_handshake_ctx);
698}
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)

References crypto_handshake_get_context(), crypto_server_is_ready(), and find_client_by_id().

Referenced by add_client().

◆ crypto_server_is_ready()

bool crypto_server_is_ready ( uint32_t  client_id)

Check if crypto handshake is ready for a specific client

Parameters
client_idClient ID to check
Returns
true if encryption is ready, false otherwise

Definition at line 663 of file src/server/crypto.c.

663 {
664 if (GET_OPTION(no_encrypt)) {
665 return false;
666 }
667
668 client_info_t *client = find_client_by_id(client_id);
669 if (!client) {
670 return false;
671 }
672
673 if (!client->crypto_initialized) {
674 return false;
675 }
676
677 bool ready = crypto_handshake_is_ready(&client->crypto_handshake_ctx);
678 return ready;
679}
bool crypto_handshake_is_ready(const crypto_handshake_context_t *ctx)

References crypto_handshake_is_ready(), and find_client_by_id().

Referenced by crypto_server_decrypt_packet(), crypto_server_encrypt_packet(), crypto_server_get_context(), and process_encrypted_packet().

◆ server_crypto_handshake()

int server_crypto_handshake ( client_info_t *  client)

Perform crypto handshake with client

Parameters
clientClient info structure
Returns
0 on success, -1 on failure

Definition at line 178 of file src/server/crypto.c.

178 {
179 if (GET_OPTION(no_encrypt)) {
180 log_debug("Crypto handshake skipped (disabled)");
181 return 0;
182 }
183
184 if (!client) {
185 FATAL(ERROR_CRYPTO_HANDSHAKE, "Client is NULL for crypto handshake");
186 return -1;
187 }
188
189 // Initialize crypto context for this specific client
190 const options_t *opts = options_get();
191 const char *password = opts && opts->password[0] != '\0' ? opts->password : "";
192
193 int init_result;
194 if (strlen(password) > 0) {
195 // Password provided - use password-based encryption (even if SSH key is also provided)
196 log_debug("SERVER_CRYPTO_HANDSHAKE: Using password-based encryption");
197 init_result = crypto_handshake_init_with_password(&client->crypto_handshake_ctx, true, password); // true = server
198 } else {
199 // Server has SSH key - use standard initialization
200 log_debug("SERVER_CRYPTO_HANDSHAKE: Using passwordless-based encryption");
201 init_result = crypto_handshake_init(&client->crypto_handshake_ctx, true); // true = server
202 }
203
204 if (init_result != ASCIICHAT_OK) {
205 FATAL(init_result, "Failed to initialize crypto handshake for client %u", atomic_load(&client->client_id));
206 return -1;
207 }
208 // Do NOT set crypto_initialized here - it must be set after handshake completes
209 client->crypto_initialized = false;
210
211 // Set up server keys in the handshake context
212 if (g_server_encryption_enabled && g_server_private_key.type == KEY_TYPE_ED25519) {
213 // Copy server private key to handshake context for signing
214 memcpy(&client->crypto_handshake_ctx.server_private_key, &g_server_private_key, sizeof(private_key_t));
215
216 // Extract Ed25519 public key from private key for identity
217 client->crypto_handshake_ctx.server_public_key.type = KEY_TYPE_ED25519;
218 memcpy(client->crypto_handshake_ctx.server_public_key.key, g_server_private_key.public_key,
219 ED25519_PUBLIC_KEY_SIZE);
220
221 // SSH key is already configured in the handshake context above
222 // No additional setup needed - SSH keys are used only for authentication
223
224 log_debug("Server identity keys configured for client %u", atomic_load(&client->client_id));
225 }
226
227 // Set up client whitelist if specified
229 client->crypto_handshake_ctx.require_client_auth = true;
230 client->crypto_handshake_ctx.client_whitelist = g_client_whitelist;
231 client->crypto_handshake_ctx.num_whitelisted_clients = g_num_whitelisted_clients;
232 log_info("Client whitelist enabled: %zu authorized keys", g_num_whitelisted_clients);
233 }
234
235 // If --require-server-verify is set, require clients to provide identity keys
236 // This ensures only clients with Ed25519 identity keys can connect
237 if (GET_OPTION(require_server_verify)) {
238 client->crypto_handshake_ctx.require_client_auth = true;
239 log_info("--require-server-verify enabled: clients must provide identity keys");
240 }
241
242 log_debug("Starting crypto handshake with client %u...", atomic_load(&client->client_id));
243
244 START_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
245
246 // Step 0a: Receive client's protocol version
247 log_debug("SERVER_CRYPTO_HANDSHAKE: Receiving client protocol version");
248 packet_type_t packet_type;
249 void *payload = NULL;
250 size_t payload_len = 0;
251
252 log_debug("SERVER_CRYPTO_HANDSHAKE: About to receive packet from client %u", atomic_load(&client->client_id));
253
254 // Protect socket access during crypto handshake
255 mutex_lock(&client->client_state_mutex);
256 socket_t socket = client->socket;
257 mutex_unlock(&client->client_state_mutex);
258
259 if (socket == INVALID_SOCKET_VALUE) {
260 log_debug("SERVER_CRYPTO_HANDSHAKE: Socket is invalid for client %u", atomic_load(&client->client_id));
261 client->crypto_initialized = false;
262 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
263 return -1;
264 }
265
266 int result = receive_packet(socket, &packet_type, &payload, &payload_len);
267 log_debug("SERVER_CRYPTO_HANDSHAKE: Received packet from client %u: result=%d, type=%u",
268 atomic_load(&client->client_id), result, packet_type);
269
270 // Handle client disconnection gracefully
271 if (result != ASCIICHAT_OK) {
272 log_info("Client %u disconnected during crypto handshake (connection error)", atomic_load(&client->client_id));
273 if (payload) {
274 buffer_pool_free(NULL, payload, payload_len);
275 }
276 client->crypto_initialized = false;
277 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
278 return -1; // Return error but don't crash the server
279 }
280
281 // If client sent something other than PROTOCOL_VERSION, it means client is using --no-encrypt
282 // In this case, we need to store the packet for the caller to process and skip the handshake
283 if (packet_type != PACKET_TYPE_PROTOCOL_VERSION) {
284 log_info("Client %u sent packet type %u instead of PROTOCOL_VERSION - using unencrypted mode",
285 atomic_load(&client->client_id), packet_type);
286
287 // Store this packet in the client structure so the caller can process it
288 // This is a bit of a hack, but necessary to preserve the packet we already received
289 client->pending_packet_type = packet_type;
290 client->pending_packet_payload = payload;
291 client->pending_packet_length = payload_len;
292
293 // Mark crypto as not initialized (no encryption)
294 client->crypto_initialized = false;
295 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
296 return 0; // Success - just no encryption
297 }
298
299 if (payload_len != sizeof(protocol_version_packet_t)) {
300 log_error("Invalid protocol version packet size: %zu, expected %zu", payload_len,
301 sizeof(protocol_version_packet_t));
302 buffer_pool_free(NULL, payload, payload_len);
303 client->crypto_initialized = false;
304 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
305 return -1;
306 }
307
308 protocol_version_packet_t client_version;
309 memcpy(&client_version, payload, sizeof(protocol_version_packet_t));
310 log_debug("SERVER_CRYPTO_HANDSHAKE: About to free payload for client %u", atomic_load(&client->client_id));
311 buffer_pool_free(NULL, payload, payload_len);
312 log_debug("SERVER_CRYPTO_HANDSHAKE: Payload freed for client %u", atomic_load(&client->client_id));
313
314 // Convert from network byte order
315 uint16_t client_proto_version = NET_TO_HOST_U16(client_version.protocol_version);
316 uint16_t client_proto_revision = NET_TO_HOST_U16(client_version.protocol_revision);
317
318 uint8_t client_mode = client_version.supports_encryption;
319 log_info("Client %u protocol version: %u.%u (crypto mode: 0x%02x)", atomic_load(&client->client_id),
320 client_proto_version, client_proto_revision, client_mode);
321
322 log_debug("SERVER_CRYPTO_HANDSHAKE: About to check encryption support for client %u",
323 atomic_load(&client->client_id));
324
325 // Reject ACIP_CRYPTO_NONE (plaintext only) - at minimum, ACIP handshake must run
326 if (client_mode == ACIP_CRYPTO_NONE) {
327 log_error("Client %u does not support any crypto protocol", atomic_load(&client->client_id));
328 log_info("Client %u disconnected - crypto protocol required", atomic_load(&client->client_id));
329 client->crypto_initialized = false;
330 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
331 return -1; // Return error but don't crash the server
332 }
333
334 // Store encryption mode for setting encrypt_data flag later
335 client->encrypt_data = ACIP_CRYPTO_HAS_ENCRYPT(client_mode);
336
337 // Step 0b: Send our protocol version to client
338 log_debug("SERVER_CRYPTO_HANDSHAKE: About to prepare server protocol version for client %u",
339 atomic_load(&client->client_id));
340 protocol_version_packet_t server_version = {0};
341 log_debug("SERVER_CRYPTO_HANDSHAKE: Initialized server_version struct for client %u",
342 atomic_load(&client->client_id));
343 server_version.protocol_version = HOST_TO_NET_U16(1); // Protocol version 1
344 server_version.protocol_revision = HOST_TO_NET_U16(0); // Revision 0
345 server_version.supports_encryption = client_mode; // Echo client's mode
346 server_version.compression_algorithms = 0; // No compression for now
347 server_version.compression_threshold = 0;
348 server_version.feature_flags = 0;
349
350 log_debug("SERVER_CRYPTO_HANDSHAKE: About to call send_protocol_version_packet for client %u",
351 atomic_load(&client->client_id));
352 result = send_protocol_version_packet(socket, &server_version);
353 log_debug("SERVER_CRYPTO_HANDSHAKE: send_protocol_version_packet returned %d for client %u", result,
354 atomic_load(&client->client_id));
355 if (result != 0) {
356 log_error("Failed to send protocol version to client %u", atomic_load(&client->client_id));
357 log_info("Client %u disconnected - failed to send protocol version", atomic_load(&client->client_id));
358 client->crypto_initialized = false;
359 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
360 return -1; // Return error but don't crash the server
361 }
362 log_debug("SERVER_CRYPTO_HANDSHAKE: Protocol version sent successfully to client %u",
363 atomic_load(&client->client_id));
364
365 // Step 0c: Receive client's crypto capabilities
366 payload = NULL;
367 payload_len = 0;
368
369 result = receive_packet(socket, &packet_type, &payload, &payload_len);
370 if (result != ASCIICHAT_OK) {
371 log_info("Client %u disconnected during crypto capabilities exchange", atomic_load(&client->client_id));
372 if (payload) {
373 buffer_pool_free(NULL, payload, payload_len);
374 }
375 client->crypto_initialized = false;
376 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
377 return -1; // Return error but don't crash the server
378 }
379
380 if (packet_type != PACKET_TYPE_CRYPTO_CAPABILITIES) {
381 log_error("Server received packet type 0x%x (decimal %u) - Expected 0x%x (decimal %d)", packet_type, packet_type,
382 PACKET_TYPE_CRYPTO_CAPABILITIES, PACKET_TYPE_CRYPTO_CAPABILITIES);
383 log_error("Raw packet type bytes: %02x %02x %02x %02x", (packet_type >> 0) & 0xFF, (packet_type >> 8) & 0xFF,
384 (packet_type >> 16) & 0xFF, (packet_type >> 24) & 0xFF);
385 if (payload) {
386 buffer_pool_free(NULL, payload, payload_len);
387 }
388 log_info("Client %u disconnected due to protocol mismatch in crypto capabilities", atomic_load(&client->client_id));
389 client->crypto_initialized = false;
390 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
391 return -1; // Return error but don't crash the server
392 }
393
394 if (payload_len != sizeof(crypto_capabilities_packet_t)) {
395 SET_ERRNO(ERROR_NETWORK_PROTOCOL,
396 "Invalid crypto capabilities packet size: %zu (expected %zu) - we should disconnect this client",
397 payload_len, sizeof(crypto_capabilities_packet_t));
398 if (payload) {
399 buffer_pool_free(NULL, payload, payload_len);
400 payload = NULL;
401 }
402 client->crypto_initialized = false;
403 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
404 return -1;
405 }
406
407 if (!payload) {
408 SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Crypto capabilities payload is NULL after validation");
409 log_error("Client %u crypto capabilities payload is NULL after validation - disconnecting",
410 atomic_load(&client->client_id));
411 LOG_ERRNO_IF_SET("Crypto handshake: missing capabilities payload");
412 client->crypto_initialized = false;
413 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
414 return -1;
415 }
416
417 // Copy and validate payload immediately after NULL check to ensure analyzer understands safety
418 crypto_capabilities_packet_t client_caps;
419 memcpy(&client_caps, payload, sizeof(crypto_capabilities_packet_t));
420
421 // Free the buffer after use
422 buffer_pool_free(NULL, payload, payload_len);
423 payload = NULL; // Clear reference to prevent use-after-free
424
425 // Convert from network byte order
426 uint16_t supported_kex = NET_TO_HOST_U16(client_caps.supported_kex_algorithms);
427 uint16_t supported_auth = NET_TO_HOST_U16(client_caps.supported_auth_algorithms);
428 uint16_t supported_cipher = NET_TO_HOST_U16(client_caps.supported_cipher_algorithms);
429
430 log_debug("Client %u crypto capabilities: KEX=0x%04x, Auth=0x%04x, Cipher=0x%04x", atomic_load(&client->client_id),
431 supported_kex, supported_auth, supported_cipher);
432
433 // Step 0d: Select crypto algorithms and send parameters to client
434 crypto_parameters_packet_t server_params = {0};
435
436 // Select algorithms (for now, we only support X25519 + Ed25519 + XSalsa20-Poly1305)
437 server_params.selected_kex = KEX_ALGO_X25519;
438 server_params.selected_cipher =
439 ACIP_CRYPTO_HAS_ENCRYPT(client_mode) ? CIPHER_ALGO_XSALSA20_POLY1305 : CIPHER_ALGO_NONE;
440
441 // Select authentication algorithm based on server configuration
442 // Note: Password authentication is not a separate algorithm - it's a mode of operation
443 // that affects key derivation. The authentication algorithm refers to signature verification.
444 //
445 // We require Ed25519 authentication if:
446 // - Server has an identity key (g_server_encryption_enabled AND g_server_private_key is Ed25519)
447 // - This is needed to send authenticated KEY_EXCHANGE_INIT with identity + signature
448 // - Client whitelist verification happens during authentication phase, not key exchange
449 if (g_server_encryption_enabled && g_server_private_key.type == KEY_TYPE_ED25519) {
450 // SSH key authentication (Ed25519 signatures) - server has identity key
451 server_params.selected_auth = AUTH_ALGO_ED25519;
452 } else {
453 // No signature-based authentication during key exchange
454 // Client authentication will be required during auth phase if whitelist is enabled
455 server_params.selected_auth = AUTH_ALGO_NONE;
456 }
457
458 // Set verification flag based on client whitelist
459 server_params.verification_enabled = (g_num_whitelisted_clients > 0) ? 1 : 0;
460
461 // Set crypto parameters for current algorithms
462 server_params.kex_public_key_size = CRYPTO_PUBLIC_KEY_SIZE; // X25519 public key size
463
464 // Only set auth/signature sizes if we're using authentication
465 if (server_params.selected_auth == AUTH_ALGO_ED25519) {
466 server_params.auth_public_key_size = ED25519_PUBLIC_KEY_SIZE; // Ed25519 public key size
467 server_params.signature_size = ED25519_SIGNATURE_SIZE; // Ed25519 signature size
468 } else {
469 server_params.auth_public_key_size = 0; // No authentication
470 server_params.signature_size = 0; // No signature
471 }
472
473 server_params.shared_secret_size = CRYPTO_PUBLIC_KEY_SIZE; // X25519 shared secret size
474 server_params.nonce_size = CRYPTO_NONCE_SIZE; // XSalsa20 nonce size
475 server_params.mac_size = CRYPTO_MAC_SIZE; // Poly1305 MAC size
476 server_params.hmac_size = CRYPTO_HMAC_SIZE; // HMAC-SHA256 size
477
478 log_debug("SERVER_CRYPTO_HANDSHAKE: Sending crypto parameters to client %u", atomic_load(&client->client_id));
479 result = send_crypto_parameters_packet(socket, &server_params);
480 if (result != 0) {
481 log_error("Failed to send crypto parameters to client %u", atomic_load(&client->client_id));
482 client->crypto_initialized = false;
483 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
484 return -1;
485 }
486 log_debug("Server selected crypto for client %u: KEX=%u, Auth=%u, Cipher=%u", atomic_load(&client->client_id),
487 server_params.selected_kex, server_params.selected_auth, server_params.selected_cipher);
488
489 // Set the crypto parameters in the handshake context
490 result = crypto_handshake_set_parameters(&client->crypto_handshake_ctx, &server_params);
491 if (result != ASCIICHAT_OK) {
492 client->crypto_initialized = false;
493 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
494 FATAL(result, "Failed to set crypto parameters for client %u", atomic_load(&client->client_id));
495 return -1;
496 }
497
498 // Step 0.5: Receive CRYPTO_CLIENT_HELLO (multi-key support)
499 // When server has multiple identity keys, client MUST send CLIENT_HELLO to select the correct key
500 // For single-key servers, CLIENT_HELLO is optional (backward compatibility)
502 log_debug("SERVER_CRYPTO_HANDSHAKE: Waiting for optional CRYPTO_CLIENT_HELLO from client %u",
503 atomic_load(&client->client_id));
504
505 // Try to receive CLIENT_HELLO packet
506 packet_type_t hello_packet_type;
507 uint8_t *hello_payload = NULL;
508 size_t hello_payload_len = 0;
509
510 result = receive_packet(socket, &hello_packet_type, (void **)&hello_payload, &hello_payload_len);
511 if (result != ASCIICHAT_OK) {
512 log_info("Client %u disconnected during handshake initialization", atomic_load(&client->client_id));
513 if (hello_payload) {
514 buffer_pool_free(NULL, hello_payload, hello_payload_len);
515 }
516 client->crypto_initialized = false;
517 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
518 return -1;
519 }
520
521 if (hello_packet_type == PACKET_TYPE_CRYPTO_CLIENT_HELLO) {
522 // Client sent expected server key - select matching key
523 if (hello_payload_len != ED25519_PUBLIC_KEY_SIZE) {
524 log_error("Invalid CRYPTO_CLIENT_HELLO size: %zu (expected %d)", hello_payload_len, ED25519_PUBLIC_KEY_SIZE);
525 buffer_pool_free(NULL, hello_payload, hello_payload_len);
526 client->crypto_initialized = false;
527 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
528 return -1;
529 }
530
531 const uint8_t *expected_server_key = hello_payload;
532
533 // Log the expected key for debugging
534 char hex_expected[65];
535 pubkey_to_hex(expected_server_key, hex_expected);
536 log_debug("Client %u expects server key: %s", atomic_load(&client->client_id), hex_expected);
537
538 // Search g_server_identity_keys[] for matching public key
539 bool key_found = false;
540 for (size_t i = 0; i < g_num_server_identity_keys; i++) {
541 if (sodium_memcmp(expected_server_key, g_server_identity_keys[i].public_key, ED25519_PUBLIC_KEY_SIZE) == 0) {
542 // Found matching key - use this one for handshake
543 memcpy(&client->crypto_handshake_ctx.server_private_key, &g_server_identity_keys[i], sizeof(private_key_t));
544
545 char hex_selected[65];
546 pubkey_to_hex(g_server_identity_keys[i].public_key, hex_selected);
547 log_debug("Client %u selected server identity key #%zu: %s", atomic_load(&client->client_id), i + 1,
548 hex_selected);
549
550 key_found = true;
551 break;
552 }
553 }
554
555 if (!key_found) {
556 // Client requested a key we don't have - reject connection
557 log_error("Client %u requested unknown server key %s - rejecting connection", atomic_load(&client->client_id),
558 hex_expected);
559 buffer_pool_free(NULL, hello_payload, hello_payload_len);
560 client->crypto_initialized = false;
561 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
562 return -1;
563 }
564
565 buffer_pool_free(NULL, hello_payload, hello_payload_len);
566 } else {
567 // Client didn't send CLIENT_HELLO
568 // This is ONLY acceptable for single-key servers (backward compatibility)
570 log_error("Client %u must send CRYPTO_CLIENT_HELLO when server has multiple keys (got packet type %u)",
571 atomic_load(&client->client_id), hello_packet_type);
572 buffer_pool_free(NULL, hello_payload, hello_payload_len);
573 client->crypto_initialized = false;
574 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
575 return -1;
576 }
577
578 // Single-key server: Old client didn't send CLIENT_HELLO
579 // Store this packet for later processing (it's probably the first handshake packet)
580 log_debug("Client %u is using old protocol (no CLIENT_HELLO) - using default key",
581 atomic_load(&client->client_id));
582
583 // Store pending packet so we can process it after KEY_EXCHANGE_INIT
584 client->pending_packet_type = hello_packet_type;
585 client->pending_packet_payload = hello_payload;
586 client->pending_packet_length = hello_payload_len;
587 }
588 }
589
590 // Step 1: Send our public key to client
591 log_debug("About to call crypto_handshake_server_start");
592 result = crypto_handshake_server_start_socket(&client->crypto_handshake_ctx, socket);
593 if (result != ASCIICHAT_OK) {
594 log_error("Crypto handshake start failed for client %u: %s", atomic_load(&client->client_id),
595 asciichat_error_string(result));
596 LOG_ERRNO_IF_SET("Crypto handshake: failed to send KEY_EXCHANGE_INIT");
597 client->crypto_initialized = false;
598 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
599 return -1;
600 }
601
602 // Step 2: Receive client's public key and send auth challenge
603 result = crypto_handshake_server_auth_challenge_socket(&client->crypto_handshake_ctx, socket);
604 if (result != ASCIICHAT_OK) {
605 log_error("Crypto authentication challenge failed for client %u: %s", atomic_load(&client->client_id),
606 asciichat_error_string(result));
607 client->crypto_initialized = false;
608 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
609 return -1; // Return error to disconnect client gracefully
610 }
611
612 // Check if handshake completed during auth challenge (no authentication needed)
613 if (client->crypto_handshake_ctx.state == CRYPTO_HANDSHAKE_READY) {
614 uint32_t cid = atomic_load(&client->client_id);
615 client->crypto_initialized = true;
616 // Propagate encryption flag to crypto context
617 client->crypto_handshake_ctx.crypto_ctx.encrypt_data = client->encrypt_data;
618 STOP_TIMER_AND_LOG(debug, 100 * NS_PER_MS_INT, "server_crypto_handshake_client_%u",
619 "Crypto handshake completed successfully for client %u (no authentication)", cid);
620 return 0;
621 }
622
623 // Step 3: Receive auth response and complete handshake
624 result = crypto_handshake_server_complete_socket(&client->crypto_handshake_ctx, socket);
625 if (result != ASCIICHAT_OK) {
626 if (result == ERROR_NETWORK || result == ERROR_NETWORK_PROTOCOL || result == ERROR_CRYPTO_AUTH ||
627 result == ERROR_CRYPTO_VERIFICATION || result == ERROR_CRYPTO) {
628 if (result == ERROR_NETWORK) {
629 log_info("Client %u disconnected during authentication", atomic_load(&client->client_id));
630 } else {
631 log_error("Crypto authentication failed for client %u: %s", atomic_load(&client->client_id),
632 asciichat_error_string(result));
633 }
634 LOG_ERRNO_IF_SET("Crypto handshake completion failed");
635 client->crypto_initialized = false;
636 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
637 return -1;
638 }
639 STOP_TIMER("server_crypto_handshake_client_%u", atomic_load(&client->client_id));
640 FATAL(result, "Crypto authentication response failed for client %u", atomic_load(&client->client_id));
641 client->crypto_initialized = false;
642 return -1;
643 }
644
645 uint32_t cid = atomic_load(&client->client_id);
646 client->crypto_initialized = true;
647 // Propagate encryption flag to crypto context
648 client->crypto_handshake_ctx.crypto_ctx.encrypt_data = client->encrypt_data;
649 STOP_TIMER_AND_LOG(debug, 100 * NS_PER_MS_INT, "server_crypto_handshake_client_%u",
650 "Crypto handshake completed successfully for client %u", cid);
651
652 // Send success notification to client (encrypted channel now established)
653 log_info_client(client, "Encryption established - secure channel ready");
654 return 0;
655}
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
asciichat_error_t crypto_handshake_init_with_password(crypto_handshake_context_t *ctx, bool is_server, const char *password)
asciichat_error_t crypto_handshake_init(crypto_handshake_context_t *ctx, bool is_server)
asciichat_error_t crypto_handshake_set_parameters(crypto_handshake_context_t *ctx, const crypto_parameters_packet_t *params)
void pubkey_to_hex(const uint8_t pubkey[32], char hex_out[65])
Definition discovery.c:49
size_t g_num_whitelisted_clients
Number of whitelisted clients.
bool g_server_encryption_enabled
Global flag indicating if server encryption is enabled.
public_key_t g_client_whitelist[MAX_CLIENTS]
Global client public key whitelist.
private_key_t g_server_private_key
Global server private key (first identity key, for backward compatibility)
private_key_t g_server_identity_keys[MAX_IDENTITY_KEYS]
Global server identity keys array (multi-key support)
size_t g_num_server_identity_keys
Number of loaded server identity keys.
int socket_t
asciichat_error_t crypto_handshake_server_auth_challenge_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Auth challenge using socket (TCP clients only)
asciichat_error_t crypto_handshake_server_start_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Start handshake using socket (TCP clients only)
asciichat_error_t crypto_handshake_server_complete_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Complete handshake using socket (TCP clients only)
int send_protocol_version_packet(socket_t sockfd, const protocol_version_packet_t *version)
Send protocol version packet.
Definition packet.c:1013
int receive_packet(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a basic packet without encryption.
Definition packet.c:766
int send_crypto_parameters_packet(socket_t sockfd, const crypto_parameters_packet_t *params)
Send crypto parameters packet.
Definition packet.c:1041
const options_t * options_get(void)
Definition rcu.c:347

References buffer_pool_free(), crypto_handshake_init(), crypto_handshake_init_with_password(), crypto_handshake_server_auth_challenge_socket(), crypto_handshake_server_complete_socket(), crypto_handshake_server_start_socket(), crypto_handshake_set_parameters(), g_client_whitelist, g_num_server_identity_keys, g_num_whitelisted_clients, g_server_encryption_enabled, g_server_identity_keys, g_server_private_key, options_get(), pubkey_to_hex(), receive_packet(), send_crypto_parameters_packet(), and send_protocol_version_packet().

Referenced by add_client().

◆ server_crypto_init()

int server_crypto_init ( void  )

Initialize server crypto system (global initialization)

Returns
0 on success, -1 on failure

Definition at line 161 of file src/server/crypto.c.

161 {
162 // Check if encryption is disabled
163 if (GET_OPTION(no_encrypt)) {
164 log_info("Encryption disabled via --no-encrypt");
165 return 0;
166 }
167
168 log_debug("Server crypto system initialized (per-client contexts will be created on demand)");
169 return 0;
170}

Referenced by add_client().