ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
Cryptographic Operations

🔐 Client-side cryptographic handshake and key management More...

Files

file  crypto.c
 🔐 Client cryptography: handshake integration, X25519 key exchange, and per-session encryption
 
file  crypto.h
 ascii-chat Client Cryptography Handler Interface
 

Functions

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 client_crypto_handshake (acip_transport_t *transport)
 Perform crypto handshake with server.
 
bool crypto_client_is_ready (void)
 Check if crypto handshake is ready.
 
const crypto_context_t * crypto_client_get_context (void)
 Get crypto context for encryption/decryption.
 
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.
 
void crypto_client_cleanup (void)
 Cleanup crypto client resources.
 
bool crypto_client_should_rekey (void)
 Check if session rekeying should be triggered.
 
int crypto_client_initiate_rekey (void)
 Initiate session rekeying (client-initiated)
 
int crypto_client_process_rekey_request (const uint8_t *packet, size_t packet_len)
 Process received REKEY_REQUEST packet from server.
 
int crypto_client_send_rekey_response (void)
 Send REKEY_RESPONSE packet to 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.
 

Detailed Description

🔐 Client-side cryptographic handshake and key management

Cryptographic Operations

Overview

The client crypto subsystem coordinates the cryptographic handshake with the server, loads client and server keys, verifies host identity, and manages the encryption context for secure communication.

Implementation: src/client/crypto.c, src/client/crypto.h

Handshake Process

{
// Perform client-side handshake
crypto_handshake_result_t result =
perform_crypto_handshake_client(socket, &handshake_ctx,
opt_server_key, server_ip, opt_client_key);
switch (result) {
case HANDSHAKE_SUCCESS:
log_info("Secure connection established");
g_crypto_ctx = handshake_ctx.crypto_ctx;
g_crypto_ready = true;
return 0;
case HANDSHAKE_WARNING_NO_CLIENT_AUTH:
log_warn("Server not verifying client identity");
g_crypto_ctx = handshake_ctx.crypto_ctx;
g_crypto_ready = true;
case HANDSHAKE_ERROR_AUTH_FAILED:
log_error("Client authentication failed - wrong client key?");
return CONNECTION_ERROR_AUTH_FAILED; // No retry
case HANDSHAKE_ERROR_HOST_KEY_FAILED:
log_error("Server host key verification failed");
default:
log_error("Handshake failed");
return CONNECTION_ERROR_GENERIC; // Allow retry
}
}
crypto_handshake_context_t g_crypto_ctx
Global crypto handshake context for this client connection.
int client_crypto_handshake(acip_transport_t *transport)
Perform crypto handshake with server.
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
int socket_t
@ CONNECTION_ERROR_GENERIC
Generic error (retry allowed)
@ CONNECTION_ERROR_HOST_KEY_FAILED
Host key verification failed (no retry)
@ CONNECTION_ERROR_AUTH_FAILED
Authentication failure (no retry)
@ CONNECTION_WARNING_NO_CLIENT_AUTH
Server not using client verification (warning)
Cryptographic handshake context structure.

Key Management

Client Keys (--client-key):

  • Ed25519 SSH private keys for client authentication
  • Loaded from filesystem or SSH agent
  • Password support via $ASCII_CHAT_KEY_PASSWORD

Server Keys (--server-key):

  • Ed25519 SSH public keys for server verification
  • Can be loaded from:
    • Filesystem path (~/.ssh/server_id_ed25519.pub)
    • GitHub URL (github:username)
    • GitLab URL (gitlab:username)
  • Verified against known_hosts file
Note
Native Decryption: Encrypted SSH keys are decrypted natively using bcrypt_pbkdf (from libsodium-bcrypt-pbkdf) + BearSSL AES-256-CTR. No external tools required. Alternatively, use $SSH_AUTH_SOCK (ssh-agent) for password-free authentication.

Known Hosts Verification

TOFU (Trust On First Use):

  • First connection: Record server public key by IP
  • Subsequent connections: Verify key matches recorded value
  • Key mismatch: Connection rejected (possible MITM attack)

