ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
src/client/crypto.c
Go to the documentation of this file.
1
149#include "main.h"
150#include "crypto.h"
151#include "server.h"
153#include <ascii-chat/options/rcu.h> // For RCU-based options access
154#include <ascii-chat/common.h>
166#include <ascii-chat/network/mdns/discovery.h> // For pubkey_to_hex
167#include <ascii-chat/util/time.h>
169#include "capture.h"
170
171#include <string.h>
172#include <stdio.h>
173#include <sys/stat.h>
174#include <sodium.h>
175
179
188static lifecycle_t g_crypto_lc = LIFECYCLE_INIT;
189static uint8_t g_crypto_mode = ACIP_CRYPTO_ENCRYPT; // Default: encrypt only, no authentication
190
199 log_debug("CLIENT_CRYPTO: Setting crypto mode to 0x%02x", mode);
200 g_crypto_mode = mode;
201}
202
211 log_debug("CLIENT_CRYPTO_INIT: Starting crypto initialization");
212
213 // Reset if already initialized (allows reinit on reconnection)
214 if (lifecycle_reset(&g_crypto_lc)) {
215 log_debug("CLIENT_CRYPTO_INIT: Already initialized, resetting for reinit");
217 }
218
219 log_debug("CLIENT_CRYPTO_INIT: Initializing crypto handshake context");
220 log_debug("CLIENT_CRYPTO_INIT: crypto_mode=%d (NONE=0, ENCRYPT=1, AUTH=2, FULL=3)", g_crypto_mode);
221
222 // Check if we have an SSH key, password, or neither
223 int result;
224 bool is_ssh_key = false;
225 private_key_t private_key;
226
227 // Get string options safely
228 const options_t *opts = options_get();
229 const char *encrypt_key = opts && opts->encrypt_key[0] != '\0' ? opts->encrypt_key : "";
230 const char *password = opts && opts->password[0] != '\0' ? opts->password : "";
231 const char *address = opts && opts->address[0] != '\0' ? opts->address : "localhost";
232 int port = opts ? opts->port : OPT_PORT_INT_DEFAULT;
233 const char *server_key = opts && opts->server_key[0] != '\0' ? opts->server_key : "";
234
235 // Load client private key if provided via --key
236 if (strlen(encrypt_key) > 0) {
237 // --key supports file-based authentication (SSH keys, GPG keys via gpg:keyid)
238
239 // For SSH key files (not gpg:keyid format), validate the file exists
240 if (strncmp(encrypt_key, "gpg:", 4) != 0) {
241 if (validate_ssh_key_file(encrypt_key) != 0) {
242 return -1;
243 }
244 }
245
246 // Parse key (handles SSH files and gpg:keyid format)
247 log_debug("CLIENT_CRYPTO_INIT: Loading private key for authentication: %s", encrypt_key);
248 if (parse_private_key(encrypt_key, &private_key) == ASCIICHAT_OK) {
249 log_debug("Successfully parsed SSH private key");
250 log_debug("CLIENT_CRYPTO_INIT: Parsed key type=%d, KEY_TYPE_ED25519=%d", private_key.type, KEY_TYPE_ED25519);
251 is_ssh_key = true;
252 } else {
253 log_error("Failed to parse SSH key file: %s", encrypt_key);
254 log_error("This may be due to:");
255 log_error(" - Wrong password for encrypted key");
256 log_error(" - Unsupported key type (only Ed25519 is currently supported)");
257 log_error(" - Corrupted key file");
258 log_error("");
259 log_error("Note: RSA and ECDSA keys are not yet supported");
260 log_error("To generate an Ed25519 key: ssh-keygen -t ed25519");
261 return -1;
262 }
263 }
264
265 if (is_ssh_key) {
266 // Use SSH private key for authentication
267 log_debug("CLIENT_CRYPTO_INIT: Using SSH key for authentication");
268
269 // Initialize crypto context (generates ephemeral X25519 keys)
270 result = crypto_handshake_init("crypto_client_local", &g_crypto_ctx, false); // false = client
271 if (result != ASCIICHAT_OK) {
272 FATAL(result, "Failed to initialize crypto handshake");
273 }
274
275 // Store the Ed25519 keys for authentication
276 memcpy(&g_crypto_ctx.client_private_key, &private_key, sizeof(private_key_t));
277
278 // Extract Ed25519 public key from private key
283
284 // Extract GPG key ID if this is a GPG key (format: "gpg:KEYID")
285 if (strncmp(encrypt_key, "gpg:", 4) == 0) {
286 const char *key_id = encrypt_key + 4;
287 size_t key_id_len = strlen(key_id);
288 // Accept 8, 16, or 40 character GPG key IDs (short/long/full fingerprint)
289 if (key_id_len == 8 || key_id_len == 16 || key_id_len == 40) {
291 log_debug("CLIENT_CRYPTO_INIT: Extracted client GPG key ID (%zu chars): %s", key_id_len,
293 } else {
294 log_warn("CLIENT_CRYPTO_INIT: Invalid GPG key ID length: %zu (expected 8, 16, or 40)", key_id_len);
296 }
297 } else {
298 // Not a GPG key, clear the field
300 }
301
302 // SSH key is already configured in the handshake context above
303 // No additional setup needed - SSH keys are used only for authentication
304
305 // Clear the temporary private_key variable (we've already copied it to g_crypto_ctx)
306 sodium_memzero(&private_key, sizeof(private_key));
307
308 // If password is also provided, derive password key for dual authentication
309 if (strlen(password) > 0) {
310 log_debug("CLIENT_CRYPTO_INIT: Password also provided, deriving password key");
312 if (crypto_result != CRYPTO_OK) {
313 log_error("Failed to derive password key: %s", crypto_result_to_string(crypto_result));
314 return -1;
315 }
317 log_debug("Password authentication enabled alongside SSH key");
318 }
319
320 } else if (strlen(GET_OPTION(password)) > 0) {
321 // Password provided - use password-based initialization
322 log_debug("CLIENT_CRYPTO_INIT: Using password authentication");
323 result = crypto_handshake_init_with_password("crypto_client_local", &g_crypto_ctx, false,
324 GET_OPTION(password)); // false = client
325 if (result != ASCIICHAT_OK) {
326 FATAL(result, "Failed to initialize crypto handshake with password");
327 }
328 } else {
329 // No password or SSH key - use standard initialization with random keys
330 log_debug("CLIENT_CRYPTO_INIT: Using standard initialization");
331 result = crypto_handshake_init("crypto_client_local", &g_crypto_ctx, false); // false = client
332 if (result != ASCIICHAT_OK) {
333 FATAL(result, "Failed to initialize crypto handshake");
334 }
335 }
336
337 log_debug("CLIENT_CRYPTO_INIT: crypto_handshake_init succeeded");
338
339 // Set up server connection info for known_hosts
341 const char *server_ip = server_connection_get_ip();
342 log_debug("CLIENT_CRYPTO_INIT: server_connection_get_ip() returned: '%s'", server_ip ? server_ip : "NULL");
343 SAFE_STRNCPY(g_crypto_ctx.server_ip, server_ip ? server_ip : "", sizeof(g_crypto_ctx.server_ip) - 1);
344
345 // Use port from server_connection_set_port() if available (e.g., from WebSocket URL),
346 // otherwise use the port from options (TCP/default path)
347 int connection_port = server_connection_get_port();
348 g_crypto_ctx.server_port = (uint16_t)(connection_port > 0 ? connection_port : port);
349 log_debug("CLIENT_CRYPTO_INIT: Set server_ip='%s', server_port=%u", g_crypto_ctx.server_ip, g_crypto_ctx.server_port);
350
351 // Configure server key verification if specified
352 if (strlen(server_key) > 0) {
355 log_debug("Server key verification enabled: %s", server_key);
356 }
357
358 // If --require-client-verify is set, perform ACDS session lookup for server identity
359 if (GET_OPTION(require_client_verify) && strlen(GET_OPTION(session_string)) > 0) {
360 log_debug("--require-client-verify enabled: performing ACDS session lookup for '%s'", GET_OPTION(session_string));
361
362 // Connect to ACDS server (configurable via --acds-server and --acds-port options)
363 acds_client_config_t acds_config;
365 SAFE_STRNCPY(acds_config.server_address, GET_OPTION(discovery_server), sizeof(acds_config.server_address));
366 acds_config.server_port = GET_OPTION(discovery_port);
367 acds_config.timeout_ms = 5 * MS_PER_SEC_INT;
368
369 // ACDS key verification (optional in debug builds, only if --discovery-service-key is provided)
370 if (strlen(GET_OPTION(discovery_service_key)) > 0) {
371 log_debug("Verifying ACDS server key for %s...", acds_config.server_address);
372 uint8_t acds_pubkey[32];
373 asciichat_error_t verify_result =
374 discovery_keys_verify(acds_config.server_address, GET_OPTION(discovery_service_key), acds_pubkey);
375 if (verify_result != ASCIICHAT_OK) {
376 log_error("ACDS key verification failed for %s", acds_config.server_address);
377 return -1;
378 }
379 log_debug("ACDS server key verified successfully");
380 }
381#ifndef NDEBUG
382 else {
383 log_debug("Skipping ACDS key verification (debug build, no --discovery-service-key provided)");
384 }
385#endif
386
387 acds_client_t acds_client;
388 asciichat_error_t acds_result = acds_client_connect(&acds_client, &acds_config);
389 if (acds_result != ASCIICHAT_OK) {
390 log_error("Failed to connect to ACDS server at %s:%d", acds_config.server_address, acds_config.server_port);
391 return -1;
392 }
393
394 // Perform SESSION_LOOKUP to get server's identity
395 acds_session_lookup_result_t lookup_result;
396 acds_result = acds_session_lookup(&acds_client, GET_OPTION(session_string), &lookup_result);
397 acds_client_disconnect(&acds_client);
398
399 if (acds_result != ASCIICHAT_OK || !lookup_result.found) {
400 log_error("ACDS session lookup failed for '%s': %s", GET_OPTION(session_string),
401 lookup_result.found ? "session not found" : "lookup error");
402 return -1;
403 }
404
405 // Convert server's Ed25519 public key (32 bytes) to hex string
406 char server_key_hex[65]; // 32 bytes * 2 + null terminator
407 for (size_t i = 0; i < 32; i++) {
408 SAFE_SNPRINTF(&server_key_hex[i * 2], 3, "%02x", lookup_result.host_pubkey[i]);
409 }
410 server_key_hex[64] = '\0';
411
412 // Set expected server key for verification during handshake
415 log_debug("ACDS session lookup succeeded - server identity will be verified");
416 log_debug("Expected server key (from ACDS): %s", server_key_hex);
417 }
418
419 // Mark initialization as complete
420 lifecycle_init(&g_crypto_lc, "client_crypto");
421
422 log_debug("Client crypto handshake initialized");
423 log_debug("CLIENT_CRYPTO_INIT: Initialization complete");
424 return 0;
425}
426
436 // If client has --no-encrypt, skip handshake entirely
437 if (GET_OPTION(no_encrypt)) {
438 log_debug("Client has --no-encrypt, skipping crypto handshake");
439 return 0;
440 }
441
442 // If we reach here, crypto must be initialized for encryption
443 if (!lifecycle_is_initialized(&g_crypto_lc)) {
444 log_error("Crypto not initialized but server requires encryption");
445 log_error("Server requires encrypted connection but client has no encryption configured");
446 log_error("Use --key to specify a client key or --password for password authentication");
447 return CONNECTION_ERROR_AUTH_FAILED; // No retry - configuration error
448 }
449
450 log_debug("Starting crypto handshake with server...");
451
452 START_TIMER("client_crypto_handshake");
453
454 // Step 0a: Send protocol version to server
455 protocol_version_packet_t client_version = {0};
456 client_version.protocol_version = HOST_TO_NET_U16(1); // Protocol version 1
457 client_version.protocol_revision = HOST_TO_NET_U16(0); // Revision 0
458 client_version.supports_encryption = g_crypto_mode; // Send crypto mode bitmask
459 client_version.compression_algorithms = 0; // No compression for now
460 client_version.compression_threshold = 0;
461 client_version.feature_flags = 0;
462
463 int result =
464 packet_send_via_transport(transport, PACKET_TYPE_PROTOCOL_VERSION, &client_version, sizeof(client_version), 0);
465 if (result != 0) {
466 log_error("Failed to send protocol version to server");
467 STOP_TIMER("client_crypto_handshake");
468 return -1;
469 }
470 log_debug("CLIENT_CRYPTO_HANDSHAKE: Protocol version sent successfully");
471
472 // Step 0b: Receive server's protocol version
473 packet_type_t packet_type;
474 void *payload = NULL;
475 void *alloc_buffer = NULL;
476 size_t payload_len = 0;
477
478 result = packet_receive_via_transport(transport, &packet_type, &payload, &payload_len, &alloc_buffer);
479 if (result != ASCIICHAT_OK || packet_type != PACKET_TYPE_PROTOCOL_VERSION) {
480 log_error("Failed to receive server protocol version (got type %u)", packet_type);
481 log_error("Packet type 0x%x (decimal %u) - Expected 0x%x (decimal %d)", packet_type, packet_type,
483 log_error("This suggests a protocol mismatch or packet corruption");
484 log_error("Raw packet type bytes: %02x %02x %02x %02x", (packet_type >> 0) & 0xFF, (packet_type >> 8) & 0xFF,
485 (packet_type >> 16) & 0xFF, (packet_type >> 24) & 0xFF);
486 if (alloc_buffer) {
487 buffer_pool_free(NULL, alloc_buffer, 0);
488 }
489 STOP_TIMER("client_crypto_handshake");
490 return -1;
491 }
492
493 if (payload_len != sizeof(protocol_version_packet_t)) {
494 log_error("Invalid protocol version packet size: %zu, expected %zu", payload_len,
496 buffer_pool_free(NULL, alloc_buffer, 0);
497 STOP_TIMER("client_crypto_handshake");
498 return -1;
499 }
500
501 protocol_version_packet_t server_version;
502 memcpy(&server_version, payload, sizeof(protocol_version_packet_t));
503 buffer_pool_free(NULL, alloc_buffer, 0);
504
505 // Convert from network byte order
506 uint16_t server_proto_version = NET_TO_HOST_U16(server_version.protocol_version);
507 uint16_t server_proto_revision = NET_TO_HOST_U16(server_version.protocol_revision);
508 uint8_t server_mode = server_version.supports_encryption;
509
510 log_info("Server protocol version: %u.%u (crypto mode: 0x%02x)", server_proto_version, server_proto_revision,
511 server_mode);
512
513 // Verify server echoed our mode
514 if (server_mode != g_crypto_mode) {
515 log_error("Server mode mismatch: got 0x%02x, expected 0x%02x", server_mode, g_crypto_mode);
516 STOP_TIMER("client_crypto_handshake");
518 }
519
520 // Step 0c: Send crypto capabilities to server
521 log_debug("CLIENT_CRYPTO_HANDSHAKE: Sending crypto capabilities");
522 crypto_capabilities_packet_t client_caps = {0};
526 client_caps.requires_verification =
528 client_caps.preferred_kex = KEX_ALGO_X25519;
530 client_caps.preferred_cipher =
532
533 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_CAPABILITIES, &client_caps, sizeof(client_caps), 0);
534 if (result != 0) {
535 log_error("Failed to send crypto capabilities to server");
536 STOP_TIMER("client_crypto_handshake");
537 return -1;
538 }
539 log_debug("CLIENT_CRYPTO_HANDSHAKE: Crypto capabilities sent successfully");
540
541 // Step 0d: Receive server's crypto parameters
542 log_debug("CLIENT_CRYPTO_HANDSHAKE: Receiving server crypto parameters");
543 payload = NULL;
544 alloc_buffer = NULL;
545 payload_len = 0;
546
547 result = packet_receive_via_transport(transport, &packet_type, &payload, &payload_len, &alloc_buffer);
548 if (result != ASCIICHAT_OK || packet_type != PACKET_TYPE_CRYPTO_PARAMETERS) {
549 log_error("Failed to receive server crypto parameters (got type %u)", packet_type);
550 if (alloc_buffer) {
551 buffer_pool_free(NULL, alloc_buffer, 0);
552 }
553 STOP_TIMER("client_crypto_handshake");
554 return -1;
555 }
556
557 if (payload_len != sizeof(crypto_parameters_packet_t)) {
558 log_error("Invalid crypto parameters packet size: %zu, expected %zu", payload_len,
560 buffer_pool_free(NULL, alloc_buffer, 0);
561 STOP_TIMER("client_crypto_handshake");
562 return -1;
563 }
564
565 crypto_parameters_packet_t server_params;
566 memcpy(&server_params, payload, sizeof(crypto_parameters_packet_t));
567 buffer_pool_free(NULL, alloc_buffer, 0);
568
569 // Convert from network byte order
570 uint16_t kex_pubkey_size = NET_TO_HOST_U16(server_params.kex_public_key_size);
571 uint16_t auth_pubkey_size = NET_TO_HOST_U16(server_params.auth_public_key_size);
572 uint16_t signature_size = NET_TO_HOST_U16(server_params.signature_size);
573 uint16_t shared_secret_size = NET_TO_HOST_U16(server_params.shared_secret_size);
574
575 log_debug("Server crypto parameters: KEX=%u, Auth=%u, Cipher=%u (key_size=%u, auth_size=%u, sig_size=%u, "
576 "secret_size=%u, verification=%u)",
577 server_params.selected_kex, server_params.selected_auth, server_params.selected_cipher, kex_pubkey_size,
578 auth_pubkey_size, signature_size, shared_secret_size, server_params.verification_enabled);
579 log_debug("Raw server_params.kex_public_key_size = %u (network byte order)", server_params.kex_public_key_size);
580
581 // Set the crypto parameters in the handshake context
582 result = crypto_handshake_set_parameters(&g_crypto_ctx, &server_params);
583 if (result != ASCIICHAT_OK) {
584 FATAL(result, "Failed to set crypto parameters");
585 }
586
587 // Store verification flag - server will verify client identity (whitelist check)
588 // This is independent of whether server provides its own identity
589 if (server_params.verification_enabled) {
592 log_info("Server will verify client identity (whitelist enabled)");
593 }
594
595 // Validate that server chose algorithms we support
596 if (server_params.selected_kex != KEX_ALGO_X25519) {
597 log_error("Server selected unsupported KEX algorithm: %u", server_params.selected_kex);
598 STOP_TIMER("client_crypto_handshake");
600 }
601
602 // Validate cipher selection based on negotiated mode
603 bool expect_cipher = ACIP_CRYPTO_HAS_ENCRYPT(g_crypto_mode);
604 if (expect_cipher && server_params.selected_cipher != CIPHER_ALGO_XSALSA20_POLY1305) {
605 log_error("Server selected unsupported cipher algorithm: %u", server_params.selected_cipher);
606 STOP_TIMER("client_crypto_handshake");
608 }
609 if (!expect_cipher && server_params.selected_cipher != CIPHER_ALGO_NONE) {
610 log_error("Server chose cipher %u but client requested no encryption", server_params.selected_cipher);
611 STOP_TIMER("client_crypto_handshake");
613 }
614
615 log_debug("CLIENT_CRYPTO_HANDSHAKE: Protocol negotiation completed successfully");
616
617 // Step 0.5: Send CRYPTO_CLIENT_HELLO with expected server key (multi-key support)
618 // This allows the server to select the correct identity key from g_server_identity_keys[]
620 log_debug("CLIENT_CRYPTO_HANDSHAKE: Sending expected server key to support multi-key selection");
621
622 // Parse expected server key to get the Ed25519 public key
623 public_key_t expected_keys[MAX_CLIENTS];
624 size_t num_expected_keys = 0;
625
626 if (parse_public_keys(g_crypto_ctx.expected_server_key, expected_keys, &num_expected_keys, MAX_CLIENTS) == 0 &&
627 num_expected_keys > 0) {
628 // Send the first expected key (client should only have one expected server key)
629 // Format: 32-byte Ed25519 public key
630 log_debug("Sending CRYPTO_CLIENT_HELLO with expected server key for multi-key selection");
631 result = packet_send_via_transport(transport, PACKET_TYPE_CRYPTO_CLIENT_HELLO, expected_keys[0].key,
633
634 if (result != ASCIICHAT_OK) {
635 FATAL(result, "Failed to send CRYPTO_CLIENT_HELLO packet");
636 }
637
638 // Log the key fingerprint for debugging
639 char hex_key[65];
640 pubkey_to_hex(expected_keys[0].key, hex_key);
641 log_debug("Sent expected server key: %s", hex_key);
642 } else {
643 log_warn("Failed to parse expected server key '%s' - server will use default key",
645 }
646 }
647
648 // Step 1: Receive server's public key and send our public key
649 log_debug("CLIENT_CRYPTO_HANDSHAKE: Starting key exchange");
650 {
651 packet_type_t packet_type;
652 void *payload = NULL;
653 void *alloc_buffer = NULL;
654 size_t payload_len = 0;
655 int recv_result = packet_receive_via_transport(transport, &packet_type, &payload, &payload_len, &alloc_buffer);
656 if (recv_result != ASCIICHAT_OK) {
657 if (alloc_buffer) {
658 buffer_pool_free(NULL, alloc_buffer, 0);
659 }
660 FATAL(recv_result, "Failed to receive KEY_EXCHANGE_INIT from server");
661 }
662 // Client handshake functions consume the received payload buffer
663 result = crypto_handshake_client_key_exchange(&g_crypto_ctx, transport, packet_type, payload, payload_len);
664 buffer_pool_free(NULL, alloc_buffer, 0);
665 }
666 if (result != ASCIICHAT_OK) {
667#ifdef _WIN32
668 // On Windows: Cleanup capture resources before exiting to prevent Media Foundation threads from hanging exit()
669 // Media Foundation creates background COM threads that can block exit() if not properly shut down
671#endif
672 FATAL(result, "Crypto key exchange failed");
673 }
674 log_debug("CLIENT_CRYPTO_HANDSHAKE: Key exchange completed successfully");
675
676 // SECURITY: Warn when server requires client verification but client has no identity key
677 bool client_has_identity = (g_crypto_ctx.client_public_key.type == KEY_TYPE_ED25519);
678
679 if (g_crypto_ctx.require_client_auth && !client_has_identity) {
680 // Server requires client verification but client has no identity key
681 log_warn("Server requires client verification but client has no identity key");
682
683 // Check if we're running interactively (stdin is a terminal)
684 // In debug builds with CLAUDECODE, skip interactive prompts (LLM can't type)
685#ifndef NDEBUG
686 bool skip_interactive = platform_getenv("CLAUDECODE") != NULL;
687#else
688 bool skip_interactive = false;
689#endif
690 if (!skip_interactive && platform_is_interactive()) {
691 // Interactive mode - prompt user for confirmation
692 // Lock terminal for the warning message
693 bool previous_terminal_state = log_lock_terminal();
694
695 log_plain("\n"
696 "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
697 "@ WARNING: CLIENT AUTHENTICATION REQUIRED @\n"
698 "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
699 "\n"
700 "The server requires client authentication (--client-keys enabled),\n"
701 "but you have not provided a client identity key with --key.\n"
702 "\n"
703 "To connect to this server, you need to:\n"
704 " 1. Generate an Ed25519 key: ssh-keygen -t ed25519\n"
705 " 2. Add the public key to the server's --client-keys list\n"
706 " 3. Connect with: ascii-chat client --key /path/to/private/key\n");
707
708 // Unlock before prompt (prompt_yes_no handles its own terminal locking)
709 log_unlock_terminal(previous_terminal_state);
710
711 // Prompt user - default is No since this will likely fail
712 if (!platform_prompt_yes_no("Do you want to continue anyway (this will likely fail)", false)) {
713 log_plain("Connection aborted by user.");
714 exit(0); // User declined - exit cleanly
715 }
716
717 log_plain("Warning: Continuing without client identity key (connection may fail).\n");
718 } else {
719 // Non-interactive mode (background/script) - just log warning and continue
720 log_warn("Non-interactive mode: Continuing without client identity key (connection may fail)");
721 }
722 }
723
724 // Step 2: Receive auth challenge and send response
725 log_debug("CLIENT_CRYPTO: Sending auth response to server...");
726 log_debug("CLIENT_CRYPTO_HANDSHAKE: Starting auth response");
727 {
728 packet_type_t packet_type;
729 void *payload = NULL;
730 void *alloc_buffer = NULL;
731 size_t payload_len = 0;
732 int recv_result = packet_receive_via_transport(transport, &packet_type, &payload, &payload_len, &alloc_buffer);
733 if (recv_result != ASCIICHAT_OK) {
734 if (alloc_buffer) {
735 buffer_pool_free(NULL, alloc_buffer, 0);
736 }
737 FATAL(recv_result, "Failed to receive auth challenge from server");
738 }
739 // Client handshake functions consume the received payload buffer
740 result = crypto_handshake_client_auth_response(&g_crypto_ctx, transport, packet_type, payload, payload_len);
741 buffer_pool_free(NULL, alloc_buffer, 0);
742 }
743 if (result != ASCIICHAT_OK) {
744 FATAL(result, "Crypto authentication failed");
745 }
746 log_debug("CLIENT_CRYPTO: Auth response sent successfully");
747 log_debug("CLIENT_CRYPTO_HANDSHAKE: Auth response completed successfully");
748
749 // Check if handshake completed during auth response (no authentication needed)
751 // Propagate encryption flag to crypto context
753 // Set crypto context on the persistent transport now that handshake is complete
755 STOP_TIMER_AND_LOG(debug, 100 * NS_PER_MS_INT, "client_crypto_handshake",
756 "Crypto handshake completed successfully (no authentication)");
757 return 0;
758 }
759
760 // Step 3: Receive handshake complete message
761 log_debug("CLIENT_CRYPTO_HANDSHAKE: Waiting for handshake complete message");
762 {
763 packet_type_t packet_type;
764 void *payload = NULL;
765 void *alloc_buffer = NULL;
766 size_t payload_len = 0;
767 int recv_result = packet_receive_via_transport(transport, &packet_type, &payload, &payload_len, &alloc_buffer);
768 if (recv_result != ASCIICHAT_OK) {
769 if (alloc_buffer) {
770 buffer_pool_free(NULL, alloc_buffer, 0);
771 }
772 FATAL(recv_result, "Failed to receive handshake completion from server");
773 }
774 // Client handshake functions consume the received payload buffer
775 result = crypto_handshake_client_complete(&g_crypto_ctx, transport, packet_type, payload, payload_len);
776 buffer_pool_free(NULL, alloc_buffer, 0);
777 }
778 if (result != ASCIICHAT_OK) {
779 FATAL(result, "Crypto handshake completion failed");
780 }
781
782 // Propagate encryption flag to crypto context
784 // Set crypto context on the persistent transport now that handshake is complete
786
787 STOP_TIMER_AND_LOG(debug, 100 * NS_PER_MS_INT, "client_crypto_handshake", "Crypto handshake completed successfully");
788 log_debug("CLIENT_CRYPTO_HANDSHAKE: Handshake completed successfully, state=%d", g_crypto_ctx.state);
789 return 0;
790}
791
800 if (!lifecycle_is_initialized(&g_crypto_lc) || GET_OPTION(no_encrypt)) {
801 return false;
802 }
803
805}
806
815 if (!crypto_client_is_ready()) {
816 return NULL;
817 }
818
820}
821
834int crypto_client_encrypt_packet(const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext,
835 size_t ciphertext_size, size_t *ciphertext_len) {
837 ciphertext, ciphertext_size, ciphertext_len);
838}
839
852int crypto_client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext,
853 size_t plaintext_size, size_t *plaintext_len) {
854 return crypto_decrypt_packet_or_passthrough(&g_crypto_ctx, crypto_client_is_ready(), ciphertext, ciphertext_len,
855 plaintext, plaintext_size, plaintext_len);
856}
857
864 // Shutdown if initialized
865 if (lifecycle_shutdown(&g_crypto_lc)) {
867 log_debug("Client crypto handshake cleaned up");
868 }
869}
870
871// =============================================================================
872// Session Rekeying Functions
873// =============================================================================
874
883 if (!lifecycle_is_initialized(&g_crypto_lc) || !crypto_client_is_ready()) {
884 return false;
885 }
887}
888
897 if (!lifecycle_is_initialized(&g_crypto_lc) || !crypto_client_is_ready()) {
898 log_error("Cannot initiate rekey: crypto not initialized or not ready");
899 return -1;
900 }
901
903 if (!transport) {
904 log_error("Cannot initiate rekey: invalid transport");
905 return -1;
906 }
907
909 if (result != ASCIICHAT_OK) {
910 log_error("Failed to send REKEY_REQUEST: %d", result);
911 return -1;
912 }
913
914 return 0;
915}
916
926int crypto_client_process_rekey_request(const uint8_t *packet, size_t packet_len) {
927 if (!lifecycle_is_initialized(&g_crypto_lc) || !crypto_client_is_ready()) {
928 log_error("Cannot process rekey request: crypto not initialized or not ready");
929 return -1;
930 }
931
933 if (result != ASCIICHAT_OK) {
934 log_error("Failed to process REKEY_REQUEST: %d", result);
935 return -1;
936 }
937
938 return 0;
939}
940
949 if (!lifecycle_is_initialized(&g_crypto_lc) || !crypto_client_is_ready()) {
950 log_error("Cannot send rekey response: crypto not initialized or not ready");
951 return -1;
952 }
953
955 if (!transport) {
956 log_error("Cannot send rekey response: invalid transport");
957 return -1;
958 }
959
961 if (result != ASCIICHAT_OK) {
962 log_error("Failed to send REKEY_RESPONSE: %d", result);
963 return -1;
964 }
965
966 return 0;
967}
968
978int crypto_client_process_rekey_response(const uint8_t *packet, size_t packet_len) {
979 if (!lifecycle_is_initialized(&g_crypto_lc) || !crypto_client_is_ready()) {
980 log_error("Cannot process rekey response: crypto not initialized or not ready");
981 return -1;
982 }
983
985 if (result != ASCIICHAT_OK) {
986 log_error("Failed to process REKEY_RESPONSE: %d", result);
987 return -1;
988 }
989
990 return 0;
991}
992
1001 if (!lifecycle_is_initialized(&g_crypto_lc) || !crypto_client_is_ready()) {
1002 log_error("Cannot send rekey complete: crypto not initialized or not ready");
1003 return -1;
1004 }
1005
1007 if (!transport) {
1008 log_error("Cannot send rekey complete: invalid transport");
1009 return -1;
1010 }
1011
1013 if (result != ASCIICHAT_OK) {
1014 log_error("Failed to send REKEY_COMPLETE: %d", result);
1015 return -1;
1016 }
1017
1018 return 0;
1019}
asciichat_error_t acds_session_lookup(acds_client_t *client, const char *session_string, acds_session_lookup_result_t *result)
Look up session by string.
void acds_client_config_init_defaults(acds_client_config_t *config)
Initialize ACDS client configuration with defaults.
asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config)
Connect to ACDS server.
void acds_client_disconnect(acds_client_t *client)
Disconnect from ACDS server.
ACIP client-side protocol library and ACDS client connection API.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
📹 Unified media capture abstraction for session-based video sources
⚙️ Common definitions, error codes, macros, and types shared throughout the application
asciichat_error_t crypto_handshake_rekey_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Send REKEY_COMPLETE packet (initiator side)
asciichat_error_t crypto_handshake_init(const char *name, crypto_handshake_context_t *ctx, bool is_server)
Initialize named crypto handshake context.
asciichat_error_t crypto_handshake_rekey_response(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Send REKEY_RESPONSE packet (responder side)
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)
Get the crypto context for encryption/decryption.
asciichat_error_t crypto_handshake_init_with_password(const char *name, crypto_handshake_context_t *ctx, bool is_server, const char *password)
Initialize named crypto handshake context with password authentication.
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
Cleanup crypto handshake context with secure memory wiping.
asciichat_error_t crypto_handshake_process_rekey_request(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process received REKEY_REQUEST packet (responder side)
bool crypto_handshake_is_ready(const crypto_handshake_context_t *ctx)
Check if handshake is complete and encryption is ready.
bool crypto_handshake_should_rekey(const crypto_handshake_context_t *ctx)
Check if rekeying should be triggered for this handshake context.
asciichat_error_t crypto_handshake_set_parameters(crypto_handshake_context_t *ctx, const crypto_parameters_packet_t *params)
Set crypto parameters from crypto_parameters_packet_t.
asciichat_error_t crypto_handshake_process_rekey_response(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process received REKEY_RESPONSE packet (initiator side)
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)
Encrypt with automatic passthrough if crypto not ready.
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)
Decrypt with automatic passthrough if crypto not ready.
asciichat_error_t crypto_handshake_rekey_request(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Send REKEY_REQUEST packet (initiator side)
Common declarations and data structures for cryptographic handshake.
void pubkey_to_hex(const uint8_t pubkey[32], char hex_out[65])
Convert Ed25519 public key to hex string.
Definition discovery.c:49
asciichat_error_t discovery_keys_verify(const char *acds_server, const char *key_spec, uint8_t pubkey_out[32])
Verify and load ACDS server public key.
Discovery Server Public Key Trust Management.
🔄 Network byte order conversion helpers
#define HOST_TO_NET_U16(val)
Definition endian.h:96
#define NET_TO_HOST_U16(val)
Definition endian.h:111
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
void capture_cleanup()
Cleanup capture subsystem.
acip_transport_t * server_connection_get_transport(void)
Get ACIP transport instance.
int server_connection_get_port(void)
Get the server port.
const char * server_connection_get_ip()
Get resolved server IP address.
crypto_handshake_context_t g_crypto_ctx
Global crypto handshake context for this client connection.
const crypto_context_t * crypto_client_get_context(void)
Get crypto context for encryption/decryption.
bool crypto_client_should_rekey(void)
Check if session rekeying should be triggered.
void crypto_client_cleanup(void)
Cleanup crypto client resources.
int crypto_client_send_rekey_response(void)
Send REKEY_RESPONSE packet to server.
int client_crypto_handshake(acip_transport_t *transport)
Perform crypto handshake with server.
int crypto_client_initiate_rekey(void)
Initiate session rekeying (client-initiated)
void client_crypto_set_mode(uint8_t mode)
Set crypto mode for handshake (encryption + authentication)
int client_crypto_init(void)
Initialize client crypto handshake.
int crypto_client_encrypt_packet(const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t ciphertext_size, size_t *ciphertext_len)
Encrypt a packet for transmission.
int crypto_client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_len)
Decrypt a received packet.
bool crypto_client_is_ready(void)
Check if crypto handshake is ready.
int crypto_client_process_rekey_request(const uint8_t *packet, size_t packet_len)
Process received REKEY_REQUEST packet from server.
int crypto_client_process_rekey_response(const uint8_t *packet, size_t packet_len)
Process received REKEY_RESPONSE packet from server.
int crypto_client_send_rekey_complete(void)
Send REKEY_COMPLETE packet to server and commit to new key.
unsigned short uint16_t
Definition common.h:57
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:492
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:158
unsigned char uint8_t
Definition common.h:56
const char * crypto_result_to_string(crypto_result_t result)
Convert crypto result to human-readable string.
crypto_result_t
Cryptographic operation result codes.
#define ED25519_PUBLIC_KEY_SIZE
Ed25519 public key size in bytes.
crypto_result_t crypto_derive_password_key(crypto_context_t *ctx, const char *password)
Derive key from password using Argon2id.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ CRYPTO_HANDSHAKE_READY
uint8_t key[32]
Definition key_types.h:71
asciichat_error_t parse_private_key(const char *key_path, private_key_t *key_out)
Parse SSH private key from file.
Definition keys.c:193
char comment[256]
Definition key_types.h:72
key_type_t type
Definition key_types.h:92
char key_comment[256]
Definition key_types.h:100
asciichat_error_t parse_public_keys(const char *input, public_key_t *keys_out, size_t *num_keys, size_t max_keys)
Parse all SSH/GPG public keys from any format (returns all keys)
Definition keys.c:344
uint8_t public_key[32]
Definition key_types.h:99
key_type_t type
Definition key_types.h:70
asciichat_error_t validate_ssh_key_file(const char *key_path)
Validate SSH key file before parsing.
Definition ssh_keys.c:939
@ KEY_TYPE_ED25519
Definition key_types.h:52
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:26
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
bool log_lock_terminal(void)
Lock terminal output for exclusive access by the calling thread.
Definition log/log.c:787
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define log_plain(...)
Plain logging - writes to both log file and stderr without timestamps or log levels.
Definition log/log.h:618
void log_unlock_terminal(bool previous_state)
Release terminal lock and flush buffered messages.
Definition log/log.c:796
#define STOP_TIMER_AND_LOG(log_level, threshold_ns, timer_name, msg_fmt,...)
Stop a timer and log the result with a custom message.
Definition time.h:612
#define START_TIMER(name_fmt,...)
Start a timer with formatted name.
Definition time.h:333
#define NS_PER_MS_INT
Definition time.h:156
#define STOP_TIMER(name_fmt,...)
Stop a timer with formatted name and return elapsed time.
Definition time.h:403
#define MS_PER_SEC_INT
Definition time.h:164
#define AUTH_ALGO_ED25519
Ed25519 authentication (Edwards-curve signatures)
Definition packet.h:1065
#define ACIP_CRYPTO_HAS_AUTH(mode)
Check if crypto mode includes authentication.
Definition packet.h:1099
#define KEX_ALGO_X25519
X25519 key exchange (Curve25519)
Definition packet.h:1064
#define ACIP_CRYPTO_HAS_ENCRYPT(mode)
Check if crypto mode includes payload encryption.
Definition packet.h:1092
#define AUTH_ALGO_NONE
No authentication (plaintext mode)
Definition packet.h:1066
#define ACIP_CRYPTO_ENCRYPT
Encryption only (no identity verification)
Definition packet.h:1083
#define CIPHER_ALGO_NONE
No encryption (plaintext mode)
Definition packet.h:1068
#define CIPHER_ALGO_XSALSA20_POLY1305
XSalsa20-Poly1305 authenticated encryption.
Definition packet.h:1067
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
#define OPT_PORT_INT_DEFAULT
Default TCP port for client/server communication (integer)
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
@ PACKET_TYPE_CRYPTO_CLIENT_HELLO
Client -> Server: Expected server key fingerprint for multi-key selection (UNENCRYPTED)
Definition packet.h:299
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
Definition packet.h:288
@ PACKET_TYPE_CRYPTO_PARAMETERS
Server -> Client: Chosen algorithms + data sizes (UNENCRYPTED)
Definition packet.h:309
@ PACKET_TYPE_CRYPTO_CAPABILITIES
Client -> Server: Supported crypto algorithms (UNENCRYPTED)
Definition packet.h:307
bool platform_prompt_yes_no(const char *question, bool default_yes)
Prompt the user for a yes/no answer.
Definition util.c:83
bool platform_is_interactive(void)
Check if interactive prompting is available.
const char * platform_getenv(const char *name)
Get an environment variable value.
Definition wasm/system.c:39
Client-side handshake functions.
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
Platform initialization and static synchronization helpers.
asciichat_error_t crypto_handshake_client_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Client: Wait for handshake complete confirmation.
asciichat_error_t crypto_handshake_client_key_exchange(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Client: Process server's public key and send our public key.
asciichat_error_t crypto_handshake_client_auth_response(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Client: Process auth challenge and send response.
bool lifecycle_is_initialized(const lifecycle_t *lc)
Definition lifecycle.c:155
bool lifecycle_init(lifecycle_t *lc, const char *name)
Definition lifecycle.c:26
bool lifecycle_reset(lifecycle_t *lc)
Definition lifecycle.c:167
bool lifecycle_shutdown(lifecycle_t *lc)
Definition lifecycle.c:108
Lock-free module lifecycle state machine using pure stdatomic.
#define LIFECYCLE_INIT
Static initializer for module-global lifecycle variables (no sync primitive)
Definition lifecycle.h:68
Cross-platform interactive prompting utilities.
asciichat_error_t packet_receive_via_transport(acip_transport_t *transport, packet_type_t *type, void **payload, size_t *payload_len, void **alloc_buffer)
Receive a packet via transport with header parsing and validation.
Definition send.c:122
asciichat_error_t packet_send_via_transport(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, uint32_t client_id)
Send packet via transport with proper header (exported for generic wrappers)
Definition send.c:41
ACIP shared/bidirectional packet sending functions.
ascii-chat Server Mode Entry Point Header
@ CONNECTION_ERROR_AUTH_FAILED
Authentication failure (no retry)
Server cryptographic operations and per-client handshake management.
Server-side H.265 decoding pipeline for multi-client streams.
ACDS client connection configuration.
char server_address[256]
ACDS server address (e.g., "discovery.ascii.chat" or "127.0.0.1")
uint32_t timeout_ms
Connection timeout in milliseconds.
uint16_t server_port
ACDS server port (default: 27225)
ACDS client connection handle.
Session lookup result.
bool found
Session exists.
uint8_t host_pubkey[32]
Host's Ed25519 public key.
Transport instance structure.
Definition transport.h:214
crypto_context_t * crypto_ctx
Optional encryption context.
Definition transport.h:216
Crypto capabilities packet structure (Packet Type 14)
Definition packet.h:952
uint8_t preferred_auth
Preferred authentication algorithm (AUTH_ALGO_*)
Definition packet.h:964
uint16_t supported_auth_algorithms
Supported authentication algorithms bitmask (AUTH_ALGO_*)
Definition packet.h:956
uint16_t supported_kex_algorithms
Supported key exchange algorithms bitmask (KEX_ALGO_*)
Definition packet.h:954
uint8_t preferred_cipher
Preferred cipher algorithm (CIPHER_ALGO_*)
Definition packet.h:966
uint16_t supported_cipher_algorithms
Supported cipher algorithms bitmask (CIPHER_ALGO_*)
Definition packet.h:958
uint8_t preferred_kex
Preferred key exchange algorithm (KEX_ALGO_*)
Definition packet.h:962
uint8_t requires_verification
Server verification requirement flag (1=required, 0=optional)
Definition packet.h:960
Cryptographic context structure.
crypto_handshake_state_t state
Crypto parameters packet structure (Packet Type 15)
Definition packet.h:981
uint8_t selected_kex
Selected key exchange algorithm (KEX_ALGO_*)
Definition packet.h:983
uint8_t selected_cipher
Selected cipher algorithm (CIPHER_ALGO_*)
Definition packet.h:987
uint16_t auth_public_key_size
Authentication public key size in bytes (e.g., 32 for Ed25519, 1952 for Dilithium3)
Definition packet.h:993
uint8_t selected_auth
Selected authentication algorithm (AUTH_ALGO_*)
Definition packet.h:985
uint8_t verification_enabled
Server verification enabled flag (1=enabled, 0=disabled)
Definition packet.h:989
uint16_t signature_size
Signature size in bytes (e.g., 64 for Ed25519, 3309 for Dilithium3)
Definition packet.h:995
uint16_t shared_secret_size
Shared secret size in bytes (e.g., 32 for X25519)
Definition packet.h:997
uint16_t kex_public_key_size
Key exchange public key size in bytes (e.g., 32 for X25519, 1568 for Kyber1024)
Definition packet.h:991
Consolidated options structure.
char password[256]
Password string.
char server_key[256]
Expected server public key (client)
char encrypt_key[256]
SSH/GPG key file path (first –key flag, kept for compatibility)
char address[256]
Server address (client) or bind address (server)
Private key structure (for server –ssh-key)
Definition key_types.h:91
Protocol version negotiation packet structure (Packet Type 1)
Definition packet.h:818
uint8_t compression_threshold
Compression threshold percentage (0-100, e.g., 80 = compress if >80% size reduction)
Definition packet.h:828
uint16_t protocol_revision
Minor protocol revision (server can be newer)
Definition packet.h:822
uint16_t protocol_version
Major protocol version (must match for compatibility)
Definition packet.h:820
uint16_t feature_flags
Feature flags bitmask (FEATURE_RLE_ENCODING, etc.)
Definition packet.h:830
uint8_t compression_algorithms
Supported compression algorithms bitmask (COMPRESS_ALGO_*)
Definition packet.h:826
uint8_t supports_encryption
Encryption & authentication mode bitmask (ACIP_CRYPTO_* constants: NONE, ENCRYPT, AUTH,...
Definition packet.h:824
Public key structure.
Definition key_types.h:69
⏱️ High-precision timing utilities using sokol_time.h and uthash
Transport abstraction layer for ACIP protocol.