209 log_debug(
"CLIENT_CRYPTO_INIT: Starting crypto initialization");
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);
217 if (was_initialized) {
218 log_debug(
"CLIENT_CRYPTO_INIT: Already initialized, cleaning up and reinitializing");
223 if (GET_OPTION(no_encrypt)) {
224 log_info(
"Encryption disabled");
225 log_debug(
"CLIENT_CRYPTO_INIT: Encryption disabled, returning 0");
229 log_debug(
"CLIENT_CRYPTO_INIT: Initializing crypto handshake context");
233 bool is_ssh_key =
false;
234 private_key_t private_key;
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 :
"";
245 if (strlen(encrypt_key) > 0) {
249 if (strncmp(encrypt_key,
"gpg:", 4) != 0) {
256 log_debug(
"CLIENT_CRYPTO_INIT: Loading private key for authentication: %s", encrypt_key);
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);
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");
268 log_error(
"Note: RSA and ECDSA keys are not yet supported");
269 log_error(
"To generate an Ed25519 key: ssh-keygen -t ed25519");
276 log_debug(
"CLIENT_CRYPTO_INIT: Using SSH key for authentication");
280 if (result != ASCIICHAT_OK) {
281 FATAL(result,
"Failed to initialize crypto handshake");
285 memcpy(&
g_crypto_ctx.client_private_key, &private_key,
sizeof(private_key_t));
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,
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);
298 if (key_id_len == 8 || key_id_len == 16 || key_id_len == 40) {
300 log_debug(
"CLIENT_CRYPTO_INIT: Extracted client GPG key ID (%zu chars): %s", key_id_len,
303 log_warn(
"CLIENT_CRYPTO_INIT: Invalid GPG key ID length: %zu (expected 8, 16, or 40)", key_id_len);
315 sodium_memzero(&private_key,
sizeof(private_key));
318 if (strlen(password) > 0) {
319 log_debug(
"CLIENT_CRYPTO_INIT: Password also provided, deriving password key");
321 if (crypto_result != CRYPTO_OK) {
326 log_debug(
"Password authentication enabled alongside SSH key");
329 }
else if (strlen(GET_OPTION(password)) > 0) {
331 log_debug(
"CLIENT_CRYPTO_INIT: Using password authentication");
333 if (result != ASCIICHAT_OK) {
334 FATAL(result,
"Failed to initialize crypto handshake with password");
338 log_debug(
"CLIENT_CRYPTO_INIT: Using standard initialization");
340 if (result != ASCIICHAT_OK) {
341 FATAL(result,
"Failed to initialize crypto handshake");
345 log_debug(
"CLIENT_CRYPTO_INIT: crypto_handshake_init succeeded");
350 log_debug(
"CLIENT_CRYPTO_INIT: server_connection_get_ip() returned: '%s'", server_ip ? server_ip :
"NULL");
353 log_debug(
"CLIENT_CRYPTO_INIT: Set server_ip='%s', server_port=%u",
g_crypto_ctx.server_ip,
g_crypto_ctx.server_port);
356 if (strlen(server_key) > 0) {
359 log_debug(
"Server key verification enabled: %s", server_key);
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));
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;
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 =
379 if (verify_result != ASCIICHAT_OK) {
380 log_error(
"ACDS key verification failed for %s", acds_config.server_address);
383 log_debug(
"ACDS server key verified successfully");
387 log_debug(
"Skipping ACDS key verification (debug build, no --discovery-service-key provided)");
391 acds_client_t acds_client;
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);
399 acds_session_lookup_result_t lookup_result;
400 acds_result =
acds_session_lookup(&acds_client, GET_OPTION(session_string), &lookup_result);
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");
410 char server_key_hex[65];
411 for (
size_t i = 0; i < 32; i++) {
412 SAFE_SNPRINTF(&server_key_hex[i * 2], 3,
"%02x", lookup_result.host_pubkey[i]);
414 server_key_hex[64] =
'\0';
419 log_debug(
"ACDS session lookup succeeded - server identity will be verified");
420 log_debug(
"Expected server key (from ACDS): %s", server_key_hex);
424 static_mutex_lock(&g_crypto_init_mutex);
425 g_crypto_initialized =
true;
426 static_mutex_unlock(&g_crypto_init_mutex);
428 log_debug(
"Client crypto handshake initialized");
429 log_debug(
"CLIENT_CRYPTO_INIT: Initialization complete, g_crypto_initialized=true");
443 if (GET_OPTION(no_encrypt)) {
444 log_debug(
"Client has --no-encrypt, skipping crypto handshake");
449 static_mutex_lock(&g_crypto_init_mutex);
450 bool is_initialized = g_crypto_initialized;
451 static_mutex_unlock(&g_crypto_init_mutex);
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");
460 log_debug(
"Starting crypto handshake with server...");
462 START_TIMER(
"client_crypto_handshake");
465 protocol_version_packet_t client_version = {0};
466 client_version.protocol_version = HOST_TO_NET_U16(1);
467 client_version.protocol_revision = HOST_TO_NET_U16(0);
468 client_version.supports_encryption = g_crypto_mode;
469 client_version.compression_algorithms = 0;
470 client_version.compression_threshold = 0;
471 client_version.feature_flags = 0;
475 log_error(
"Failed to send protocol version to server");
476 STOP_TIMER(
"client_crypto_handshake");
479 log_debug(
"CLIENT_CRYPTO_HANDSHAKE: Protocol version sent successfully");
482 packet_type_t packet_type;
483 void *payload = NULL;
484 size_t payload_len = 0;
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);
497 STOP_TIMER(
"client_crypto_handshake");
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));
505 STOP_TIMER(
"client_crypto_handshake");
509 protocol_version_packet_t server_version;
510 memcpy(&server_version, payload,
sizeof(protocol_version_packet_t));
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;
518 log_info(
"Server protocol version: %u.%u (crypto mode: 0x%02x)", server_proto_version, server_proto_revision,
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");
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;
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;
542 log_error(
"Failed to send crypto capabilities to server");
543 STOP_TIMER(
"client_crypto_handshake");
546 log_debug(
"CLIENT_CRYPTO_HANDSHAKE: Crypto capabilities sent successfully");
549 log_debug(
"CLIENT_CRYPTO_HANDSHAKE: Receiving server crypto parameters");
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);
559 STOP_TIMER(
"client_crypto_handshake");
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));
567 STOP_TIMER(
"client_crypto_handshake");
571 crypto_parameters_packet_t server_params;
572 memcpy(&server_params, payload,
sizeof(crypto_parameters_packet_t));
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);
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);
589 if (result != ASCIICHAT_OK) {
590 FATAL(result,
"Failed to set crypto parameters");
595 if (server_params.verification_enabled) {
598 log_info(
"Server will verify client identity (whitelist enabled)");
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");
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");
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");
621 log_debug(
"CLIENT_CRYPTO_HANDSHAKE: Protocol negotiation completed successfully");
626 log_debug(
"CLIENT_CRYPTO_HANDSHAKE: Sending expected server key to support multi-key selection");
629 public_key_t expected_keys[MAX_CLIENTS];
630 size_t num_expected_keys = 0;
633 num_expected_keys > 0) {
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);
639 if (result != ASCIICHAT_OK) {
640 FATAL(result,
"Failed to send CRYPTO_CLIENT_HELLO packet");
646 log_debug(
"Sent expected server key: %s", hex_key);
648 log_warn(
"Failed to parse expected server key '%s' - server will use default key",
654 log_debug(
"CLIENT_CRYPTO_HANDSHAKE: Starting key exchange");
656 if (result != ASCIICHAT_OK) {
662 FATAL(result,
"Crypto key exchange failed");
664 log_debug(
"CLIENT_CRYPTO_HANDSHAKE: Key exchange completed successfully");
667 bool client_has_identity = (
g_crypto_ctx.client_public_key.type == KEY_TYPE_ED25519);
669 if (
g_crypto_ctx.require_client_auth && !client_has_identity) {
671 log_warn(
"Server requires client verification but client has no identity key");
678 bool skip_interactive =
false;
680 if (!skip_interactive && platform_is_interactive()) {
686 "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
687 "@ WARNING: CLIENT AUTHENTICATION REQUIRED @\n"
688 "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
690 "The server requires client authentication (--client-keys enabled),\n"
691 "but you have not provided a client identity key with --key.\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");
703 log_plain(
"Connection aborted by user.");
707 log_plain(
"Warning: Continuing without client identity key (connection may fail).\n");
710 log_warn(
"Non-interactive mode: Continuing without client identity key (connection may fail)");
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");
721 log_debug(
"CLIENT_CRYPTO: Auth response sent successfully");
722 log_debug(
"CLIENT_CRYPTO_HANDSHAKE: Auth response completed successfully");
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)");
734 log_debug(
"CLIENT_CRYPTO_HANDSHAKE: Waiting for handshake complete message");
736 if (result != ASCIICHAT_OK) {
737 FATAL(result,
"Crypto handshake completion failed");
741 g_crypto_ctx.crypto_ctx.encrypt_data = ACIP_CRYPTO_HAS_ENCRYPT(g_crypto_mode);
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);
ascii-chat Server Mode Entry Point Header