Known hosts file: ~/.ascii-chat/known_hosts

See also
src/client/crypto.c
src/client/crypto.h
lib/crypto/handshake.h
lib/crypto/keys/keys.h
Client Overview

Function Documentation

◆ client_crypto_handshake()

int client_crypto_handshake ( acip_transport_t *  transport)

#include <crypto.c>

Perform crypto handshake with server.

Perform crypto handshake with server

Parameters
transportClient's persistent transport (wraps the connected socket)
Returns
0 on success, -1 on failure

Executes the complete cryptographic handshake protocol with the server, including protocol negotiation, key exchange, and authentication.

Handshake Steps:

  1. Send CLIENT_HELLO with ephemeral public key
  2. Receive SERVER_HELLO with server's ephemeral public key
  3. Derive shared secret using ECDH (both sides)
  4. Receive SERVER_AUTH (server's identity proof)
  5. Verify server's signature against known_hosts or provided key
  6. Send CLIENT_AUTH (client's identity proof or password)
  7. Server verifies client authentication
  8. Encryption keys ready for payload exchange

Error Handling:

  • Protocol Error: Invalid format, timeout, socket error → CONNECTION_ERROR_GENERIC
  • Auth Failure: Invalid password, wrong signature → CONNECTION_ERROR_AUTH_FAILED
  • Host Key Failure: Server key not in known_hosts → CONNECTION_ERROR_HOST_KEY_FAILED

Retryability:

  • CONNECTION_ERROR_GENERIC: Retryable (network issue)
  • CONNECTION_ERROR_AUTH_FAILED: Non-retryable (wrong password/key)
  • CONNECTION_ERROR_HOST_KEY_FAILED: Non-retryable (need known_hosts update)
Parameters
socketConnected TCP socket to server (must be connected before calling)
Returns
Connection status:
  • 0: Handshake successful, encryption ready
  • CONNECTION_ERROR_GENERIC: Network or protocol error (retryable)
  • CONNECTION_ERROR_AUTH_FAILED: Authentication failed (non-retryable)
  • CONNECTION_ERROR_HOST_KEY_FAILED: Host key verification failed (non-retryable)
Note
Called by server_connection_establish() after TCP connection succeeds. If this fails with AUTH_FAILED or HOST_KEY_FAILED, the connection attempt is abandoned and the main thread does NOT retry.
See also
client_crypto_init "Initialize crypto context"
server_connection_establish "Which calls this handshake"

Definition at line 435 of file src/client/crypto.c.

435 {
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}
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.
void pubkey_to_hex(const uint8_t pubkey[32], char hex_out[65])
Convert Ed25519 public key to hex string.
Definition discovery.c:49
#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.
const crypto_context_t * crypto_client_get_context(void)
Get crypto context for encryption/decryption.
unsigned short uint16_t
Definition common.h:57
#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
#define ED25519_PUBLIC_KEY_SIZE
Ed25519 public key size in bytes.
@ ASCIICHAT_OK
Definition error_codes.h:51
@ CRYPTO_HANDSHAKE_READY
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
key_type_t type
Definition key_types.h:70
@ 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
bool log_lock_terminal(void)
Lock terminal output for exclusive access by the calling thread.
Definition log/log.c:787
#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 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 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)
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
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
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
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
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

References ACIP_CRYPTO_HAS_AUTH, ACIP_CRYPTO_HAS_ENCRYPT, ASCIICHAT_OK, AUTH_ALGO_ED25519, AUTH_ALGO_NONE, crypto_parameters_packet_t::auth_public_key_size, buffer_pool_free(), capture_cleanup(), CIPHER_ALGO_NONE, CIPHER_ALGO_XSALSA20_POLY1305, crypto_handshake_context_t::client_public_key, protocol_version_packet_t::compression_algorithms, protocol_version_packet_t::compression_threshold, CONNECTION_ERROR_AUTH_FAILED, crypto_client_get_context(), crypto_handshake_context_t::crypto_ctx, acip_transport::crypto_ctx, crypto_handshake_client_auth_response(), crypto_handshake_client_complete(), crypto_handshake_client_key_exchange(), CRYPTO_HANDSHAKE_READY, crypto_handshake_set_parameters(), ED25519_PUBLIC_KEY_SIZE, crypto_context_t::encrypt_data, crypto_handshake_context_t::expected_server_key, FATAL, protocol_version_packet_t::feature_flags, g_crypto_ctx, GET_OPTION, HOST_TO_NET_U16, KEX_ALGO_X25519, crypto_parameters_packet_t::kex_public_key_size, KEY_TYPE_ED25519, lifecycle_is_initialized(), log_debug, log_error, log_info, log_lock_terminal(), log_plain, log_unlock_terminal(), log_warn, MAX_CLIENTS, NET_TO_HOST_U16, NS_PER_MS_INT, packet_receive_via_transport(), packet_send_via_transport(), PACKET_TYPE_CRYPTO_CAPABILITIES, PACKET_TYPE_CRYPTO_CLIENT_HELLO, PACKET_TYPE_CRYPTO_PARAMETERS, PACKET_TYPE_PROTOCOL_VERSION, parse_public_keys(), platform_getenv(), platform_is_interactive(), platform_prompt_yes_no(), crypto_capabilities_packet_t::preferred_auth, crypto_capabilities_packet_t::preferred_cipher, crypto_capabilities_packet_t::preferred_kex, protocol_version_packet_t::protocol_revision, protocol_version_packet_t::protocol_version, pubkey_to_hex(), crypto_handshake_context_t::require_client_auth, crypto_capabilities_packet_t::requires_verification, crypto_parameters_packet_t::selected_auth, crypto_parameters_packet_t::selected_cipher, crypto_parameters_packet_t::selected_kex, crypto_handshake_context_t::server_uses_client_auth, crypto_parameters_packet_t::shared_secret_size, crypto_parameters_packet_t::signature_size, START_TIMER, crypto_handshake_context_t::state, STOP_TIMER, STOP_TIMER_AND_LOG, crypto_capabilities_packet_t::supported_auth_algorithms, crypto_capabilities_packet_t::supported_cipher_algorithms, crypto_capabilities_packet_t::supported_kex_algorithms, protocol_version_packet_t::supports_encryption, public_key_t::type, crypto_parameters_packet_t::verification_enabled, and crypto_handshake_context_t::verify_server_key.

Referenced by client_main(), connection_attempt_tcp(), and server_connection_establish().

◆ client_crypto_init()

int client_crypto_init ( void  )

#include <crypto.c>

Initialize client crypto handshake.

Initialize client crypto handshake

Returns
0 on success, -1 on failure

Sets up the client-side cryptographic context with authentication credentials and generates ephemeral key pair for key exchange.

Options affecting initialization:

  • --client-key <path>: SSH private key for Ed25519 authentication
  • --server-key <path|url>: Server public key for verification
  • --password <pass>: Password for authentication (stored in options)

Supported Key Types:

  • OpenSSH Ed25519 private keys (encrypted or plaintext)
  • OpenSSH RSA private keys (converted to Ed25519)
  • GitHub/GitLab user keys (fetched via HTTPS)
  • Agent-based keys (ssh-agent or gpg-agent)

Key Decryption:

  • Encrypted OpenSSH keys: Uses bcrypt_pbkdf + AES-256-CTR
  • Environment variable: $ASCII_CHAT_KEY_PASSWORD
  • SSH agent: $SSH_AUTH_SOCK (automatic fallback)
  • GPG agent: $GPG_AGENT_INFO (automatic fallback)
Returns
0 on success, -1 on failure:
  • Key file not found
  • Key decryption failed (wrong password)
  • Invalid key format
Note
This is called before the handshake, during connection setup. Failures here are fatal and non-retryable.
See also
client_crypto_handshake "Perform actual handshake"

Definition at line 210 of file src/client/crypto.c.

210 {
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}
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.
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_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 discovery_keys_verify(const char *acds_server, const char *key_spec, uint8_t pubkey_out[32])
Verify and load ACDS server public key.
int server_connection_get_port(void)
Get the server port.
const char * server_connection_get_ip()
Get resolved server IP address.
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:492
const char * crypto_result_to_string(crypto_result_t result)
Convert crypto result to human-readable string.
crypto_result_t
Cryptographic operation result codes.
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
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
uint8_t public_key[32]
Definition key_types.h:99
asciichat_error_t validate_ssh_key_file(const char *key_path)
Validate SSH key file before parsing.
Definition ssh_keys.c:939
#define MS_PER_SEC_INT
Definition time.h:164
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)
bool lifecycle_init(lifecycle_t *lc, const char *name)
Definition lifecycle.c:26
bool lifecycle_reset(lifecycle_t *lc)
Definition lifecycle.c:167
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.
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

References acds_client_config_init_defaults(), acds_client_connect(), acds_client_disconnect(), acds_session_lookup(), options_state::address, ASCIICHAT_OK, crypto_handshake_context_t::client_gpg_key_id, crypto_handshake_context_t::client_private_key, crypto_handshake_context_t::client_public_key, public_key_t::comment, crypto_handshake_context_t::crypto_ctx, crypto_derive_password_key(), crypto_handshake_destroy(), crypto_handshake_init(), crypto_handshake_init_with_password(), CRYPTO_OK, crypto_result_to_string(), discovery_keys_verify(), ED25519_PUBLIC_KEY_SIZE, options_state::encrypt_key, crypto_handshake_context_t::expected_server_key, FATAL, acds_session_lookup_result_t::found, g_crypto_ctx, GET_OPTION, crypto_context_t::has_password, acds_session_lookup_result_t::host_pubkey, public_key_t::key, private_key_t::key_comment, KEY_TYPE_ED25519, lifecycle_init(), lifecycle_reset(), log_debug, log_error, log_warn, MS_PER_SEC_INT, OPT_PORT_INT_DEFAULT, options_get(), parse_private_key(), options_state::password, options_state::port, private_key_t::public_key, SAFE_SNPRINTF, SAFE_STRNCPY, acds_client_config_t::server_address, server_connection_get_ip(), server_connection_get_port(), crypto_handshake_context_t::server_hostname, crypto_handshake_context_t::server_ip, options_state::server_key, crypto_handshake_context_t::server_port, acds_client_config_t::server_port, acds_client_config_t::timeout_ms, public_key_t::type, private_key_t::type, validate_ssh_key_file(), and crypto_handshake_context_t::verify_server_key.

Referenced by client_main(), connection_attempt_tcp(), connection_attempt_websocket(), and server_connection_establish().

◆ client_crypto_set_mode()

void client_crypto_set_mode ( uint8_t  mode)

#include <crypto.c>

Set crypto mode for handshake (encryption + authentication)

Set crypto mode for handshake (encryption + authentication)

Parameters
modeCrypto mode bitmask (ACIP_CRYPTO_*)

Sets the crypto mode bitmask (ACIP_CRYPTO_*) to negotiate with server. Determines which security levels are requested in the handshake. Must be called before client_crypto_init().

Supported Modes:

  • ACIP_CRYPTO_NONE: No encryption (insecure, testing only)
  • ACIP_CRYPTO_ENCRYPT: Encryption only, no authentication
  • ACIP_CRYPTO_AUTH: Authentication only (unusual)
  • ACIP_CRYPTO_FULL: Both encryption AND authentication (recommended)
Parameters
modeCrypto mode bitmask to request
Note
The final negotiated mode is the intersection of client and server requests. Both must agree on encryption level or handshake fails.

Definition at line 198 of file src/client/crypto.c.

198 {
199 log_debug("CLIENT_CRYPTO: Setting crypto mode to 0x%02x", mode);
200 g_crypto_mode = mode;
201}

References log_debug.

Referenced by client_main(), and connection_attempt_tcp().

◆ crypto_client_cleanup()

void crypto_client_cleanup ( void  )

#include <crypto.c>

Cleanup crypto client resources.

Cleanup crypto client resources

Definition at line 863 of file src/client/crypto.c.

863 {
864 // Shutdown if initialized
865 if (lifecycle_shutdown(&g_crypto_lc)) {
867 log_debug("Client crypto handshake cleaned up");
868 }
869}
bool lifecycle_shutdown(lifecycle_t *lc)
Definition lifecycle.c:108

References crypto_handshake_destroy(), g_crypto_ctx, lifecycle_shutdown(), and log_debug.

◆ crypto_client_decrypt_packet()

int crypto_client_decrypt_packet ( const uint8_t *  ciphertext,
size_t  ciphertext_len,
uint8_t *  plaintext,
size_t  plaintext_size,
size_t *  plaintext_len 
)

#include <crypto.c>

Decrypt a received packet.

Decrypt a received packet

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

Definition at line 852 of file src/client/crypto.c.

853 {
854 return crypto_decrypt_packet_or_passthrough(&g_crypto_ctx, crypto_client_is_ready(), ciphertext, ciphertext_len,
855 plaintext, plaintext_size, plaintext_len);
856}
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.
bool crypto_client_is_ready(void)
Check if crypto handshake is ready.

References crypto_client_is_ready(), crypto_decrypt_packet_or_passthrough(), and g_crypto_ctx.

◆ crypto_client_encrypt_packet()

int crypto_client_encrypt_packet ( const uint8_t *  plaintext,
size_t  plaintext_len,
uint8_t *  ciphertext,
size_t  ciphertext_size,
size_t *  ciphertext_len 
)

#include <crypto.c>

Encrypt a packet for transmission.

Encrypt a packet for transmission

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

Definition at line 834 of file src/client/crypto.c.

835 {
837 ciphertext, ciphertext_size, ciphertext_len);
838}
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.

