ascii-chat 0.8.38
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 "main.h"
150#include "crypto.h"
151#include "server.h"
152#include <ascii-chat/options/options.h>
153#include <ascii-chat/options/rcu.h> // For RCU-based options access
154#include <ascii-chat/common.h>
155#include <ascii-chat/util/endian.h>
156#include <ascii-chat/crypto/handshake/common.h>
157#include <ascii-chat/crypto/handshake/client.h>
158#include <ascii-chat/crypto/crypto.h>
159#include <ascii-chat/crypto/keys.h>
160#include <ascii-chat/crypto/discovery_keys.h>
161#include <ascii-chat/buffer_pool.h>
162#include <ascii-chat/network/packet.h>
163#include <ascii-chat/network/acip/acds_client.h>
164#include <ascii-chat/network/mdns/discovery.h> // For pubkey_to_hex
165#include <ascii-chat/util/time.h>
166#include <ascii-chat/util/endian.h>
167#include "capture.h"
168
169#include <string.h>
170#include <stdio.h>
171#include <sys/stat.h>
172#include <sodium.h>
173
174#include <ascii-chat/platform/question.h>
175#include <ascii-chat/platform/init.h>
176
185static bool g_crypto_initialized = false;
186static static_mutex_t g_crypto_init_mutex = STATIC_MUTEX_INIT;
187static uint8_t g_crypto_mode = ACIP_CRYPTO_ENCRYPT; // Default: encrypt only, no authentication
188
196void client_crypto_set_mode(uint8_t mode) {
197 log_debug("CLIENT_CRYPTO: Setting crypto mode to 0x%02x", mode);
198 g_crypto_mode = mode;
199}
200
209 log_debug("CLIENT_CRYPTO_INIT: Starting crypto initialization");
210
211 // Check and reset initialization state with thread safety
212 static_mutex_lock(&g_crypto_init_mutex);
213 bool was_initialized = g_crypto_initialized;
214 g_crypto_initialized = false;
215 static_mutex_unlock(&g_crypto_init_mutex);
216
217 if (was_initialized) {
218 log_debug("CLIENT_CRYPTO_INIT: Already initialized, cleaning up and reinitializing");
220 }
221
222 // Check if encryption is disabled
223 if (GET_OPTION(no_encrypt)) {
224 log_info("Encryption disabled");
225 log_debug("CLIENT_CRYPTO_INIT: Encryption disabled, returning 0");
226 return 0;
227 }
228
229 log_debug("CLIENT_CRYPTO_INIT: Initializing crypto handshake context");
230
231 // Check if we have an SSH key, password, or neither
232 int result;
233 bool is_ssh_key = false;
234 private_key_t private_key;
235
236 // Get string options safely
237 const options_t *opts = options_get();
238 const char *encrypt_key = opts && opts->encrypt_key[0] != '\0' ? opts->encrypt_key : "";
239 const char *password = opts && opts->password[0] != '\0' ? opts->password : "";
240 const char *address = opts && opts->address[0] != '\0' ? opts->address : "localhost";
241 int port = opts ? opts->port : OPT_PORT_INT_DEFAULT;
242 const char *server_key = opts && opts->server_key[0] != '\0' ? opts->server_key : "";
243
244 // Load client private key if provided via --key
245 if (strlen(encrypt_key) > 0) {
246 // --key supports file-based authentication (SSH keys, GPG keys via gpg:keyid)
247
248 // For SSH key files (not gpg:keyid format), validate the file exists
249 if (strncmp(encrypt_key, "gpg:", 4) != 0) {
250 if (validate_ssh_key_file(encrypt_key) != 0) {
251 return -1;
252 }
253 }
254
255 // Parse key (handles SSH files and gpg:keyid format)
256 log_debug("CLIENT_CRYPTO_INIT: Loading private key for authentication: %s", encrypt_key);
257 if (parse_private_key(encrypt_key, &private_key) == ASCIICHAT_OK) {
258 log_debug("Successfully parsed SSH private key");
259 log_debug("CLIENT_CRYPTO_INIT: Parsed key type=%d, KEY_TYPE_ED25519=%d", private_key.type, KEY_TYPE_ED25519);
260 is_ssh_key = true;
261 } else {
262 log_error("Failed to parse SSH key file: %s", encrypt_key);
263 log_error("This may be due to:");
264 log_error(" - Wrong password for encrypted key");
265 log_error(" - Unsupported key type (only Ed25519 is currently supported)");
266 log_error(" - Corrupted key file");
267 log_error("");
268 log_error("Note: RSA and ECDSA keys are not yet supported");
269 log_error("To generate an Ed25519 key: ssh-keygen -t ed25519");
270 return -1;
271 }
272 }
273
274 if (is_ssh_key) {
275 // Use SSH private key for authentication
276 log_debug("CLIENT_CRYPTO_INIT: Using SSH key for authentication");
277
278 // Initialize crypto context (generates ephemeral X25519 keys)
279 result = crypto_handshake_init(&g_crypto_ctx, false); // false = client
280 if (result != ASCIICHAT_OK) {
281 FATAL(result, "Failed to initialize crypto handshake");
282 }
283
284 // Store the Ed25519 keys for authentication
285 memcpy(&g_crypto_ctx.client_private_key, &private_key, sizeof(private_key_t));
286
287 // Extract Ed25519 public key from private key
288 g_crypto_ctx.client_public_key.type = KEY_TYPE_ED25519;
289 memcpy(g_crypto_ctx.client_public_key.key, private_key.public_key, ED25519_PUBLIC_KEY_SIZE);
290 SAFE_STRNCPY(g_crypto_ctx.client_public_key.comment, private_key.key_comment,
291 sizeof(g_crypto_ctx.client_public_key.comment) - 1);
292
293 // Extract GPG key ID if this is a GPG key (format: "gpg:KEYID")
294 if (strncmp(encrypt_key, "gpg:", 4) == 0) {
295 const char *key_id = encrypt_key + 4;
296 size_t key_id_len = strlen(key_id);
297 // Accept 8, 16, or 40 character GPG key IDs (short/long/full fingerprint)
298 if (key_id_len == 8 || key_id_len == 16 || key_id_len == 40) {
299 SAFE_STRNCPY(g_crypto_ctx.client_gpg_key_id, key_id, sizeof(g_crypto_ctx.client_gpg_key_id));
300 log_debug("CLIENT_CRYPTO_INIT: Extracted client GPG key ID (%zu chars): %s", key_id_len,
301 g_crypto_ctx.client_gpg_key_id);
302 } else {
303 log_warn("CLIENT_CRYPTO_INIT: Invalid GPG key ID length: %zu (expected 8, 16, or 40)", key_id_len);
304 g_crypto_ctx.client_gpg_key_id[0] = '\0';
305 }
306 } else {
307 // Not a GPG key, clear the field
308 g_crypto_ctx.client_gpg_key_id[0] = '\0';
309 }
310
311 // SSH key is already configured in the handshake context above
312 // No additional setup needed - SSH keys are used only for authentication
313
314 // Clear the temporary private_key variable (we've already copied it to g_crypto_ctx)
315 sodium_memzero(&private_key, sizeof(private_key));
316
317 // If password is also provided, derive password key for dual authentication
318 if (strlen(password) > 0) {
319 log_debug("CLIENT_CRYPTO_INIT: Password also provided, deriving password key");
320 crypto_result_t crypto_result = crypto_derive_password_key(&g_crypto_ctx.crypto_ctx, password);
321 if (crypto_result != CRYPTO_OK) {
322 log_error("Failed to derive password key: %s", crypto_result_to_string(crypto_result));
323 return -1;
324 }
325 g_crypto_ctx.crypto_ctx.has_password = true;
326 log_debug("Password authentication enabled alongside SSH key");
327 }
328
329 } else if (strlen(GET_OPTION(password)) > 0) {
330 // Password provided - use password-based initialization
331 log_debug("CLIENT_CRYPTO_INIT: Using password authentication");
332 result = crypto_handshake_init_with_password(&g_crypto_ctx, false, GET_OPTION(password)); // false = client
333 if (result != ASCIICHAT_OK) {
334 FATAL(result, "Failed to initialize crypto handshake with password");
335 }
336 } else {
337 // No password or SSH key - use standard initialization with random keys
338 log_debug("CLIENT_CRYPTO_INIT: Using standard initialization");
339 result = crypto_handshake_init(&g_crypto_ctx, false); // false = client
340 if (result != ASCIICHAT_OK) {
341 FATAL(result, "Failed to initialize crypto handshake");
342 }
343 }
344
345 log_debug("CLIENT_CRYPTO_INIT: crypto_handshake_init succeeded");
346
347 // Set up server connection info for known_hosts
348 SAFE_STRNCPY(g_crypto_ctx.server_hostname, address, sizeof(g_crypto_ctx.server_hostname) - 1);
349 const char *server_ip = server_connection_get_ip();
350 log_debug("CLIENT_CRYPTO_INIT: server_connection_get_ip() returned: '%s'", server_ip ? server_ip : "NULL");
351 SAFE_STRNCPY(g_crypto_ctx.server_ip, server_ip ? server_ip : "", sizeof(g_crypto_ctx.server_ip) - 1);
352 g_crypto_ctx.server_port = (uint16_t)port;
353 log_debug("CLIENT_CRYPTO_INIT: Set server_ip='%s', server_port=%u", g_crypto_ctx.server_ip, g_crypto_ctx.server_port);
354
355 // Configure server key verification if specified
356 if (strlen(server_key) > 0) {
357 g_crypto_ctx.verify_server_key = true;
358 SAFE_STRNCPY(g_crypto_ctx.expected_server_key, server_key, sizeof(g_crypto_ctx.expected_server_key) - 1);
359 log_debug("Server key verification enabled: %s", server_key);
360 }
361
362 // If --require-client-verify is set, perform ACDS session lookup for server identity
363 if (GET_OPTION(require_client_verify) && strlen(GET_OPTION(session_string)) > 0) {
364 log_debug("--require-client-verify enabled: performing ACDS session lookup for '%s'", GET_OPTION(session_string));
365
366 // Connect to ACDS server (configurable via --acds-server and --acds-port options)
367 acds_client_config_t acds_config;
369 SAFE_STRNCPY(acds_config.server_address, GET_OPTION(discovery_server), sizeof(acds_config.server_address));
370 acds_config.server_port = GET_OPTION(discovery_port);
371 acds_config.timeout_ms = 5 * MS_PER_SEC_INT;
372
373 // ACDS key verification (optional in debug builds, only if --discovery-service-key is provided)
374 if (strlen(GET_OPTION(discovery_service_key)) > 0) {
375 log_debug("Verifying ACDS server key for %s...", acds_config.server_address);
376 uint8_t acds_pubkey[32];
377 asciichat_error_t verify_result =
378 discovery_keys_verify(acds_config.server_address, GET_OPTION(discovery_service_key), acds_pubkey);
379 if (verify_result != ASCIICHAT_OK) {
380 log_error("ACDS key verification failed for %s", acds_config.server_address);
381 return -1;
382 }
383 log_debug("ACDS server key verified successfully");
384 }
385#ifndef NDEBUG
386 else {
387 log_debug("Skipping ACDS key verification (debug build, no --discovery-service-key provided)");
388 }
389#endif
390
391 acds_client_t acds_client;
392 asciichat_error_t acds_result = acds_client_connect(&acds_client, &acds_config);
393 if (acds_result != ASCIICHAT_OK) {
394 log_error("Failed to connect to ACDS server at %s:%d", acds_config.server_address, acds_config.server_port);
395 return -1;
396 }
397
398 // Perform SESSION_LOOKUP to get server's identity
399 acds_session_lookup_result_t lookup_result;
400 acds_result = acds_session_lookup(&acds_client, GET_OPTION(session_string), &lookup_result);
401 acds_client_disconnect(&acds_client);
402
403 if (acds_result != ASCIICHAT_OK || !lookup_result.found) {
404 log_error("ACDS session lookup failed for '%s': %s", GET_OPTION(session_string),
405 lookup_result.found ? "session not found" : "lookup error");
406 return -1;
407 }
408
409 // Convert server's Ed25519 public key (32 bytes) to hex string
410 char server_key_hex[65]; // 32 bytes * 2 + null terminator
411 for (size_t i = 0; i < 32; i++) {
412 SAFE_SNPRINTF(&server_key_hex[i * 2], 3, "%02x", lookup_result.host_pubkey[i]);
413 }
414 server_key_hex[64] = '\0';
415
416 // Set expected server key for verification during handshake
417 g_crypto_ctx.verify_server_key = true;
418 SAFE_STRNCPY(g_crypto_ctx.expected_server_key, server_key_hex, sizeof(g_crypto_ctx.expected_server_key) - 1);
419 log_debug("ACDS session lookup succeeded - server identity will be verified");
420 log_debug("Expected server key (from ACDS): %s", server_key_hex);
421 }
422
423 // Mark initialization as complete with thread safety
424 static_mutex_lock(&g_crypto_init_mutex);
425 g_crypto_initialized = true;
426 static_mutex_unlock(&g_crypto_init_mutex);
427
428 log_debug("Client crypto handshake initialized");
429 log_debug("CLIENT_CRYPTO_INIT: Initialization complete, g_crypto_initialized=true");
430 return 0;
431}
432
442 // If client has --no-encrypt, skip handshake entirely
443 if (GET_OPTION(no_encrypt)) {
444 log_debug("Client has --no-encrypt, skipping crypto handshake");
445 return 0;
446 }
447
448 // If we reach here, crypto must be initialized for encryption
449 static_mutex_lock(&g_crypto_init_mutex);
450 bool is_initialized = g_crypto_initialized;
451 static_mutex_unlock(&g_crypto_init_mutex);
452
453 if (!is_initialized) {
454 log_error("Crypto not initialized but server requires encryption");
455 log_error("Server requires encrypted connection but client has no encryption configured");
456 log_error("Use --key to specify a client key or --password for password authentication");
457 return CONNECTION_ERROR_AUTH_FAILED; // No retry - configuration error
458 }
459
460 log_debug("Starting crypto handshake with server...");
461
462 START_TIMER("client_crypto_handshake");
463
464 // Step 0a: Send protocol version to server
465 protocol_version_packet_t client_version = {0};
466 client_version.protocol_version = HOST_TO_NET_U16(1); // Protocol version 1
467 client_version.protocol_revision = HOST_TO_NET_U16(0); // Revision 0
468 client_version.supports_encryption = g_crypto_mode; // Send crypto mode bitmask
469 client_version.compression_algorithms = 0; // No compression for now
470 client_version.compression_threshold = 0;
471 client_version.feature_flags = 0;
472
473 int result = send_protocol_version_packet(socket, &client_version);
474 if (result != 0) {
475 log_error("Failed to send protocol version to server");
476 STOP_TIMER("client_crypto_handshake");
477 return -1;
478 }
479 log_debug("CLIENT_CRYPTO_HANDSHAKE: Protocol version sent successfully");
480
481 // Step 0b: Receive server's protocol version
482 packet_type_t packet_type;
483 void *payload = NULL;
484 size_t payload_len = 0;
485
486 result = receive_packet(socket, &packet_type, &payload, &payload_len);
487 if (result != ASCIICHAT_OK || packet_type != PACKET_TYPE_PROTOCOL_VERSION) {
488 log_error("Failed to receive server protocol version (got type %u)", packet_type);
489 log_error("Packet type 0x%x (decimal %u) - Expected 0x%x (decimal %d)", packet_type, packet_type,
490 PACKET_TYPE_PROTOCOL_VERSION, PACKET_TYPE_PROTOCOL_VERSION);
491 log_error("This suggests a protocol mismatch or packet corruption");
492 log_error("Raw packet type bytes: %02x %02x %02x %02x", (packet_type >> 0) & 0xFF, (packet_type >> 8) & 0xFF,
493 (packet_type >> 16) & 0xFF, (packet_type >> 24) & 0xFF);
494 if (payload) {
495 buffer_pool_free(NULL, payload, payload_len);
496 }
497 STOP_TIMER("client_crypto_handshake");
498 return -1;
499 }
500
501 if (payload_len != sizeof(protocol_version_packet_t)) {
502 log_error("Invalid protocol version packet size: %zu, expected %zu", payload_len,
503 sizeof(protocol_version_packet_t));
504 buffer_pool_free(NULL, payload, payload_len);
505 STOP_TIMER("client_crypto_handshake");
506 return -1;
507 }
508
509 protocol_version_packet_t server_version;
510 memcpy(&server_version, payload, sizeof(protocol_version_packet_t));
511 buffer_pool_free(NULL, payload, payload_len);
512
513 // Convert from network byte order
514 uint16_t server_proto_version = NET_TO_HOST_U16(server_version.protocol_version);
515 uint16_t server_proto_revision = NET_TO_HOST_U16(server_version.protocol_revision);
516 uint8_t server_mode = server_version.supports_encryption;
517
518 log_info("Server protocol version: %u.%u (crypto mode: 0x%02x)", server_proto_version, server_proto_revision,
519 server_mode);
520
521 // Verify server echoed our mode
522 if (server_mode != g_crypto_mode) {
523 log_error("Server mode mismatch: got 0x%02x, expected 0x%02x", server_mode, g_crypto_mode);
524 STOP_TIMER("client_crypto_handshake");
526 }
527
528 // Step 0c: Send crypto capabilities to server
529 log_debug("CLIENT_CRYPTO_HANDSHAKE: Sending crypto capabilities");
530 crypto_capabilities_packet_t client_caps = {0};
531 client_caps.supported_kex_algorithms = HOST_TO_NET_U16(KEX_ALGO_X25519);
532 client_caps.supported_auth_algorithms = HOST_TO_NET_U16(AUTH_ALGO_ED25519 | AUTH_ALGO_NONE);
533 client_caps.supported_cipher_algorithms = HOST_TO_NET_U16(CIPHER_ALGO_XSALSA20_POLY1305 | CIPHER_ALGO_NONE);
534 client_caps.requires_verification = 0; // Client doesn't require server verification (uses known_hosts)
535 client_caps.preferred_kex = KEX_ALGO_X25519;
536 client_caps.preferred_auth = ACIP_CRYPTO_HAS_AUTH(g_crypto_mode) ? AUTH_ALGO_ED25519 : AUTH_ALGO_NONE;
537 client_caps.preferred_cipher =
538 ACIP_CRYPTO_HAS_ENCRYPT(g_crypto_mode) ? CIPHER_ALGO_XSALSA20_POLY1305 : CIPHER_ALGO_NONE;
539
540 result = send_crypto_capabilities_packet(socket, &client_caps);
541 if (result != 0) {
542 log_error("Failed to send crypto capabilities to server");
543 STOP_TIMER("client_crypto_handshake");
544 return -1;
545 }
546 log_debug("CLIENT_CRYPTO_HANDSHAKE: Crypto capabilities sent successfully");
547
548 // Step 0d: Receive server's crypto parameters
549 log_debug("CLIENT_CRYPTO_HANDSHAKE: Receiving server crypto parameters");
550 payload = NULL;
551 payload_len = 0;
552
553 result = receive_packet(socket, &packet_type, &payload, &payload_len);
554 if (result != ASCIICHAT_OK || packet_type != PACKET_TYPE_CRYPTO_PARAMETERS) {
555 log_error("Failed to receive server crypto parameters (got type %u)", packet_type);
556 if (payload) {
557 buffer_pool_free(NULL, payload, payload_len);
558 }
559 STOP_TIMER("client_crypto_handshake");
560 return -1;
561 }
562
563 if (payload_len != sizeof(crypto_parameters_packet_t)) {
564 log_error("Invalid crypto parameters packet size: %zu, expected %zu", payload_len,
565 sizeof(crypto_parameters_packet_t));
566 buffer_pool_free(NULL, payload, payload_len);
567 STOP_TIMER("client_crypto_handshake");
568 return -1;
569 }
570
571 crypto_parameters_packet_t server_params;
572 memcpy(&server_params, payload, sizeof(crypto_parameters_packet_t));
573 buffer_pool_free(NULL, payload, payload_len);
574
575 // Convert from network byte order
576 uint16_t kex_pubkey_size = NET_TO_HOST_U16(server_params.kex_public_key_size);
577 uint16_t auth_pubkey_size = NET_TO_HOST_U16(server_params.auth_public_key_size);
578 uint16_t signature_size = NET_TO_HOST_U16(server_params.signature_size);
579 uint16_t shared_secret_size = NET_TO_HOST_U16(server_params.shared_secret_size);
580
581 log_debug("Server crypto parameters: KEX=%u, Auth=%u, Cipher=%u (key_size=%u, auth_size=%u, sig_size=%u, "
582 "secret_size=%u, verification=%u)",
583 server_params.selected_kex, server_params.selected_auth, server_params.selected_cipher, kex_pubkey_size,
584 auth_pubkey_size, signature_size, shared_secret_size, server_params.verification_enabled);
585 log_debug("Raw server_params.kex_public_key_size = %u (network byte order)", server_params.kex_public_key_size);
586
587 // Set the crypto parameters in the handshake context
588 result = crypto_handshake_set_parameters(&g_crypto_ctx, &server_params);
589 if (result != ASCIICHAT_OK) {
590 FATAL(result, "Failed to set crypto parameters");
591 }
592
593 // Store verification flag - server will verify client identity (whitelist check)
594 // This is independent of whether server provides its own identity
595 if (server_params.verification_enabled) {
596 g_crypto_ctx.server_uses_client_auth = true;
597 g_crypto_ctx.require_client_auth = true;
598 log_info("Server will verify client identity (whitelist enabled)");
599 }
600
601 // Validate that server chose algorithms we support
602 if (server_params.selected_kex != KEX_ALGO_X25519) {
603 log_error("Server selected unsupported KEX algorithm: %u", server_params.selected_kex);
604 STOP_TIMER("client_crypto_handshake");
606 }
607
608 // Validate cipher selection based on negotiated mode
609 bool expect_cipher = ACIP_CRYPTO_HAS_ENCRYPT(g_crypto_mode);
610 if (expect_cipher && server_params.selected_cipher != CIPHER_ALGO_XSALSA20_POLY1305) {
611 log_error("Server selected unsupported cipher algorithm: %u", server_params.selected_cipher);
612 STOP_TIMER("client_crypto_handshake");
614 }
615 if (!expect_cipher && server_params.selected_cipher != CIPHER_ALGO_NONE) {
616 log_error("Server chose cipher %u but client requested no encryption", server_params.selected_cipher);
617 STOP_TIMER("client_crypto_handshake");
619 }
620
621 log_debug("CLIENT_CRYPTO_HANDSHAKE: Protocol negotiation completed successfully");
622
623 // Step 0.5: Send CRYPTO_CLIENT_HELLO with expected server key (multi-key support)
624 // This allows the server to select the correct identity key from g_server_identity_keys[]
625 if (g_crypto_ctx.verify_server_key && strlen(g_crypto_ctx.expected_server_key) > 0) {
626 log_debug("CLIENT_CRYPTO_HANDSHAKE: Sending expected server key to support multi-key selection");
627
628 // Parse expected server key to get the Ed25519 public key
629 public_key_t expected_keys[MAX_CLIENTS];
630 size_t num_expected_keys = 0;
631
632 if (parse_public_keys(g_crypto_ctx.expected_server_key, expected_keys, &num_expected_keys, MAX_CLIENTS) == 0 &&
633 num_expected_keys > 0) {
634 // Send the first expected key (client should only have one expected server key)
635 // Format: 32-byte Ed25519 public key
636 log_debug("Sending CRYPTO_CLIENT_HELLO with expected server key for multi-key selection");
637 result = send_packet(socket, PACKET_TYPE_CRYPTO_CLIENT_HELLO, expected_keys[0].key, ED25519_PUBLIC_KEY_SIZE);
638
639 if (result != ASCIICHAT_OK) {
640 FATAL(result, "Failed to send CRYPTO_CLIENT_HELLO packet");
641 }
642
643 // Log the key fingerprint for debugging
644 char hex_key[65];
645 pubkey_to_hex(expected_keys[0].key, hex_key);
646 log_debug("Sent expected server key: %s", hex_key);
647 } else {
648 log_warn("Failed to parse expected server key '%s' - server will use default key",
649 g_crypto_ctx.expected_server_key);
650 }
651 }
652
653 // Step 1: Receive server's public key and send our public key
654 log_debug("CLIENT_CRYPTO_HANDSHAKE: Starting key exchange");
656 if (result != ASCIICHAT_OK) {
657#ifdef _WIN32
658 // On Windows: Cleanup capture resources before exiting to prevent Media Foundation threads from hanging exit()
659 // Media Foundation creates background COM threads that can block exit() if not properly shut down
661#endif
662 FATAL(result, "Crypto key exchange failed");
663 }
664 log_debug("CLIENT_CRYPTO_HANDSHAKE: Key exchange completed successfully");
665
666 // SECURITY: Warn when server requires client verification but client has no identity key
667 bool client_has_identity = (g_crypto_ctx.client_public_key.type == KEY_TYPE_ED25519);
668
669 if (g_crypto_ctx.require_client_auth && !client_has_identity) {
670 // Server requires client verification but client has no identity key
671 log_warn("Server requires client verification but client has no identity key");
672
673 // Check if we're running interactively (stdin is a terminal)
674 // In debug builds with CLAUDECODE, skip interactive prompts (LLM can't type)
675#ifndef NDEBUG
676 bool skip_interactive = platform_getenv("CLAUDECODE") != NULL;
677#else
678 bool skip_interactive = false;
679#endif
680 if (!skip_interactive && platform_is_interactive()) {
681 // Interactive mode - prompt user for confirmation
682 // Lock terminal for the warning message
683 bool previous_terminal_state = log_lock_terminal();
684
685 log_plain("\n"
686 "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
687 "@ WARNING: CLIENT AUTHENTICATION REQUIRED @\n"
688 "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
689 "\n"
690 "The server requires client authentication (--client-keys enabled),\n"
691 "but you have not provided a client identity key with --key.\n"
692 "\n"
693 "To connect to this server, you need to:\n"
694 " 1. Generate an Ed25519 key: ssh-keygen -t ed25519\n"
695 " 2. Add the public key to the server's --client-keys list\n"
696 " 3. Connect with: ascii-chat client --key /path/to/private/key\n");
697
698 // Unlock before prompt (prompt_yes_no handles its own terminal locking)
699 log_unlock_terminal(previous_terminal_state);
700
701 // Prompt user - default is No since this will likely fail
702 if (!platform_prompt_yes_no("Do you want to continue anyway (this will likely fail)", false)) {
703 log_plain("Connection aborted by user.");
704 exit(0); // User declined - exit cleanly
705 }
706
707 log_plain("Warning: Continuing without client identity key (connection may fail).\n");
708 } else {
709 // Non-interactive mode (background/script) - just log warning and continue
710 log_warn("Non-interactive mode: Continuing without client identity key (connection may fail)");
711 }
712 }
713
714 // Step 2: Receive auth challenge and send response
715 log_debug("CLIENT_CRYPTO: Sending auth response to server...");
716 log_debug("CLIENT_CRYPTO_HANDSHAKE: Starting auth response");
718 if (result != ASCIICHAT_OK) {
719 FATAL(result, "Crypto authentication failed");
720 }
721 log_debug("CLIENT_CRYPTO: Auth response sent successfully");
722 log_debug("CLIENT_CRYPTO_HANDSHAKE: Auth response completed successfully");
723
724 // Check if handshake completed during auth response (no authentication needed)
725 if (g_crypto_ctx.state == CRYPTO_HANDSHAKE_READY) {
726 // Propagate encryption flag to crypto context
727 g_crypto_ctx.crypto_ctx.encrypt_data = ACIP_CRYPTO_HAS_ENCRYPT(g_crypto_mode);
728 STOP_TIMER_AND_LOG(debug, 100 * NS_PER_MS_INT, "client_crypto_handshake",
729 "Crypto handshake completed successfully (no authentication)");
730 return 0;
731 }
732
733 // Step 3: Receive handshake complete message
734 log_debug("CLIENT_CRYPTO_HANDSHAKE: Waiting for handshake complete message");
736 if (result != ASCIICHAT_OK) {
737 FATAL(result, "Crypto handshake completion failed");
738 }
739
740 // Propagate encryption flag to crypto context
741 g_crypto_ctx.crypto_ctx.encrypt_data = ACIP_CRYPTO_HAS_ENCRYPT(g_crypto_mode);
742
743 STOP_TIMER_AND_LOG(debug, 100 * NS_PER_MS_INT, "client_crypto_handshake", "Crypto handshake completed successfully");
744 log_debug("CLIENT_CRYPTO_HANDSHAKE: Handshake completed successfully, state=%d", g_crypto_ctx.state);
745 return 0;
746}
747
756 if (!g_crypto_initialized || GET_OPTION(no_encrypt)) {
757 return false;
758 }
759
761}
762
770const crypto_context_t *crypto_client_get_context(void) {
771 if (!crypto_client_is_ready()) {
772 return NULL;
773 }
774
776}
777
790int crypto_client_encrypt_packet(const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext,
791 size_t ciphertext_size, size_t *ciphertext_len) {
793 ciphertext, ciphertext_size, ciphertext_len);
794}
795
808int crypto_client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext,
809 size_t plaintext_size, size_t *plaintext_len) {
810 return crypto_decrypt_packet_or_passthrough(&g_crypto_ctx, crypto_client_is_ready(), ciphertext, ciphertext_len,
811 plaintext, plaintext_size, plaintext_len);
812}
813
820 // Check and reset initialization state with thread safety
821 static_mutex_lock(&g_crypto_init_mutex);
822 bool was_initialized = g_crypto_initialized;
823 g_crypto_initialized = false;
824 static_mutex_unlock(&g_crypto_init_mutex);
825
826 if (was_initialized) {
828 log_debug("Client crypto handshake cleaned up");
829 }
830}
831
832// =============================================================================
833// Session Rekeying Functions
834// =============================================================================
835
844 if (!g_crypto_initialized || !crypto_client_is_ready()) {
845 return false;
846 }
848}
849
858 if (!g_crypto_initialized || !crypto_client_is_ready()) {
859 log_error("Cannot initiate rekey: crypto not initialized or not ready");
860 return -1;
861 }
862
864 if (socket == INVALID_SOCKET_VALUE) {
865 log_error("Cannot initiate rekey: invalid socket");
866 return -1;
867 }
868
869 asciichat_error_t result = crypto_handshake_rekey_request(&g_crypto_ctx, socket);
870 if (result != ASCIICHAT_OK) {
871 log_error("Failed to send REKEY_REQUEST: %d", result);
872 return -1;
873 }
874
875 return 0;
876}
877
887int crypto_client_process_rekey_request(const uint8_t *packet, size_t packet_len) {
888 if (!g_crypto_initialized || !crypto_client_is_ready()) {
889 log_error("Cannot process rekey request: crypto not initialized or not ready");
890 return -1;
891 }
892
893 asciichat_error_t result = crypto_handshake_process_rekey_request(&g_crypto_ctx, packet, packet_len);
894 if (result != ASCIICHAT_OK) {
895 log_error("Failed to process REKEY_REQUEST: %d", result);
896 return -1;
897 }
898
899 return 0;
900}
901
910 if (!g_crypto_initialized || !crypto_client_is_ready()) {
911 log_error("Cannot send rekey response: crypto not initialized or not ready");
912 return -1;
913 }
914
916 if (socket == INVALID_SOCKET_VALUE) {
917 log_error("Cannot send rekey response: invalid socket");
918 return -1;
919 }
920
921 asciichat_error_t result = crypto_handshake_rekey_response(&g_crypto_ctx, socket);
922 if (result != ASCIICHAT_OK) {
923 log_error("Failed to send REKEY_RESPONSE: %d", result);
924 return -1;
925 }
926
927 return 0;
928}
929
939int crypto_client_process_rekey_response(const uint8_t *packet, size_t packet_len) {
940 if (!g_crypto_initialized || !crypto_client_is_ready()) {
941 log_error("Cannot process rekey response: crypto not initialized or not ready");
942 return -1;
943 }
944
945 asciichat_error_t result = crypto_handshake_process_rekey_response(&g_crypto_ctx, packet, packet_len);
946 if (result != ASCIICHAT_OK) {
947 log_error("Failed to process REKEY_RESPONSE: %d", result);
948 return -1;
949 }
950
951 return 0;
952}
953
962 if (!g_crypto_initialized || !crypto_client_is_ready()) {
963 log_error("Cannot send rekey complete: crypto not initialized or not ready");
964 return -1;
965 }
966
968 if (socket == INVALID_SOCKET_VALUE) {
969 log_error("Cannot send rekey complete: invalid socket");
970 return -1;
971 }
972
973 asciichat_error_t result = crypto_handshake_rekey_complete(&g_crypto_ctx, socket);
974 if (result != ASCIICHAT_OK) {
975 log_error("Failed to send REKEY_COMPLETE: %d", result);
976 return -1;
977 }
978
979 return 0;
980}
asciichat_error_t acds_session_lookup(acds_client_t *client, const char *session_string, acds_session_lookup_result_t *result)
void acds_client_config_init_defaults(acds_client_config_t *config)
Definition acds_client.c:45
asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config)
Definition acds_client.c:60
void acds_client_disconnect(acds_client_t *client)
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
ascii-chat Client Media Capture Management Interface
@ CONNECTION_ERROR_AUTH_FAILED
Authentication failure (no retry)
asciichat_error_t crypto_handshake_rekey_complete(crypto_handshake_context_t *ctx, socket_t socket)
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
asciichat_error_t crypto_handshake_process_rekey_request(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
asciichat_error_t crypto_handshake_init_with_password(crypto_handshake_context_t *ctx, bool is_server, const char *password)
bool crypto_handshake_is_ready(const crypto_handshake_context_t *ctx)
bool crypto_handshake_should_rekey(const crypto_handshake_context_t *ctx)
asciichat_error_t crypto_handshake_init(crypto_handshake_context_t *ctx, bool is_server)
asciichat_error_t crypto_handshake_rekey_response(crypto_handshake_context_t *ctx, socket_t socket)
asciichat_error_t crypto_handshake_set_parameters(crypto_handshake_context_t *ctx, const crypto_parameters_packet_t *params)
asciichat_error_t crypto_handshake_process_rekey_response(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
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)
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)
asciichat_error_t crypto_handshake_rekey_request(crypto_handshake_context_t *ctx, socket_t socket)
void pubkey_to_hex(const uint8_t pubkey[32], char hex_out[65])
Definition discovery.c:49
asciichat_error_t discovery_keys_verify(const char *acds_server, const char *key_spec, uint8_t pubkey_out[32])
void capture_cleanup()
Cleanup capture subsystem.
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
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 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 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.
int socket_t
asciichat_error_t parse_private_key(const char *key_path, private_key_t *key_out)
Definition keys.c:194
asciichat_error_t parse_public_keys(const char *input, public_key_t *keys_out, size_t *num_keys, size_t max_keys)
Definition keys.c:345
const char * crypto_result_to_string(crypto_result_t result)
crypto_result_t crypto_derive_password_key(crypto_context_t *ctx, const char *password)
asciichat_error_t crypto_handshake_client_complete_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Complete handshake using socket (TCP clients only)
asciichat_error_t crypto_handshake_client_auth_response_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Auth response using socket (TCP clients only)
asciichat_error_t crypto_handshake_client_key_exchange_socket(crypto_handshake_context_t *ctx, socket_t client_socket)
Legacy wrapper: Key exchange using socket (TCP clients only)
bool log_lock_terminal(void)
void log_unlock_terminal(bool previous_state)
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_packet(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a basic packet without encryption.
Definition packet.c:753
int send_crypto_capabilities_packet(socket_t sockfd, const crypto_capabilities_packet_t *caps)
Send crypto capabilities packet.
Definition packet.c:1027
const options_t * options_get(void)
Definition rcu.c:347
Server cryptographic operations and per-client handshake management.
ascii-chat Server Mode Entry Point Header
asciichat_error_t validate_ssh_key_file(const char *key_path)
Definition ssh_keys.c:940
bool platform_prompt_yes_no(const char *question, bool default_yes)
Definition util.c:81
const char * platform_getenv(const char *name)
Definition wasm/system.c:13