ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
src/client/crypto.c
Go to the documentation of this file.
1
149#include "crypto.h"
150#include "server.h"
151#include "options/options.h"
152#include "options/rcu.h" // For RCU-based options access
153#include "common.h"
154#include "util/endian.h"
157#include "crypto/crypto.h"
158#include "crypto/keys.h"
159#include "buffer_pool.h"
160#include "network/packet.h"
162#include "util/time.h"
163#include "util/endian.h"
164#include "capture.h"
165
166#include <string.h>
167#include <stdio.h>
168#include <sys/stat.h>
169#include <sodium.h>
170
171#include "platform/question.h"
172
173// Global crypto handshake context for this client connection
174// NOTE: We use the crypto context from server.c to match the handshake
176
185static bool g_crypto_initialized = false;
186
195 log_debug("CLIENT_CRYPTO_INIT: Starting crypto initialization");
196 if (g_crypto_initialized) {
197 log_debug("CLIENT_CRYPTO_INIT: Already initialized, cleaning up and reinitializing");
199 g_crypto_initialized = false;
200 }
201
202 // Check if encryption is disabled
203 if (GET_OPTION(no_encrypt)) {
204 log_info("Encryption disabled via --no-encrypt");
205 log_debug("CLIENT_CRYPTO_INIT: Encryption disabled, returning 0");
206 return 0;
207 }
208
209 log_debug("CLIENT_CRYPTO_INIT: Initializing crypto handshake context");
210
211 // Check if we have an SSH key, password, or neither
212 int result;
213 bool is_ssh_key = false;
214 private_key_t private_key;
215
216 // Get string options safely
217 const options_t *opts = options_get();
218 const char *encrypt_key = opts && opts->encrypt_key[0] != '\0' ? opts->encrypt_key : "";
219 const char *password = opts && opts->password[0] != '\0' ? opts->password : "";
220 const char *address = opts && opts->address[0] != '\0' ? opts->address : "localhost";
221 const char *port = opts && opts->port[0] != '\0' ? opts->port : "27224";
222 const char *server_key = opts && opts->server_key[0] != '\0' ? opts->server_key : "";
223
224 // Load client private key if provided via --key
225 if (strlen(encrypt_key) > 0) {
226 // --key supports file-based authentication (SSH keys, GPG keys via gpg:keyid)
227
228 // For SSH key files (not gpg:keyid format), validate the file exists
229 if (strncmp(encrypt_key, "gpg:", 4) != 0) {
230 if (validate_ssh_key_file(encrypt_key) != 0) {
231 return -1;
232 }
233 }
234
235 // Parse key (handles SSH files and gpg:keyid format)
236 log_debug("CLIENT_CRYPTO_INIT: Loading private key for authentication: %s", encrypt_key);
237 if (parse_private_key(encrypt_key, &private_key) == ASCIICHAT_OK) {
238 log_info("Successfully parsed SSH private key");
239 log_debug("CLIENT_CRYPTO_INIT: Parsed key type=%d, KEY_TYPE_ED25519=%d", private_key.type, KEY_TYPE_ED25519);
240 is_ssh_key = true;
241 } else {
242 log_error("Failed to parse SSH key file: %s", encrypt_key);
243 log_error("This may be due to:");
244 log_error(" - Wrong password for encrypted key");
245 log_error(" - Unsupported key type (only Ed25519 is currently supported)");
246 log_error(" - Corrupted key file");
247 log_error("");
248 log_error("Note: RSA and ECDSA keys are not yet supported");
249 log_error("To generate an Ed25519 key: ssh-keygen -t ed25519");
250 return -1;
251 }
252 }
253
254 if (is_ssh_key) {
255 // Use SSH private key for authentication
256 log_debug("CLIENT_CRYPTO_INIT: Using SSH key for authentication");
257
258 // Initialize crypto context (generates ephemeral X25519 keys)
259 result = crypto_handshake_init(&g_crypto_ctx, false); // false = client
260 if (result != ASCIICHAT_OK) {
261 FATAL(result, "Failed to initialize crypto handshake");
262 }
263
264 // Store the Ed25519 keys for authentication
265 memcpy(&g_crypto_ctx.client_private_key, &private_key, sizeof(private_key_t));
266
267 // Extract Ed25519 public key from private key
272
273 // Extract GPG key ID if this is a GPG key (format: "gpg:KEYID")
274 if (strncmp(encrypt_key, "gpg:", 4) == 0) {
275 const char *key_id = encrypt_key + 4;
276 size_t key_id_len = strlen(key_id);
277 // Accept 8, 16, or 40 character GPG key IDs (short/long/full fingerprint)
278 if (key_id_len == 8 || key_id_len == 16 || key_id_len == 40) {
280 log_debug("CLIENT_CRYPTO_INIT: Extracted client GPG key ID (%zu chars): %s", key_id_len,
282 } else {
283 log_warn("CLIENT_CRYPTO_INIT: Invalid GPG key ID length: %zu (expected 8, 16, or 40)", key_id_len);
285 }
286 } else {
287 // Not a GPG key, clear the field
289 }
290
291 // SSH key is already configured in the handshake context above
292 // No additional setup needed - SSH keys are used only for authentication
293
294 // Clear the temporary private_key variable (we've already copied it to g_crypto_ctx)
295 sodium_memzero(&private_key, sizeof(private_key));
296
297 // If password is also provided, derive password key for dual authentication
298 if (strlen(password) > 0) {
299 log_debug("CLIENT_CRYPTO_INIT: Password also provided, deriving password key");
301 if (crypto_result != CRYPTO_OK) {
302 log_error("Failed to derive password key: %s", crypto_result_to_string(crypto_result));
303 return -1;
304 }
306 log_info("Password authentication enabled alongside SSH key");
307 }
308
309 } else if (strlen(GET_OPTION(password)) > 0) {
310 // Password provided - use password-based initialization
311 log_debug("CLIENT_CRYPTO_INIT: Using password authentication");
312 result = crypto_handshake_init_with_password(&g_crypto_ctx, false, GET_OPTION(password)); // false = client
313 if (result != ASCIICHAT_OK) {
314 FATAL(result, "Failed to initialize crypto handshake with password");
315 }
316 } else {
317 // No password or SSH key - use standard initialization with random keys
318 log_debug("CLIENT_CRYPTO_INIT: Using standard initialization");
319 result = crypto_handshake_init(&g_crypto_ctx, false); // false = client
320 if (result != ASCIICHAT_OK) {
321 FATAL(result, "Failed to initialize crypto handshake");
322 }
323 }
324
325 log_debug("CLIENT_CRYPTO_INIT: crypto_handshake_init succeeded");
326
327 // Set up server connection info for known_hosts
329 const char *server_ip = server_connection_get_ip();
330 log_debug("CLIENT_CRYPTO_INIT: server_connection_get_ip() returned: '%s'", server_ip ? server_ip : "NULL");
331 SAFE_STRNCPY(g_crypto_ctx.server_ip, server_ip ? server_ip : "", sizeof(g_crypto_ctx.server_ip) - 1);
333 log_debug("CLIENT_CRYPTO_INIT: Set server_ip='%s', server_port=%u", g_crypto_ctx.server_ip, g_crypto_ctx.server_port);
334
335 // Configure server key verification if specified
336 if (strlen(server_key) > 0) {
339 log_info("Server key verification enabled: %s", server_key);
340 }
341
342 // If --require-client-verify is set, perform ACDS session lookup for server identity
343 if (GET_OPTION(require_client_verify) && strlen(GET_OPTION(session_string)) > 0) {
344 log_info("--require-client-verify enabled: performing ACDS session lookup for '%s'", GET_OPTION(session_string));
345
346 // Connect to ACDS server (configurable via --acds-server and --acds-port options)
347 acds_client_config_t acds_config;
349 SAFE_STRNCPY(acds_config.server_address, GET_OPTION(acds_server), sizeof(acds_config.server_address));
350 acds_config.server_port = GET_OPTION(acds_port);
351 acds_config.timeout_ms = 5000;
352
353 acds_client_t acds_client;
354 asciichat_error_t acds_result = acds_client_connect(&acds_client, &acds_config);
355 if (acds_result != ASCIICHAT_OK) {
356 log_error("Failed to connect to ACDS server at %s:%d", acds_config.server_address, acds_config.server_port);
357 return -1;
358 }
359
360 // Perform SESSION_LOOKUP to get server's identity
361 acds_session_lookup_result_t lookup_result;
362 acds_result = acds_session_lookup(&acds_client, GET_OPTION(session_string), &lookup_result);
363 acds_client_disconnect(&acds_client);
364
365 if (acds_result != ASCIICHAT_OK || !lookup_result.found) {
366 log_error("ACDS session lookup failed for '%s': %s", GET_OPTION(session_string),
367 lookup_result.found ? "session not found" : "lookup error");
368 return -1;
369 }
370
371 // Convert server's Ed25519 public key (32 bytes) to hex string
372 char server_key_hex[65]; // 32 bytes * 2 + null terminator
373 for (size_t i = 0; i < 32; i++) {
374 SAFE_SNPRINTF(&server_key_hex[i * 2], 3, "%02x", lookup_result.host_pubkey[i]);
375 }
376 server_key_hex[64] = '\0';
377
378 // Set expected server key for verification during handshake
381 log_info("ACDS session lookup succeeded - server identity will be verified");
382 log_debug("Expected server key (from ACDS): %s", server_key_hex);
383 }
384
385 g_crypto_initialized = true;
386 log_info("Client crypto handshake initialized");
387 log_debug("CLIENT_CRYPTO_INIT: Initialization complete, g_crypto_initialized=true");
388 return 0;
389}
390
400 // If client has --no-encrypt, skip handshake entirely
401 if (GET_OPTION(no_encrypt)) {
402 log_debug("Client has --no-encrypt, skipping crypto handshake");
403 return 0;
404 }
405
406 // If we reach here, crypto must be initialized for encryption
407 if (!g_crypto_initialized) {
408 log_error("Crypto not initialized but server requires encryption");
409 log_error("Server requires encrypted connection but client has no encryption configured");
410 log_error("Use --key to specify a client key or --password for password authentication");
411 return CONNECTION_ERROR_AUTH_FAILED; // No retry - configuration error
412 }
413
414 log_info("Starting crypto handshake with server...");
415
416 START_TIMER("client_crypto_handshake");
417
418 // Step 0a: Send protocol version to server
419 protocol_version_packet_t client_version = {0};
420 client_version.protocol_version = HOST_TO_NET_U16(1); // Protocol version 1
421 client_version.protocol_revision = HOST_TO_NET_U16(0); // Revision 0
422 client_version.supports_encryption = 1; // We support encryption
423 client_version.compression_algorithms = 0; // No compression for now
424 client_version.compression_threshold = 0;
425 client_version.feature_flags = 0;
426
427 int result = send_protocol_version_packet(socket, &client_version);
428 if (result != 0) {
429 log_error("Failed to send protocol version to server");
430 STOP_TIMER("client_crypto_handshake");
431 return -1;
432 }
433 log_debug("CLIENT_CRYPTO_HANDSHAKE: Protocol version sent successfully");
434
435 // Step 0b: Receive server's protocol version
436 packet_type_t packet_type;
437 void *payload = NULL;
438 size_t payload_len = 0;
439
440 result = receive_packet(socket, &packet_type, &payload, &payload_len);
441 if (result != ASCIICHAT_OK || packet_type != PACKET_TYPE_PROTOCOL_VERSION) {
442 log_error("Failed to receive server protocol version (got type %u)", packet_type);
443 log_error("Packet type 0x%x (decimal %u) - Expected 0x%x (decimal %d)", packet_type, packet_type,
445 log_error("This suggests a protocol mismatch or packet corruption");
446 log_error("Raw packet type bytes: %02x %02x %02x %02x", (packet_type >> 0) & 0xFF, (packet_type >> 8) & 0xFF,
447 (packet_type >> 16) & 0xFF, (packet_type >> 24) & 0xFF);
448 if (payload) {
449 buffer_pool_free(NULL, payload, payload_len);
450 }
451 STOP_TIMER("client_crypto_handshake");
452 return -1;
453 }
454
455 if (payload_len != sizeof(protocol_version_packet_t)) {
456 log_error("Invalid protocol version packet size: %zu, expected %zu", payload_len,
458 buffer_pool_free(NULL, payload, payload_len);
459 STOP_TIMER("client_crypto_handshake");
460 return -1;
461 }
462
463 protocol_version_packet_t server_version;
464 memcpy(&server_version, payload, sizeof(protocol_version_packet_t));
465 buffer_pool_free(NULL, payload, payload_len);
466
467 // Convert from network byte order
468 uint16_t server_proto_version = NET_TO_HOST_U16(server_version.protocol_version);
469 uint16_t server_proto_revision = NET_TO_HOST_U16(server_version.protocol_revision);
470
471 log_info("Server protocol version: %u.%u (encryption: %s)", server_proto_version, server_proto_revision,
472 server_version.supports_encryption ? "yes" : "no");
473
474 if (!server_version.supports_encryption) {
475 log_error("Server does not support encryption");
476 STOP_TIMER("client_crypto_handshake");
478 }
479
480 // Step 0c: Send crypto capabilities to server
481 log_debug("CLIENT_CRYPTO_HANDSHAKE: Sending crypto capabilities");
482 crypto_capabilities_packet_t client_caps = {0};
486 client_caps.requires_verification = 0; // Client doesn't require server verification (uses known_hosts)
487 client_caps.preferred_kex = KEX_ALGO_X25519;
488 client_caps.preferred_auth = AUTH_ALGO_ED25519;
490
491 result = send_crypto_capabilities_packet(socket, &client_caps);
492 if (result != 0) {
493 log_error("Failed to send crypto capabilities to server");
494 STOP_TIMER("client_crypto_handshake");
495 return -1;
496 }
497 log_debug("CLIENT_CRYPTO_HANDSHAKE: Crypto capabilities sent successfully");
498
499 // Step 0d: Receive server's crypto parameters
500 log_debug("CLIENT_CRYPTO_HANDSHAKE: Receiving server crypto parameters");
501 payload = NULL;
502 payload_len = 0;
503
504 result = receive_packet(socket, &packet_type, &payload, &payload_len);
505 if (result != ASCIICHAT_OK || packet_type != PACKET_TYPE_CRYPTO_PARAMETERS) {
506 log_error("Failed to receive server crypto parameters (got type %u)", packet_type);
507 if (payload) {
508 buffer_pool_free(NULL, payload, payload_len);
509 }
510 STOP_TIMER("client_crypto_handshake");
511 return -1;
512 }
513
514 if (payload_len != sizeof(crypto_parameters_packet_t)) {
515 log_error("Invalid crypto parameters packet size: %zu, expected %zu", payload_len,
517 buffer_pool_free(NULL, payload, payload_len);
518 STOP_TIMER("client_crypto_handshake");
519 return -1;
520 }
521
522 crypto_parameters_packet_t server_params;
523 memcpy(&server_params, payload, sizeof(crypto_parameters_packet_t));
524 buffer_pool_free(NULL, payload, payload_len);
525
526 // Convert from network byte order
527 uint16_t kex_pubkey_size = NET_TO_HOST_U16(server_params.kex_public_key_size);
528 uint16_t auth_pubkey_size = NET_TO_HOST_U16(server_params.auth_public_key_size);
529 uint16_t signature_size = NET_TO_HOST_U16(server_params.signature_size);
530 uint16_t shared_secret_size = NET_TO_HOST_U16(server_params.shared_secret_size);
531
532 log_info("Server crypto parameters: KEX=%u, Auth=%u, Cipher=%u (key_size=%u, auth_size=%u, sig_size=%u, "
533 "secret_size=%u, verification=%u)",
534 server_params.selected_kex, server_params.selected_auth, server_params.selected_cipher, kex_pubkey_size,
535 auth_pubkey_size, signature_size, shared_secret_size, server_params.verification_enabled);
536 log_debug("Raw server_params.kex_public_key_size = %u (network byte order)", server_params.kex_public_key_size);
537
538 // Set the crypto parameters in the handshake context
539 result = crypto_handshake_set_parameters(&g_crypto_ctx, &server_params);
540 if (result != ASCIICHAT_OK) {
541 FATAL(result, "Failed to set crypto parameters");
542 }
543
544 // Store verification flag - server will verify client identity (whitelist check)
545 // This is independent of whether server provides its own identity
546 if (server_params.verification_enabled) {
549 log_info("Server will verify client identity (whitelist enabled)");
550 }
551
552 // Validate that server chose algorithms we support
553 if (server_params.selected_kex != KEX_ALGO_X25519) {
554 log_error("Server selected unsupported KEX algorithm: %u", server_params.selected_kex);
555 STOP_TIMER("client_crypto_handshake");
557 }
558
559 if (server_params.selected_cipher != CIPHER_ALGO_XSALSA20_POLY1305) {
560 log_error("Server selected unsupported cipher algorithm: %u", server_params.selected_cipher);
561 STOP_TIMER("client_crypto_handshake");
563 }
564
565 log_debug("CLIENT_CRYPTO_HANDSHAKE: Protocol negotiation completed successfully");
566
567 // Step 1: Receive server's public key and send our public key
568 log_debug("CLIENT_CRYPTO_HANDSHAKE: Starting key exchange");
570 if (result != ASCIICHAT_OK) {
571#ifdef _WIN32
572 // On Windows: Cleanup capture resources before exiting to prevent Media Foundation threads from hanging exit()
573 // Media Foundation creates background COM threads that can block exit() if not properly shut down
575#endif
576 FATAL(result, "Crypto key exchange failed");
577 }
578 log_debug("CLIENT_CRYPTO_HANDSHAKE: Key exchange completed successfully");
579
580 // SECURITY: Warn when server requires client verification but client has no identity key
581 bool client_has_identity = (g_crypto_ctx.client_public_key.type == KEY_TYPE_ED25519);
582
583 if (g_crypto_ctx.require_client_auth && !client_has_identity) {
584 // Server requires client verification but client has no identity key
585 log_warn("Server requires client verification but client has no identity key");
586
587 // Check if we're running interactively (stdin is a terminal)
588 // In debug builds with CLAUDECODE, skip interactive prompts (LLM can't type)
589#ifndef NDEBUG
590 bool skip_interactive = platform_getenv("CLAUDECODE") != NULL;
591#else
592 bool skip_interactive = false;
593#endif
594 if (!skip_interactive && platform_is_interactive()) {
595 // Interactive mode - prompt user for confirmation
596 // Lock terminal for the warning message
597 bool previous_terminal_state = log_lock_terminal();
598
599 log_plain("\n"
600 "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
601 "@ WARNING: CLIENT AUTHENTICATION REQUIRED @\n"
602 "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
603 "\n"
604 "The server requires client authentication (--client-keys enabled),\n"
605 "but you have not provided a client identity key with --key.\n"
606 "\n"
607 "To connect to this server, you need to:\n"
608 " 1. Generate an Ed25519 key: ssh-keygen -t ed25519\n"
609 " 2. Add the public key to the server's --client-keys list\n"
610 " 3. Connect with: ascii-chat client --key /path/to/private/key\n");
611
612 // Unlock before prompt (prompt_yes_no handles its own terminal locking)
613 log_unlock_terminal(previous_terminal_state);
614
615 // Prompt user - default is No since this will likely fail
616 if (!platform_prompt_yes_no("Do you want to continue anyway (this will likely fail)", false)) {
617 log_plain("Connection aborted by user.");
618 exit(0); // User declined - exit cleanly
619 }
620
621 log_plain("Warning: Continuing without client identity key (connection may fail).\n");
622 } else {
623 // Non-interactive mode (background/script) - just log warning and continue
624 log_warn("Non-interactive mode: Continuing without client identity key (connection may fail)");
625 }
626 }
627
628 // Step 2: Receive auth challenge and send response
629 log_debug("CLIENT_CRYPTO: Sending auth response to server...");
630 log_debug("CLIENT_CRYPTO_HANDSHAKE: Starting auth response");
632 if (result != ASCIICHAT_OK) {
633 FATAL(result, "Crypto authentication failed");
634 }
635 log_debug("CLIENT_CRYPTO: Auth response sent successfully");
636 log_debug("CLIENT_CRYPTO_HANDSHAKE: Auth response completed successfully");
637
638 // Check if handshake completed during auth response (no authentication needed)
640 STOP_TIMER_AND_LOG("client_crypto_handshake", log_info,
641 "Crypto handshake completed successfully (no authentication)");
642 return 0;
643 }
644
645 // Step 3: Receive handshake complete message
646 log_debug("CLIENT_CRYPTO_HANDSHAKE: Waiting for handshake complete message");
648 if (result != ASCIICHAT_OK) {
649 FATAL(result, "Crypto handshake completion failed");
650 }
651
652 STOP_TIMER_AND_LOG("client_crypto_handshake", log_info, "Crypto handshake completed successfully");
653 log_debug("CLIENT_CRYPTO_HANDSHAKE: Handshake completed successfully, state=%d", g_crypto_ctx.state);
654 return 0;
655}
656
665 if (!g_crypto_initialized || GET_OPTION(no_encrypt)) {
666 return false;
667 }
668
670}
671
680 if (!crypto_client_is_ready()) {
681 return NULL;
682 }
683
685}
686
699int crypto_client_encrypt_packet(const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext,
700 size_t ciphertext_size, size_t *ciphertext_len) {
702 ciphertext, ciphertext_size, ciphertext_len);
703}
704
717int crypto_client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext,
718 size_t plaintext_size, size_t *plaintext_len) {
719 return crypto_decrypt_packet_or_passthrough(&g_crypto_ctx, crypto_client_is_ready(), ciphertext, ciphertext_len,
720 plaintext, plaintext_size, plaintext_len);
721}
722
729 if (g_crypto_initialized) {
731 g_crypto_initialized = false;
732 log_debug("Client crypto handshake cleaned up");
733 }
734}
735
736// =============================================================================
737// Session Rekeying Functions
738// =============================================================================
739
748 if (!g_crypto_initialized || !crypto_client_is_ready()) {
749 return false;
750 }
752}
753
762 if (!g_crypto_initialized || !crypto_client_is_ready()) {
763 log_error("Cannot initiate rekey: crypto not initialized or not ready");
764 return -1;
765 }
766
768 if (socket == INVALID_SOCKET_VALUE) {
769 log_error("Cannot initiate rekey: invalid socket");
770 return -1;
771 }
772
774 if (result != ASCIICHAT_OK) {
775 log_error("Failed to send REKEY_REQUEST: %d", result);
776 return -1;
777 }
778
779 return 0;
780}
781
791int crypto_client_process_rekey_request(const uint8_t *packet, size_t packet_len) {
792 if (!g_crypto_initialized || !crypto_client_is_ready()) {
793 log_error("Cannot process rekey request: crypto not initialized or not ready");
794 return -1;
795 }
796
798 if (result != ASCIICHAT_OK) {
799 log_error("Failed to process REKEY_REQUEST: %d", result);
800 return -1;
801 }
802
803 return 0;
804}
805
814 if (!g_crypto_initialized || !crypto_client_is_ready()) {
815 log_error("Cannot send rekey response: crypto not initialized or not ready");
816 return -1;
817 }
818
820 if (socket == INVALID_SOCKET_VALUE) {
821 log_error("Cannot send rekey response: invalid socket");
822 return -1;
823 }
824
826 if (result != ASCIICHAT_OK) {
827 log_error("Failed to send REKEY_RESPONSE: %d", result);
828 return -1;
829 }
830
831 return 0;
832}
833
843int crypto_client_process_rekey_response(const uint8_t *packet, size_t packet_len) {
844 if (!g_crypto_initialized || !crypto_client_is_ready()) {
845 log_error("Cannot process rekey response: crypto not initialized or not ready");
846 return -1;
847 }
848
850 if (result != ASCIICHAT_OK) {
851 log_error("Failed to process REKEY_RESPONSE: %d", result);
852 return -1;
853 }
854
855 return 0;
856}
857
866 if (!g_crypto_initialized || !crypto_client_is_ready()) {
867 log_error("Cannot send rekey complete: crypto not initialized or not ready");
868 return -1;
869 }
870
872 if (socket == INVALID_SOCKET_VALUE) {
873 log_error("Cannot send rekey complete: invalid socket");
874 return -1;
875 }
876
878 if (result != ASCIICHAT_OK) {
879 log_error("Failed to send REKEY_COMPLETE: %d", result);
880 return -1;
881 }
882
883 return 0;
884}
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.
Definition acds_client.c:38
asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config)
Connect to ACDS server.
Definition acds_client.c:53
void acds_client_disconnect(acds_client_t *client)
Disconnect from ACDS server.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
ascii-chat Client Media Capture Management Interface
asciichat_error_t crypto_handshake_rekey_complete(crypto_handshake_context_t *ctx, socket_t socket)
Send REKEY_COMPLETE packet (initiator 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_process_rekey_request(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process received REKEY_REQUEST packet (responder side)
asciichat_error_t crypto_handshake_init_with_password(crypto_handshake_context_t *ctx, bool is_server, const char *password)
Initialize crypto handshake context with password authentication.
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_init(crypto_handshake_context_t *ctx, bool is_server)
Initialize crypto handshake context.
asciichat_error_t crypto_handshake_rekey_response(crypto_handshake_context_t *ctx, socket_t socket)
Send REKEY_RESPONSE packet (responder side)
void crypto_handshake_cleanup(crypto_handshake_context_t *ctx)
Cleanup crypto handshake context with secure memory wiping.
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, socket_t socket)
Send REKEY_REQUEST packet (initiator side)
Common declarations and data structures for cryptographic handshake.
🔄 Network byte order conversion helpers
#define HOST_TO_NET_U16(val)
Definition endian.h:101
#define NET_TO_HOST_U16(val)
Definition endian.h:116
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
Free a buffer back to the pool (lock-free)
void capture_cleanup()
Cleanup capture subsystem.
Definition capture.c:526
socket_t server_connection_get_socket()
Get current socket file descriptor.
const char * server_connection_get_ip()
Get resolved server IP address.
crypto_handshake_context_t g_crypto_ctx
Per-connection crypto handshake context.
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 crypto_client_initiate_rekey(void)
Initiate session rekeying (client-initiated)
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 client_crypto_handshake(socket_t socket)
Perform crypto handshake with server.
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:358
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:151
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.
@ CRYPTO_OK
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ASCIICHAT_OK
Definition error_codes.h:48
@ 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:108
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
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:977
@ KEY_TYPE_ED25519
Definition key_types.h:52
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
bool log_lock_terminal(void)
Lock terminal output for exclusive access by the calling thread.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define log_plain(...)
Plain logging - writes to both log file and stderr without timestamps or log levels.
void log_unlock_terminal(bool previous_state)
Release terminal lock and flush buffered messages.
#define START_TIMER(name_fmt,...)
Start a timer with formatted name.
Definition time.h:141
#define STOP_TIMER(name_fmt,...)
Stop a timer with formatted name and return elapsed time.
Definition time.h:165
#define STOP_TIMER_AND_LOG(timer_name, log_func, msg_fmt,...)
Stop a timer and log the result with a custom message.
Definition time.h:271
uint8_t preferred_auth
Preferred authentication algorithm (AUTH_ALGO_*)
Definition packet.h:856
uint16_t supported_auth_algorithms
Supported authentication algorithms bitmask (AUTH_ALGO_*)
Definition packet.h:848
uint8_t selected_kex
Selected key exchange algorithm (KEX_ALGO_*)
Definition packet.h:875
uint16_t supported_kex_algorithms
Supported key exchange algorithms bitmask (KEX_ALGO_*)
Definition packet.h:846
#define AUTH_ALGO_ED25519
Ed25519 authentication (Edwards-curve signatures)
Definition packet.h:950
uint8_t preferred_cipher
Preferred cipher algorithm (CIPHER_ALGO_*)
Definition packet.h:858
uint8_t selected_cipher
Selected cipher algorithm (CIPHER_ALGO_*)
Definition packet.h:879
uint8_t compression_threshold
Compression threshold percentage (0-100, e.g., 80 = compress if >80% size reduction)
Definition packet.h:720
uint16_t auth_public_key_size
Authentication public key size in bytes (e.g., 32 for Ed25519, 1952 for Dilithium3)
Definition packet.h:885
uint8_t selected_auth
Selected authentication algorithm (AUTH_ALGO_*)
Definition packet.h:877
int send_protocol_version_packet(socket_t sockfd, const protocol_version_packet_t *version)
Send protocol version packet.
Definition packet.c:1016
#define KEX_ALGO_X25519
X25519 key exchange (Curve25519)
Definition packet.h:949
uint8_t verification_enabled
Server verification enabled flag (1=enabled, 0=disabled)
Definition packet.h:881
uint16_t protocol_revision
Minor protocol revision (server can be newer)
Definition packet.h:714
uint16_t protocol_version
Major protocol version (must match for compatibility)
Definition packet.h:712
uint16_t supported_cipher_algorithms
Supported cipher algorithms bitmask (CIPHER_ALGO_*)
Definition packet.h:850
uint16_t signature_size
Signature size in bytes (e.g., 64 for Ed25519, 3309 for Dilithium3)
Definition packet.h:887
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
#define AUTH_ALGO_NONE
No authentication (plaintext mode)
Definition packet.h:951
uint16_t feature_flags
Feature flags bitmask (FEATURE_RLE_ENCODING, etc.)
Definition packet.h:722
uint8_t preferred_kex
Preferred key exchange algorithm (KEX_ALGO_*)
Definition packet.h:854
int send_crypto_capabilities_packet(socket_t sockfd, const crypto_capabilities_packet_t *caps)
Send crypto capabilities packet.
Definition packet.c:1030
uint8_t requires_verification
Server verification requirement flag (1=required, 0=optional)
Definition packet.h:852
uint8_t compression_algorithms
Supported compression algorithms bitmask (COMPRESS_ALGO_*)
Definition packet.h:718
uint16_t shared_secret_size
Shared secret size in bytes (e.g., 32 for X25519)
Definition packet.h:889
uint16_t kex_public_key_size
Key exchange public key size in bytes (e.g., 32 for X25519, 1568 for Kyber1024)
Definition packet.h:883
uint8_t supports_encryption
Encryption support flag (1=support encryption, 0=plaintext only)
Definition packet.h:716
#define CIPHER_ALGO_XSALSA20_POLY1305
XSalsa20-Poly1305 authenticated encryption.
Definition packet.h:952
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
Definition options.h:644
int strtoint_safe(const char *str)
Safely parse string to integer with validation.
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:222
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:281
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
Definition packet.h:283
@ PACKET_TYPE_CRYPTO_PARAMETERS
Server -> Client: Chosen algorithms + data sizes (UNENCRYPTED)
Definition packet.h:315
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
bool platform_prompt_yes_no(const char *prompt, bool default_yes)
Prompt the user for a yes/no answer.
bool platform_is_interactive(void)
Check if interactive prompting is available.
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:52
const char * platform_getenv(const char *name)
Get an environment variable value.
asciichat_error_t crypto_handshake_client_auth_response(crypto_handshake_context_t *ctx, socket_t client_socket)
Client: Process auth challenge and send response.
asciichat_error_t crypto_handshake_client_key_exchange(crypto_handshake_context_t *ctx, socket_t client_socket)
Client: Process server's public key and send our public key.
asciichat_error_t crypto_handshake_client_complete(crypto_handshake_context_t *ctx, socket_t client_socket)
Client: Wait for handshake complete confirmation.
Client-side handshake functions.
⚙️ Command-line options parsing and configuration management for ascii-chat
Packet protocol implementation with encryption and compression support.
Cross-platform interactive prompting utilities.
ascii-chat Client Server Connection Management Interface
@ CONNECTION_ERROR_AUTH_FAILED
Authentication failure (no retry)
Server cryptographic operations and per-client handshake management.
ACDS client connection configuration.
Definition acds_client.h:44
char server_address[256]
ACDS server address (e.g., "discovery.ascii.chat" or "127.0.0.1")
Definition acds_client.h:45
uint32_t timeout_ms
Connection timeout in milliseconds.
Definition acds_client.h:47
uint16_t server_port
ACDS server port (default: 27225)
Definition acds_client.h:46
ACDS client connection handle.
Definition acds_client.h:53
Session lookup result.
bool found
Session exists.
uint8_t host_pubkey[32]
Host's Ed25519 public key.
Crypto capabilities packet structure (Packet Type 14)
Definition packet.h:844
Cryptographic context structure.
Cryptographic handshake context structure.
crypto_handshake_state_t state
Crypto parameters packet structure (Packet Type 15)
Definition packet.h:873
Consolidated options structure.
Definition options.h:439
char port[256]
Server port number.
Definition options.h:464
char password[256]
Password string.
Definition options.h:543
char server_key[256]
Expected server public key (client)
Definition options.h:546
char encrypt_key[256]
SSH/GPG key file path.
Definition options.h:542
char address[256]
Server address (client) or bind address (server)
Definition options.h:462
Private key structure (for server –ssh-key)
Definition key_types.h:91
Protocol version negotiation packet structure (Packet Type 1)
Definition packet.h:710
⏱️ High-precision timing utilities using sokol_time.h and uthash
Common SIMD utilities and structures.