References crypto_client_is_ready(), crypto_encrypt_packet_or_passthrough(), and g_crypto_ctx.

◆ crypto_client_get_context()

const crypto_context_t * crypto_client_get_context ( void  )

#include <crypto.c>

Get crypto context for encryption/decryption.

Get crypto context for encryption/decryption

Returns
crypto context or NULL if not ready
crypto context or NULL if not ready

Definition at line 814 of file src/client/crypto.c.

814 {
815 if (!crypto_client_is_ready()) {
816 return NULL;
817 }
818
820}
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)
Get the crypto context for encryption/decryption.

References crypto_client_is_ready(), crypto_handshake_get_context(), and g_crypto_ctx.

Referenced by client_crypto_handshake(), client_main(), connection_attempt_tcp(), connection_attempt_websocket(), server_connection_establish(), and threaded_send_client_join_packet().

◆ crypto_client_initiate_rekey()

int crypto_client_initiate_rekey ( void  )

#include <crypto.c>

Initiate session rekeying (client-initiated)

Initiate session rekeying (client-initiated)

Returns
0 on success, -1 on failure
0 on success, -1 on failure

Definition at line 896 of file src/client/crypto.c.

896 {
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}
asciichat_error_t crypto_handshake_rekey_request(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Send REKEY_REQUEST packet (initiator side)
acip_transport_t * server_connection_get_transport(void)
Get ACIP transport instance.
Transport instance structure.
Definition transport.h:214

References ASCIICHAT_OK, crypto_client_is_ready(), crypto_handshake_rekey_request(), g_crypto_ctx, lifecycle_is_initialized(), log_error, and server_connection_get_transport().

◆ crypto_client_is_ready()

bool crypto_client_is_ready ( void  )

#include <crypto.c>

Check if crypto handshake is ready.

Check if crypto handshake is ready

Returns
true if encryption is ready, false otherwise
true if encryption is ready, false otherwise

Definition at line 799 of file src/client/crypto.c.

799 {
800 if (!lifecycle_is_initialized(&g_crypto_lc) || GET_OPTION(no_encrypt)) {
801 return false;
802 }
803
805}
bool crypto_handshake_is_ready(const crypto_handshake_context_t *ctx)
Check if handshake is complete and encryption is ready.

References crypto_handshake_is_ready(), g_crypto_ctx, GET_OPTION, and lifecycle_is_initialized().

Referenced by client_main(), connection_attempt_tcp(), connection_attempt_websocket(), crypto_client_decrypt_packet(), crypto_client_encrypt_packet(), crypto_client_get_context(), crypto_client_initiate_rekey(), crypto_client_process_rekey_request(), crypto_client_process_rekey_response(), crypto_client_send_rekey_complete(), crypto_client_send_rekey_response(), crypto_client_should_rekey(), protocol_start_connection(), server_connection_establish(), and threaded_send_client_join_packet().

◆ crypto_client_process_rekey_request()

int crypto_client_process_rekey_request ( const uint8_t *  packet,
size_t  packet_len 
)

#include <crypto.c>

Process received REKEY_REQUEST packet from server.

Process received REKEY_REQUEST packet from server

Parameters
packetPacket data
packet_lenPacket length
Returns
0 on success, -1 on failure
Parameters
packetPacket data
packet_lenPacket length
Returns
0 on success, -1 on failure

Definition at line 926 of file src/client/crypto.c.

926 {
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}
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)

References ASCIICHAT_OK, crypto_client_is_ready(), crypto_handshake_process_rekey_request(), g_crypto_ctx, lifecycle_is_initialized(), and log_error.

◆ crypto_client_process_rekey_response()

int crypto_client_process_rekey_response ( const uint8_t *  packet,
size_t  packet_len 
)

#include <crypto.c>

Process received REKEY_RESPONSE packet from server.

Process received REKEY_RESPONSE packet from server

Parameters
packetPacket data
packet_lenPacket length
Returns
0 on success, -1 on failure
Parameters
packetPacket data
packet_lenPacket length
Returns
0 on success, -1 on failure

Definition at line 978 of file src/client/crypto.c.

978 {
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}
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)

References ASCIICHAT_OK, crypto_client_is_ready(), crypto_handshake_process_rekey_response(), g_crypto_ctx, lifecycle_is_initialized(), and log_error.

◆ crypto_client_send_rekey_complete()

int crypto_client_send_rekey_complete ( void  )

#include <crypto.c>

Send REKEY_COMPLETE packet to server and commit to new key.

Send REKEY_COMPLETE packet to server and commit to new key

Returns
0 on success, -1 on failure
0 on success, -1 on failure

Definition at line 1000 of file src/client/crypto.c.

1000 {
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 crypto_handshake_rekey_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Send REKEY_COMPLETE packet (initiator side)

References ASCIICHAT_OK, crypto_client_is_ready(), crypto_handshake_rekey_complete(), g_crypto_ctx, lifecycle_is_initialized(), log_error, and server_connection_get_transport().

◆ crypto_client_send_rekey_response()

int crypto_client_send_rekey_response ( void  )

#include <crypto.c>

Send REKEY_RESPONSE packet to server.

Send REKEY_RESPONSE packet to server

Returns
0 on success, -1 on failure
0 on success, -1 on failure

Definition at line 948 of file src/client/crypto.c.

948 {
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}
asciichat_error_t crypto_handshake_rekey_response(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Send REKEY_RESPONSE packet (responder side)

References ASCIICHAT_OK, crypto_client_is_ready(), crypto_handshake_rekey_response(), g_crypto_ctx, lifecycle_is_initialized(), log_error, and server_connection_get_transport().

◆ crypto_client_should_rekey()

bool crypto_client_should_rekey ( void  )

#include <crypto.c>

Check if session rekeying should be triggered.

Check if session rekeying should be triggered

Returns
true if rekey should be initiated, false otherwise
true if rekey should be initiated, false otherwise

Definition at line 882 of file src/client/crypto.c.

882 {
883 if (!lifecycle_is_initialized(&g_crypto_lc) || !crypto_client_is_ready()) {
884 return false;
885 }
887}
bool crypto_handshake_should_rekey(const crypto_handshake_context_t *ctx)
Check if rekeying should be triggered for this handshake context.

References crypto_client_is_ready(), crypto_handshake_should_rekey(), g_crypto_ctx, and lifecycle_is_initialized().